summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--.github/workflows/build-test-docker.yml226
-rw-r--r--bgpd/bgp_attr.h8
-rw-r--r--bgpd/bgp_bfd.c11
-rw-r--r--bgpd/bgp_bmp.c829
-rw-r--r--bgpd/bgp_bmp.h20
-rw-r--r--bgpd/bgp_debug.c139
-rw-r--r--bgpd/bgp_debug.h5
-rw-r--r--bgpd/bgp_dump.c8
-rw-r--r--bgpd/bgp_fsm.c21
-rw-r--r--bgpd/bgp_mplsvpn.c28
-rw-r--r--bgpd/bgp_mplsvpn.h5
-rw-r--r--bgpd/bgp_network.c56
-rw-r--r--bgpd/bgp_network.h1
-rw-r--r--bgpd/bgp_packet.c110
-rw-r--r--bgpd/bgp_packet.h6
-rw-r--r--bgpd/bgp_route.c190
-rw-r--r--bgpd/bgp_route.h4
-rw-r--r--bgpd/bgp_routemap.c90
-rw-r--r--bgpd/bgp_routemap_nb.c7
-rw-r--r--bgpd/bgp_routemap_nb.h4
-rw-r--r--bgpd/bgp_routemap_nb_config.c51
-rw-r--r--bgpd/bgp_script.c4
-rw-r--r--bgpd/bgp_snmp_bgp4.c18
-rw-r--r--bgpd/bgp_snmp_bgp4v2.c47
-rw-r--r--bgpd/bgp_trace.h3
-rw-r--r--bgpd/bgp_updgrp.h1
-rw-r--r--bgpd/bgp_updgrp_packet.c4
-rw-r--r--bgpd/bgp_vty.c53
-rw-r--r--bgpd/bgp_vty.h13
-rw-r--r--bgpd/bgp_zebra.c18
-rw-r--r--bgpd/bgpd.c22
-rw-r--r--bgpd/bgpd.h5
-rw-r--r--bgpd/rfapi/rfapi.c2
-rw-r--r--bgpd/rfapi/rfapi_import.c4
-rw-r--r--configure.ac53
-rw-r--r--doc/developer/building-doc.rst62
-rw-r--r--doc/developer/building-frr-for-ubuntu2004.rst161
-rw-r--r--doc/developer/building-frr-for-ubuntu2204.rst162
-rw-r--r--doc/developer/building-frr-for-ubuntu2404.rst4
-rw-r--r--doc/developer/building-frr-for-ubuntu2x04.rst162
-rw-r--r--doc/developer/mgmtd-dev.rst13
-rw-r--r--doc/developer/subdir.am1
-rw-r--r--doc/manpages/frr-zebra.rst2
-rw-r--r--doc/user/_static/overrides.js2
-rw-r--r--doc/user/about.rst26
-rw-r--r--doc/user/basic.rst11
-rw-r--r--doc/user/bgp.rst11
-rw-r--r--doc/user/bmp.rst5
-rw-r--r--doc/user/pim.rst14
-rw-r--r--doc/user/static.rst49
-rw-r--r--doc/user/zebra.rst2
-rw-r--r--docker/ubuntu-ci/Dockerfile6
-rw-r--r--docker/ubuntu22-ci/README.md2
-rw-r--r--docker/ubuntu24-ci/README.md66
-rw-r--r--isisd/isis_srv6.c2
-rw-r--r--isisd/isis_srv6.h3
-rw-r--r--lib/command.h1
-rw-r--r--lib/darr.h20
-rw-r--r--lib/event.c12
-rw-r--r--lib/frrscript.c2
-rw-r--r--lib/if.c171
-rw-r--r--lib/if.h10
-rw-r--r--lib/libfrr.c14
-rw-r--r--lib/mgmt_be_client.c277
-rw-r--r--lib/mgmt_be_client.h2
-rw-r--r--lib/mgmt_msg_native.c3
-rw-r--r--lib/mgmt_msg_native.h1
-rw-r--r--lib/northbound.c33
-rw-r--r--lib/northbound.h96
-rw-r--r--lib/northbound_notif.c706
-rw-r--r--lib/northbound_oper.c96
-rw-r--r--lib/plist.c8
-rw-r--r--lib/privs.c2
-rw-r--r--lib/routemap.h1
-rw-r--r--lib/routemap_cli.c4
-rw-r--r--lib/srv6.h38
-rw-r--r--lib/subdir.am2
-rw-r--r--lib/vrf.c64
-rw-r--r--lib/vrf.h4
-rw-r--r--lib/yang.c165
-rw-r--r--lib/yang.h79
-rw-r--r--m4/ax_lua.m492
-rw-r--r--mgmtd/mgmt_be_adapter.c56
-rw-r--r--mgmtd/mgmt_fe_adapter.c245
-rw-r--r--mgmtd/mgmt_fe_adapter.h7
-rw-r--r--mgmtd/mgmt_history.c1
-rw-r--r--mgmtd/mgmt_main.c16
-rw-r--r--mgmtd/mgmt_testc.c101
-rw-r--r--mgmtd/mgmt_txn.c47
-rw-r--r--mgmtd/mgmt_txn.h10
-rw-r--r--ospf6d/ospf6_lsdb.c7
-rw-r--r--ospfd/ospf_asbr.c3
-rw-r--r--pimd/pim_bsm.c41
-rw-r--r--pimd/pim_bsr_rpdb.c2
-rw-r--r--pimd/pim_cmd.c28
-rw-r--r--pimd/pim_igmp_mtrace.c13
-rw-r--r--pimd/pim_instance.h3
-rw-r--r--pimd/pim_mroute.c6
-rw-r--r--pimd/pim_msdp.c2
-rw-r--r--pimd/pim_nb.c9
-rw-r--r--pimd/pim_nb.h6
-rw-r--r--pimd/pim_nb_config.c55
-rw-r--r--pimd/pim_nht.c417
-rw-r--r--pimd/pim_nht.h30
-rw-r--r--pimd/pim_vty.c11
-rw-r--r--pimd/pim_zlookup.c26
-rw-r--r--pimd/pim_zlookup.h6
-rw-r--r--ripd/rip_main.c1
-rw-r--r--ripngd/ripng_main.c1
-rw-r--r--staticd/static_debug.c7
-rw-r--r--staticd/static_debug.h4
-rw-r--r--staticd/static_main.c9
-rw-r--r--staticd/static_nb.c29
-rw-r--r--staticd/static_nb.h48
-rw-r--r--staticd/static_nb_config.c222
-rw-r--r--staticd/static_srv6.c174
-rw-r--r--staticd/static_srv6.h108
-rw-r--r--staticd/static_vty.c306
-rw-r--r--staticd/static_zebra.c657
-rw-r--r--staticd/static_zebra.h10
-rw-r--r--staticd/subdir.am2
-rw-r--r--tests/topotests/bfd_vrf_topo1/test_bfd_vrf_topo1.py6
-rw-r--r--tests/topotests/bgp_bmp/bmp1import/bmp-update-loc-rib-step1.json34
-rw-r--r--tests/topotests/bgp_bmp/bmp1import/bmp-update-loc-rib-step2.json34
-rw-r--r--tests/topotests/bgp_bmp/bmp1import/bmp-update-post-policy-step1.json36
-rw-r--r--tests/topotests/bgp_bmp/bmp1import/bmp-update-post-policy-step2.json36
-rw-r--r--tests/topotests/bgp_bmp/bmp1import/bmp-update-pre-policy-step1.json36
-rw-r--r--tests/topotests/bgp_bmp/bmp1import/bmp-update-pre-policy-step2.json36
-rw-r--r--tests/topotests/bgp_bmp/bmp1import/bmp-withdraw-loc-rib-step1.json28
-rw-r--r--tests/topotests/bgp_bmp/bmp1import/bmp-withdraw-loc-rib-step2.json34
-rw-r--r--tests/topotests/bgp_bmp/bmp1import/bmp-withdraw-post-policy-step1.json30
-rw-r--r--tests/topotests/bgp_bmp/bmp1import/bmp-withdraw-pre-policy-step1.json30
-rw-r--r--tests/topotests/bgp_bmp/r1import/frr.conf73
-rw-r--r--tests/topotests/bgp_bmp/r1import/show-bgp-vrf1-ipv4-update-step1.json21
-rw-r--r--tests/topotests/bgp_bmp/r1import/show-bgp-vrf1-ipv4-withdraw-step1.json6
-rw-r--r--tests/topotests/bgp_bmp/r1import/show-bgp-vrf1-ipv6-update-step1.json27
-rw-r--r--tests/topotests/bgp_bmp/r1import/show-bgp-vrf1-ipv6-withdraw-step1.json6
-rw-r--r--tests/topotests/bgp_bmp/r3/frr.conf18
-rw-r--r--tests/topotests/bgp_bmp/test_bgp_bmp_3.py567
-rw-r--r--tests/topotests/bgp_comm_list_match/r1/bgpd.conf10
-rw-r--r--tests/topotests/bgp_comm_list_match/r1/zebra.conf2
-rw-r--r--tests/topotests/bgp_comm_list_match/test_bgp_comm_list_match.py64
-rw-r--r--tests/topotests/bgp_dynamic_capability/r1/frr.conf9
-rw-r--r--tests/topotests/bgp_dynamic_capability/r2/frr.conf12
-rw-r--r--tests/topotests/bgp_dynamic_capability/test_bgp_dynamic_capability_enhe.py189
-rw-r--r--tests/topotests/bgp_evpn_rt5/test_bgp_evpn.py6
-rw-r--r--tests/topotests/bgp_ipv6_rtadv/r1/bgp_ipv4_routes.json35
-rw-r--r--tests/topotests/bgp_ipv6_rtadv/r1/bgp_ipv6_routes.json35
-rw-r--r--tests/topotests/bgp_ipv6_rtadv/test_bgp_ipv6_rtadv.py39
-rwxr-xr-xtests/topotests/bgp_srv6_sid_reachability/test_bgp_srv6_sid_reachability.py2
-rw-r--r--tests/topotests/bgp_srv6l3vpn_to_bgp_vrf3/test_bgp_srv6l3vpn_to_bgp_vrf3.py6
-rw-r--r--tests/topotests/bgp_vpnv4_import_allowas_in_between_vrf/__init__.py0
-rw-r--r--tests/topotests/bgp_vpnv4_import_allowas_in_between_vrf/r1/frr.conf35
-rw-r--r--tests/topotests/bgp_vpnv4_import_allowas_in_between_vrf/r2/frr.conf48
-rw-r--r--tests/topotests/bgp_vpnv4_import_allowas_in_between_vrf/test_bgp_vpnv4_import_allowas_in_between_vrf.py142
-rw-r--r--tests/topotests/bgp_vrf_netns/test_bgp_vrf_netns_topo.py6
-rwxr-xr-xtests/topotests/conftest.py2
-rw-r--r--tests/topotests/grpc_basic/test_basic_grpc.py9
-rw-r--r--tests/topotests/ldp_snmp/test_ldp_snmp_topo1.py5
-rw-r--r--tests/topotests/lib/topogen.py6
-rw-r--r--tests/topotests/lib/topotest.py6
-rw-r--r--tests/topotests/mgmt_notif/r1/frr.conf2
-rw-r--r--tests/topotests/mgmt_notif/r2/frr.conf2
-rw-r--r--tests/topotests/mgmt_notif/test_ds_notify.py238
-rw-r--r--tests/topotests/mgmt_notif/test_notif.py139
-rw-r--r--tests/topotests/msdp_topo3/r1/frr.conf1
-rw-r--r--tests/topotests/msdp_topo3/r2/frr.conf1
-rw-r--r--tests/topotests/msdp_topo3/test_msdp_topo3.py29
-rw-r--r--tests/topotests/munet/base.py28
-rw-r--r--tests/topotests/munet/munet-schema.json22
-rw-r--r--tests/topotests/munet/mutest/userapi.py2
-rw-r--r--tests/topotests/munet/native.py218
-rw-r--r--tests/topotests/munet/testing/util.py97
-rw-r--r--tests/topotests/ospf_metric_propagation/r1/frr.conf9
-rw-r--r--tests/topotests/ospf_metric_propagation/r1/show_ip_route_static.json31
-rw-r--r--tests/topotests/ospf_metric_propagation/r4/frr.conf5
-rw-r--r--tests/topotests/ospf_metric_propagation/test_ospf_metric_propagation.py6
-rw-r--r--tests/topotests/ospf_netns_vrf/test_ospf_netns_vrf.py6
-rw-r--r--tests/topotests/pim_boundary_acl/test_pim_boundary_acl.py16
-rw-r--r--tests/topotests/pim_mrib/r1/frr.conf3
-rw-r--r--tests/topotests/pim_mrib/r2/frr.conf1
-rw-r--r--tests/topotests/pim_mrib/r3/frr.conf1
-rw-r--r--tests/topotests/pim_mrib/r4/frr.conf14
-rw-r--r--tests/topotests/pim_mrib/test_pim_mrib.py965
-rw-r--r--tests/topotests/static_srv6_sids/__init__.py0
-rw-r--r--tests/topotests/static_srv6_sids/expected_srv6_sids.json107
-rw-r--r--tests/topotests/static_srv6_sids/r1/frr.conf16
-rw-r--r--tests/topotests/static_srv6_sids/r1/setup.sh13
-rwxr-xr-xtests/topotests/static_srv6_sids/test_static_srv6_sids.py83
-rwxr-xr-xtools/frr-reload.py19
-rw-r--r--tools/gen_northbound_callbacks.c20
-rw-r--r--vtysh/vtysh.c38
-rw-r--r--yang/frr-backend.yang102
-rw-r--r--yang/frr-bgp-route-map.yang17
-rw-r--r--yang/frr-pim.yang26
-rw-r--r--yang/frr-staticd.yang102
-rw-r--r--yang/frr-test-module.yang25
-rw-r--r--yang/subdir.am1
-rw-r--r--zebra/dplane_fpm_nl.c20
-rw-r--r--zebra/interface.c32
-rw-r--r--zebra/main.c8
-rw-r--r--zebra/rt_netlink.c460
-rw-r--r--zebra/rt_netlink.h13
-rw-r--r--zebra/zebra_dplane.c89
-rw-r--r--zebra/zebra_dplane.h26
-rw-r--r--zebra/zebra_nhg.c53
-rw-r--r--zebra/zebra_rib.c36
-rw-r--r--zebra/zebra_vrf.c27
-rw-r--r--zebra/zebra_vrf.h3
209 files changed, 10736 insertions, 1623 deletions
diff --git a/.github/workflows/build-test-docker.yml b/.github/workflows/build-test-docker.yml
index 3f53f32d3..aef41d573 100644
--- a/.github/workflows/build-test-docker.yml
+++ b/.github/workflows/build-test-docker.yml
@@ -12,8 +12,8 @@ defaults:
shell: bash
jobs:
- build-docker:
- name: Build the ubuntu 22.04 docker image
+ build-x86-docker:
+ name: Build the x86 ubuntu 22.04 docker image
runs-on: ubuntu-latest
steps:
- name: Checkout
@@ -22,32 +22,32 @@ jobs:
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
+ docker build -t frr-x86-ubuntu22 -f docker/ubuntu-ci/Dockerfile .
+ docker save --output /tmp/frr-x86-ubuntu22.tar frr-x86-ubuntu22
- name: Upload docker image artifact
uses: actions/upload-artifact@v4
with:
- name: ubuntu-image
- path: /tmp/frr-ubuntu22.tar
+ name: ubuntu-x86-image
+ path: /tmp/frr-x86-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
+ rm -rf test-results-x86*
+ mkdir -p test-results-x86
+ touch test-results-x86/cleared-results.txt
- name: Save cleared previous results
uses: actions/upload-artifact@v4
with:
- name: test-results
- path: test-results
+ name: test-results-x86
+ path: test-results-x86
overwrite: true
- name: Cleanup
if: ${{ always() }}
- run: rm -rf test-results* /tmp/frr-ubuntu22.tar
+ run: rm -rf test-results-x86* /tmp/frr-x86-ubuntu22.tar
- test-docker:
- name: Test ubuntu docker image
- needs: build-docker
+ test-x86-docker:
+ name: Test ubuntu x86 docker image
+ needs: build-x86-docker
runs-on: ubuntu-latest
steps:
- name: Checkout
@@ -57,14 +57,14 @@ jobs:
- name: Fetch docker image artifact
uses: actions/download-artifact@v4
with:
- name: ubuntu-image
+ name: ubuntu-x86-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: test-results-x86
+ path: test-results-x86
- name: Run topotests
run: |
uname -a
@@ -75,37 +75,37 @@ jobs:
sudo modprobe vrf || true
sudo modprobe mpls-iptunnel
sudo modprobe mpls-router
- docker load --input /tmp/frr-ubuntu22.tar
+ docker load --input /tmp/frr-x86-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)
+ if [ -f test-results-x86/topotests.xml ]; then
+ ./tests/topotests/analyze.py -r test-results-x86
+ ls -l test-results-x86/topotests.xml
+ run_tests=$(./tests/topotests/analyze.py -r test-results-x86 | cut -f1 -d: | sort -u)
else
echo "No test results dir"
run_tests=""
fi
- rm -rf test-results* /tmp/topotests
+ rm -rf test-results-x86* /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 \
+ if docker run --init -i --privileged --name frr-ubuntu-cont ${ADD_DOCKER_ENV} -v /lib/modules:/lib/modules frr-x86-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
+ if ! ./tests/topotests/analyze.py -Ar test-results-x86 -C frr-ubuntu-cont; then
+ if [ ! -d test-results-x86 ]; then
echo "ERROR: Basic failure in docker run, no test results directory available." >&2
exit 1;
fi
- if [ ! -f test-results/topotests.xml ]; then
+ if [ ! -f test-results-x86/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;
@@ -114,11 +114,11 @@ jobs:
fi
# Save some information useful for debugging
- cp /boot/config* test-results/
- sysctl -a > test-results/sysctl.out 2> /dev/null
+ cp /boot/config* test-results-x86/
+ sysctl -a > test-results-x86/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)
+ rerun_tests=$(./tests/topotests/analyze.py -r test-results-x86 | cut -f1 -d: | sort -u)
if [ -z "$rerun_tests" ]; then
echo "All tests passed during parallel run."
exit 0
@@ -129,8 +129,8 @@ jobs:
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 \
+ mv test-results-x86 test-results-x86-initial
+ if docker run --init -i --privileged --name frr-ubuntu-cont ${ADD_DOCKER_ENV} -v /lib/modules:/lib/modules frr-x86-ubuntu22 \
bash -c 'cd ~/frr/tests/topotests ; sudo -E pytest '$rerun_tests; then
echo "All rerun tests passed."
exit 0
@@ -140,8 +140,8 @@ jobs:
- name: Gather results
if: ${{ always() }}
run: |
- if [ ! -d test-results ]; then
- if ! ./tests/topotests/analyze.py -Ar test-results -C frr-ubuntu-cont; then
+ if [ ! -d test-results-x86 ]; then
+ if ! ./tests/topotests/analyze.py -Ar test-results-x86 -C frr-ubuntu-cont; then
echo "ERROR: gathering results produced an error, perhaps due earlier run cancellation." >&2
fi
fi
@@ -149,15 +149,163 @@ jobs:
if: ${{ always() }}
uses: actions/upload-artifact@v4
with:
- name: test-results
+ name: test-results-x86
path: |
- test-results
- test-results-initial
+ test-results-x86
+ test-results-x86-initial
overwrite: true
- name: Cleanup
if: ${{ always() }}
run: |
- rm -rf test-results* /tmp/frr-ubuntu22.tar
+ rm -rf test-results-x86* /tmp/frr-x86-ubuntu22.tar
docker stop frr-ubuntu-cont || true
docker rm frr-ubuntu-cont || true
+ build-arm-docker:
+ name: Build the ARM ubuntu 22.04 docker image
+ runs-on: ubuntu-22.04-arm
+ steps:
+ - name: Checkout
+ uses: actions/checkout@v4
+ with:
+ fetch-depth: 1
+ - name: Build docker image
+ run: |
+ docker build -t frr-arm-ubuntu22 -f docker/ubuntu-ci/Dockerfile .
+ docker save --output /tmp/frr-arm-ubuntu22.tar frr-arm-ubuntu22
+ - name: Upload docker image artifact
+ uses: actions/upload-artifact@v4
+ with:
+ name: ubuntu-arm-image
+ path: /tmp/frr-arm-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-arm*
+ mkdir -p test-results-arm
+ touch test-results-arm/cleared-results.txt
+ - name: Save cleared previous results
+ uses: actions/upload-artifact@v4
+ with:
+ name: test-results-arm
+ path: test-results-arm
+ overwrite: true
+ - name: Cleanup
+ if: ${{ always() }}
+ run: rm -rf test-results-arm* /tmp/frr-arm-ubuntu22.tar
+
+ test-arm-docker:
+ name: Test ubuntu ARM docker image
+ needs: build-arm-docker
+ runs-on: ubuntu-22.04-arm
+ steps:
+ - name: Checkout
+ uses: actions/checkout@v4
+ with:
+ fetch-depth: 1
+ - name: Fetch docker image artifact
+ uses: actions/download-artifact@v4
+ with:
+ name: ubuntu-arm-image
+ path: /tmp
+ - name: Fetch previous results
+ if: ${{ github.run_attempt > 1 }}
+ uses: actions/download-artifact@v4
+ with:
+ name: test-results-arm
+ path: test-results-arm
+ - 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-arm-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-arm/topotests.xml ]; then
+ ./tests/topotests/analyze.py -r test-results-arm
+ ls -l test-results-arm/topotests.xml
+ run_tests=$(./tests/topotests/analyze.py -r test-results-arm | cut -f1 -d: | sort -u)
+ else
+ echo "No test results dir"
+ run_tests=""
+ fi
+ rm -rf test-results-arm* /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-arm-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-arm -C frr-ubuntu-cont; then
+ if [ ! -d test-results-arm ]; then
+ echo "ERROR: Basic failure in docker run, no test results directory available." >&2
+ exit 1;
+ fi
+ if [ ! -f test-results-arm/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-arm/
+ sysctl -a > test-results-arm/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-arm | 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-arm test-results-arm-initial
+ if docker run --init -i --privileged --name frr-ubuntu-cont ${ADD_DOCKER_ENV} -v /lib/modules:/lib/modules frr-arm-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-arm ]; then
+ if ! ./tests/topotests/analyze.py -Ar test-results-arm -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-arm
+ path: |
+ test-results-arm
+ test-results-arm-initial
+ overwrite: true
+ - name: Cleanup
+ if: ${{ always() }}
+ run: |
+ rm -rf test-results-arm* /tmp/frr-arm-ubuntu22.tar
+ docker stop frr-ubuntu-cont || true
+ docker rm frr-ubuntu-cont || true
diff --git a/bgpd/bgp_attr.h b/bgpd/bgp_attr.h
index c5532f400..341c06251 100644
--- a/bgpd/bgp_attr.h
+++ b/bgpd/bgp_attr.h
@@ -323,7 +323,6 @@ struct attr {
/* 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)
@@ -604,10 +603,11 @@ static inline 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
+ /* Don't increment if it's locally sourced */
+ if (bpi->peer == bpi->peer->bgp->peer_self)
return aigp;
+
+ return bpi->extra ? (aigp + bpi->extra->igpmetric) : aigp;
}
static inline void bgp_attr_set_med(struct attr *attr, uint32_t med)
diff --git a/bgpd/bgp_bfd.c b/bgpd/bgp_bfd.c
index 50b00d21b..78759ae2b 100644
--- a/bgpd/bgp_bfd.c
+++ b/bgpd/bgp_bfd.c
@@ -188,7 +188,7 @@ void bgp_peer_bfd_update_source(struct peer *p)
}
}
} else {
- source = p->su_local;
+ source = p->connection->su_local;
}
/* Update peer's source/destination addresses. */
@@ -316,13 +316,14 @@ void bgp_peer_configure_bfd(struct peer *p, bool manual)
/* 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_local
+ ? &p->connection->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
+ p->connection->su_local
+ ? &p->connection->su_local->sin6.sin6_addr
: NULL,
&p->connection->su.sin6.sin6_addr);
diff --git a/bgpd/bgp_bmp.c b/bgpd/bgp_bmp.c
index 036bece35..21f921255 100644
--- a/bgpd/bgp_bmp.c
+++ b/bgpd/bgp_bmp.c
@@ -50,6 +50,12 @@ 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);
+static int bmp_route_update_bgpbmp(struct bmp_targets *bt, afi_t afi, safi_t safi,
+ struct bgp_dest *bn, struct bgp_path_info *old_route,
+ struct bgp_path_info *new_route);
+static void bmp_send_all_bgp(struct peer *peer, bool down);
+static struct bmp_imported_bgp *bmp_imported_bgp_find(struct bmp_targets *bt, char *name);
+static void bmp_stats_per_instance(struct bgp *bgp, struct bmp_targets *bt);
DEFINE_MGROUP(BMP, "BMP (BGP Monitoring Protocol)");
@@ -64,6 +70,7 @@ 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_MTYPE_STATIC(BMP, BMP_IMPORTED_BGP, "BMP imported BGP instance");
DEFINE_QOBJ_TYPE(bmp_targets);
@@ -141,6 +148,17 @@ static int bmp_targets_cmp(const struct bmp_targets *a,
DECLARE_SORTLIST_UNIQ(bmp_targets, struct bmp_targets, bti, bmp_targets_cmp);
+static int bmp_imported_bgps_cmp(const struct bmp_imported_bgp *a, const struct bmp_imported_bgp *b)
+{
+ if (a->name == NULL && b->name == NULL)
+ return 0;
+ if (a->name == NULL || b->name == NULL)
+ return 1;
+ return strcmp(a->name, b->name);
+}
+
+DECLARE_SORTLIST_UNIQ(bmp_imported_bgps, struct bmp_imported_bgp, bib, bmp_imported_bgps_cmp);
+
DECLARE_LIST(bmp_session, struct bmp, bsi);
DECLARE_DLIST(bmp_qlist, struct bmp_queue_entry, bli);
@@ -228,6 +246,7 @@ static struct bmp *bmp_new(struct bmp_targets *bt, int bmp_sock)
new->targets = bt;
new->socket = bmp_sock;
new->syncafi = AFI_MAX;
+ new->sync_bgp = NULL;
FOREACH_AFI_SAFI (afi, safi) {
new->afistate[afi][safi] = bt->afimon[afi][safi]
@@ -399,14 +418,17 @@ static void bmp_put_info_tlv(struct stream *s, uint16_t type,
/* put the vrf table name of the bgp instance bmp is bound to in a tlv on the
* stream
*/
-static void bmp_put_vrftablename_info_tlv(struct stream *s, struct bgp *bgp)
+static void bmp_put_vrftablename_info_tlv(struct stream *s, struct peer *peer)
{
const char *vrftablename = "global";
+ struct vrf *vrf;
#define BMP_INFO_TYPE_VRFTABLENAME 3
- if (bgp->inst_type != BGP_INSTANCE_TYPE_DEFAULT)
- vrftablename = bgp->name;
+ if (peer->bgp->inst_type != BGP_INSTANCE_TYPE_DEFAULT) {
+ vrf = vrf_lookup_by_id(peer->bgp->vrf_id);
+ vrftablename = vrf ? vrf->name : NULL;
+ }
if (vrftablename != NULL)
bmp_put_info_tlv(s, BMP_INFO_TYPE_VRFTABLENAME, vrftablename);
@@ -496,29 +518,29 @@ static struct stream *bmp_peerstate(struct peer *peer, bool down)
/* Local Address (16 bytes) */
if (is_locrib)
stream_put(s, 0, 16);
- else 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) {
+ else if (peer->connection->su_local->sa.sa_family == AF_INET6)
+ stream_put(s, &peer->connection->su_local->sin6.sin6_addr, 16);
+ else if (peer->connection->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);
+ stream_put_in_addr(s, &peer->connection->su_local->sin.sin_addr);
}
/* Local Port, Remote Port */
- if (!peer->su_local || is_locrib)
+ if (!peer->connection->su_local || is_locrib)
stream_putw(s, 0);
- else 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));
+ else if (peer->connection->su_local->sa.sa_family == AF_INET6)
+ stream_putw(s, htons(peer->connection->su_local->sin6.sin6_port));
+ else if (peer->connection->su_local->sa.sa_family == AF_INET)
+ stream_putw(s, htons(peer->connection->su_local->sin.sin_port));
- if (!peer->su_remote || is_locrib)
+ if (!peer->connection->su_remote || is_locrib)
stream_putw(s, 0);
- else 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));
+ else if (peer->connection->su_remote->sa.sa_family == AF_INET6)
+ stream_putw(s, htons(peer->connection->su_remote->sin6.sin6_port));
+ else if (peer->connection->su_remote->sa.sa_family == AF_INET)
+ stream_putw(s, htons(peer->connection->su_remote->sin.sin_port));
/* TODO craft message with fields & capabilities for loc-rib */
static const uint8_t dummy_open[] = {
@@ -590,63 +612,123 @@ static struct stream *bmp_peerstate(struct peer *peer, bool down)
}
if (is_locrib)
- bmp_put_vrftablename_info_tlv(s, peer->bgp);
+ bmp_put_vrftablename_info_tlv(s, peer);
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)
+static int bmp_send_peerup_per_instance(struct bmp *bmp, struct bgp *bgp)
{
struct peer *peer;
struct listnode *node;
struct stream *s;
/* Walk down all peers */
- for (ALL_LIST_ELEMENTS_RO(bmp->targets->bgp->peer, node, peer)) {
+ for (ALL_LIST_ELEMENTS_RO(bgp->peer, node, peer)) {
s = bmp_peerstate(peer, false);
if (s) {
pullwr_write_stream(bmp->pullwr, s);
stream_free(s);
}
}
+ return 0;
+}
+
+static int bmp_send_peerup(struct bmp *bmp)
+{
+ struct bmp_imported_bgp *bib;
+ struct bgp *bgp;
+
+ bmp_send_peerup_per_instance(bmp, bmp->targets->bgp);
+ frr_each (bmp_imported_bgps, &bmp->targets->imported_bgps, bib) {
+ bgp = bgp_lookup_by_name(bib->name);
+ if (bgp)
+ bmp_send_peerup_per_instance(bmp, bgp);
+ }
return 0;
}
-static int bmp_send_peerup_vrf(struct bmp *bmp)
+static void bmp_send_peerup_vrf_per_instance(struct bmp *bmp, enum bmp_vrf_state *vrf_state,
+ struct bgp *bgp)
{
- struct bmp_bgp *bmpbgp = bmp->targets->bmpbgp;
struct stream *s;
/* send unconditionally because state may has been set before the
* session was up. and in this case the peer up has not been sent.
*/
- bmp_bgp_update_vrf_status(bmpbgp, vrf_state_unknown);
+ bmp_bgp_update_vrf_status(vrf_state, bgp, vrf_state_unknown);
- s = bmp_peerstate(bmpbgp->bgp->peer_self, bmpbgp->vrf_state == vrf_state_down);
+ s = bmp_peerstate(bgp->peer_self, *vrf_state == vrf_state_down);
if (s) {
pullwr_write_stream(bmp->pullwr, s);
stream_free(s);
}
+}
+
+static int bmp_send_peerup_vrf(struct bmp *bmp)
+{
+ struct bgp *bgp;
+ struct bmp_imported_bgp *bib;
+ struct bmp_bgp *bmpbgp = bmp->targets->bmpbgp;
+ struct bmp_targets *bt;
+
+ bmp_send_peerup_vrf_per_instance(bmp, &bmpbgp->vrf_state, bmpbgp->bgp);
+
+ frr_each (bmp_targets, &bmpbgp->targets, bt) {
+ frr_each (bmp_imported_bgps, &bt->imported_bgps, bib) {
+ bgp = bgp_lookup_by_name(bib->name);
+ if (!bgp)
+ continue;
+ bmp_send_peerup_vrf_per_instance(bmp, &bib->vrf_state, bgp);
+ }
+ }
return 0;
}
+static void bmp_send_bt(struct bmp_targets *bt, struct stream *s)
+{
+ struct bmp *bmp;
+
+ frr_each (bmp_session, &bt->sessions, bmp)
+ pullwr_write_stream(bmp->pullwr, s);
+}
+
+static void bmp_send_bt_safe(struct bmp_targets *bt, struct stream *s)
+{
+ if (!s)
+ return;
+
+ bmp_send_bt(bt, s);
+
+ stream_free(s);
+}
+
+static void bmp_send_peerdown_vrf_per_instance(struct bmp_targets *bt, struct bgp *bgp)
+{
+ struct stream *s;
+
+ s = bmp_peerstate(bgp->peer_self, true);
+ if (!s)
+ return;
+ bmp_send_bt(bt, s);
+ stream_free(s);
+}
+
/* send a stream to all bmp sessions configured in a bgp instance */
/* 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;
if (!s)
return;
frr_each(bmp_targets, &bmpbgp->targets, bt)
- frr_each(bmp_session, &bt->sessions, bmp)
- pullwr_write_stream(bmp->pullwr, s);
+ bmp_send_bt(bt, s);
+
stream_free(s);
}
@@ -725,11 +807,13 @@ static void bmp_mirror_cull(struct bmp_bgp *bmpbgp)
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 bmp_bgp *bmpbgp;
struct timeval tv;
- struct bmp_mirrorq *qitem;
+ struct bmp_mirrorq *qitem = NULL;
struct bmp_targets *bt;
struct bmp *bmp;
+ struct bgp *bgp_vrf;
+ struct listnode *node;
frrtrace(3, frr_bgp, bmp_mirror_packet, peer, type, packet);
@@ -745,8 +829,6 @@ static int bmp_mirror_packet(struct peer *peer, uint8_t type, bgp_size_t 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;
@@ -754,27 +836,41 @@ static int bmp_mirror_packet(struct peer *peer, uint8_t type, bgp_size_t size,
qitem->len = size;
memcpy(qitem->data, packet->data, size);
- frr_each(bmp_targets, &bmpbgp->targets, bt) {
- if (!bt->mirror)
+ for (ALL_LIST_ELEMENTS_RO(bm->bgp, node, bgp_vrf)) {
+ bmpbgp = bmp_bgp_find(bgp_vrf);
+ if (!bmpbgp)
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);
+ frr_each (bmp_targets, &bmpbgp->targets, bt) {
+ if (!bt->mirror)
+ continue;
+
+ if (bgp_vrf != peer->bgp && !bmp_imported_bgp_find(bt, peer->bgp->name))
+ continue;
+
+ frr_each (bmp_session, &bt->sessions, bmp) {
+ if (!qitem) {
+ 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);
+ }
+
+ qitem->refcount++;
+ if (!bmp->mirrorpos)
+ bmp->mirrorpos = qitem;
+ pullwr_bump(bmp->pullwr);
+ }
+ bmpbgp->mirror_qsize += sizeof(*qitem) + size;
+ bmp_mirrorq_add_tail(&bmpbgp->mirrorq, qitem);
- bmp_mirror_cull(bmpbgp);
+ bmp_mirror_cull(bmpbgp);
- bmpbgp->mirror_qsizemax = MAX(bmpbgp->mirror_qsizemax,
- bmpbgp->mirror_qsize);
+ bmpbgp->mirror_qsizemax = MAX(bmpbgp->mirror_qsizemax, bmpbgp->mirror_qsize);
+ }
}
+ if (qitem && qitem->refcount == 0)
+ XFREE(MTYPE_BMP_MIRRORQ, qitem);
return 0;
}
@@ -847,8 +943,7 @@ static bool bmp_wrmirror(struct bmp *bmp, struct pullwr *pullwr)
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, peer_type_flag, peer_distinguisher,
- &bmq->tv);
+ bmp_per_peer_hdr(s, peer->bgp, peer, 0, peer_type_flag, peer_distinguisher, &bmq->tv);
/* BMP Mirror TLV. */
stream_putw(s, BMP_MIRROR_TLV_TYPE_BGP_MESSAGE);
@@ -887,14 +982,10 @@ static int bmp_outgoing_packet(struct peer *peer, uint8_t type, bgp_size_t size,
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) {
@@ -929,20 +1020,16 @@ static int bmp_peer_status_changed(struct peer *peer)
}
}
- bmp_send_all_safe(bmpbgp, bmp_peerstate(peer, false));
+ bmp_send_all_bgp(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);
@@ -951,12 +1038,12 @@ static int bmp_peer_backward(struct peer *peer)
bbpeer->open_rx_len = 0;
}
- bmp_send_all_safe(bmpbgp, bmp_peerstate(peer, true));
+ bmp_send_all_bgp(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)
+static void bmp_eor(struct bmp *bmp, afi_t afi, safi_t safi, uint8_t flags, uint8_t peer_type_flag,
+ struct bgp *bgp)
{
struct peer *peer;
struct listnode *node;
@@ -964,7 +1051,7 @@ static void bmp_eor(struct bmp *bmp, afi_t afi, safi_t safi, uint8_t flags,
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);
+ frrtrace(5, frr_bgp, bmp_eor, afi, safi, flags, peer_type_flag, bgp);
s = stream_new(BGP_MAX_PACKET_SIZE);
@@ -992,7 +1079,7 @@ static void bmp_eor(struct bmp *bmp, afi_t afi, safi_t safi, uint8_t flags,
bgp_packet_set_size(s);
- for (ALL_LIST_ELEMENTS_RO(bmp->targets->bgp->peer, node, peer)) {
+ for (ALL_LIST_ELEMENTS_RO(bgp->peer, node, peer)) {
if (!peer->afc_nego[afi][safi])
continue;
@@ -1009,8 +1096,7 @@ static void bmp_eor(struct bmp *bmp, afi_t afi, safi_t safi, uint8_t flags,
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);
+ bmp_per_peer_hdr(s2, bgp, peer, flags, peer_type_flag, peer_distinguisher, NULL);
stream_putl_at(s2, BMP_LENGTH_POS,
stream_get_endp(s) + stream_get_endp(s2));
@@ -1141,8 +1227,7 @@ static void bmp_monitor(struct bmp *bmp, struct peer *peer, uint8_t flags,
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,
+ bmp_per_peer_hdr(hdr, peer->bgp, peer, flags, peer_type_flag, peer_distinguisher,
uptime == (time_t)(-1L) ? NULL : &uptime_real);
stream_putl_at(hdr, BMP_LENGTH_POS,
@@ -1155,6 +1240,93 @@ static void bmp_monitor(struct bmp *bmp, struct peer *peer, uint8_t flags,
stream_free(msg);
}
+static struct bgp *bmp_get_next_bgp(struct bmp_targets *bt, struct bgp *bgp, afi_t afi, safi_t safi)
+{
+ struct bmp_imported_bgp *bib;
+ struct bgp *bgp_inst;
+ bool get_first = false;
+
+ if (bgp == NULL && bt->bgp_request_sync[afi][safi])
+ return bt->bgp;
+ if (bgp == NULL)
+ get_first = true;
+ frr_each (bmp_imported_bgps, &bt->imported_bgps, bib) {
+ bgp_inst = bgp_lookup_by_name(bib->name);
+ if (get_first && bgp_inst && bib->bgp_request_sync[afi][safi])
+ return bgp_inst;
+ if (bgp_inst == bgp)
+ get_first = true;
+ }
+ return NULL;
+}
+
+static void bmp_update_syncro(struct bmp *bmp, afi_t afi, safi_t safi, struct bgp *bgp)
+{
+ struct bmp_imported_bgp *bib;
+
+ if (bmp->syncafi == afi && bmp->syncsafi == safi) {
+ bmp->syncafi = AFI_MAX;
+ bmp->syncsafi = SAFI_MAX;
+ bmp->sync_bgp = NULL;
+ }
+
+ if (!bmp->targets->afimon[afi][safi]) {
+ bmp->afistate[afi][safi] = BMP_AFI_INACTIVE;
+ return;
+ }
+
+ bmp->afistate[afi][safi] = BMP_AFI_NEEDSYNC;
+
+ if (bgp == NULL || bmp->targets->bgp == bgp)
+ bmp->targets->bgp_request_sync[afi][safi] = true;
+
+ frr_each (bmp_imported_bgps, &bmp->targets->imported_bgps, bib) {
+ if (bgp != NULL && bgp_lookup_by_name(bib->name) != bgp)
+ continue;
+ bib->bgp_request_sync[afi][safi] = true;
+ }
+}
+
+static void bmp_update_syncro_set(struct bmp *bmp, afi_t afi, safi_t safi, struct bgp *bgp,
+ enum bmp_afi_state state)
+{
+ struct bmp_imported_bgp *bib;
+
+ bmp->afistate[afi][safi] = state;
+ bmp->syncafi = AFI_MAX;
+ bmp->syncsafi = SAFI_MAX;
+ if (bgp == NULL || bmp->targets->bgp == bmp->sync_bgp)
+ bmp->targets->bgp_request_sync[afi][safi] = false;
+
+ frr_each (bmp_imported_bgps, &bmp->targets->imported_bgps, bib) {
+ if (bgp == NULL || bgp_lookup_by_name(bib->name) != bmp->sync_bgp)
+ continue;
+ bib->bgp_request_sync[afi][safi] = false;
+ }
+}
+
+static void bmp_eor_afi_safi(struct bmp *bmp, afi_t afi, safi_t safi, uint8_t peer_type_flag)
+{
+ struct bgp *sync_bgp;
+
+ zlog_info("bmp[%s] %s %s table completed (EoR) (BGP %s)", bmp->remote, afi2str(afi),
+ safi2str(safi), bmp->sync_bgp->name_pretty);
+
+ bmp_eor(bmp, afi, safi, BMP_PEER_FLAG_L, peer_type_flag, bmp->sync_bgp);
+ bmp_eor(bmp, afi, safi, 0, peer_type_flag, bmp->sync_bgp);
+ bmp_eor(bmp, afi, safi, 0, BMP_PEER_TYPE_LOC_RIB_INSTANCE, bmp->sync_bgp);
+
+ sync_bgp = bmp_get_next_bgp(bmp->targets, bmp->sync_bgp, afi, safi);
+ if (sync_bgp) {
+ memset(&bmp->syncpos, 0, sizeof(bmp->syncpos));
+ bmp->syncpos.family = afi2family(afi);
+ bmp->syncrdpos = NULL;
+ bmp->syncpeerid = 0;
+ } else
+ bmp_update_syncro_set(bmp, afi, safi, bmp->sync_bgp, BMP_AFI_LIVE);
+ bmp->sync_bgp = sync_bgp;
+}
+
static bool bmp_wrsync(struct bmp *bmp, struct pullwr *pullwr)
{
uint8_t bpi_num_labels, adjin_num_labels;
@@ -1175,10 +1347,13 @@ static bool bmp_wrsync(struct bmp *bmp, struct pullwr *pullwr)
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));
+ bmp->sync_bgp = bmp_get_next_bgp(bmp->targets, NULL, afi, safi);
+ if (bmp->sync_bgp == NULL)
+ /* all BGP instances already synced*/
+ return true;
+ zlog_info("bmp[%s] %s %s sending table (BGP %s)", bmp->remote,
+ afi2str(bmp->syncafi), safi2str(bmp->syncsafi),
+ bmp->sync_bgp->name_pretty);
/* break does not work here, 2 loops... */
goto afibreak;
}
@@ -1192,18 +1367,22 @@ afibreak:
if (!bmp->targets->afimon[afi][safi]) {
/* shouldn't happen */
- bmp->afistate[afi][safi] = BMP_AFI_INACTIVE;
- bmp->syncafi = AFI_MAX;
- bmp->syncsafi = SAFI_MAX;
+ bmp_update_syncro_set(bmp, afi, safi, bmp->sync_bgp, BMP_AFI_INACTIVE);
+ bmp->sync_bgp = NULL;
return true;
}
+ if (bmp->sync_bgp == NULL) {
+ bmp->sync_bgp = bmp_get_next_bgp(bmp->targets, NULL, afi, safi);
+ if (bmp->sync_bgp == NULL)
+ return true;
+ }
- struct bgp_table *table = bmp->targets->bgp->rib[afi][safi];
+ struct bgp_table *table = bmp->sync_bgp->rib[afi][safi];
struct bgp_dest *bn = NULL;
struct bgp_path_info *bpi = NULL, *bpiter;
struct bgp_adj_in *adjin = NULL, *adjiter;
- peer_type_flag = bmp_get_peer_type_vrf(bmp->targets->bgp->vrf_id);
+ peer_type_flag = bmp_get_peer_type_vrf(bmp->sync_bgp->vrf_id);
if ((afi == AFI_L2VPN && safi == SAFI_EVPN) ||
(safi == SAFI_MPLS_VPN)) {
@@ -1255,19 +1434,9 @@ afibreak:
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, peer_type_flag);
- bmp_eor(bmp, afi, safi, 0, peer_type_flag);
- 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_eor_afi_safi(bmp, afi, safi,
+ peer_type_flag);
+ return true;
}
bmp->syncpeerid = 0;
prefix_copy(&bmp->syncpos, bgp_dest_get_prefix(bn));
@@ -1446,7 +1615,7 @@ static bool bmp_wrqueue_locrib(struct bmp *bmp, struct pullwr *pullwr)
*/
goto out;
}
- if (peer != bmp->targets->bgp->peer_self && !peer_established(peer->connection)) {
+ if (peer != peer->bgp->peer_self && !peer_established(peer->connection)) {
/* peer is neither self, nor established
*/
goto out;
@@ -1457,8 +1626,7 @@ static bool bmp_wrqueue_locrib(struct bmp *bmp, struct pullwr *pullwr)
struct prefix_rd *prd = is_vpn ? &bqe->rd : NULL;
- bn = bgp_safi_node_lookup(bmp->targets->bgp->rib[afi][safi], safi,
- &bqe->p, prd);
+ bn = bgp_safi_node_lookup(peer->bgp->rib[afi][safi], safi, &bqe->p, prd);
struct bgp_path_info *bpi;
@@ -1534,8 +1702,7 @@ static bool bmp_wrqueue(struct bmp *bmp, struct pullwr *pullwr)
(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);
+ bn = bgp_safi_node_lookup(peer->bgp->rib[afi][safi], safi, &bqe->p, prd);
peer_type_flag = bmp_get_peer_type(peer);
@@ -1585,11 +1752,16 @@ out:
static void bmp_wrfill(struct bmp *bmp, struct pullwr *pullwr)
{
+ afi_t afi;
+ safi_t safi;
+
switch(bmp->state) {
case BMP_PeerUp:
bmp_send_peerup_vrf(bmp);
bmp_send_peerup(bmp);
bmp->state = BMP_Run;
+ FOREACH_AFI_SAFI (afi, safi)
+ bmp_update_syncro(bmp, afi, safi, NULL);
break;
case BMP_Run:
@@ -1667,9 +1839,12 @@ bmp_process_one(struct bmp_targets *bt, struct bmp_qhash_head *updhash,
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_bgp *bmpbgp;
struct bmp_targets *bt;
struct bmp *bmp;
+ struct bgp *bgp_vrf;
+ struct listnode *node;
+ struct bmp_queue_entry *last_item;
if (frrtrace_enabled(frr_bgp, bmp_process)) {
char pfxprint[PREFIX2STR_BUFFER];
@@ -1679,31 +1854,34 @@ static int bmp_process(struct bgp *bgp, afi_t afi, safi_t 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))
+ for (ALL_LIST_ELEMENTS_RO(bm->bgp, node, bgp_vrf)) {
+ bmpbgp = bmp_bgp_find(bgp_vrf);
+ if (!bmpbgp)
continue;
+ 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 (bgp_vrf != peer->bgp && !bmp_imported_bgp_find(bt, peer->bgp->name))
+ continue;
- /* if bmp_process_one returns NULL
- * we don't have anything to do next
- */
- if (!last_item)
- continue;
+ 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;
+ frr_each (bmp_session, &bt->sessions, bmp) {
+ if (!bmp->queuepos)
+ bmp->queuepos = last_item;
- pullwr_bump(bmp->pullwr);
+ pullwr_bump(bmp->pullwr);
+ }
}
}
return 0;
@@ -1750,6 +1928,22 @@ static void bmp_stat_put_u64(struct stream *s, size_t *cnt, uint16_t type,
static void bmp_stats(struct event *thread)
{
struct bmp_targets *bt = EVENT_ARG(thread);
+ struct bmp_imported_bgp *bib;
+ struct bgp *bgp;
+
+ if (bt->stat_msec)
+ event_add_timer_msec(bm->master, bmp_stats, bt, bt->stat_msec, &bt->t_stats);
+
+ bmp_stats_per_instance(bt->bgp, bt);
+ frr_each (bmp_imported_bgps, &bt->imported_bgps, bib) {
+ bgp = bgp_lookup_by_name(bib->name);
+ if (bgp)
+ bmp_stats_per_instance(bgp, bt);
+ }
+}
+
+static void bmp_stats_per_instance(struct bgp *bgp, struct bmp_targets *bt)
+{
struct stream *s;
struct peer *peer;
struct listnode *node;
@@ -1757,14 +1951,10 @@ static void bmp_stats(struct event *thread)
uint8_t peer_type_flag;
uint64_t peer_distinguisher = 0;
- 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)) {
+ for (ALL_LIST_ELEMENTS_RO(bgp->peer, node, peer)) {
size_t count = 0, count_pos, len;
if (!peer_established(peer->connection))
@@ -1980,6 +2170,7 @@ static struct bmp_bgp *bmp_bgp_get(struct bgp *bgp)
bmpbgp->bgp = bgp;
bmpbgp->vrf_state = vrf_state_unknown;
bmpbgp->mirror_qsizelimit = ~0UL;
+ bmp_targets_init(&bmpbgp->targets);
bmp_mirrorq_init(&bmpbgp->mirrorq);
bmp_bgph_add(&bmp_bgph, bmpbgp);
@@ -2054,30 +2245,29 @@ static void bmp_bgp_peer_vrf(struct bmp_bgp_peer *bbpeer, struct bgp *bgp)
*
* returns true if state has changed
*/
-bool bmp_bgp_update_vrf_status(struct bmp_bgp *bmpbgp, enum bmp_vrf_state force)
+bool bmp_bgp_update_vrf_status(enum bmp_vrf_state *vrf_state, struct bgp *bgp,
+ enum bmp_vrf_state force)
{
enum bmp_vrf_state old_state;
struct bmp_bgp_peer *bbpeer;
struct peer *peer;
struct vrf *vrf;
- struct bgp *bgp;
bool changed;
- if (!bmpbgp || !bmpbgp->bgp)
+ if (!vrf_state || !bgp)
return false;
- bgp = bmpbgp->bgp;
- old_state = bmpbgp->vrf_state;
+ old_state = *vrf_state;
vrf = bgp_vrf_lookup_by_instance_type(bgp);
- bmpbgp->vrf_state = force != vrf_state_unknown ? force
- : vrf_is_enabled(vrf) ? vrf_state_up
- : vrf_state_down;
+ *vrf_state = force != vrf_state_unknown ? force
+ : vrf_is_enabled(vrf) ? vrf_state_up
+ : vrf_state_down;
- changed = old_state != bmpbgp->vrf_state;
+ changed = old_state != *vrf_state;
if (changed) {
- peer = bmpbgp->bgp->peer_self;
- if (bmpbgp->vrf_state == vrf_state_up) {
+ peer = bgp->peer_self;
+ if (*vrf_state == vrf_state_up) {
bbpeer = bmp_bgp_peer_get(peer);
bmp_bgp_peer_vrf(bbpeer, bgp);
} else {
@@ -2085,6 +2275,7 @@ bool bmp_bgp_update_vrf_status(struct bmp_bgp *bmpbgp, enum bmp_vrf_state force)
if (bbpeer) {
XFREE(MTYPE_BMP_OPEN, bbpeer->open_tx);
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);
}
@@ -2129,6 +2320,8 @@ static struct bmp_targets *bmp_targets_find1(struct bgp *bgp, const char *name)
static struct bmp_targets *bmp_targets_get(struct bgp *bgp, const char *name)
{
struct bmp_targets *bt;
+ afi_t afi;
+ safi_t safi;
bt = bmp_targets_find1(bgp, name);
if (bt)
@@ -2139,6 +2332,8 @@ static struct bmp_targets *bmp_targets_get(struct bgp *bgp, const char *name)
bt->bgp = bgp;
bt->bmpbgp = bmp_bgp_get(bgp);
bt->stats_send_experimental = true;
+ FOREACH_AFI_SAFI (afi, safi)
+ bt->bgp_request_sync[afi][safi] = false;
bmp_session_init(&bt->sessions);
bmp_qhash_init(&bt->updhash);
bmp_qlist_init(&bt->updlist);
@@ -2146,16 +2341,25 @@ static struct bmp_targets *bmp_targets_get(struct bgp *bgp, const char *name)
bmp_qlist_init(&bt->locupdlist);
bmp_actives_init(&bt->actives);
bmp_listeners_init(&bt->listeners);
+ bmp_imported_bgps_init(&bt->imported_bgps);
QOBJ_REG(bt, bmp_targets);
bmp_targets_add(&bt->bmpbgp->targets, bt);
return bt;
}
+static void bmp_imported_bgp_free(struct bmp_imported_bgp *bib)
+{
+ if (bib->name)
+ XFREE(MTYPE_BMP_IMPORTED_BGP, bib->name);
+ XFREE(MTYPE_BMP_IMPORTED_BGP, bib);
+}
+
static void bmp_targets_put(struct bmp_targets *bt)
{
struct bmp *bmp;
struct bmp_active *ba;
+ struct bmp_imported_bgp *bib;
EVENT_OFF(bt->t_stats);
@@ -2170,6 +2374,10 @@ static void bmp_targets_put(struct bmp_targets *bt)
bmp_targets_del(&bt->bmpbgp->targets, bt);
QOBJ_UNREG(bt);
+ frr_each_safe (bmp_imported_bgps, &bt->imported_bgps, bib)
+ bmp_imported_bgp_free(bib);
+
+ bmp_imported_bgps_fini(&bt->imported_bgps);
bmp_listeners_fini(&bt->listeners);
bmp_actives_fini(&bt->actives);
bmp_qhash_fini(&bt->updhash);
@@ -2214,6 +2422,71 @@ static struct bmp_listener *bmp_listener_get(struct bmp_targets *bt,
return bl;
}
+static struct bmp_imported_bgp *bmp_imported_bgp_find(struct bmp_targets *bt, char *name)
+{
+ struct bmp_imported_bgp dummy;
+
+ dummy.name = name;
+ return bmp_imported_bgps_find(&bt->imported_bgps, &dummy);
+}
+
+static void bmp_send_all_bgp(struct peer *peer, bool down)
+{
+ struct bmp_bgp *bmpbgp = bmp_bgp_find(peer->bgp);
+ struct bgp *bgp_vrf;
+ struct listnode *node;
+ struct stream *s = NULL;
+ struct bmp_targets *bt;
+
+ s = bmp_peerstate(peer, down);
+ if (!s)
+ return;
+
+ if (bmpbgp) {
+ frr_each (bmp_targets, &bmpbgp->targets, bt)
+ bmp_send_bt(bt, s);
+ }
+ for (ALL_LIST_ELEMENTS_RO(bm->bgp, node, bgp_vrf)) {
+ bmpbgp = bmp_bgp_find(bgp_vrf);
+ if (!bmpbgp)
+ continue;
+ frr_each (bmp_targets, &bmpbgp->targets, bt) {
+ if (bgp_vrf != peer->bgp && !bmp_imported_bgp_find(bt, peer->bgp->name))
+ continue;
+ bmp_send_bt(bt, s);
+ }
+ }
+ stream_free(s);
+}
+
+static void bmp_imported_bgp_put(struct bmp_targets *bt, struct bmp_imported_bgp *bib)
+{
+ bmp_imported_bgps_del(&bt->imported_bgps, bib);
+ bmp_imported_bgp_free(bib);
+}
+
+static struct bmp_imported_bgp *bmp_imported_bgp_get(struct bmp_targets *bt, char *name)
+{
+ struct bmp_imported_bgp *bib = bmp_imported_bgp_find(bt, name);
+ afi_t afi;
+ safi_t safi;
+
+ if (bib)
+ return bib;
+
+ bib = XCALLOC(MTYPE_BMP_IMPORTED_BGP, sizeof(*bib));
+ if (name)
+ bib->name = XSTRDUP(MTYPE_BMP_IMPORTED_BGP, name);
+ bib->vrf_state = vrf_state_unknown;
+ FOREACH_AFI_SAFI (afi, safi)
+ bib->bgp_request_sync[afi][safi] = false;
+
+ bib->targets = bt;
+ bmp_imported_bgps_add(&bt->imported_bgps, bib);
+
+ return bib;
+}
+
static void bmp_listener_start(struct bmp_listener *bl)
{
int sock, ret;
@@ -2574,6 +2847,63 @@ DEFPY(no_bmp_targets_main,
return CMD_SUCCESS;
}
+DEFPY(bmp_import_vrf,
+ bmp_import_vrf_cmd,
+ "[no] bmp import-vrf-view VRFNAME$vrfname",
+ NO_STR
+ BMP_STR
+ "Import BMP information from another VRF\n"
+ "Specify the VRF or view instance name\n")
+{
+ VTY_DECLVAR_CONTEXT_SUB(bmp_targets, bt);
+ struct bmp_imported_bgp *bib;
+ struct bgp *bgp;
+ struct bmp *bmp;
+ afi_t afi;
+ safi_t safi;
+
+ if (!bt->bgp) {
+ vty_out(vty, "%% BMP target, BGP instance not found\n");
+ return CMD_WARNING;
+ }
+ if ((bt->bgp->name == NULL && vrfname == NULL) ||
+ (bt->bgp->name && vrfname && strmatch(vrfname, bt->bgp->name))) {
+ vty_out(vty, "%% BMP target, can not import our own BGP instance\n");
+ return CMD_WARNING;
+ }
+ if (no) {
+ bib = bmp_imported_bgp_find(bt, (char *)vrfname);
+ if (!bib) {
+ vty_out(vty, "%% BMP imported BGP instance not found\n");
+ return CMD_WARNING;
+ }
+ bgp = bgp_lookup_by_name(bib->name);
+ if (!bgp)
+ return CMD_WARNING;
+ bmp_send_peerdown_vrf_per_instance(bt, bgp);
+ bmp_imported_bgp_put(bt, bib);
+ return CMD_SUCCESS;
+ }
+ bib = bmp_imported_bgp_find(bt, (char *)vrfname);
+ if (bib)
+ return CMD_SUCCESS;
+
+ bib = bmp_imported_bgp_get(bt, (char *)vrfname);
+ bgp = bgp_lookup_by_name(bib->name);
+ if (!bgp)
+ return CMD_SUCCESS;
+
+ frr_each (bmp_session, &bt->sessions, bmp) {
+ if (bmp->state != BMP_PeerUp && bmp->state != BMP_Run)
+ continue;
+ bmp_send_peerup_per_instance(bmp, bgp);
+ bmp_send_peerup_vrf_per_instance(bmp, &bib->vrf_state, bgp);
+ FOREACH_AFI_SAFI (afi, safi)
+ bmp_update_syncro(bmp, afi, safi, bgp);
+ }
+ return CMD_SUCCESS;
+}
+
DEFPY(bmp_listener_main,
bmp_listener_cmd,
"bmp listener <X:X::X:X|A.B.C.D> port (1-65535)",
@@ -2776,19 +3106,8 @@ DEFPY(bmp_monitor_cfg, bmp_monitor_cmd,
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;
- }
+ frr_each (bmp_session, &bt->sessions, bmp)
+ bmp_update_syncro(bmp, afi, safi, NULL);
return CMD_SUCCESS;
}
@@ -3011,6 +3330,7 @@ static int bmp_config_write(struct bgp *bgp, struct vty *vty)
struct bmp_targets *bt;
struct bmp_listener *bl;
struct bmp_active *ba;
+ struct bmp_imported_bgp *bib;
afi_t afi;
safi_t safi;
@@ -3053,6 +3373,11 @@ static int bmp_config_write(struct bgp *bgp, struct vty *vty)
vty_out(vty, " bmp monitor %s %s loc-rib\n",
afi2str_lower(afi), safi2str(safi));
}
+
+ frr_each (bmp_imported_bgps, &bt->imported_bgps, bib)
+ vty_out(vty, " bmp import-vrf-view %s\n",
+ bib->name ? bib->name : VRF_DEFAULT_NAME);
+
frr_each (bmp_listeners, &bt->listeners, bl)
vty_out(vty, " bmp listener %pSU port %d\n", &bl->addr, bl->port);
@@ -3090,6 +3415,7 @@ static int bgp_bmp_init(struct event_loop *tm)
install_element(BMP_NODE, &bmp_stats_cmd);
install_element(BMP_NODE, &bmp_monitor_cmd);
install_element(BMP_NODE, &bmp_mirror_cmd);
+ install_element(BMP_NODE, &bmp_import_vrf_cmd);
install_element(BGP_NODE, &bmp_mirror_limit_cmd);
install_element(BGP_NODE, &no_bmp_mirror_limit_cmd);
@@ -3105,11 +3431,14 @@ static int bmp_route_update(struct bgp *bgp, afi_t afi, safi_t safi,
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;
-
+ struct bmp_bgp *bmpbgp;
+ struct bmp_targets *bt;
+ int ret = 0;
+ struct bgp *bgp_vrf;
+ struct listnode *node;
/* this should never happen */
if (!updated_route) {
@@ -3117,23 +3446,30 @@ static int bmp_route_update(struct bgp *bgp, afi_t afi, safi_t safi,
return 0;
}
- struct bmp_bgp *bmpbgp = bmp_bgp_find(bgp);
- struct peer *peer = updated_route->peer;
- struct bmp_targets *bt;
- struct bmp *bmp;
-
- if (!bmpbgp)
- return 0;
-
- frr_each (bmp_targets, &bmpbgp->targets, bt) {
- if (CHECK_FLAG(bt->afimon[afi][safi], BMP_MON_LOC_RIB)) {
- is_locribmon_enabled = true;
- break;
+ for (ALL_LIST_ELEMENTS_RO(bm->bgp, node, bgp_vrf)) {
+ bmpbgp = bmp_bgp_find(bgp_vrf);
+ if (!bmpbgp)
+ continue;
+ frr_each (bmp_targets, &bmpbgp->targets, bt) {
+ if (!CHECK_FLAG(bt->afimon[afi][safi], BMP_MON_LOC_RIB))
+ continue;
+ if (bgp_vrf != bgp && !bmp_imported_bgp_find(bt, bgp->name))
+ continue;
+ ret = bmp_route_update_bgpbmp(bt, afi, safi, bn, old_route, new_route);
}
}
+ return ret;
+}
- if (!is_locribmon_enabled)
- return 0;
+static int bmp_route_update_bgpbmp(struct bmp_targets *bt, afi_t afi, safi_t safi,
+ struct bgp_dest *bn, struct bgp_path_info *old_route,
+ struct bgp_path_info *new_route)
+{
+ bool is_withdraw = old_route && !new_route;
+ struct bgp_path_info *updated_route = is_withdraw ? old_route : new_route;
+ struct peer *peer = updated_route->peer;
+ struct bmp *bmp;
+ struct bmp_queue_entry *last_item;
/* route is not installed in locrib anymore and rib uptime was saved */
if (old_route && old_route->extra)
@@ -3147,26 +3483,20 @@ static int bmp_route_update(struct bgp *bgp, afi_t afi, safi_t safi,
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);
+ last_item = bmp_process_one(bt, &bt->locupdhash, &bt->locupdlist, bt->bgp, afi, safi, bn,
+ peer);
- /* if bmp_process_one returns NULL
- * we don't have anything to do next
- */
- if (!last_item)
- continue;
+ /* if bmp_process_one returns NULL
+ * we don't have anything to do next
+ */
+ if (!last_item)
+ return 0;
- frr_each (bmp_session, &bt->sessions, bmp) {
- if (!bmp->locrib_queuepos)
- bmp->locrib_queuepos = last_item;
+ frr_each (bmp_session, &bt->sessions, bmp) {
+ if (!bmp->locrib_queuepos)
+ bmp->locrib_queuepos = last_item;
- pullwr_bump(bmp->pullwr);
- };
- }
+ pullwr_bump(bmp->pullwr);
};
return 0;
@@ -3179,24 +3509,76 @@ static int bgp_bmp_early_fini(void)
return 0;
}
+static int bmp_bgp_attribute_updated_instance(struct bmp_targets *bt, enum bmp_vrf_state *vrf_state,
+ struct bgp *bgp, bool withdraw, struct stream *s)
+{
+ bmp_bgp_update_vrf_status(vrf_state, bgp, vrf_state_unknown);
+ if (*vrf_state == vrf_state_down)
+ /* do not send peer events, router id will not be enough to set state to up
+ */
+ return 0;
+
+ /* vrf_state is up: trigger a peer event
+ */
+ bmp_send_bt(bt, s);
+ return 1;
+}
+
/* called when the routerid of an instance changes */
static int bmp_bgp_attribute_updated(struct bgp *bgp, bool withdraw)
{
struct bmp_bgp *bmpbgp = bmp_bgp_find(bgp);
+ struct bgp *bgp_vrf;
+ struct bmp_targets *bt;
+ struct listnode *node;
+ struct bmp_imported_bgp *bib;
+ int ret = 0;
+ struct stream *s = bmp_peerstate(bgp->peer_self, withdraw);
+ struct bmp *bmp;
+ afi_t afi;
+ safi_t safi;
- if (!bmpbgp)
+ if (!s)
return 0;
- bmp_bgp_update_vrf_status(bmpbgp, vrf_state_unknown);
-
- if (bmpbgp->vrf_state == vrf_state_down)
- /* do not send peer events, router id will not be enough to set state to up
- */
- return 0;
+ if (bmpbgp) {
+ frr_each (bmp_targets, &bmpbgp->targets, bt) {
+ ret = bmp_bgp_attribute_updated_instance(bt, &bmpbgp->vrf_state, bgp,
+ withdraw, s);
+ if (withdraw)
+ continue;
+ frr_each (bmp_session, &bt->sessions, bmp) {
+ bmp_send_peerup_per_instance(bmp, bgp);
+ FOREACH_AFI_SAFI (afi, safi)
+ bmp_update_syncro(bmp, afi, safi, bgp);
+ }
+ }
+ }
- /* vrf_state is up: trigger a peer event
- */
- bmp_send_all_safe(bmpbgp, bmp_peerstate(bgp->peer_self, withdraw));
+ for (ALL_LIST_ELEMENTS_RO(bm->bgp, node, bgp_vrf)) {
+ if (bgp == bgp_vrf)
+ continue;
+ bmpbgp = bmp_bgp_find(bgp_vrf);
+ if (!bmpbgp)
+ continue;
+ frr_each (bmp_targets, &bmpbgp->targets, bt) {
+ frr_each (bmp_imported_bgps, &bt->imported_bgps, bib) {
+ if (bgp_lookup_by_name(bib->name) != bgp)
+ continue;
+ ret += bmp_bgp_attribute_updated_instance(bt, &bib->vrf_state, bgp,
+ withdraw, s);
+ if (withdraw)
+ continue;
+ frr_each (bmp_session, &bt->sessions, bmp) {
+ bmp_send_peerup_per_instance(bmp, bgp);
+ FOREACH_AFI_SAFI (afi, safi) {
+ bmp_update_syncro(bmp, afi, safi, bgp);
+ }
+ }
+ }
+ }
+ }
+ stream_free(s);
return 1;
}
@@ -3210,19 +3592,67 @@ static int bmp_route_distinguisher_update(struct bgp *bgp, afi_t afi, bool preco
return bmp_bgp_attribute_updated(bgp, preconfig);
}
-/* called when a bgp instance goes up/down, implying that the underlying VRF
- * has been created or deleted in zebra
- */
-static int bmp_vrf_state_changed(struct bgp *bgp)
+static void _bmp_vrf_state_changed_internal(struct bgp *bgp, enum bmp_vrf_state vrf_state)
{
struct bmp_bgp *bmpbgp = bmp_bgp_find(bgp);
+ struct bgp *bgp_vrf;
+ struct bmp_targets *bt;
+ struct listnode *node;
+ struct bmp_imported_bgp *bib;
+ struct bmp *bmp;
+ afi_t afi;
+ safi_t safi;
- if (!bmp_bgp_update_vrf_status(bmpbgp, vrf_state_unknown))
- return 1;
+ if (bmpbgp && bmp_bgp_update_vrf_status(&bmpbgp->vrf_state, bgp, vrf_state)) {
+ bmp_send_all_safe(bmpbgp, bmp_peerstate(bgp->peer_self,
+ bmpbgp->vrf_state == vrf_state_down));
+ if (vrf_state == vrf_state_up && bmpbgp->vrf_state == vrf_state_up) {
+ frr_each (bmp_targets, &bmpbgp->targets, bt) {
+ frr_each (bmp_session, &bt->sessions, bmp) {
+ bmp_send_peerup_per_instance(bmp, bgp);
+ FOREACH_AFI_SAFI (afi, safi)
+ bmp_update_syncro(bmp, afi, safi, bgp);
+ }
+ }
+ }
+ }
- bmp_send_all_safe(bmpbgp,
- bmp_peerstate(bgp->peer_self, bmpbgp->vrf_state == vrf_state_down));
+ for (ALL_LIST_ELEMENTS_RO(bm->bgp, node, bgp_vrf)) {
+ bmpbgp = bmp_bgp_find(bgp_vrf);
+ if (!bmpbgp)
+ continue;
+ if (bgp_vrf == bgp)
+ continue;
+ frr_each (bmp_targets, &bmpbgp->targets, bt) {
+ frr_each (bmp_imported_bgps, &bt->imported_bgps, bib) {
+ if (bgp_lookup_by_name(bib->name) != bgp)
+ continue;
+ if (bmp_bgp_update_vrf_status(&bib->vrf_state, bgp, vrf_state)) {
+ bmp_send_bt_safe(bt, bmp_peerstate(bgp->peer_self,
+ bib->vrf_state ==
+ vrf_state_down));
+ if (vrf_state == vrf_state_up &&
+ bib->vrf_state == vrf_state_up) {
+ frr_each (bmp_session, &bt->sessions, bmp) {
+ bmp_send_peerup_per_instance(bmp, bgp);
+ FOREACH_AFI_SAFI (afi, safi)
+ bmp_update_syncro(bmp, afi, safi,
+ bgp);
+ }
+ }
+ break;
+ }
+ }
+ }
+ }
+}
+/* called when a bgp instance goes up/down, implying that the underlying VRF
+ * has been created or deleted in zebra
+ */
+static int bmp_vrf_state_changed(struct bgp *bgp)
+{
+ _bmp_vrf_state_changed_internal(bgp, vrf_state_unknown);
return 0;
}
@@ -3231,7 +3661,6 @@ static int bmp_vrf_state_changed(struct bgp *bgp)
*/
static int bmp_vrf_itf_state_changed(struct bgp *bgp, struct interface *itf)
{
- struct bmp_bgp *bmpbgp;
enum bmp_vrf_state new_state;
/* if the update is not about the vrf device double-check
@@ -3240,10 +3669,8 @@ static int bmp_vrf_itf_state_changed(struct bgp *bgp, struct interface *itf)
if (!itf || !if_is_vrf(itf))
return bmp_vrf_state_changed(bgp);
- bmpbgp = bmp_bgp_find(bgp);
new_state = if_is_up(itf) ? vrf_state_up : vrf_state_down;
- if (bmp_bgp_update_vrf_status(bmpbgp, new_state))
- bmp_send_all(bmpbgp, bmp_peerstate(bgp->peer_self, new_state == vrf_state_down));
+ _bmp_vrf_state_changed_internal(bgp, new_state);
return 0;
}
diff --git a/bgpd/bgp_bmp.h b/bgpd/bgp_bmp.h
index d45a4278f..d81b8f9b0 100644
--- a/bgpd/bgp_bmp.h
+++ b/bgpd/bgp_bmp.h
@@ -92,7 +92,7 @@ struct bmp_mirrorq {
uint8_t data[0];
};
-enum {
+enum bmp_afi_state {
BMP_AFI_INACTIVE = 0,
BMP_AFI_NEEDSYNC,
BMP_AFI_SYNC,
@@ -148,6 +148,7 @@ struct bmp {
uint64_t syncpeerid;
afi_t syncafi;
safi_t syncsafi;
+ struct bgp *sync_bgp;
};
/* config & state for an active outbound connection. When the connection
@@ -195,6 +196,9 @@ struct bmp_listener {
int sock;
};
+/* config for imported bgp instances */
+PREDECL_SORTLIST_UNIQ(bmp_imported_bgps);
+
/* 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.
@@ -206,6 +210,7 @@ struct bmp_targets {
struct bmp_bgp *bmpbgp;
struct bgp *bgp;
+ bool bgp_request_sync[AFI_MAX][SAFI_MAX];
char *name;
struct bmp_listeners_head listeners;
@@ -238,6 +243,8 @@ struct bmp_targets {
struct bmp_qhash_head locupdhash;
struct bmp_qlist_head locupdlist;
+ struct bmp_imported_bgps_head imported_bgps;
+
uint64_t cnt_accept, cnt_aclrefused;
bool stats_send_experimental;
@@ -274,6 +281,14 @@ enum bmp_vrf_state {
vrf_state_up = 1,
};
+struct bmp_imported_bgp {
+ struct bmp_imported_bgps_item bib;
+ struct bmp_targets *targets;
+ char *name;
+ enum bmp_vrf_state vrf_state;
+ bool bgp_request_sync[AFI_MAX][SAFI_MAX];
+};
+
struct bmp_bgp {
struct bmp_bgph_item bbi;
@@ -289,7 +304,8 @@ struct bmp_bgp {
size_t mirror_qsizelimit;
};
-extern bool bmp_bgp_update_vrf_status(struct bmp_bgp *bmpbgp, enum bmp_vrf_state force);
+extern bool bmp_bgp_update_vrf_status(enum bmp_vrf_state *vrf_state, struct bgp *bgp,
+ enum bmp_vrf_state force);
enum {
/* RFC7854 - 10.8 */
diff --git a/bgpd/bgp_debug.c b/bgpd/bgp_debug.c
index 097d3684f..319638e41 100644
--- a/bgpd/bgp_debug.c
+++ b/bgpd/bgp_debug.c
@@ -60,6 +60,7 @@ 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 conf_bgp_debug_aggregate;
unsigned long term_bgp_debug_as4;
unsigned long term_bgp_debug_neighbor_events;
@@ -80,6 +81,7 @@ 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;
+unsigned long term_bgp_debug_aggregate;
struct list *bgp_debug_neighbor_events_peers = NULL;
struct list *bgp_debug_keepalive_peers = NULL;
@@ -88,6 +90,7 @@ 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;
+struct list *bgp_debug_aggregate_prefixes;
/* messages for BGP-4 status */
const struct message bgp_status_msg[] = {{Idle, "Idle"},
@@ -1812,6 +1815,107 @@ DEFPY (no_debug_bgp_zebra_prefix,
return CMD_SUCCESS;
}
+/* debug bgp aggregate */
+DEFPY (debug_bgp_aggregate,
+ debug_bgp_aggregate_cmd,
+ "debug bgp aggregate",
+ DEBUG_STR
+ BGP_STR
+ "BGP aggregate\n")
+{
+ if (vty->node == CONFIG_NODE)
+ DEBUG_ON(aggregate, AGGREGATE);
+ else {
+ TERM_DEBUG_ON(aggregate, AGGREGATE);
+ vty_out(vty, "BGP aggregate debugging is on\n");
+ }
+ return CMD_SUCCESS;
+}
+
+DEFPY (no_debug_bgp_aggregate,
+ no_debug_bgp_aggregate_cmd,
+ "no debug bgp aggregate",
+ NO_STR
+ DEBUG_STR
+ BGP_STR
+ "BGP aggregate\n")
+{
+ bgp_debug_list_free(bgp_debug_aggregate_prefixes);
+
+ if (vty->node == CONFIG_NODE)
+ DEBUG_OFF(aggregate, AGGREGATE);
+ else {
+ TERM_DEBUG_OFF(aggregate, AGGREGATE);
+ vty_out(vty, "BGP aggregate debugging is off\n");
+ }
+ return CMD_SUCCESS;
+}
+
+DEFPY (debug_bgp_aggregate_prefix,
+ debug_bgp_aggregate_prefix_cmd,
+ "debug bgp aggregate prefix <A.B.C.D/M|X:X::X:X/M>$prefix",
+ DEBUG_STR
+ BGP_STR
+ "BGP aggregate\n"
+ "Specify a prefix to debug\n"
+ "IPv4 prefix\n"
+ "IPv6 prefix\n")
+{
+ if (!bgp_debug_aggregate_prefixes)
+ bgp_debug_aggregate_prefixes = list_new();
+
+ if (bgp_debug_list_has_entry(bgp_debug_aggregate_prefixes, NULL, prefix, NULL)) {
+ vty_out(vty, "BGP aggregate debugging is already enabled for %s\n", prefix_str);
+ return CMD_SUCCESS;
+ }
+
+ bgp_debug_list_add_entry(bgp_debug_aggregate_prefixes, NULL, prefix, NULL);
+
+ if (vty->node == CONFIG_NODE)
+ DEBUG_ON(aggregate, AGGREGATE);
+ else {
+ TERM_DEBUG_ON(aggregate, AGGREGATE);
+ vty_out(vty, "BGP aggregate debugging is on for %s\n", prefix_str);
+ }
+
+ return CMD_SUCCESS;
+}
+
+DEFPY (no_debug_bgp_aggregate_prefix,
+ no_debug_bgp_aggregate_prefix_cmd,
+ "no debug bgp aggregate prefix <A.B.C.D/M|X:X::X:X/M>$prefix",
+ NO_STR
+ DEBUG_STR
+ BGP_STR
+ "BGP aggregate\n"
+ "Specify a prefix to debug\n"
+ "IPv4 prefix\n"
+ "IPv6 prefix\n")
+{
+ bool found_prefix = false;
+
+ if (bgp_debug_aggregate_prefixes && !list_isempty(bgp_debug_aggregate_prefixes)) {
+ found_prefix = bgp_debug_list_remove_entry(bgp_debug_aggregate_prefixes, NULL,
+ (struct prefix *)prefix);
+
+ if (list_isempty(bgp_debug_aggregate_prefixes)) {
+ if (vty->node == CONFIG_NODE)
+ DEBUG_OFF(aggregate, AGGREGATE);
+ else {
+ TERM_DEBUG_OFF(aggregate, AGGREGATE);
+ vty_out(vty, "BGP aggregate debugging is off\n");
+ }
+ }
+ }
+
+ if (found_prefix)
+ vty_out(vty, "BGP aggregate debugging is off for %s\n", prefix_str);
+ else
+ vty_out(vty, "BGP aggregate 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,
@@ -2239,6 +2343,10 @@ DEFUN_NOSH (show_debugging_bgp,
bgp_debug_list_print(vty, " BGP zebra debugging is on",
bgp_debug_zebra_prefixes);
+ if (BGP_DEBUG(aggregate, AGGREGATE))
+ bgp_debug_list_print(vty, " BGP aggregate debugging is on",
+ bgp_debug_aggregate_prefixes);
+
if (BGP_DEBUG(graceful_restart, GRACEFUL_RESTART))
vty_out(vty, " BGP graceful-restart debugging is on\n");
@@ -2412,6 +2520,16 @@ static int bgp_config_write_debug(struct vty *vty)
write++;
}
+ if (CONF_BGP_DEBUG(aggregate, AGGREGATE)) {
+ if (!bgp_debug_aggregate_prefixes || list_isempty(bgp_debug_aggregate_prefixes)) {
+ vty_out(vty, "debug bgp aggregate\n");
+ write++;
+ } else {
+ write += bgp_debug_list_conf_print(vty, "debug bgp aggregate prefix",
+ bgp_debug_aggregate_prefixes);
+ }
+ }
+
if (hook_call(bgp_hook_config_write_debug, vty, true))
write++;
@@ -2485,6 +2603,16 @@ void bgp_debug_init(void)
install_element(ENABLE_NODE, &no_debug_bgp_zebra_prefix_cmd);
install_element(CONFIG_NODE, &no_debug_bgp_zebra_prefix_cmd);
+ /* debug bgp aggregate prefix A.B.C.D/M */
+ install_element(ENABLE_NODE, &debug_bgp_aggregate_cmd);
+ install_element(CONFIG_NODE, &debug_bgp_aggregate_cmd);
+ install_element(ENABLE_NODE, &no_debug_bgp_aggregate_cmd);
+ install_element(CONFIG_NODE, &no_debug_bgp_aggregate_cmd);
+ install_element(ENABLE_NODE, &debug_bgp_aggregate_prefix_cmd);
+ install_element(CONFIG_NODE, &debug_bgp_aggregate_prefix_cmd);
+ install_element(ENABLE_NODE, &no_debug_bgp_aggregate_prefix_cmd);
+ install_element(CONFIG_NODE, &no_debug_bgp_aggregate_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);
@@ -2714,6 +2842,17 @@ bool bgp_debug_zebra(const struct prefix *p)
return false;
}
+bool bgp_debug_aggregate(const struct prefix *p)
+{
+ if (BGP_DEBUG(aggregate, AGGREGATE)) {
+ if (bgp_debug_per_prefix(p, term_bgp_debug_aggregate, BGP_DEBUG_AGGREGATE,
+ bgp_debug_aggregate_prefixes))
+ return true;
+ }
+
+ return false;
+}
+
const char *bgp_debug_rdpfxpath2str(afi_t afi, safi_t safi,
const struct prefix_rd *prd,
union prefixconstptr pu,
diff --git a/bgpd/bgp_debug.h b/bgpd/bgp_debug.h
index 061d966dc..11b5e5209 100644
--- a/bgpd/bgp_debug.h
+++ b/bgpd/bgp_debug.h
@@ -71,6 +71,7 @@ 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 conf_bgp_debug_aggregate;
extern unsigned long term_bgp_debug_as4;
extern unsigned long term_bgp_debug_neighbor_events;
@@ -89,6 +90,7 @@ 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 unsigned long term_bgp_debug_aggregate;
extern struct list *bgp_debug_neighbor_events_peers;
extern struct list *bgp_debug_keepalive_peers;
@@ -97,6 +99,7 @@ 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;
+extern struct list *bgp_debug_aggregate_prefixes;
struct bgp_debug_filter {
char *host;
@@ -135,6 +138,7 @@ struct bgp_debug_filter {
#define BGP_DEBUG_BFD_LIB 0x01
#define BGP_DEBUG_COND_ADV 0x01
+#define BGP_DEBUG_AGGREGATE 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))
@@ -172,6 +176,7 @@ 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 bool bgp_debug_aggregate(const struct prefix *p);
extern const char *bgp_debug_rdpfxpath2str(
afi_t afi, safi_t safi, const struct prefix_rd *prd,
diff --git a/bgpd/bgp_dump.c b/bgpd/bgp_dump.c
index 53b521248..e71835d1c 100644
--- a/bgpd/bgp_dump.c
+++ b/bgpd/bgp_dump.c
@@ -480,8 +480,8 @@ static void bgp_dump_common(struct stream *obuf, struct peer *peer,
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,
+ if (peer->connection->su_local)
+ stream_put(obuf, &peer->connection->su_local->sin.sin_addr,
IPV4_MAX_BYTELEN);
else
stream_put(obuf, empty, IPV4_MAX_BYTELEN);
@@ -494,8 +494,8 @@ static void bgp_dump_common(struct stream *obuf, struct peer *peer,
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,
+ if (peer->connection->su_local)
+ stream_put(obuf, &peer->connection->su_local->sin6.sin6_addr,
IPV6_MAX_BYTELEN);
else
stream_put(obuf, empty, IPV6_MAX_BYTELEN);
diff --git a/bgpd/bgp_fsm.c b/bgpd/bgp_fsm.c
index 3d02214ca..0e3ed9f0d 100644
--- a/bgpd/bgp_fsm.c
+++ b/bgpd/bgp_fsm.c
@@ -1722,8 +1722,8 @@ bgp_connect_success(struct peer_connection *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);
+ zlog_debug("%s open active, local address %pSU", peer->host,
+ connection->su_local);
else
zlog_debug("%s passive open", peer->host);
}
@@ -1768,8 +1768,8 @@ bgp_connect_success_w_delayopen(struct peer_connection *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);
+ zlog_debug("%s open active, local address %pSU", peer->host,
+ connection->su_local);
else
zlog_debug("%s passive open", peer->host);
}
@@ -1819,14 +1819,13 @@ static void bgp_connect_in_progress_update_connection(struct peer_connection *co
{
struct peer *peer = connection->peer;
- bgp_updatesockname(peer, connection);
- if (!peer->su_remote && !BGP_CONNECTION_SU_UNSPEC(peer->connection)) {
+ if (!connection->su_remote && !BGP_CONNECTION_SU_UNSPEC(connection)) {
/* if connect initiated, then dest port and dest addresses are well known */
- peer->su_remote = sockunion_dup(&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);
+ connection->su_remote = sockunion_dup(&connection->su);
+ if (sockunion_family(connection->su_remote) == AF_INET)
+ connection->su_remote->sin.sin_port = htons(peer->port);
+ else if (sockunion_family(connection->su_remote) == AF_INET6)
+ connection->su_remote->sin6.sin6_port = htons(peer->port);
}
}
diff --git a/bgpd/bgp_mplsvpn.c b/bgpd/bgp_mplsvpn.c
index b96c287f8..46e529f03 100644
--- a/bgpd/bgp_mplsvpn.c
+++ b/bgpd/bgp_mplsvpn.c
@@ -1312,8 +1312,8 @@ leak_update(struct bgp *to_bgp, struct bgp_dest *bn,
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_aggregate_increment(to_bgp, p, new, afi, safi);
bgp_process(to_bgp, bn, new, afi, safi);
@@ -1951,7 +1951,7 @@ void vpn_leak_from_vrf_update(struct bgp *to_bgp, /* to */
* because of loop checking.
*/
if (new_info)
- vpn_leak_to_vrf_update(from_bgp, new_info, NULL);
+ vpn_leak_to_vrf_update(from_bgp, new_info, NULL, path_vrf->peer);
else
bgp_dest_unlock_node(bn);
}
@@ -2143,10 +2143,10 @@ static struct bgp *bgp_lookup_by_rd(struct bgp_path_info *bpi,
return NULL;
}
-static void vpn_leak_to_vrf_update_onevrf(struct bgp *to_bgp, /* to */
+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)
+ struct bgp_path_info *path_vpn, struct prefix_rd *prd,
+ struct peer *from)
{
const struct prefix *p = bgp_dest_get_prefix(path_vpn->net);
afi_t afi = family2afi(p->family);
@@ -2231,6 +2231,12 @@ static void vpn_leak_to_vrf_update_onevrf(struct bgp *to_bgp, /* to */
/* Check if leaked route has our asn. If so, don't import it. */
if (CHECK_FLAG(peer->af_flags[afi][SAFI_MPLS_VPN], PEER_FLAG_ALLOWAS_IN))
aspath_loop_count = peer->allowas_in[afi][SAFI_MPLS_VPN];
+ else if (peer == peer->bgp->peer_self && from)
+ /* If this is an import from one VRF to another and the source
+ * VRF's peer has allowas-in applied, respect it.
+ */
+ aspath_loop_count = from->allowas_in[afi][SAFI_UNICAST];
+
if (aspath_loop_check(path_vpn->attr->aspath, to_bgp->as) > aspath_loop_count) {
for (bpi = bgp_dest_get_bgp_path_info(bn); bpi;
bpi = bpi->next) {
@@ -2511,9 +2517,8 @@ bool vpn_leak_to_vrf_no_retain_filter_check(struct bgp *from_bgp,
return true;
}
-void vpn_leak_to_vrf_update(struct bgp *from_bgp,
- struct bgp_path_info *path_vpn,
- struct prefix_rd *prd)
+void vpn_leak_to_vrf_update(struct bgp *from_bgp, struct bgp_path_info *path_vpn,
+ struct prefix_rd *prd, struct peer *peer)
{
struct listnode *mnode, *mnnode;
struct bgp *bgp;
@@ -2528,8 +2533,7 @@ void vpn_leak_to_vrf_update(struct bgp *from_bgp,
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);
+ vpn_leak_to_vrf_update_onevrf(bgp, from_bgp, path_vpn, prd, peer);
}
}
}
@@ -2728,8 +2732,8 @@ void vpn_leak_to_vrf_update_all(struct bgp *to_bgp, struct bgp *vpn_from,
bpi->extra->vrfleak->bgp_orig == to_bgp)
continue;
- vpn_leak_to_vrf_update_onevrf(to_bgp, vpn_from,
- bpi, NULL);
+ vpn_leak_to_vrf_update_onevrf(to_bgp, vpn_from, bpi, NULL,
+ bpi->peer);
}
}
}
diff --git a/bgpd/bgp_mplsvpn.h b/bgpd/bgp_mplsvpn.h
index 18639fc69..56dd33f9b 100644
--- a/bgpd/bgp_mplsvpn.h
+++ b/bgpd/bgp_mplsvpn.h
@@ -67,9 +67,8 @@ 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_update(struct bgp *from_bgp, struct bgp_path_info *path_vpn,
+ struct prefix_rd *prd, struct peer *peer);
extern void vpn_leak_to_vrf_withdraw(struct bgp_path_info *path_vpn);
diff --git a/bgpd/bgp_network.c b/bgpd/bgp_network.c
index f1bea1c18..af5d815d3 100644
--- a/bgpd/bgp_network.c
+++ b/bgpd/bgp_network.c
@@ -484,6 +484,9 @@ static void bgp_accept(struct event *thread)
/* Dynamic neighbor has been created, let it proceed */
connection1->fd = bgp_sock;
+ connection1->su_local = sockunion_getsockname(connection1->fd);
+ connection1->su_remote = sockunion_dup(&su);
+
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)",
@@ -623,7 +626,10 @@ static void bgp_accept(struct event *thread)
peer->doppelganger = peer1;
peer1->doppelganger = peer;
+
connection->fd = bgp_sock;
+ connection->su_local = sockunion_getsockname(connection->fd);
+ connection->su_remote = sockunion_dup(&su);
if (bgp_set_socket_ttl(connection) < 0)
if (bgp_debug_neighbor_events(peer))
@@ -857,24 +863,28 @@ enum connect_result bgp_connect(struct peer_connection *connection)
peer->host, connection->fd);
/* Connect to the remote peer. */
- return sockunion_connect(connection->fd, &connection->su,
- htons(peer->port), ifindex);
-}
+ enum connect_result res;
-void bgp_updatesockname(struct peer *peer, struct peer_connection *connection)
-{
- if (peer->su_local) {
- sockunion_free(peer->su_local);
- peer->su_local = NULL;
- }
+ res = sockunion_connect(connection->fd, &connection->su, htons(peer->port), ifindex);
- if (peer->su_remote) {
- sockunion_free(peer->su_remote);
- peer->su_remote = NULL;
+ if (connection->su_remote)
+ sockunion_free(connection->su_remote);
+
+ connection->su_remote = sockunion_dup(&connection->su);
+ switch (connection->su.sa.sa_family) {
+ case AF_INET:
+ connection->su_remote->sin.sin_port = htons(peer->port);
+ break;
+ case AF_INET6:
+ connection->su_remote->sin6.sin6_port = htons(peer->port);
+ break;
}
- peer->su_local = sockunion_getsockname(connection->fd);
- peer->su_remote = sockunion_getpeername(connection->fd);
+ if (connection->su_local)
+ sockunion_free(connection->su_local);
+ connection->su_local = sockunion_getsockname(connection->fd);
+
+ return res;
}
/* After TCP connection is established. Get local address and port. */
@@ -882,17 +892,13 @@ int bgp_getsockname(struct peer_connection *connection)
{
struct peer *peer = connection->peer;
- bgp_updatesockname(peer, peer->connection);
-
- 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)");
+ if (!bgp_zebra_nexthop_set(connection->su_local, connection->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, connection->su_local, connection->su_remote,
+ peer->update_if ? peer->update_if : "(None)",
+ peer->nexthop.ifp ? peer->nexthop.ifp->name : "(Unknown)");
return -1;
}
return 0;
diff --git a/bgpd/bgp_network.h b/bgpd/bgp_network.h
index ed1a72ec8..a2f4851f1 100644
--- a/bgpd/bgp_network.h
+++ b/bgpd/bgp_network.h
@@ -23,7 +23,6 @@ extern void bgp_close_vrf_socket(struct bgp *bgp);
extern void bgp_close(void);
extern enum connect_result bgp_connect(struct peer_connection *connection);
extern int bgp_getsockname(struct peer_connection *connection);
-extern void bgp_updatesockname(struct peer *peer, struct peer_connection *connection);
extern int bgp_md5_set_prefix(struct bgp *bgp, struct prefix *p,
const char *password);
diff --git a/bgpd/bgp_packet.c b/bgpd/bgp_packet.c
index c5e390b04..ca2e8de04 100644
--- a/bgpd/bgp_packet.c
+++ b/bgpd/bgp_packet.c
@@ -1620,9 +1620,38 @@ void bgp_capability_send(struct peer *peer, afi_t afi, safi_t safi,
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_ENHE:
+ FOREACH_AFI_SAFI (afi, safi) {
+ if (!peer->afc[afi][safi])
+ continue;
+
+ bgp_map_afi_safi_int2iana(afi, safi, &pkt_afi, &pkt_safi);
+
+ 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)) {
+ stream_putc(s, action);
+ stream_putc(s, CAPABILITY_CODE_ENHE);
+ stream_putc(s, CAPABILITY_CODE_ENHE_LEN);
+ stream_putw(s, pkt_afi);
+ stream_putw(s, pkt_safi);
+ stream_putw(s, afi_int2iana(AFI_IP6));
+
+ COND_FLAG(peer->af_cap[AFI_IP][safi], PEER_CAP_ENHE_AF_ADV,
+ action == CAPABILITY_ACTION_SET);
+
+ if (CHECK_FLAG(peer->af_cap[afi][safi], PEER_CAP_ENHE_AF_RCV))
+ COND_FLAG(peer->af_cap[afi][safi], PEER_CAP_ENHE_AF_NEGO,
+ action == CAPABILITY_ACTION_SET);
+ }
+ }
+ COND_FLAG(peer->cap, PEER_CAP_ENHE_ADV, action == CAPABILITY_ACTION_SET);
+ update_group_adjust_peer_afs(peer);
+ bgp_announce_route_all(peer);
+ break;
case CAPABILITY_CODE_ROLE:
stream_putc(s, action);
stream_putc(s, CAPABILITY_CODE_ROLE);
@@ -3321,6 +3350,81 @@ ignore:
}
}
+static void bgp_dynamic_capability_enhe(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;
+
+ if (data + CAPABILITY_CODE_ENHE_LEN > end) {
+ flog_warn(EC_BGP_CAPABILITY_INVALID_LENGTH,
+ "Extended NH: Received invalid length %zu, less than %d", len,
+ CAPABILITY_CODE_ENHE_LEN);
+ return;
+ }
+
+ if (action == CAPABILITY_ACTION_SET) {
+ if (hdr->length % CAPABILITY_CODE_ENHE_LEN) {
+ flog_warn(EC_BGP_CAPABILITY_INVALID_LENGTH,
+ "Extended NH: Received invalid length %d, non-multiple of %d",
+ hdr->length, CAPABILITY_CODE_ENHE_LEN);
+ return;
+ }
+
+ while (data + CAPABILITY_CODE_ENHE_LEN <= end) {
+ afi_t afi;
+ safi_t safi;
+ afi_t nh_afi;
+ struct bgp_enhe_capability bec = {};
+
+ memcpy(&bec, data, sizeof(bec));
+ afi = ntohs(bec.afi);
+ safi = ntohs(bec.safi);
+ nh_afi = afi_iana2int(ntohs(bec.nh_afi));
+
+ /* 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).
+ */
+ 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, afi2str(afi), safi2str(safi), nh_afi);
+ goto ignore;
+ }
+
+ 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);
+
+ignore:
+ data += CAPABILITY_CODE_ENHE_LEN;
+ }
+
+ SET_FLAG(peer->cap, PEER_CAP_ENHE_RCV);
+ update_group_adjust_peer_afs(peer);
+ bgp_announce_route_all(peer);
+ } else {
+ afi_t afi;
+ safi_t safi;
+
+ UNSET_FLAG(peer->cap, PEER_CAP_ENHE_RCV);
+
+ FOREACH_AFI_SAFI (afi, safi) {
+ UNSET_FLAG(peer->af_cap[afi][safi], PEER_CAP_ENHE_AF_RCV);
+
+ if (CHECK_FLAG(peer->af_cap[afi][safi], PEER_CAP_ENHE_AF_ADV))
+ UNSET_FLAG(peer->af_cap[afi][safi], PEER_CAP_ENHE_AF_NEGO);
+ }
+ }
+}
+
static void bgp_dynamic_capability_orf(uint8_t *pnt, int action,
struct capability_header *hdr,
struct peer *peer)
@@ -3943,9 +4047,11 @@ static int bgp_capability_msg_parse(struct peer *peer, uint8_t *pnt,
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_ENHE:
+ bgp_dynamic_capability_enhe(pnt, action, hdr, peer);
+ break;
case CAPABILITY_CODE_ROLE:
bgp_dynamic_capability_role(pnt, action, peer);
break;
diff --git a/bgpd/bgp_packet.h b/bgpd/bgp_packet.h
index c266b1726..866b8f617 100644
--- a/bgpd/bgp_packet.h
+++ b/bgpd/bgp_packet.h
@@ -8,6 +8,12 @@
#include "hook.h"
+struct bgp_enhe_capability {
+ uint16_t afi;
+ uint16_t safi;
+ uint16_t nh_afi;
+};
+
DECLARE_HOOK(bgp_packet_dump,
(struct peer *peer, uint8_t type, bgp_size_t size,
struct stream *s),
diff --git a/bgpd/bgp_route.c b/bgpd/bgp_route.c
index f51953419..a1b12e5a8 100644
--- a/bgpd/bgp_route.c
+++ b/bgpd/bgp_route.c
@@ -1576,17 +1576,17 @@ int bgp_path_info_cmp(struct bgp *bgp, struct bgp_path_info *new,
}
/* locally configured routes to advertise do not have su_remote */
- if (peer_new->su_remote == NULL) {
+ if (peer_new->connection->su_remote == NULL) {
*reason = bgp_path_selection_local_configured;
return 0;
}
- if (peer_exist->su_remote == NULL) {
+ if (peer_exist->connection->su_remote == NULL) {
*reason = bgp_path_selection_local_configured;
return 1;
}
- ret = sockunion_cmp(peer_new->su_remote, peer_exist->su_remote);
+ ret = sockunion_cmp(peer_new->connection->su_remote, peer_exist->connection->su_remote);
if (ret == 1) {
*reason = bgp_path_selection_neighbor_ip;
@@ -2464,8 +2464,6 @@ bool subgroup_announce_check(struct bgp_dest *dest, struct bgp_path_info *pi,
* announced to an EBGP peer (and they have the same attributes barring
* their nexthop).
*/
- if (ibgp_to_ibgp)
- SET_FLAG(attr->rmap_change_flags, BATTR_REFLECTED);
#define NEXTHOP_IS_V6 \
((safi != SAFI_ENCAP && safi != SAFI_MPLS_VPN \
@@ -5539,7 +5537,7 @@ void bgp_update(struct peer *peer, const struct prefix *p, uint32_t addpath_id,
}
if ((SAFI_MPLS_VPN == safi)
&& (bgp->inst_type == BGP_INSTANCE_TYPE_DEFAULT)) {
- vpn_leak_to_vrf_update(bgp, pi, prd);
+ vpn_leak_to_vrf_update(bgp, pi, prd, peer);
}
#ifdef ENABLE_BGP_VNC
@@ -5592,12 +5590,12 @@ void bgp_update(struct peer *peer, const struct prefix *p, uint32_t addpath_id,
/* 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);
+ /* Increment prefix */
+ bgp_aggregate_increment(bgp, p, new, afi, safi);
+
/* route_node_get lock */
bgp_dest_unlock_node(dest);
@@ -5633,7 +5631,7 @@ void bgp_update(struct peer *peer, const struct prefix *p, uint32_t addpath_id,
}
if ((SAFI_MPLS_VPN == safi)
&& (bgp->inst_type == BGP_INSTANCE_TYPE_DEFAULT)) {
- vpn_leak_to_vrf_update(bgp, new, prd);
+ vpn_leak_to_vrf_update(bgp, new, prd, peer);
}
#ifdef ENABLE_BGP_VNC
if (SAFI_MPLS_VPN == safi) {
@@ -7142,8 +7140,7 @@ void bgp_static_update(struct bgp *bgp, const struct prefix *p,
if (SAFI_MPLS_VPN == safi &&
bgp->inst_type == BGP_INSTANCE_TYPE_DEFAULT) {
- vpn_leak_to_vrf_update(bgp, pi,
- &bgp_static->prd);
+ vpn_leak_to_vrf_update(bgp, pi, &bgp_static->prd, NULL);
}
#ifdef ENABLE_BGP_VNC
if (safi == SAFI_MPLS_VPN || safi == SAFI_ENCAP ||
@@ -7187,12 +7184,12 @@ void bgp_static_update(struct bgp *bgp, const struct prefix *p,
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);
+ /* Aggregate address increment. */
+ bgp_aggregate_increment(bgp, p, new, afi, safi);
+
/* route_node_get lock */
bgp_dest_unlock_node(dest);
@@ -7207,7 +7204,7 @@ void bgp_static_update(struct bgp *bgp, const struct prefix *p,
if (SAFI_MPLS_VPN == safi &&
bgp->inst_type == BGP_INSTANCE_TYPE_DEFAULT) {
- vpn_leak_to_vrf_update(bgp, new, &bgp_static->prd);
+ vpn_leak_to_vrf_update(bgp, new, &bgp_static->prd, NULL);
}
#ifdef ENABLE_BGP_VNC
if (safi == SAFI_MPLS_VPN || safi == SAFI_ENCAP || safi == SAFI_EVPN)
@@ -7934,44 +7931,6 @@ static bool aggr_unsuppress_path(struct bgp_aggregate *aggregate,
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,
@@ -7980,14 +7939,18 @@ static void bgp_aggregate_install(
{
struct bgp_dest *dest;
struct bgp_table *table;
- struct bgp_path_info *pi, *orig, *new;
+ struct bgp_path_info *pi, *new;
struct attr *attr;
+ bool debug = bgp_debug_aggregate(p);
+
+ if (debug)
+ zlog_debug("%s: aggregate %pFX, count %lu", __func__, p, aggregate->count);
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)
+ 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_AGGREGATE)
break;
@@ -7996,28 +7959,40 @@ static void bgp_aggregate_install(
* If we have paths with different MEDs, then don't install
* (or uninstall) the aggregate route.
*/
- if (aggregate->match_med && aggregate->med_mismatched)
+ if (aggregate->match_med && aggregate->med_mismatched) {
+ aspath_free(aspath);
+ community_free(&community);
+ ecommunity_free(&ecommunity);
+ lcommunity_free(&lcommunity);
+ if (debug)
+ zlog_debug(" aggregate %pFX: med mismatch", p);
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))) {
+ 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 (debug)
+ zlog_debug("%s: %pFX null attribute", __func__, p);
+ return;
+ }
- if (aspath)
- aspath_free(aspath);
- if (community)
- community_free(&community);
- if (ecommunity)
- ecommunity_free(&ecommunity);
- if (lcommunity)
- lcommunity_free(&lcommunity);
-
+ if (pi && CHECK_FLAG(pi->flags, BGP_PATH_VALID) && attrhash_cmp(pi->attr, attr)) {
+ bgp_attr_unintern(&attr);
+ bgp_dest_unlock_node(dest);
+ if (debug)
+ zlog_debug(" aggregate %pFX: duplicate", p);
return;
}
@@ -8027,24 +8002,10 @@ static void bgp_aggregate_install(
if (pi) {
bgp_path_info_delete(dest, pi);
bgp_process(bgp, dest, pi, afi, safi);
+ if (debug)
+ zlog_debug(" aggregate %pFX: existing, removed", p);
}
- 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);
@@ -8053,19 +8014,17 @@ static void bgp_aggregate_install(
bgp_path_info_add(dest, new);
bgp_process(bgp, dest, new, afi, safi);
+ if (debug)
+ zlog_debug(" aggregate %pFX: installed", p);
} 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);
- }
+ /* Withdraw the aggregate route from routing table. */
+ if (pi) {
+ bgp_path_info_delete(dest, pi);
+ bgp_process(bgp, dest, pi, afi, safi);
+ if (debug)
+ zlog_debug(" aggregate %pFX: uninstall", p);
+ }
}
bgp_dest_unlock_node(dest);
@@ -8912,6 +8871,27 @@ static int bgp_aggregate_unset(struct vty *vty, const char *prefix_str,
return CMD_SUCCESS;
}
+static bool bgp_aggregate_cmp_params(struct bgp_aggregate *aggregate, const char *rmap,
+ uint8_t summary_only, uint8_t as_set, uint8_t origin,
+ bool match_med, const char *suppress_map)
+{
+ if ((aggregate->origin != origin) || (aggregate->as_set != as_set) ||
+ (aggregate->match_med != match_med) || (aggregate->summary_only != summary_only))
+ return false;
+
+ if ((!rmap && aggregate->rmap.name) || (rmap && !aggregate->rmap.name) ||
+ (rmap && aggregate->rmap.name && !strmatch(rmap, aggregate->rmap.name)))
+ return false;
+
+ if ((!suppress_map && aggregate->suppress_map_name) ||
+ (suppress_map && !aggregate->suppress_map_name) ||
+ (suppress_map && aggregate->suppress_map_name &&
+ !strmatch(suppress_map, aggregate->suppress_map_name)))
+ return false;
+
+ return true;
+}
+
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,
@@ -8951,6 +8931,11 @@ static int bgp_aggregate_set(struct vty *vty, const char *prefix_str, afi_t afi,
aggregate = bgp_dest_get_bgp_aggregate_info(dest);
if (aggregate) {
+ /* Check for duplicate configs */
+ if (bgp_aggregate_cmp_params(aggregate, rmap, summary_only, as_set, origin,
+ match_med, suppress_map))
+ return CMD_SUCCESS;
+
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);
@@ -8986,7 +8971,7 @@ static int bgp_aggregate_set(struct vty *vty, const char *prefix_str, afi_t afi,
}
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.
@@ -9000,7 +8985,6 @@ static int bgp_aggregate_set(struct vty *vty, const char *prefix_str, afi_t afi,
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);
}
@@ -9350,8 +9334,8 @@ void bgp_redistribute_add(struct bgp *bgp, struct prefix *p,
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_aggregate_increment(bgp, p, new, afi, SAFI_UNICAST);
bgp_dest_unlock_node(bn);
SET_FLAG(bn->flags, BGP_NODE_FIB_INSTALLED);
bgp_process(bgp, bn, new, afi, SAFI_UNICAST);
@@ -9931,6 +9915,9 @@ void route_vty_out(struct vty *vty, const struct prefix *p,
== BGP_ATTR_NHLEN_IPV6_GLOBAL_AND_LL)
|| (path->peer->conf_if)) {
json_nexthop_ll = json_object_new_object();
+ if (path->peer->conf_if)
+ json_object_string_add(json_nexthop_ll, "interface",
+ path->peer->conf_if);
json_object_string_addf(
json_nexthop_ll, "ip", "%pI6",
&attr->mp_nexthop_local);
@@ -12042,9 +12029,8 @@ static int bgp_show_table(struct vty *vty, struct bgp *bgp, afi_t afi, safi_t sa
|| 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))
+ if (pi->peer == NULL || pi->peer->connection->su_remote == NULL ||
+ !sockunion_same(pi->peer->connection->su_remote, su))
continue;
}
if (type == bgp_show_type_cidr_only) {
diff --git a/bgpd/bgp_route.h b/bgpd/bgp_route.h
index c071120de..7f4a3b918 100644
--- a/bgpd/bgp_route.h
+++ b/bgpd/bgp_route.h
@@ -449,7 +449,6 @@ struct bgp_aggregate {
struct {
char *name;
struct route_map *map;
- bool changed;
} rmap;
/* Suppress-count. */
@@ -493,9 +492,6 @@ struct bgp_aggregate {
/* Aggregate route's as-path. */
struct aspath *aspath;
- /* SAFI configuration. */
- safi_t safi;
-
/** MED value found in current group. */
uint32_t med_matched_value;
diff --git a/bgpd/bgp_routemap.c b/bgpd/bgp_routemap.c
index ce236f87b..fa8701dc5 100644
--- a/bgpd/bgp_routemap.c
+++ b/bgpd/bgp_routemap.c
@@ -1303,6 +1303,61 @@ static const struct route_map_rule_cmd route_match_evpn_rd_cmd = {
route_match_rd_free
};
+/* `match community-limit' */
+
+/* Match function should return :
+ * - RMAP_MATCH if the bgp update community list count
+ * is less or equal to the configured limit.
+ * - RMAP_NOMATCH if the community list count is greater than the
+ * configured limit.
+ */
+static enum route_map_cmd_result_t
+route_match_community_limit(void *rule, const struct prefix *prefix, void *object)
+{
+ struct bgp_path_info *path = NULL;
+ struct community *picomm = NULL;
+ uint16_t count = 0;
+ uint16_t *limit_rule = rule;
+
+ path = (struct bgp_path_info *)object;
+
+ picomm = bgp_attr_get_community(path->attr);
+ if (picomm)
+ count = picomm->size;
+
+ if (count <= *limit_rule)
+ return RMAP_MATCH;
+
+ return RMAP_NOMATCH;
+}
+
+/* Route map `community-limit' match statement. */
+static void *route_match_community_limit_compile(const char *arg)
+{
+ uint16_t *limit = NULL;
+ char *end = NULL;
+
+ limit = XMALLOC(MTYPE_ROUTE_MAP_COMPILED, sizeof(uint16_t));
+ *limit = strtoul(arg, &end, 10);
+ if (*end != '\0') {
+ XFREE(MTYPE_ROUTE_MAP_COMPILED, limit);
+ return NULL;
+ }
+ return limit;
+}
+
+/* Free route map's compiled `community-limit' value. */
+static void route_match_community_limit_free(void *rule)
+{
+ XFREE(MTYPE_ROUTE_MAP_COMPILED, rule);
+}
+
+/* Route map commands for community limit matching. */
+static const struct route_map_rule_cmd route_match_community_limit_cmd = {
+ "community-limit", route_match_community_limit,
+ route_match_community_limit_compile, route_match_community_limit_free
+};
+
static enum route_map_cmd_result_t
route_set_evpn_gateway_ip(void *rule, const struct prefix *prefix, void *object)
{
@@ -2066,10 +2121,9 @@ route_set_ip_nexthop(void *rule, const struct prefix *prefix, void *object)
BATTR_RMAP_NEXTHOP_UNCHANGED);
} else if (rins->peer_address) {
if ((CHECK_FLAG(peer->rmap_type, PEER_RMAP_TYPE_IN)) &&
- peer->su_remote &&
- sockunion_family(peer->su_remote) == AF_INET) {
- path->attr->nexthop.s_addr =
- sockunion2ip(peer->su_remote);
+ peer->connection->su_remote &&
+ sockunion_family(peer->connection->su_remote) == AF_INET) {
+ path->attr->nexthop.s_addr = sockunion2ip(peer->connection->su_remote);
SET_FLAG(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
@@ -4143,9 +4197,9 @@ route_set_ipv6_nexthop_peer(void *rule, const struct prefix *pfx, void *object)
path = object;
peer = path->peer;
- if ((CHECK_FLAG(peer->rmap_type, PEER_RMAP_TYPE_IN)) &&
- peer->su_remote && sockunion_family(peer->su_remote) == AF_INET6) {
- peer_address = peer->su_remote->sin6.sin6_addr;
+ if ((CHECK_FLAG(peer->rmap_type, PEER_RMAP_TYPE_IN)) && peer->connection->su_remote &&
+ sockunion_family(peer->connection->su_remote) == AF_INET6) {
+ peer_address = peer->connection->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;
@@ -4684,7 +4738,6 @@ static void bgp_route_map_process_update(struct bgp *bgp, const char *rmap_name,
route_map_counter_increment(map);
aggregate->rmap.map = map;
- aggregate->rmap.changed = true;
matched = true;
}
@@ -5709,6 +5762,25 @@ DEFPY_YANG(
return nb_cli_apply_changes(vty, NULL);
}
+DEFPY_YANG(
+ match_community_limit, match_community_limit_cmd,
+ "[no$no] match community-limit ![(0-65535)$limit]",
+ NO_STR
+ MATCH_STR
+ "Match BGP community limit\n"
+ "Community limit number\n")
+{
+ const char *xpath = "./match-condition[condition='frr-bgp-route-map:match-community-limit']";
+ char xpath_value[XPATH_MAXLEN];
+
+ nb_cli_enqueue_change(vty, xpath, no ? NB_OP_DESTROY : NB_OP_CREATE, NULL);
+ snprintf(xpath_value, sizeof(xpath_value),
+ "%s/rmap-match-condition/frr-bgp-route-map:community-limit", xpath);
+
+ nb_cli_enqueue_change(vty, xpath_value, no ? NB_OP_DESTROY : NB_OP_MODIFY, limit_str);
+ 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> [<exact-match$exact|any$any>]]",
@@ -7907,6 +7979,7 @@ void bgp_route_map_init(void)
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_community_limit_cmd);
route_map_install_match(&route_match_evpn_default_route_cmd);
route_map_install_match(&route_match_vrl_source_vrf_cmd);
@@ -7979,6 +8052,7 @@ void bgp_route_map_init(void)
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_community_limit_cmd);
install_element(RMAP_NODE, &match_lcommunity_cmd);
install_element(RMAP_NODE, &no_match_lcommunity_cmd);
install_element(RMAP_NODE, &match_ecommunity_cmd);
diff --git a/bgpd/bgp_routemap_nb.c b/bgpd/bgp_routemap_nb.c
index d8fdb4fbc..464559344 100644
--- a/bgpd/bgp_routemap_nb.c
+++ b/bgpd/bgp_routemap_nb.c
@@ -166,6 +166,13 @@ const struct frr_yang_module_info frr_bgp_route_map_info = {
}
},
{
+ .xpath = "/frr-route-map:lib/route-map/entry/match-condition/rmap-match-condition/frr-bgp-route-map:community-limit",
+ .cbs = {
+ .modify = lib_route_map_entry_match_condition_rmap_match_condition_community_limit_modify,
+ .destroy = lib_route_map_entry_match_condition_rmap_match_condition_community_limit_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,
diff --git a/bgpd/bgp_routemap_nb.h b/bgpd/bgp_routemap_nb.h
index f59686f38..45689242a 100644
--- a/bgpd/bgp_routemap_nb.h
+++ b/bgpd/bgp_routemap_nb.h
@@ -72,6 +72,10 @@ int lib_route_map_entry_match_condition_rmap_match_condition_evpn_route_type_mod
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_community_limit_modify(
+ struct nb_cb_modify_args *args);
+int lib_route_map_entry_match_condition_rmap_match_condition_community_limit_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(
diff --git a/bgpd/bgp_routemap_nb_config.c b/bgpd/bgp_routemap_nb_config.c
index 0dca196ed..223c416dc 100644
--- a/bgpd/bgp_routemap_nb_config.c
+++ b/bgpd/bgp_routemap_nb_config.c
@@ -1275,6 +1275,57 @@ lib_route_map_entry_match_condition_rmap_match_condition_route_distinguisher_des
}
/*
+ * XPath: /frr-route-map:lib/route-map/entry/match-condition/rmap-match-condition/frr-bgp-route-map:community-limit
+ */
+int lib_route_map_entry_match_condition_rmap_match_condition_community_limit_modify(
+ struct nb_cb_modify_args *args)
+{
+ struct routemap_hook_context *rhc;
+ const char *limit;
+ 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);
+ limit = yang_dnode_get_string(args->dnode, NULL);
+
+ rhc->rhc_mhook = bgp_route_match_delete;
+ rhc->rhc_rule = "community-limit";
+ rhc->rhc_event = RMAP_EVENT_MATCH_DELETED;
+
+ ret = bgp_route_match_add(rhc->rhc_rmi, "community-limit", limit,
+ 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_community_limit_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(
diff --git a/bgpd/bgp_script.c b/bgpd/bgp_script.c
index b37385812..4874813be 100644
--- a/bgpd/bgp_script.c
+++ b/bgpd/bgp_script.c
@@ -37,9 +37,9 @@ void lua_pushpeer(lua_State *L, const struct peer *peer)
lua_setfield(L, -2, "last_readtime");
lua_pushinteger(L, peer->resettime);
lua_setfield(L, -2, "last_resettime");
- lua_pushsockunion(L, peer->su_local);
+ lua_pushsockunion(L, peer->connection->su_local);
lua_setfield(L, -2, "local_address");
- lua_pushsockunion(L, peer->su_remote);
+ lua_pushsockunion(L, peer->connection->su_remote);
lua_setfield(L, -2, "remote_address");
lua_pushinteger(L, peer->cap);
lua_setfield(L, -2, "capabilities");
diff --git a/bgpd/bgp_snmp_bgp4.c b/bgpd/bgp_snmp_bgp4.c
index 755777c16..32430f42a 100644
--- a/bgpd/bgp_snmp_bgp4.c
+++ b/bgpd/bgp_snmp_bgp4.c
@@ -266,25 +266,23 @@ static uint8_t *bgpPeerTable(struct variable *v, oid name[], size_t *length,
case BGPPEERNEGOTIATEDVERSION:
return SNMP_INTEGER(BGP_VERSION_4);
case BGPPEERLOCALADDR:
- if (peer->su_local)
- return SNMP_IPADDRESS(peer->su_local->sin.sin_addr);
+ if (peer->connection->su_local)
+ return SNMP_IPADDRESS(peer->connection->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));
+ if (peer->connection->su_local)
+ return SNMP_INTEGER(ntohs(peer->connection->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);
+ if (peer->connection->su_remote)
+ return SNMP_IPADDRESS(peer->connection->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));
+ if (peer->connection->su_remote)
+ return SNMP_INTEGER(ntohs(peer->connection->su_remote->sin.sin_port));
else
return SNMP_INTEGER(0);
case BGPPEERREMOTEAS:
diff --git a/bgpd/bgp_snmp_bgp4v2.c b/bgpd/bgp_snmp_bgp4v2.c
index 5f36e2987..724eefe60 100644
--- a/bgpd/bgp_snmp_bgp4v2.c
+++ b/bgpd/bgp_snmp_bgp4v2.c
@@ -208,49 +208,42 @@ static uint8_t *bgpv2PeerTable(struct variable *v, oid name[], size_t *length,
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
+ if (peer->connection->su_local)
+ return SNMP_INTEGER(peer->connection->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);
+ if (peer->connection->su_local)
+ if (peer->connection->su_local->sa.sa_family == AF_INET)
+ return SNMP_IPADDRESS(peer->connection->su_local->sin.sin_addr);
else
- return SNMP_IP6ADDRESS(
- peer->su_local->sin6.sin6_addr);
+ return SNMP_IP6ADDRESS(peer->connection->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
+ if (peer->connection->su_remote)
+ return SNMP_INTEGER(peer->connection->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);
+ if (peer->connection->su_remote)
+ if (peer->connection->su_remote->sa.sa_family == AF_INET)
+ return SNMP_IPADDRESS(peer->connection->su_remote->sin.sin_addr);
else
- return SNMP_IP6ADDRESS(
- peer->su_remote->sin6.sin6_addr);
+ return SNMP_IP6ADDRESS(peer->connection->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));
+ if (peer->connection->su_local)
+ if (peer->connection->su_local->sa.sa_family == AF_INET)
+ return SNMP_INTEGER(ntohs(peer->connection->su_local->sin.sin_port));
else
return SNMP_INTEGER(
- ntohs(peer->su_local->sin6.sin6_port));
+ ntohs(peer->connection->su_local->sin6.sin6_port));
else
return SNMP_INTEGER(0);
case BGP4V2_PEER_LOCAL_AS:
@@ -258,13 +251,13 @@ static uint8_t *bgpv2PeerTable(struct variable *v, oid name[], size_t *length,
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)
+ if (peer->connection->su_remote)
+ if (peer->connection->su_remote->sa.sa_family == AF_INET)
return SNMP_INTEGER(
- ntohs(peer->su_remote->sin.sin_port));
+ ntohs(peer->connection->su_remote->sin.sin_port));
else
return SNMP_INTEGER(
- ntohs(peer->su_remote->sin6.sin6_port));
+ ntohs(peer->connection->su_remote->sin6.sin6_port));
else
return SNMP_INTEGER(0);
case BGP4V2_PEER_REMOTE_AS:
diff --git a/bgpd/bgp_trace.h b/bgpd/bgp_trace.h
index 43bc7a5a1..ce8692063 100644
--- a/bgpd/bgp_trace.h
+++ b/bgpd/bgp_trace.h
@@ -135,12 +135,13 @@ TRACEPOINT_LOGLEVEL(frr_bgp, bmp_mirror_packet, TRACE_INFO)
TRACEPOINT_EVENT(
frr_bgp,
bmp_eor,
- TP_ARGS(afi_t, afi, safi_t, safi, uint8_t, flags, uint8_t, peer_type_flag),
+ TP_ARGS(afi_t, afi, safi_t, safi, uint8_t, flags, uint8_t, peer_type_flag, bgp),
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)
+ ctf_string(bgp, bgp->name_pretty)
)
)
diff --git a/bgpd/bgp_updgrp.h b/bgpd/bgp_updgrp.h
index d0fd226d9..6549c99e8 100644
--- a/bgpd/bgp_updgrp.h
+++ b/bgpd/bgp_updgrp.h
@@ -66,7 +66,6 @@ typedef struct {
#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)
diff --git a/bgpd/bgp_updgrp_packet.c b/bgpd/bgp_updgrp_packet.c
index 3ce136ef8..ec418f2b1 100644
--- a/bgpd/bgp_updgrp_packet.c
+++ b/bgpd/bgp_updgrp_packet.c
@@ -1284,10 +1284,6 @@ bpacket_vec_arr_inherit_attr_flags(struct bpacket_attr_vec_arr *vecarr,
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);
diff --git a/bgpd/bgp_vty.c b/bgpd/bgp_vty.c
index c6b09481b..33b220d3e 100644
--- a/bgpd/bgp_vty.c
+++ b/bgpd/bgp_vty.c
@@ -93,18 +93,6 @@ 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 = BGP_DEFAULT_CONNECT_RETRY },
-);
-FRR_CFG_DEFAULT_ULONG(BGP_HOLDTIME,
- { .val_ulong = 9, .match_profile = "datacenter", },
- { .val_ulong = BGP_DEFAULT_KEEPALIVE },
-);
-FRR_CFG_DEFAULT_ULONG(BGP_KEEPALIVE,
- { .val_ulong = 3, .match_profile = "datacenter", },
- { .val_ulong = BGP_DEFAULT_KEEPALIVE },
-);
FRR_CFG_DEFAULT_BOOL(BGP_EBGP_REQUIRES_POLICY,
{ .val_bool = false, .match_profile = "datacenter", },
{ .val_bool = false, .match_version = "< 7.4", },
@@ -5989,13 +5977,17 @@ DEFUN (neighbor_capability_enhe,
{
int idx_peer = 1;
struct peer *peer;
+ int ret;
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);
+ ret = peer_flag_set_vty(vty, argv[idx_peer]->arg, PEER_FLAG_CAPABILITY_ENHE);
+
+ bgp_capability_send(peer, AFI_IP, SAFI_UNICAST, CAPABILITY_CODE_ENHE, CAPABILITY_ACTION_SET);
+
+ return ret;
}
DEFUN (no_neighbor_capability_enhe,
@@ -6009,6 +6001,7 @@ DEFUN (no_neighbor_capability_enhe,
{
int idx_peer = 2;
struct peer *peer;
+ int ret;
peer = peer_and_group_lookup_vty(vty, argv[idx_peer]->arg);
if (peer && peer->conf_if) {
@@ -6018,8 +6011,12 @@ DEFUN (no_neighbor_capability_enhe,
return CMD_WARNING_CONFIG_FAILED;
}
- return peer_flag_unset_vty(vty, argv[idx_peer]->arg,
- PEER_FLAG_CAPABILITY_ENHE);
+ ret = peer_flag_unset_vty(vty, argv[idx_peer]->arg, PEER_FLAG_CAPABILITY_ENHE);
+
+ bgp_capability_send(peer, AFI_IP, SAFI_UNICAST, CAPABILITY_CODE_ENHE,
+ CAPABILITY_ACTION_UNSET);
+
+ return ret;
}
/* neighbor capability software-version */
@@ -15857,15 +15854,15 @@ CPP_NOTICE("Remove `gracefulRestartCapability` JSON field")
}
/* Local address. */
- if (p->su_local) {
+ if (p->connection->su_local) {
if (use_json) {
json_object_string_addf(json_neigh, "hostLocal", "%pSU",
- p->su_local);
+ p->connection->su_local);
json_object_int_add(json_neigh, "portLocal",
- ntohs(p->su_local->sin.sin_port));
+ ntohs(p->connection->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));
+ vty_out(vty, "Local host: %pSU, Local port: %d\n", p->connection->su_local,
+ ntohs(p->connection->su_local->sin.sin_port));
} else {
if (use_json) {
json_object_string_add(json_neigh, "hostLocal",
@@ -15875,16 +15872,16 @@ CPP_NOTICE("Remove `gracefulRestartCapability` JSON field")
}
/* Remote address. */
- if (p->su_remote) {
+ if (p->connection->su_remote) {
if (use_json) {
- json_object_string_addf(json_neigh, "hostForeign",
- "%pSU", p->su_remote);
+ json_object_string_addf(json_neigh, "hostForeign", "%pSU",
+ p->connection->su_remote);
json_object_int_add(json_neigh, "portForeign",
- ntohs(p->su_remote->sin.sin_port));
+ ntohs(p->connection->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));
+ p->connection->su_remote,
+ ntohs(p->connection->su_remote->sin.sin_port));
} else {
if (use_json) {
json_object_string_add(json_neigh, "hostForeign",
@@ -15894,7 +15891,7 @@ CPP_NOTICE("Remove `gracefulRestartCapability` JSON field")
}
/* Nexthop display. */
- if (p->su_local) {
+ if (p->connection->su_local) {
if (use_json) {
json_object_string_addf(json_neigh, "nexthop", "%pI4",
&p->nexthop.v4);
diff --git a/bgpd/bgp_vty.h b/bgpd/bgp_vty.h
index f88f5c812..00a313507 100644
--- a/bgpd/bgp_vty.h
+++ b/bgpd/bgp_vty.h
@@ -10,6 +10,19 @@
#include "stream.h"
struct bgp;
+FRR_CFG_DEFAULT_ULONG(BGP_KEEPALIVE,
+ { .val_ulong = 3, .match_profile = "datacenter", },
+ { .val_ulong = BGP_DEFAULT_KEEPALIVE },
+);
+FRR_CFG_DEFAULT_ULONG(BGP_HOLDTIME,
+ { .val_ulong = 9, .match_profile = "datacenter", },
+ { .val_ulong = BGP_DEFAULT_HOLDTIME },
+);
+FRR_CFG_DEFAULT_ULONG(BGP_CONNECT_RETRY,
+ { .val_ulong = 10, .match_profile = "datacenter", },
+ { .val_ulong = BGP_DEFAULT_CONNECT_RETRY },
+);
+
#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"
diff --git a/bgpd/bgp_zebra.c b/bgpd/bgp_zebra.c
index 7ad9ce472..8e8616c15 100644
--- a/bgpd/bgp_zebra.c
+++ b/bgpd/bgp_zebra.c
@@ -953,13 +953,10 @@ bgp_path_info_to_ipv6_nexthop(struct bgp_path_info *path, ifindex_t *ifindex)
*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_UNSPECIFIED(&path->attr->mp_nexthop_global) &&
+ path->peer->connection->su_remote &&
+ path->peer->connection->su_remote->sa.sa_family == AF_INET6) {
+ nexthop = &path->peer->connection->su_remote->sin6.sin6_addr;
if (IN6_IS_ADDR_LINKLOCAL(nexthop))
*ifindex = path->peer->nexthop.ifp
->ifindex;
@@ -3378,12 +3375,15 @@ static int bgp_ifp_create(struct interface *ifp)
zlog_debug("Rx Intf add VRF %s IF %s", ifp->vrf->name,
ifp->name);
+ /* We don't need to check for vrf->bgp link to add this local MAC
+ * to the hash table as the tenant VRF might not have the BGP instance.
+ */
+ bgp_mac_add_mac_entry(ifp);
+
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);
diff --git a/bgpd/bgpd.c b/bgpd/bgpd.c
index 05bc804db..c2254ae79 100644
--- a/bgpd/bgpd.c
+++ b/bgpd/bgpd.c
@@ -583,9 +583,9 @@ void bgp_timers_set(struct vty *vty, struct bgp *bgp, uint32_t keepalive,
/* 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_keepalive = DFLT_BGP_KEEPALIVE;
+ bgp->default_holdtime = DFLT_BGP_HOLDTIME;
+ bgp->default_connect_retry = DFLT_BGP_CONNECT_RETRY;
bgp->default_delayopen = BGP_DEFAULT_DELAYOPEN;
}
@@ -2768,14 +2768,14 @@ int peer_delete(struct peer *peer)
}
/* Local and remote addresses. */
- if (peer->su_local) {
- sockunion_free(peer->su_local);
- peer->su_local = NULL;
+ if (peer->connection->su_local) {
+ sockunion_free(peer->connection->su_local);
+ peer->connection->su_local = NULL;
}
- if (peer->su_remote) {
- sockunion_free(peer->su_remote);
- peer->su_remote = NULL;
+ if (peer->connection->su_remote) {
+ sockunion_free(peer->connection->su_remote);
+ peer->connection->su_remote = NULL;
}
/* Free filter related memory. */
@@ -4953,6 +4953,10 @@ static void peer_flag_modify_action(struct peer *peer, uint64_t flag)
peer->v_start = BGP_INIT_START_TIMER;
BGP_EVENT_ADD(peer->connection, BGP_Stop);
}
+ } else if (CHECK_FLAG(peer->cap, PEER_CAP_DYNAMIC_RCV) &&
+ CHECK_FLAG(peer->cap, PEER_CAP_DYNAMIC_ADV) &&
+ flag == PEER_FLAG_CAPABILITY_ENHE) {
+ peer->last_reset = PEER_DOWN_CAPABILITY_CHANGE;
} else if (!peer_notify_config_change(peer->connection))
bgp_session_reset(peer);
}
diff --git a/bgpd/bgpd.h b/bgpd/bgpd.h
index 65b268c4e..c72072852 100644
--- a/bgpd/bgpd.h
+++ b/bgpd/bgpd.h
@@ -1258,6 +1258,9 @@ struct peer_connection {
union sockunion su;
#define BGP_CONNECTION_SU_UNSPEC(connection) \
(connection->su.sa.sa_family == AF_UNSPEC)
+
+ union sockunion *su_local; /* Sockunion of local address. */
+ union sockunion *su_remote; /* Sockunion of remote address. */
};
extern struct peer_connection *bgp_peer_connection_new(struct peer *peer);
extern void bgp_peer_connection_free(struct peer_connection **connection);
@@ -1350,8 +1353,6 @@ struct peer {
char *update_if;
union sockunion *update_source;
- union sockunion *su_local; /* Sockunion of local address. */
- union sockunion *su_remote; /* Sockunion of remote address. */
bool shared_network; /* Is this peer shared same network. */
struct bgp_nexthop nexthop; /* Nexthop */
diff --git a/bgpd/rfapi/rfapi.c b/bgpd/rfapi/rfapi.c
index 61d154f1b..241cbcb35 100644
--- a/bgpd/rfapi/rfapi.c
+++ b/bgpd/rfapi/rfapi.c
@@ -1029,8 +1029,8 @@ void add_vnc_route(struct rfapi_descriptor *rfd, /* cookie, VPN UN addr, peer */
rfapiPrintBi(NULL, new);
}
- bgp_aggregate_increment(bgp, p, new, afi, safi);
bgp_path_info_add(bn, new);
+ bgp_aggregate_increment(bgp, p, new, afi, safi);
if (safi == SAFI_MPLS_VPN) {
struct bgp_dest *pdest = NULL;
diff --git a/bgpd/rfapi/rfapi_import.c b/bgpd/rfapi/rfapi_import.c
index 44dfc88cf..99d8bcfce 100644
--- a/bgpd/rfapi/rfapi_import.c
+++ b/bgpd/rfapi/rfapi_import.c
@@ -1931,8 +1931,8 @@ static void rfapiBgpInfoAttachSorted(struct agg_node *rn,
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);
+ vnc_zlog_debug_verbose("%s: info_new->peer->su_remote=%p", __func__,
+ info_new->peer->connection->su_remote);
}
for (prev = NULL, next = rn->info; next;
diff --git a/configure.ac b/configure.ac
index e8036fcff..e04c0b6d4 100644
--- a/configure.ac
+++ b/configure.ac
@@ -375,26 +375,45 @@ 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_PROG_LUA([5.3], [], [], [
+ 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.])
+ 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.])
- ])
- ])
+
+ for version in 5.3 5.4; do
+ PKG_CHECK_MODULES([LUA], [lua >= $version], [
+ AC_DEFINE([HAVE_SCRIPTING], [1], [Have support for scripting])
+ LIBS="$LIBS $LUA_LIBS"
+ SCRIPTING=true
+ break
+ ], [
+ PKG_CHECK_MODULES([LUA], [lua$version], [
+ AC_DEFINE([HAVE_SCRIPTING], [1], [Have support for scripting])
+ LIBS="$LIBS $LUA_LIBS"
+ SCRIPTING=true
+ break
+ ], [
+ PKG_CHECK_MODULES([LUA], [lua-$version], [
+ AC_DEFINE([HAVE_SCRIPTING], [1], [Have support for scripting])
+ LIBS="$LIBS $LUA_LIBS"
+ SCRIPTING=true
+ break
+ ], [])
+ ])
+ ])
+ done
+
+ if [ "$SCRIPTING" != "true" ]; then
+ AX_LUA_LIBS([
+ AC_DEFINE([HAVE_SCRIPTING], [1], [Have support for scripting])
+ LIBS="$LIBS $LUA_LIB"
+ SCRIPTING=true
+ ], [
+ AC_MSG_ERROR([Lua >= 5.3 libraries are required to build with Lua support. No other version is supported.])
+ ])
+ fi
fi
dnl the following flags go in CFLAGS rather than AC_CFLAGS since they make
diff --git a/doc/developer/building-doc.rst b/doc/developer/building-doc.rst
new file mode 100644
index 000000000..bf0544ccc
--- /dev/null
+++ b/doc/developer/building-doc.rst
@@ -0,0 +1,62 @@
+Building Documentation
+======================
+
+To build FRR documentation, first install the dependencies.
+Notice that if you plan to only build html documenation, you only
+need the package ``python3-sphinx``.
+
+.. code-block:: console
+
+ sudo apt-get install -y python3-sphinx \
+ texlive-latex-base texlive-latex-extra latexmk
+
+To prepate for building both user and developer documentation, do:
+
+.. code-block:: console
+
+ cd doc
+ make
+
+User documentation
+------------------
+
+To build html user documentation:
+
+.. code-block:: console
+
+ cd user
+ make html
+
+This will generate html documentation files under ``_build/html/``.
+With the main page named ``index.html``.
+
+PFD can then be built by:
+
+.. code-block:: console
+
+ cd user
+ make pdf
+
+The generated PDF file will be saved at ``_build/latex/FRR.pdf``
+
+Developer documentation
+-----------------------
+
+To build the developer documentation:
+
+.. code-block:: console
+
+ cd developer
+ make html
+
+This will generate html documentation files under ``_build/html/``.
+With the main page named ``index.html``.
+
+PFD can then be built by:
+
+.. code-block:: console
+
+ cd developer
+ make pdf
+
+The generated PDF file will be saved at ``_build/latex/FRR.pdf``
diff --git a/doc/developer/building-frr-for-ubuntu2004.rst b/doc/developer/building-frr-for-ubuntu2004.rst
index 3db97c4b2..19353e317 100644
--- a/doc/developer/building-frr-for-ubuntu2004.rst
+++ b/doc/developer/building-frr-for-ubuntu2004.rst
@@ -1,163 +1,4 @@
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-<kernel-version>-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
+.. include:: building-frr-for-ubuntu2x04.rst
diff --git a/doc/developer/building-frr-for-ubuntu2204.rst b/doc/developer/building-frr-for-ubuntu2204.rst
index c898c3cd2..726cf0a91 100644
--- a/doc/developer/building-frr-for-ubuntu2204.rst
+++ b/doc/developer/building-frr-for-ubuntu2204.rst
@@ -1,164 +1,4 @@
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-<kernel-version>-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
+.. include:: building-frr-for-ubuntu2x04.rst
diff --git a/doc/developer/building-frr-for-ubuntu2404.rst b/doc/developer/building-frr-for-ubuntu2404.rst
new file mode 100644
index 000000000..e6b264993
--- /dev/null
+++ b/doc/developer/building-frr-for-ubuntu2404.rst
@@ -0,0 +1,4 @@
+Ubuntu 24.04 LTS
+================
+
+.. include:: building-frr-for-ubuntu2x04.rst
diff --git a/doc/developer/building-frr-for-ubuntu2x04.rst b/doc/developer/building-frr-for-ubuntu2x04.rst
new file mode 100644
index 000000000..78b45e141
--- /dev/null
+++ b/doc/developer/building-frr-for-ubuntu2x04.rst
@@ -0,0 +1,162 @@
+
+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-<kernel-version>-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/mgmtd-dev.rst b/doc/developer/mgmtd-dev.rst
index 4c56cadb2..6cbd617f8 100644
--- a/doc/developer/mgmtd-dev.rst
+++ b/doc/developer/mgmtd-dev.rst
@@ -160,14 +160,19 @@ Back-End Interface:
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.,
+#. In ``mgmtd/mgmt_be_adapter.c`` add xpath prefix mappings to a each of the
+ mapping arrays (``be_client_config_xpaths``, ``be_client_oper_xpaths``, and
+ ``be_client_rpc_xpaths``) to direct ``mgmtd`` to send config, oper-state, and
+ RPC 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.
+#. In ``python/xref2vtysh.py`` add ``VTYSH_xxxD`` (for client xxx) to
+ ``lib/mgmt_be_client.c`` entry in the ``daemon_falgs`` dictionary.
+
Add YANG and CLI into MGMTD
^^^^^^^^^^^^^^^^^^^^^^^^^^^^
diff --git a/doc/developer/subdir.am b/doc/developer/subdir.am
index 652ee4e1a..bdf93a05c 100644
--- a/doc/developer/subdir.am
+++ b/doc/developer/subdir.am
@@ -28,6 +28,7 @@ dev_RSTFILES = \
doc/developer/building-frr-for-ubuntu1804.rst \
doc/developer/building-frr-for-ubuntu2004.rst \
doc/developer/building-frr-for-ubuntu2204.rst \
+ doc/developer/building-doc.rst \
doc/developer/building-libunwind-note.rst \
doc/developer/building-libyang.rst \
doc/developer/building.rst \
diff --git a/doc/manpages/frr-zebra.rst b/doc/manpages/frr-zebra.rst
index 6cc46b806..356c128e3 100644
--- a/doc/manpages/frr-zebra.rst
+++ b/doc/manpages/frr-zebra.rst
@@ -38,6 +38,8 @@ OPTIONS available for the |DAEMON| command:
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.
+ This option is deprecated. Please use the global -w option instead.
+
ROUTES
------
diff --git a/doc/user/_static/overrides.js b/doc/user/_static/overrides.js
index 73bf6123b..f6af539bf 100644
--- a/doc/user/_static/overrides.js
+++ b/doc/user/_static/overrides.js
@@ -5,7 +5,7 @@
*/
$(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('>=')").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");
diff --git a/doc/user/about.rst b/doc/user/about.rst
index e16ed7e3c..d9470f5f3 100644
--- a/doc/user/about.rst
+++ b/doc/user/about.rst
@@ -153,7 +153,7 @@ feature you're interested in, it should be supported on your platform.
.. 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
+ 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.)
@@ -165,9 +165,9 @@ feature you're interested in, it should be supported on your platform.
+-----------------------------------+----------------+--------------+------------+------------+
| `zebra` | :mark:`Y` | :mark:`Y` | :mark:`Y` | :mark:`Y` |
+-----------------------------------+----------------+--------------+------------+------------+
-| VRF | :mark:`≥4.8` | :mark:`N` | :mark:`N` | :mark:`N` |
+| VRF | :mark:`>=4.8` | :mark:`N` | :mark:`N` | :mark:`N` |
+-----------------------------------+----------------+--------------+------------+------------+
-| MPLS | :mark:`≥4.5` | :mark:`Y` | :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` |
+-----------------------------------+----------------+--------------+------------+------------+
@@ -175,21 +175,21 @@ feature you're interested in, it should be supported on your platform.
+-----------------------------------+----------------+--------------+------------+------------+
| `bgpd` (BGP) | :mark:`Y` | :mark:`Y` | :mark:`Y` | :mark:`Y` |
+-----------------------------------+----------------+--------------+------------+------------+
-| VRF / L3VPN | :mark:`≥4.8` | :mark:`CP` | :mark:`CP` | :mark:`CP` |
+| VRF / L3VPN | :mark:`>=4.8` | :mark:`CP` | :mark:`CP` | :mark:`CP` |
| | :mark:`†4.3` | | | |
+-----------------------------------+----------------+--------------+------------+------------+
-| EVPN | :mark:`≥4.18` | :mark:`CP` | :mark:`CP` | :mark:`CP` |
+| 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` |
+| `ldpd` (LDP) | :mark:`>=4.5` | :mark:`Y` | :mark:`N` | :mark:`N` |
+-----------------------------------+----------------+--------------+------------+------------+
-| VPWS / PW | :mark:`N` | :mark:`≥5.8` | :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` |
+| VPLS | :mark:`N` | :mark:`>=5.8`| :mark:`N` | :mark:`N` |
+-----------------------------------+----------------+--------------+------------+------------+
| `nhrpd` (NHRP) | :mark:`Y` | :mark:`N` | :mark:`N` | :mark:`N` |
+-----------------------------------+----------------+--------------+------------+------------+
@@ -197,7 +197,7 @@ feature you're interested in, it should be supported on your platform.
+-----------------------------------+----------------+--------------+------------+------------+
| `ospfd` (OSPFv2) | :mark:`Y` | :mark:`Y` | :mark:`Y` | :mark:`Y` |
+-----------------------------------+----------------+--------------+------------+------------+
-| Segment Routing | :mark:`≥4.12` | :mark:`N` | :mark:`N` | :mark:`N` |
+| Segment Routing | :mark:`>=4.12` | :mark:`N` | :mark:`N` | :mark:`N` |
+-----------------------------------+----------------+--------------+------------+------------+
| `ospf6d` (OSPFv3) | :mark:`Y` | :mark:`Y` | :mark:`Y` | :mark:`Y` |
+-----------------------------------+----------------+--------------+------------+------------+
@@ -215,21 +215,21 @@ feature you're interested in, it should be supported on your platform.
+-----------------------------------+----------------+--------------+------------+------------+
| **Multicast Routing** | | | | |
+-----------------------------------+----------------+--------------+------------+------------+
-| `pimd` (PIM) | :mark:`≥4.19` | :mark:`N` | :mark:`Y` | :mark:`Y` |
+| `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` |
+| EVPN BUM Forwarding | :mark:`>=5.0` | :mark:`N` | :mark:`N` | :mark:`N` |
+-----------------------------------+----------------+--------------+------------+------------+
-| `vrrpd` (VRRP) | :mark:`≥5.1` | :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` - 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
diff --git a/doc/user/basic.rst b/doc/user/basic.rst
index 5fdd1887f..b2d47a38e 100644
--- a/doc/user/basic.rst
+++ b/doc/user/basic.rst
@@ -754,6 +754,17 @@ These options apply to all |PACKAGE_NAME| daemons.
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/<namespace>".
+.. option:: -w, --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 them to FRR VRF contexts. This option must be
+ the same for all running daemons. 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 <daemons-configuration-file>`.
+
+ .. seealso:: :ref:`zebra-vrf`
+
.. option:: -o, --vrfdefaultname <name>
Set the name used for the *Default VRF* in CLI commands and YANG models.
diff --git a/doc/user/bgp.rst b/doc/user/bgp.rst
index 364268176..1493c2fb9 100644
--- a/doc/user/bgp.rst
+++ b/doc/user/bgp.rst
@@ -2693,6 +2693,12 @@ The following commands can be used in route maps:
happen only when BGP updates have completely same communities value
specified in the community list.
+.. clicmd:: match community-limit (0-65535)
+
+ This command matches BGP updates that use community list, and with a community
+ list count less or equal than the defined limit. Setting community-limit to 0
+ will only match BGP updates with no community.
+
.. clicmd:: set community <none|COMMUNITY> additive
This command sets the community value in BGP updates. If the attribute is
@@ -4149,6 +4155,11 @@ Debugging
Enable or disable debugging of communications between *bgpd* and *zebra*.
+.. clicmd:: debug bgp aggregate [prefix <A.B.C.D/M|X:X::X:X/M>]
+
+ Enable or disable debugging of route aggregation, either for one or more
+ aggregate addresses or for all aggregate addresses.
+
Dumping Messages and Routing Tables
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
diff --git a/doc/user/bmp.rst b/doc/user/bmp.rst
index 14d0849b3..07c3c1c8b 100644
--- a/doc/user/bmp.rst
+++ b/doc/user/bmp.rst
@@ -171,3 +171,8 @@ associated with a particular ``bmp targets``:
All BGP neighbors are included in Route Mirroring. Options to select
a subset of BGP sessions may be added in the future.
+
+.. clicmd:: bmp import-vrf-view VRF_OR_VIEW_NAME
+
+ Perform Route Mirroring and Route Monitoring from an other BGP
+ instance.
diff --git a/doc/user/pim.rst b/doc/user/pim.rst
index ff45f21b5..c139e6488 100644
--- a/doc/user/pim.rst
+++ b/doc/user/pim.rst
@@ -217,7 +217,7 @@ PIM Routers
never do SM over. This command is vrf aware, to configure for a vrf, specify
the vrf in the router pim block.
-.. clicmd:: rpf-lookup-mode MODE
+.. clicmd:: rpf-lookup-mode MODE [group-list PREFIX_LIST] [source-list PREFIX_LIST]
MODE sets the method used to perform RPF lookups. Supported modes:
@@ -246,6 +246,18 @@ PIM Routers
configured to make the configuration immune against possible changes in
what the default behavior is.
+ If a group and/or source prefix list is provided, then the RPF lookup mode
+ will only apply to source, group addresses that match the given prefix list(s).
+ Not all RPF lookups have a valid group address when performing a lookup, e.g. RPF
+ to an RP only does a lookup to the RP address and has no specific group.
+ Lookups that do not have a specific group will only use lookup modes that do not
+ specify a group-list.
+ A global rpf lookup mode that does not have a group or source list is always installed
+ and, as documented above, uses the ``mrib-then-urib`` mode by default.
+ This can be changed with an rpf-lookup-mode MODE that does not specify group or source lists.
+ There can be any number of rpf lookup modes, as long as the combination of group and source
+ list is unique.
+
.. warning::
Unreachable routes do not receive special treatment and do not cause
diff --git a/doc/user/static.rst b/doc/user/static.rst
index 922c71a07..5bf5004a6 100644
--- a/doc/user/static.rst
+++ b/doc/user/static.rst
@@ -176,3 +176,52 @@ multiple segments instructions.
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
+
+SRv6 Static SIDs Commands
+=========================
+
+.. clicmd:: segment-routing
+
+ Move from configure mode to segment-routing node.
+
+.. clicmd:: srv6
+
+ Move from segment-routing node to srv6 node.
+
+.. clicmd:: static-sids
+
+ Move from srv6 node to static-sids node. In this static-sids node, user can
+ configure static SRv6 SIDs.
+
+.. clicmd:: sid X:X::X:X/M locator NAME behavior <uN|uDT4|uDT6|uDT46> [vrf VRF]
+
+ Specify the locator sid manually. Configuring a local sid in a purely static mode
+ by specifying the sid value would generate a unique SID.
+ This feature will support the configuration of static SRv6 decapsulation on the system.
+
+ It supports four parameter options, corresponding to the following functions:
+ uN, uDT4, uDT6, uDT46
+
+ When configuring the local sid, if the action is set to 'uN', no vrf should be set.
+ While for any other action, it is necessary to specify a specific vrf.
+
+::
+
+ router# configure terminal
+ router(config)# segment-routing
+ router(config-sr)# srv6
+ router(config-srv6)# static-sids
+ router(config-srv6-sids)# sid fcbb:bbbb:1:fe01::/64 locator LOC1 behavior uDT6 vrf Vrf1
+ router(config-srv6-sids)# sid fcbb:bbbb:1:fe02::/64 locator LOC1 behavior uDT4 vrf Vrf1
+ router(config-srv6-sids)# sid fcbb:bbbb:1:fe03::/64 locator LOC1 behavior uDT46 vrf Vrf2
+
+ router(config-srv6-locator)# show run
+ ...
+ segment-routing
+ srv6
+ static-sids
+ sid fcbb:bbbb:1:fe01::/64 locator LOC1 behavior uDT6 vrf Vrf1
+ sid fcbb:bbbb:1:fe02::/64 locator LOC1 behavior uDT4 vrf Vrf1
+ sid fcbb:bbbb:1:fe03::/64 locator LOC1 behavior uDT46 vrf Vrf2
+ !
+ ... \ No newline at end of file
diff --git a/doc/user/zebra.rst b/doc/user/zebra.rst
index ac29b1c7d..ef3a61985 100644
--- a/doc/user/zebra.rst
+++ b/doc/user/zebra.rst
@@ -53,6 +53,8 @@ Besides the common invocation options (:ref:`common-invocation-options`), the
VRF defined by *Zebra*, as usual. If this option is specified when running
*Zebra*, one must also specify the same option for *mgmtd*.
+ This options is deprecated. Please use the global -w option instead.
+
.. seealso:: :ref:`zebra-vrf`
.. option:: -z <path_to_socket>, --socket <path_to_socket>
diff --git a/docker/ubuntu-ci/Dockerfile b/docker/ubuntu-ci/Dockerfile
index aaad3bc17..0bfcb5187 100644
--- a/docker/ubuntu-ci/Dockerfile
+++ b/docker/ubuntu-ci/Dockerfile
@@ -2,7 +2,6 @@ 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 && \
@@ -77,14 +76,15 @@ RUN apt update && apt upgrade -y && \
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 && \
+ rm -f /usr/lib/python3.*/EXTERNALLY-MANAGED && \
python3 -m pip install wheel && \
- python3 -m pip install 'protobuf<4' grpcio grpcio-tools && \
+ python3 -m pip install protobuf 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
-ARG UID=1000
+ARG UID=1010
RUN groupadd -r -g 92 frr && \
groupadd -r -g 85 frrvty && \
adduser --system --ingroup frr --home /home/frr \
diff --git a/docker/ubuntu22-ci/README.md b/docker/ubuntu22-ci/README.md
index 617192eb7..116b3c0e4 100644
--- a/docker/ubuntu22-ci/README.md
+++ b/docker/ubuntu22-ci/README.md
@@ -5,7 +5,7 @@ This builds an ubuntu 22.04 container for dev / test
# Build
```
-docker build -t frr-ubuntu22:latest -f docker/ubuntu-ci/Dockerfile .
+docker build -t frr-ubuntu22:latest --build-arg=UBUNTU_VERSION=22.04 -f docker/ubuntu-ci/Dockerfile .
```
# Run
diff --git a/docker/ubuntu24-ci/README.md b/docker/ubuntu24-ci/README.md
new file mode 100644
index 000000000..38ba0ee17
--- /dev/null
+++ b/docker/ubuntu24-ci/README.md
@@ -0,0 +1,66 @@
+# Ubuntu 24.04
+
+This builds an ubuntu 24.04 container for dev / test
+
+# Build
+
+```
+docker build -t frr-ubuntu24:latest --build-arg=UBUNTU_VERSION=24.04 -f docker/ubuntu-ci/Dockerfile .
+```
+
+# Run
+
+```
+docker run -d --init --privileged --name frr-ubuntu24 --mount type=bind,source=/lib/modules,target=/lib/modules frr-ubuntu24:latest
+```
+
+# Running full topotest (container stops at end)
+
+```
+docker run --init -it --privileged --name frr-ubuntu24 \
+ -v /lib/modules:/lib/modules frr-ubuntu24: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-ubuntu24 -Ar run-results
+```
+
+# Extract coverage from a stopped container into host FRR source tree
+
+```
+docker export frr-ubuntu24 | tar --strip=3 --wildcards -vx '*.gc??'
+lcov -b $(pwd) --capture --directory . --output-file=coverage.info
+```
+
+# make check
+
+```
+docker exec frr-ubuntu24 bash -c 'cd ~/frr ; make check'
+```
+
+# interactive bash
+
+```
+docker exec -it frr-ubuntu24 bash
+```
+
+# Run a specific topotest
+
+```
+docker exec frr-ubuntu24 bash -c 'cd ~/frr/tests/topotests ; sudo pytest ospf_topo1/test_ospf_topo1.py'
+```
+
+# stop & remove container
+
+```
+docker stop frr-ubuntu24 ; docker rm frr-ubuntu24
+```
+
+# remove image
+
+```
+docker rmi frr-ubuntu24:latest
+```
diff --git a/isisd/isis_srv6.c b/isisd/isis_srv6.c
index 2348bd043..4b97b5372 100644
--- a/isisd/isis_srv6.c
+++ b/isisd/isis_srv6.c
@@ -698,7 +698,7 @@ void isis_srv6_area_init(struct isis_area *area)
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));
+ strlcpy(srv6db->config.srv6_ifname, DEFAULT_SRV6_IFNAME, sizeof(srv6db->config.srv6_ifname));
#endif
/* Initialize SRv6 Locator chunks list */
diff --git a/isisd/isis_srv6.h b/isisd/isis_srv6.h
index bde14965f..eeb76c0b8 100644
--- a/isisd/isis_srv6.h
+++ b/isisd/isis_srv6.h
@@ -16,8 +16,7 @@
#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"
+#define ISIS_DEFAULT_SRV6_MAX_END_D_MSD 5
/* SRv6 SID structure */
struct isis_srv6_sid_structure {
diff --git a/lib/command.h b/lib/command.h
index c60751789..dfd732893 100644
--- a/lib/command.h
+++ b/lib/command.h
@@ -154,6 +154,7 @@ enum node_type {
PCEP_PCE_NODE, /* PCE configuration node */
PCEP_PCC_NODE, /* PCC configuration node */
SRV6_NODE, /* SRv6 node */
+ SRV6_SIDS_NODE, /* SRv6 SIDs node */
SRV6_LOCS_NODE, /* SRv6 locators node */
SRV6_LOC_NODE, /* SRv6 locator node */
SRV6_ENCAP_NODE, /* SRv6 encapsulation node */
diff --git a/lib/darr.h b/lib/darr.h
index 121e3dd14..084c2a103 100644
--- a/lib/darr.h
+++ b/lib/darr.h
@@ -571,16 +571,16 @@ void *__darr_resize(void *a, uint count, size_t esize, struct memtype *mt);
* 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; \
+#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(&(D)[darr_strlen(D)] /* darr_last(D) clangSA :( */, (S), __slen + 1); \
+ _darr_len(D) += __slen; \
+ D; \
})
/**
diff --git a/lib/event.c b/lib/event.c
index cfe8c3adc..d95b3021a 100644
--- a/lib/event.c
+++ b/lib/event.c
@@ -429,9 +429,6 @@ DEFUN_NOSH (show_event_poll,
return CMD_SUCCESS;
}
-#if CONFDATE > 20241231
-CPP_NOTICE("Remove `clear thread cpu` command")
-#endif
DEFUN (clear_event_cpu,
clear_event_cpu_cmd,
"clear event cpu [FILTER]",
@@ -457,14 +454,6 @@ DEFUN (clear_event_cpu,
return CMD_SUCCESS;
}
-ALIAS (clear_event_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")
-
static void show_event_timers_helper(struct vty *vty, struct event_loop *m)
{
const char *name = m->name ? m->name : "main";
@@ -504,7 +493,6 @@ void event_cmd_init(void)
{
install_element(VIEW_NODE, &show_event_cpu_cmd);
install_element(VIEW_NODE, &show_event_poll_cmd);
- install_element(ENABLE_NODE, &clear_thread_cpu_cmd);
install_element(ENABLE_NODE, &clear_event_cpu_cmd);
install_element(CONFIG_NODE, &service_cputime_stats_cmd);
diff --git a/lib/frrscript.c b/lib/frrscript.c
index 06460b014..8b068ba61 100644
--- a/lib/frrscript.c
+++ b/lib/frrscript.c
@@ -248,10 +248,12 @@ int _frrscript_call_lua(struct lua_function_state *lfs, int nargs)
zlog_err("Lua hook call '%s' : error handler error: %s",
lfs->name, lua_tostring(lfs->L, -1));
break;
+#if defined(LUA_VERSION_NUM) && LUA_VERSION_NUM <= 503
case LUA_ERRGCMM:
zlog_err("Lua hook call '%s' : garbage collector error: %s",
lfs->name, lua_tostring(lfs->L, -1));
break;
+#endif
default:
zlog_err("Lua hook call '%s' : unknown error: %s", lfs->name,
lua_tostring(lfs->L, -1));
diff --git a/lib/if.c b/lib/if.c
index 586fc1d5c..68724a65e 100644
--- a/lib/if.c
+++ b/lib/if.c
@@ -29,6 +29,10 @@
#include "admin_group.h"
#include "lib/if_clippy.c"
+
+/* Set by the owner (zebra). */
+bool if_notify_oper_changes;
+
DEFINE_MTYPE_STATIC(LIB, IF, "Interface");
DEFINE_MTYPE_STATIC(LIB, IFDESC, "Intf Desc");
DEFINE_MTYPE_STATIC(LIB, CONNECTED, "Connected");
@@ -208,6 +212,104 @@ void if_down_via_zapi(struct interface *ifp)
hook_call(if_down, ifp);
}
+void if_update_state_metric(struct interface *ifp, uint32_t metric)
+{
+ if (ifp->metric == metric)
+ return;
+ ifp->metric = metric;
+ if (ifp->state && if_notify_oper_changes)
+ nb_op_updatef(ifp->state, "metric", "%u", ifp->metric);
+}
+
+void if_update_state_mtu(struct interface *ifp, uint mtu)
+{
+ if (ifp->mtu == mtu)
+ return;
+ ifp->mtu = mtu;
+ if (ifp->state && if_notify_oper_changes)
+ nb_op_updatef(ifp->state, "mtu", "%u", ifp->mtu);
+}
+
+void if_update_state_mtu6(struct interface *ifp, uint mtu)
+{
+ if (ifp->mtu6 == mtu)
+ return;
+ ifp->mtu6 = mtu;
+ if (ifp->state && if_notify_oper_changes)
+ nb_op_updatef(ifp->state, "mtu6", "%u", ifp->mtu);
+}
+
+void if_update_state_hw_addr(struct interface *ifp, const uint8_t *hw_addr, uint len)
+{
+ if (len == (uint)ifp->hw_addr_len && (len == 0 || !memcmp(hw_addr, ifp->hw_addr, len)))
+ return;
+ memcpy(ifp->hw_addr, hw_addr, len);
+ ifp->hw_addr_len = len;
+ if (ifp->state && if_notify_oper_changes)
+ nb_op_updatef(ifp->state, "phy-address", "%pEA", (struct ethaddr *)ifp->hw_addr);
+}
+
+void if_update_state_speed(struct interface *ifp, uint32_t speed)
+{
+ if (ifp->speed == speed)
+ return;
+ ifp->speed = speed;
+ if (ifp->state && if_notify_oper_changes)
+ nb_op_updatef(ifp->state, "speed", "%u", ifp->speed);
+}
+
+void if_update_state(struct interface *ifp)
+{
+ struct lyd_node *state = ifp->state;
+
+ if (!state || !if_notify_oper_changes)
+ return;
+
+ /*
+ * Remove top level container update when we have patch support, for now
+ * this keeps us from generating 6 separate REPLACE messages though.
+ */
+ // nb_op_update(state, ".", NULL);
+ nb_op_updatef(state, "if-index", "%d", ifp->ifindex);
+ nb_op_updatef(state, "mtu", "%u", ifp->mtu);
+ nb_op_updatef(state, "mtu6", "%u", ifp->mtu);
+ nb_op_updatef(state, "speed", "%u", ifp->speed);
+ nb_op_updatef(state, "metric", "%u", ifp->metric);
+ nb_op_updatef(state, "phy-address", "%pEA", (struct ethaddr *)ifp->hw_addr);
+}
+
+static void if_update_state_remove(struct interface *ifp)
+{
+ if (!if_notify_oper_changes || ifp->name[0] == 0)
+ return;
+
+ if (vrf_is_backend_netns())
+ nb_op_update_delete_pathf(NULL, "/frr-interface:lib/interface[name=\"%s:%s\"]/state",
+ ifp->vrf->name, ifp->name);
+ else
+ nb_op_update_delete_pathf(NULL, "/frr-interface:lib/interface[name=\"%s\"]/state",
+ ifp->name);
+ if (ifp->state) {
+ lyd_free_all(ifp->state);
+ ifp->state = NULL;
+ }
+}
+
+static void if_update_state_add(struct interface *ifp)
+{
+ if (!if_notify_oper_changes || ifp->name[0] == 0)
+ return;
+
+ if (vrf_is_backend_netns())
+ ifp->state = nb_op_update_pathf(NULL,
+ "/frr-interface:lib/interface[name=\"%s:%s\"]/state",
+ NULL, ifp->vrf->name, ifp->name);
+ else
+ ifp->state = nb_op_update_pathf(NULL,
+ "/frr-interface:lib/interface[name=\"%s\"]/state",
+ NULL, ifp->name);
+}
+
static struct interface *if_create_name(const char *name, struct vrf *vrf)
{
struct interface *ifp;
@@ -216,7 +318,11 @@ static struct interface *if_create_name(const char *name, struct vrf *vrf)
if_set_name(ifp, name);
+ if (if_notify_oper_changes && ifp->state)
+ if_update_state(ifp);
+
hook_call(if_add, ifp);
+
return ifp;
}
@@ -228,8 +334,10 @@ void if_update_to_new_vrf(struct interface *ifp, vrf_id_t vrf_id)
/* remove interface from old master vrf list */
old_vrf = ifp->vrf;
- if (ifp->name[0] != '\0')
+ if (ifp->name[0] != '\0') {
IFNAME_RB_REMOVE(old_vrf, ifp);
+ if_update_state_remove(ifp);
+ }
if (ifp->ifindex != IFINDEX_INTERNAL)
IFINDEX_RB_REMOVE(old_vrf, ifp);
@@ -237,8 +345,11 @@ void if_update_to_new_vrf(struct interface *ifp, vrf_id_t vrf_id)
vrf = vrf_get(vrf_id, NULL);
ifp->vrf = vrf;
- if (ifp->name[0] != '\0')
+ if (ifp->name[0] != '\0') {
IFNAME_RB_INSERT(vrf, ifp);
+ if_update_state_add(ifp);
+ if_update_state(ifp);
+ }
if (ifp->ifindex != IFINDEX_INTERNAL)
IFINDEX_RB_INSERT(vrf, ifp);
@@ -280,6 +391,8 @@ void if_delete(struct interface **ifp)
XFREE(MTYPE_IFDESC, ptr->desc);
+ if_update_state_remove(ptr);
+
XFREE(MTYPE_IF, ptr);
*ifp = NULL;
}
@@ -303,7 +416,6 @@ static struct interface *if_lookup_by_ifindex(ifindex_t ifindex,
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:
@@ -573,7 +685,6 @@ struct interface *if_get_by_name(const char *name, vrf_id_t vrf_id,
struct vrf *vrf;
switch (vrf_get_backend()) {
- case VRF_BACKEND_UNKNOWN:
case VRF_BACKEND_NETNS:
vrf = vrf_get(vrf_id, vrf_name);
assert(vrf);
@@ -630,6 +741,9 @@ int if_set_index(struct interface *ifp, ifindex_t ifindex)
ifp->ifindex = ifindex;
+ if (if_notify_oper_changes)
+ nb_op_updatef(ifp->state, "if-index", "%d", ifp->ifindex);
+
if (ifp->ifindex != IFINDEX_INTERNAL) {
/*
* This should never happen, since we checked if there was
@@ -648,13 +762,17 @@ 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')
+ if (ifp->name[0] != '\0') {
IFNAME_RB_REMOVE(ifp->vrf, ifp);
+ if_update_state_remove(ifp);
+ }
strlcpy(ifp->name, name, sizeof(ifp->name));
- if (ifp->name[0] != '\0')
+ if (ifp->name[0] != '\0') {
IFNAME_RB_INSERT(ifp->vrf, ifp);
+ if_update_state_add(ifp);
+ }
}
/* Does interface up ? */
@@ -858,47 +976,6 @@ struct nbr_connected *nbr_connected_check(struct interface *ifp,
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)
{
diff --git a/lib/if.h b/lib/if.h
index c2ec73378..1e52020b6 100644
--- a/lib/if.h
+++ b/lib/if.h
@@ -297,6 +297,8 @@ struct interface {
struct vrf *vrf;
+ struct lyd_node *state;
+
/*
* Has the end users entered `interface XXXX` from the cli in some
* fashion?
@@ -633,6 +635,14 @@ 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 void if_update_state(struct interface *ifp);
+extern void if_update_state_metric(struct interface *ifp, uint32_t metric);
+extern void if_update_state_mtu(struct interface *ifp, uint mtu);
+extern void if_update_state_mtu6(struct interface *ifp, uint mtu);
+extern void if_update_state_hw_addr(struct interface *ifp, const uint8_t *hw_addr, uint len);
+extern void if_update_state_speed(struct interface *ifp, uint32_t speed);
+
+extern bool if_notify_oper_changes;
extern const struct frr_yang_module_info frr_interface_info;
extern const struct frr_yang_module_info frr_interface_cli_info;
diff --git a/lib/libfrr.c b/lib/libfrr.c
index d1a9f0b1c..261d3aa87 100644
--- a/lib/libfrr.c
+++ b/lib/libfrr.c
@@ -108,6 +108,9 @@ static const struct option lo_always[] = {
{ "module", no_argument, NULL, 'M' },
{ "profile", required_argument, NULL, 'F' },
{ "pathspace", required_argument, NULL, 'N' },
+#ifdef HAVE_NETLINK
+ { "vrfwnetns", no_argument, NULL, 'w' },
+#endif
{ "vrfdefaultname", required_argument, NULL, 'o' },
{ "graceful_restart", optional_argument, NULL, 'K' },
{ "vty_socket", required_argument, NULL, OPTION_VTYSOCK },
@@ -120,6 +123,9 @@ static const struct option lo_always[] = {
{ NULL }
};
static const struct optspec os_always = {
+#ifdef HAVE_NETLINK
+ "w"
+#endif
"hvdM:F:N:o:K::",
" -h, --help Display this help and exit\n"
" -v, --version Print program version\n"
@@ -127,6 +133,9 @@ static const struct optspec os_always = {
" -M, --module Load specified module\n"
" -F, --profile Use specified configuration profile\n"
" -N, --pathspace Insert prefix into config & socket paths\n"
+#ifdef HAVE_NETLINK
+ " -w, --vrfwnetns Use network namespaces for VRFs\n"
+#endif
" -o, --vrfdefaultname Set default VRF name.\n"
" -K, --graceful_restart FRR starting in Graceful Restart mode, with optional route-cleanup timer\n"
" --vty_socket Override vty socket path\n"
@@ -516,6 +525,11 @@ static int frr_opt(int opt)
snprintf(frr_zclientpath, sizeof(frr_zclientpath),
ZAPI_SOCK_NAME);
break;
+#ifdef HAVE_NETLINK
+ case 'w':
+ vrf_configure_backend(VRF_BACKEND_NETNS);
+ break;
+#endif
case 'o':
vrf_set_default_name(optarg);
break;
diff --git a/lib/mgmt_be_client.c b/lib/mgmt_be_client.c
index 155d56aa6..806242ed5 100644
--- a/lib/mgmt_be_client.c
+++ b/lib/mgmt_be_client.c
@@ -99,12 +99,12 @@ struct mgmt_be_client {
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;
+ uint64_t num_edit_nb_cfg;
+ uint64_t avg_edit_nb_cfg_tm;
+ uint64_t num_prep_nb_cfg;
+ uint64_t avg_prep_nb_cfg_tm;
+ uint64_t num_apply_nb_cfg;
+ uint64_t avg_apply_nb_cfg_tm;
struct mgmt_be_txns_head txn_head;
@@ -117,7 +117,7 @@ struct mgmt_be_client {
struct debug mgmt_dbg_be_client = {
.conf = "debug mgmt client backend",
- .desc = "Management backend client operations"
+ .desc = "Management backend client operations",
};
/* NOTE: only one client per proc for now. */
@@ -322,8 +322,7 @@ static int __send_notification(struct mgmt_be_client *client, const char *xpath,
LY_ERR err;
int ret = 0;
- assert(tree);
-
+ assert(op != NOTIFY_OP_NOTIFICATION || xpath || tree);
debug_be_client("%s: sending %sYANG %snotification: %s", __func__,
op == NOTIFY_OP_DS_DELETE ? "delete "
: op == NOTIFY_OP_DS_REPLACE ? "replace "
@@ -622,7 +621,7 @@ static int mgmt_be_txn_cfg_prepare(struct mgmt_be_txn_ctx *txn)
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",
+ 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);
@@ -771,10 +770,9 @@ static int mgmt_be_txn_proc_cfgapply(struct mgmt_be_txn_ctx *txn)
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->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;
@@ -790,8 +788,8 @@ static int mgmt_be_txn_proc_cfgapply(struct mgmt_be_txn_ctx *txn)
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);
+ debug_be_client("Nb-apply-duration %lu (avg: %Lu) uSec", apply_nb_cfg_tm,
+ client_ctx->avg_apply_nb_cfg_tm);
return 0;
}
@@ -1116,19 +1114,24 @@ 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;
+ struct nb_node *nb_node, *nb_parent;
+ struct lyd_node *dnode = NULL;
const char *data = NULL;
const char *notif;
- LY_ERR err;
+ bool is_yang_notify;
+ LY_ERR err = LY_SUCCESS;
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) {
+ if (!notif) {
log_err_be_client("Corrupt notify msg");
return;
}
+ if (!data && (notif_msg->op == NOTIFY_OP_DS_REPLACE || notif_msg->op == NOTIFY_OP_DS_PATCH)) {
+ log_err_be_client("Corrupt replace/patch notify msg: missing data");
+ return;
+ }
nb_node = nb_node_find(notif);
if (!nb_node) {
@@ -1136,25 +1139,62 @@ static void be_client_handle_notify(struct mgmt_be_client *client, void *msgbuf,
return;
}
- if (!nb_node->cbs.notify) {
+ is_yang_notify = !!CHECK_FLAG(nb_node->snode->nodetype, LYS_NOTIF);
+
+ if (is_yang_notify && !nb_node->cbs.notify) {
debug_be_client("No notification callback for: %s", notif);
return;
}
- err = yang_parse_notification(notif, notif_msg->result_type, data,
+ if (!nb_node->cbs.notify) {
+ /*
+ * See if a parent has a callback, this is so backend's can
+ * listen for changes on an entire datastore sub-tree.
+ */
+ for (nb_parent = nb_node->parent; nb_parent; nb_parent = nb_node->parent)
+ if (nb_parent->cbs.notify)
+ break;
+ if (!nb_parent) {
+ debug_be_client("Including parents, no DS notification callback for: %s",
+ notif);
+ return;
+ }
+ nb_node = nb_parent;
+ }
+
+ if (data && is_yang_notify) {
+ err = yang_parse_notification(notif, notif_msg->result_type, data, &dnode);
+ } else if (data) {
+ err = yang_parse_data(notif, notif_msg->result_type, false, true, false, data,
&dnode);
+ }
if (err) {
- log_err_be_client("Can't parse notification data for: %s",
- notif);
+ log_err_be_client("Can't parse notification data for: %s", notif);
return;
}
- nb_callback_notify(nb_node, notif, dnode);
+ nb_callback_notify(nb_node, notif_msg->op, notif, dnode);
lyd_free_all(dnode);
}
/*
+ * Process a notify select msg
+ */
+static void be_client_handle_notify_select(struct mgmt_be_client *client, void *msgbuf,
+ size_t msg_len)
+{
+ struct mgmt_msg_notify_select *msg = msgbuf;
+ const char **selectors = NULL;
+
+ debug_be_client("Received notify-select for client %s", client->name);
+
+ if (msg_len >= sizeof(*msg))
+ selectors = mgmt_msg_native_strings_decode(msg, msg_len, msg->selectors);
+ nb_notif_set_filters(selectors, msg->replace);
+}
+
+/*
* Handle a native encoded message
*
* We don't create transactions with native messaging.
@@ -1175,6 +1215,9 @@ static void be_client_handle_native_msg(struct mgmt_be_client *client,
case MGMT_MSG_CODE_NOTIFY:
be_client_handle_notify(client, msg, msg_len);
break;
+ case MGMT_MSG_CODE_NOTIFY_SELECT:
+ be_client_handle_notify_select(client, msg, msg_len);
+ break;
default:
log_err_be_client("unknown native message txn-id %" PRIu64
" req-id %" PRIu64 " code %u to client %s",
@@ -1315,6 +1358,190 @@ DEFPY(debug_mgmt_client_be, debug_mgmt_client_be_cmd,
return CMD_SUCCESS;
}
+/*
+ * XPath: /frr-backend:clients/client
+ *
+ * We only implement a list of one entry (for the this backend client) the
+ * results will be merged inside mgmtd.
+ */
+static const void *clients_client_get_next(struct nb_cb_get_next_args *args)
+{
+ if (args->list_entry == NULL)
+ return __be_client;
+ return NULL;
+}
+
+static int clients_client_get_keys(struct nb_cb_get_keys_args *args)
+{
+ args->keys->num = 1;
+ strlcpy(args->keys->key[0], __be_client->name, sizeof(args->keys->key[0]));
+
+ return NB_OK;
+}
+
+static const void *clients_client_lookup_entry(struct nb_cb_lookup_entry_args *args)
+{
+ const char *name = args->keys->key[0];
+
+ if (!strcmp(name, __be_client->name))
+ return __be_client;
+
+ return NULL;
+}
+
+/*
+ * XPath: /frr-backend:clients/client/name
+ */
+static enum nb_error clients_client_name_get(const struct nb_node *nb_node,
+ const void *parent_list_entry, struct lyd_node *parent)
+{
+ const struct lysc_node *snode = nb_node->snode;
+ LY_ERR err;
+
+ err = lyd_new_term(parent, snode->module, snode->name, __be_client->name, false, NULL);
+ if (err != LY_SUCCESS)
+ return NB_ERR_RESOURCE;
+
+ return NB_OK;
+}
+
+/*
+ * XPath: /frr-backend:clients/client/state/candidate-config-version
+ */
+static enum nb_error clients_client_state_candidate_config_version_get(
+ const struct nb_node *nb_node, const void *parent_list_entry, struct lyd_node *parent)
+{
+ const struct lysc_node *snode = nb_node->snode;
+ uint64_t value = __be_client->candidate_config->version;
+
+ if (lyd_new_term_bin(parent, snode->module, snode->name, &value, sizeof(value),
+ LYD_NEW_PATH_UPDATE, NULL))
+ return NB_ERR_RESOURCE;
+
+ return NB_OK;
+}
+
+/*
+ * XPath: /frr-backend:clients/client/state/running-config-version
+ */
+static enum nb_error clients_client_state_running_config_version_get(const struct nb_node *nb_node,
+ const void *parent_list_entry,
+ struct lyd_node *parent)
+{
+ const struct lysc_node *snode = nb_node->snode;
+ uint64_t value = __be_client->running_config->version;
+
+ if (lyd_new_term_bin(parent, snode->module, snode->name, &value, sizeof(value),
+ LYD_NEW_PATH_UPDATE, NULL))
+ return NB_ERR_RESOURCE;
+
+ return NB_OK;
+}
+
+/*
+ * XPath: /frr-backend:clients/client/state/notify-selectors
+ *
+ * Is this better in northbound_notif.c? Let's decide when we add more to this module.
+ */
+
+static enum nb_error clients_client_state_notify_selectors_get(const struct nb_node *nb_node,
+ const void *parent_list_entry,
+ struct lyd_node *parent)
+{
+ const struct lysc_node *snode = nb_node->snode;
+ const char **p;
+ LY_ERR err;
+
+ darr_foreach_p (nb_notif_filters, p) {
+ err = lyd_new_term(parent, snode->module, snode->name, *p, false, NULL);
+ if (err != LY_SUCCESS)
+ return NB_ERR_RESOURCE;
+ }
+
+ return NB_OK;
+}
+
+/* clang-format off */
+const struct frr_yang_module_info frr_backend_info = {
+ .name = "frr-backend",
+ .nodes = {
+ {
+ .xpath = "/frr-backend:clients/client",
+ .cbs = {
+ .get_next = clients_client_get_next,
+ .get_keys = clients_client_get_keys,
+ .lookup_entry = clients_client_lookup_entry,
+ }
+ },
+ {
+ .xpath = "/frr-backend:clients/client/name",
+ .cbs.get = clients_client_name_get,
+ },
+ {
+ .xpath = "/frr-backend:clients/client/state/candidate-config-version",
+ .cbs = {
+ .get = clients_client_state_candidate_config_version_get,
+ }
+ },
+ {
+ .xpath = "/frr-backend:clients/client/state/running-config-version",
+ .cbs = {
+ .get = clients_client_state_running_config_version_get,
+ }
+ },
+ {
+ .xpath = "/frr-backend:clients/client/state/edit-count",
+ .cbs = {
+ .get = nb_oper_uint64_get,
+ .get_elem = (void *)(intptr_t)offsetof(struct mgmt_be_client, num_edit_nb_cfg),
+ }
+ },
+ {
+ .xpath = "/frr-backend:clients/client/state/avg-edit-time",
+ .cbs = {
+ .get = nb_oper_uint64_get,
+ .get_elem = (void *)(intptr_t)offsetof(struct mgmt_be_client, avg_edit_nb_cfg_tm),
+ }
+ },
+ {
+ .xpath = "/frr-backend:clients/client/state/prep-count",
+ .cbs = {
+ .get = nb_oper_uint64_get,
+ .get_elem = (void *)(intptr_t)offsetof(struct mgmt_be_client, num_prep_nb_cfg),
+ }
+ },
+ {
+ .xpath = "/frr-backend:clients/client/state/avg-prep-time",
+ .cbs = {
+ .get = nb_oper_uint64_get,
+ .get_elem = (void *)(intptr_t)offsetof(struct mgmt_be_client, avg_prep_nb_cfg_tm),
+ }
+ },
+ {
+ .xpath = "/frr-backend:clients/client/state/apply-count",
+ .cbs = {
+ .get = nb_oper_uint64_get,
+ .get_elem = (void *)(intptr_t)offsetof(struct mgmt_be_client, num_apply_nb_cfg),
+ }
+ },
+ {
+ .xpath = "/frr-backend:clients/client/state/avg-apply-time",
+ .cbs = {
+ .get = nb_oper_uint64_get,
+ .get_elem = (void *)(intptr_t)offsetof(struct mgmt_be_client, avg_apply_nb_cfg_tm),
+ }
+ },
+ {
+ .xpath = "/frr-backend:clients/client/state/notify-selectors",
+ .cbs.get = clients_client_state_notify_selectors_get,
+ },
+ {
+ .xpath = NULL,
+ },
+ }
+};
+/* clang-format on */
+
struct mgmt_be_client *mgmt_be_client_create(const char *client_name,
struct mgmt_be_client_cbs *cbs,
uintptr_t user_data,
diff --git a/lib/mgmt_be_client.h b/lib/mgmt_be_client.h
index a3e3896d5..5e78f0f43 100644
--- a/lib/mgmt_be_client.h
+++ b/lib/mgmt_be_client.h
@@ -85,6 +85,8 @@ struct mgmt_be_client_cbs {
extern struct debug mgmt_dbg_be_client;
+extern const struct frr_yang_module_info frr_backend_info;
+
/***************************************************************
* API prototypes
***************************************************************/
diff --git a/lib/mgmt_msg_native.c b/lib/mgmt_msg_native.c
index b85c7d1b6..46dfe7f2e 100644
--- a/lib/mgmt_msg_native.c
+++ b/lib/mgmt_msg_native.c
@@ -14,7 +14,8 @@ 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_NOTIFY, "native notify msg");
+DEFINE_MTYPE(MSG_NATIVE, MSG_NATIVE_NOTIFY_SELECT, "native notify select 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");
diff --git a/lib/mgmt_msg_native.h b/lib/mgmt_msg_native.h
index 4076977a2..73303846e 100644
--- a/lib/mgmt_msg_native.h
+++ b/lib/mgmt_msg_native.h
@@ -159,6 +159,7 @@ 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_NOTIFY_SELECT);
DECLARE_MTYPE(MSG_NATIVE_EDIT);
DECLARE_MTYPE(MSG_NATIVE_EDIT_REPLY);
DECLARE_MTYPE(MSG_NATIVE_RPC);
diff --git a/lib/northbound.c b/lib/northbound.c
index c67ed924a..60794b872 100644
--- a/lib/northbound.c
+++ b/lib/northbound.c
@@ -685,19 +685,30 @@ void nb_config_diff(const struct nb_config *config1,
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)
+/**
+ * dnode_create() - create a new node in the tree
+ * @candidate: config tree to create node in.
+ * @xpath: target node to create.
+ * @value: value for the new if required.
+ * @options: lyd_new_path options
+ * @new_dnode: the newly created node. If options includes LYD_NEW_PATH_UPDATE,
+ * and the node exists (i.e., isn't create but updated), then
+ * new_node will be set to NULL not the existing node).
+ *
+ * Return: NB_OK or NB_ERR.
+ */
+static LY_ERR 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);
+ err = lyd_new_path2(candidate->dnode, ly_native_ctx, xpath, value, 0, 0, options, NULL,
+ &dnode);
if (err) {
flog_warn(EC_LIB_LIBYANG, "%s: lyd_new_path(%s) failed: %d",
__func__, xpath, err);
- return NB_ERR;
+ return err;
} else if (dnode) {
err = lyd_new_implicit_tree(dnode, LYD_IMPLICIT_NO_STATE, NULL);
if (err) {
@@ -708,7 +719,7 @@ static int dnode_create(struct nb_config *candidate, const char *xpath,
}
if (new_dnode)
*new_dnode = dnode;
- return NB_OK;
+ return LY_SUCCESS;
}
int nb_candidate_edit(struct nb_config *candidate, const struct nb_node *nb_node,
@@ -1857,7 +1868,7 @@ int nb_callback_rpc(const struct nb_node *nb_node, const char *xpath,
return nb_node->cbs.rpc(&args);
}
-void nb_callback_notify(const struct nb_node *nb_node, const char *xpath,
+void nb_callback_notify(const struct nb_node *nb_node, uint8_t op, const char *xpath,
struct lyd_node *dnode)
{
struct nb_cb_notify_args args = {};
@@ -1865,6 +1876,7 @@ void nb_callback_notify(const struct nb_node *nb_node, const char *xpath,
DEBUGD(&nb_dbg_cbs_notify, "northbound notify: %s", xpath);
args.xpath = xpath;
+ args.op = op;
args.dnode = dnode;
nb_node->cbs.notify(&args);
}
@@ -2754,10 +2766,15 @@ void nb_init(struct event_loop *tm,
/* Initialize oper-state */
nb_oper_init(tm);
+
+ /* Initialize notification-state */
+ nb_notif_init(tm);
}
void nb_terminate(void)
{
+ nb_notif_terminate();
+
nb_oper_terminate();
/* Terminate the northbound CLI. */
diff --git a/lib/northbound.h b/lib/northbound.h
index 38d8c2bdc..c31f007e7 100644
--- a/lib/northbound.h
+++ b/lib/northbound.h
@@ -305,6 +305,7 @@ struct nb_cb_rpc_args {
struct nb_cb_notify_args {
/* XPath of the notification. */
const char *xpath;
+ uint8_t op;
/*
* libyang data node representing the notification. If the notification
@@ -836,6 +837,9 @@ extern struct debug nb_dbg_libyang;
/* Global running configuration. */
extern struct nb_config *running_config;
+/* Global notification filters */
+extern const char **nb_notif_filters;
+
/* Wrappers for the northbound callbacks. */
extern struct yang_data *nb_callback_has_new_get_elem(const struct nb_node *nb_node);
@@ -858,7 +862,7 @@ extern const void *nb_callback_lookup_next(const struct nb_node *nb_node,
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,
+extern void nb_callback_notify(const struct nb_node *nb_node, uint8_t op, const char *xpath,
struct lyd_node *dnode);
/*
@@ -1512,6 +1516,22 @@ extern void nb_oper_cancel_walk(void *walk);
*/
extern void nb_oper_cancel_all_walks(void);
+/**
+ * nb_oper_walk_finish_arg() - return the finish arg for this walk
+ */
+extern void *nb_oper_walk_finish_arg(void *walk);
+/**
+ * nb_oper_walk_cb_arg() - return the callback arg for this walk
+ */
+extern void *nb_oper_walk_cb_arg(void *walk);
+
+/* Generic getter functions */
+extern enum nb_error nb_oper_uint32_get(const struct nb_node *nb_node,
+ const void *parent_list_entry, struct lyd_node *parent);
+
+extern enum nb_error nb_oper_uint64_get(const struct nb_node *nb_node,
+ const void *parent_list_entry, struct lyd_node *parent);
+
/*
* Validate if the northbound callback operation is valid for the given node.
*
@@ -1744,6 +1764,80 @@ extern void nb_oper_init(struct event_loop *loop);
extern void nb_oper_terminate(void);
extern bool nb_oper_is_yang_lib_query(const char *xpath);
+
+/**
+ * nb_op_update() - Create new state data.
+ * @tree: subtree @path is relative to or NULL in which case @path must be
+ * absolute.
+ * @path: The path of the state node to create.
+ * @value: The canonical value of the state.
+ *
+ * Return: The new libyang node.
+ */
+extern struct lyd_node *nb_op_update(struct lyd_node *tree, const char *path, const char *value);
+
+/**
+ * nb_op_update_delete() - Delete state data.
+ * @tree: subtree @path is relative to or NULL in which case @path must be
+ * absolute.
+ * @path: The path of the state node to delete, or NULL if @tree should just be
+ * deleted.
+ */
+extern void nb_op_update_delete(struct lyd_node *tree, const char *path);
+
+/**
+ * nb_op_update_pathf() - Create new state data.
+ * @tree: subtree @path_fmt is relative to or NULL in which case @path_fmt must
+ * be absolute.
+ * @path_fmt: The path format string of the state node to create.
+ * @value: The canonical value of the state.
+ * @...: The values to substitute into @path_fmt.
+ *
+ * Return: The new libyang node.
+ */
+extern struct lyd_node *nb_op_update_pathf(struct lyd_node *tree, const char *path_fmt,
+ const char *value, ...) PRINTFRR(2, 4);
+extern struct lyd_node *nb_op_update_vpathf(struct lyd_node *tree, const char *path_fmt,
+ const char *value, va_list ap);
+/**
+ * nb_op_update_delete_pathf() - Delete state data.
+ * @tree: subtree @path_fmt is relative to or NULL in which case @path_fmt must
+ * be absolute.
+ * @path: The path of the state node to delete.
+ * @...: The values to substitute into @path_fmt.
+ */
+extern void nb_op_update_delete_pathf(struct lyd_node *tree, const char *path_fmt, ...)
+ PRINTFRR(2, 3);
+extern void nb_op_update_delete_vpathf(struct lyd_node *tree, const char *path_fmt, va_list ap);
+
+/**
+ * nb_op_updatef() - Create new state data.
+ * @tree: subtree @path is relative to or NULL in which case @path must be
+ * absolute.
+ * @path: The path of the state node to create.
+ * @val_fmt: The value format string to set the canonical value of the state.
+ * @...: The values to substitute into @val_fmt.
+ *
+ * Return: The new libyang node.
+ */
+extern struct lyd_node *nb_op_updatef(struct lyd_node *tree, const char *path, const char *val_fmt,
+ ...) PRINTFRR(3, 4);
+
+extern struct lyd_node *nb_op_vupdatef(struct lyd_node *tree, const char *path, const char *val_fmt,
+ va_list ap);
+
+/**
+ * nb_notif_set_filters() - add or replace notification filters
+ * @selectors: darr array of selector (filter) xpath strings, can be NULL if
+ * @replace is true. nb_notif_set_filters takes ownership of this
+ * array and the contained darr strings.
+ * @replace: true to replace existing set otherwise append.
+ */
+extern void nb_notif_set_filters(const char **selectors, bool replace);
+
+extern void nb_notif_init(struct event_loop *loop);
+extern void nb_notif_terminate(void);
+
#ifdef __cplusplus
}
#endif
diff --git a/lib/northbound_notif.c b/lib/northbound_notif.c
new file mode 100644
index 000000000..10a81d05f
--- /dev/null
+++ b/lib/northbound_notif.c
@@ -0,0 +1,706 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * December 1 2024, Christian Hopps <chopps@labn.net>
+ *
+ * Copyright (c) 2024, LabN Consulting, L.L.C.
+ *
+ */
+#include <zebra.h>
+#include "debug.h"
+#include "lib_errors.h"
+#include "typesafe.h"
+#include "northbound.h"
+#include "mgmt_be_client.h"
+
+#define __dbg(fmt, ...) DEBUGD(&nb_dbg_notif, "NB_OP_CHANGE: %s: " fmt, __func__, ##__VA_ARGS__)
+#define __log_err(fmt, ...) zlog_err("NB_OP_CHANGE: %s: ERROR: " fmt, __func__, ##__VA_ARGS__)
+
+#define NB_NOTIF_TIMER_MSEC (10) /* 10msec */
+
+/*
+ * ADDS:
+ * - Less specific:
+ * - Any new add will cause more specific pending adds to be dropped and equal
+ * or more specific deletes to be dropped.
+ * - More specific:
+ * - Ignore any new add that is the same or more specific than an existing add.
+ * - A new add that is more specific than a delete should change the delete
+ * into an add query (since adds are reported as a replace).
+ *
+ * DELETES:
+ * - Less specific:
+ * - Any new delete will cause more specific pending deletes to be dropped and
+ * equal or more specific adds to be dropped.
+ * - More specific:
+ * - Ignore new deletes that are the same or more specific than existing
+ * deletes.
+ * - A new delete that is more specific than an add can be dropped since we
+ * use replacement methodology for the add.
+ *
+ * One thing we have to pay close attention to is that the state is going to be
+ * queried when the notification sent, not when we are told of the change.
+ */
+
+DEFINE_MTYPE_STATIC(LIB, OP_CHANGE, "NB Oper Change");
+DEFINE_MTYPE_STATIC(LIB, OP_CHANGES_GROUP, "NB Oper Changes Group");
+DEFINE_MTYPE_STATIC(LIB, NB_NOTIF_WALK_ARGS, "NB Notify Oper Walk");
+
+struct op_change {
+ RB_ENTRY(op_change) link;
+ char path[];
+};
+
+/*
+ * RB tree for op_change
+ */
+static int op_change_cmp(const struct op_change *e1, const struct op_change *e2);
+RB_HEAD(op_changes, op_change);
+RB_PROTOTYPE(op_changes, op_change, link, op_change_cmp)
+RB_GENERATE(op_changes, op_change, link, op_change_cmp)
+
+struct op_changes nb_notif_adds = RB_INITIALIZER(&nb_notif_adds);
+struct op_changes nb_notif_dels = RB_INITIALIZER(&nb_notif_dels);
+struct event_loop *nb_notif_master;
+struct event *nb_notif_timer;
+void *nb_notif_walk;
+
+const char **nb_notif_filters;
+
+/*
+ * We maintain a queue of change lists one entry per query and notification send
+ * action
+ */
+PREDECL_LIST(op_changes_queue);
+struct op_changes_group {
+ struct op_changes_queue_item item;
+ struct op_changes adds;
+ struct op_changes dels;
+ struct op_changes *cur_changes; /* used when walking */
+ struct op_change *cur_change; /* " " " */
+};
+
+DECLARE_LIST(op_changes_queue, struct op_changes_group, item);
+static struct op_changes_queue_head op_changes_queue;
+
+struct nb_notif_walk_args {
+ struct op_changes_group *group;
+ struct lyd_node *tree;
+};
+
+static void nb_notif_set_walk_timer(void);
+
+
+static int pathncmp(const char *s1, const char *s2, size_t n)
+{
+ size_t i = 0;
+
+ while (i < n && *s1 && *s2) {
+ char c1 = *s1;
+ char c2 = *s2;
+
+ if ((c1 == '\'' && c2 == '\"') || (c1 == '\"' && c2 == '\'')) {
+ s1++;
+ s2++;
+ i++;
+ continue;
+ }
+ if (c1 != c2)
+ return (unsigned char)c1 - (unsigned char)c2;
+ s1++;
+ s2++;
+ i++;
+ }
+ if (i < n)
+ return (unsigned char)*s1 - (unsigned char)*s2;
+ return 0;
+}
+
+static int pathcmp(const char *s1, const char *s2)
+{
+ while (*s1 && *s2) {
+ char c1 = *s1;
+ char c2 = *s2;
+
+ if ((c1 == '\'' && c2 == '\"') || (c1 == '\"' && c2 == '\'')) {
+ s1++;
+ s2++;
+ continue;
+ }
+ if (c1 != c2)
+ return (unsigned char)c1 - (unsigned char)c2;
+ s1++;
+ s2++;
+ }
+ return (unsigned char)*s1 - (unsigned char)*s2;
+}
+
+
+static int op_change_cmp(const struct op_change *e1, const struct op_change *e2)
+{
+ return pathcmp(e1->path, e2->path);
+}
+
+static struct op_change *op_change_alloc(const char *path)
+{
+ struct op_change *note;
+ size_t ssize = strlen(path) + 1;
+
+ note = XMALLOC(MTYPE_OP_CHANGE, sizeof(*note) + ssize);
+ memset(note, 0, sizeof(*note));
+ strlcpy(note->path, path, ssize);
+
+ return note;
+}
+
+static void op_change_free(struct op_change *note)
+{
+ XFREE(MTYPE_OP_CHANGE, note);
+}
+
+/**
+ * op_changes_group_push() - Save the current set of changes on the queue.
+ *
+ * This function will save the current set of changes on the queue and
+ * initialize a new set of changes.
+ */
+static void op_changes_group_push(void)
+{
+ struct op_changes_group *changes;
+
+ if (RB_EMPTY(op_changes, &nb_notif_adds) && RB_EMPTY(op_changes, &nb_notif_dels))
+ return;
+
+ __dbg("pushing current oper changes onto queue");
+
+ changes = XCALLOC(MTYPE_OP_CHANGES_GROUP, sizeof(*changes));
+ changes->adds = nb_notif_adds;
+ changes->dels = nb_notif_dels;
+ op_changes_queue_add_tail(&op_changes_queue, changes);
+
+ RB_INIT(op_changes, &nb_notif_adds);
+ RB_INIT(op_changes, &nb_notif_dels);
+}
+
+static void op_changes_group_free(struct op_changes_group *group)
+{
+ struct op_change *e, *next;
+
+ RB_FOREACH_SAFE (e, op_changes, &group->adds, next) {
+ RB_REMOVE(op_changes, &group->adds, e);
+ op_change_free(e);
+ }
+ RB_FOREACH_SAFE (e, op_changes, &group->dels, next) {
+ RB_REMOVE(op_changes, &group->dels, e);
+ op_change_free(e);
+ }
+ XFREE(MTYPE_OP_CHANGES_GROUP, group);
+}
+
+static struct op_change *__find_less_specific(struct op_changes *head, struct op_change *note)
+{
+ struct op_change *e;
+ size_t plen;
+
+ /*
+ * RB_NFIND finds equal or greater (more specific) than the key,
+ * so the previous node will be a less specific or no match that
+ * sorts earlier. We want to find when we are a more specific
+ * match.
+ */
+ e = RB_NFIND(op_changes, head, note);
+ if (e)
+ e = RB_PREV(op_changes, e);
+ else
+ e = RB_MAX(op_changes, head);
+ if (!e)
+ return NULL;
+ plen = strlen(e->path);
+ if (pathncmp(e->path, note->path, plen))
+ return NULL;
+ /* equal would have been returned from RB_NFIND() then we went RB_PREV */
+ assert(strlen(note->path) != plen);
+ return e;
+}
+
+static void __drop_eq_or_more_specific(struct op_changes *head, const char *path, int plen,
+ struct op_change *next)
+{
+ struct op_change *e;
+
+ for (e = next; e != NULL; e = next) {
+ /* if the prefix no longer matches we are done */
+ if (pathncmp(path, e->path, plen))
+ break;
+ __dbg("dropping more specific %s: %s", head == &nb_notif_adds ? "add" : "delete",
+ e->path);
+ next = RB_NEXT(op_changes, e);
+ RB_REMOVE(op_changes, head, e);
+ op_change_free(e);
+ }
+}
+
+static void __op_change_add_del(const char *path, struct op_changes *this_head,
+ struct op_changes *other_head)
+{
+ /* find out if this has been subsumed or will subsume */
+
+ const char *op = this_head == &nb_notif_adds ? "add" : "delete";
+ struct op_change *note = op_change_alloc(path);
+ struct op_change *next, *e;
+ int plen;
+
+ __dbg("processing oper %s change path: %s", op, path);
+
+ /*
+ * See if we are already covered by a more general `op`.
+ */
+ e = __find_less_specific(this_head, note);
+ if (e) {
+ __dbg("%s path already covered by: %s", op, e->path);
+ op_change_free(note);
+ return;
+ }
+
+ /*
+ * Handle having a less-specific `other op`.
+ */
+ e = __find_less_specific(other_head, note);
+ if (e) {
+ if (this_head == &nb_notif_dels) {
+ /*
+ * If we have a less-specific add then drop this
+ * more-specific delete as the add-replace will remove
+ * this missing state.
+ */
+ __dbg("delete path already covered add-replace: %s", e->path);
+ } else {
+ /*
+ * If we have a less-specific delete, convert the delete
+ * to an add, and drop this more-specific add. The new
+ * less-specific add will pick up the more specific add
+ * during the walk and as adds are processed as replaces
+ * any other existing state that was to be deleted will
+ * still be deleted (unless it also returns) by the replace.
+ */
+ __dbg("add covered, converting covering delete to add-replace: %s", e->path);
+ RB_REMOVE(op_changes, other_head, e);
+ __op_change_add_del(e->path, &nb_notif_adds, &nb_notif_dels);
+ op_change_free(e);
+ }
+ op_change_free(note);
+ return;
+ }
+
+ e = RB_INSERT(op_changes, this_head, note);
+ if (e) {
+ __dbg("path already in %s tree: %s", op, path);
+ op_change_free(note);
+ return;
+ }
+
+ __dbg("scanning for subsumed or subsuming: %s", path);
+
+ plen = strlen(path);
+
+ next = RB_NEXT(op_changes, note);
+ __drop_eq_or_more_specific(this_head, path, plen, next);
+
+ /* Drop exact match or more specific `other op` */
+ next = RB_NFIND(op_changes, other_head, note);
+ __drop_eq_or_more_specific(other_head, path, plen, next);
+
+ nb_notif_set_walk_timer();
+}
+
+static void nb_notif_add(const char *path)
+{
+ __op_change_add_del(path, &nb_notif_adds, &nb_notif_dels);
+}
+
+
+static void nb_notif_delete(const char *path)
+{
+ __op_change_add_del(path, &nb_notif_dels, &nb_notif_adds);
+}
+
+struct lyd_node *nb_op_update(struct lyd_node *tree, const char *path, const char *value)
+{
+ struct lyd_node *dnode;
+ const char *abs_path = NULL;
+
+ __dbg("updating path: %s with value: %s", path, value);
+
+ dnode = yang_state_new(tree, path, value);
+
+ if (path[0] == '/')
+ abs_path = path;
+ else
+ abs_path = lyd_path(dnode, LYD_PATH_STD, NULL, 0);
+
+ nb_notif_add(abs_path);
+
+ if (abs_path != path)
+ free((char *)abs_path);
+
+ return dnode;
+}
+
+void nb_op_update_delete(struct lyd_node *tree, const char *path)
+{
+ char *abs_path = NULL;
+
+ __dbg("deleting path: %s", path);
+
+ if (path && path[0] == '/')
+ abs_path = (char *)path;
+ else {
+ assert(tree);
+ abs_path = lyd_path(tree, LYD_PATH_STD, NULL, 0);
+ assert(abs_path);
+ if (path) {
+ char *tmp = darr_strdup(abs_path);
+
+ free(abs_path);
+ abs_path = tmp;
+ if (*darr_last(abs_path) != '/')
+ darr_in_strcat(abs_path, "/");
+ assert(abs_path); /* silence bad CLANG NULL warning */
+ darr_in_strcat(abs_path, path);
+ }
+ }
+
+ yang_state_delete(tree, path);
+
+ nb_notif_delete(abs_path);
+
+ if (abs_path != path) {
+ if (path)
+ darr_free(abs_path);
+ else
+ free(abs_path);
+ }
+}
+
+PRINTFRR(2, 0)
+struct lyd_node *nb_op_update_vpathf(struct lyd_node *tree, const char *path_fmt, const char *value,
+ va_list ap)
+{
+ struct lyd_node *dnode;
+ char *path;
+
+ path = darr_vsprintf(path_fmt, ap);
+ dnode = nb_op_update(tree, path, value);
+ darr_free(path);
+
+ return dnode;
+}
+
+struct lyd_node *nb_op_update_pathf(struct lyd_node *tree, const char *path_fmt, const char *value,
+ ...)
+{
+ struct lyd_node *dnode;
+ va_list ap;
+
+ va_start(ap, value);
+ dnode = nb_op_update_vpathf(tree, path_fmt, value, ap);
+ va_end(ap);
+
+ return dnode;
+}
+
+PRINTFRR(2, 0)
+void nb_op_update_delete_vpathf(struct lyd_node *tree, const char *path_fmt, va_list ap)
+{
+ char *path;
+
+ path = darr_vsprintf(path_fmt, ap);
+ nb_op_update_delete(tree, path);
+ darr_free(path);
+}
+
+void nb_op_update_delete_pathf(struct lyd_node *tree, const char *path_fmt, ...)
+{
+ va_list ap;
+
+ va_start(ap, path_fmt);
+ nb_op_update_delete_vpathf(tree, path_fmt, ap);
+ va_end(ap);
+}
+
+
+PRINTFRR(3, 0)
+struct lyd_node *nb_op_vupdatef(struct lyd_node *tree, const char *path, const char *val_fmt,
+ va_list ap)
+{
+ struct lyd_node *dnode;
+ char *value;
+
+ value = darr_vsprintf(val_fmt, ap);
+ dnode = nb_op_update(tree, path, value);
+ darr_free(value);
+
+ return dnode;
+}
+
+
+struct lyd_node *nb_op_updatef(struct lyd_node *tree, const char *path, const char *val_fmt, ...)
+{
+ struct lyd_node *dnode;
+ va_list ap;
+
+ va_start(ap, val_fmt);
+ dnode = nb_op_vupdatef(tree, path, val_fmt, ap);
+ va_end(ap);
+
+ return dnode;
+}
+
+static struct op_changes_group *op_changes_group_next(void)
+{
+ struct op_changes_group *group;
+
+ group = op_changes_queue_pop(&op_changes_queue);
+ if (!group) {
+ op_changes_group_push();
+ group = op_changes_queue_pop(&op_changes_queue);
+ }
+ if (!group)
+ return NULL;
+ group->cur_changes = &group->dels;
+ group->cur_change = RB_MIN(op_changes, group->cur_changes);
+ if (!group->cur_change) {
+ group->cur_changes = &group->adds;
+ group->cur_change = RB_MIN(op_changes, group->cur_changes);
+ assert(group->cur_change);
+ }
+ return group;
+}
+
+/* ---------------------------- */
+/* Query for changes and notify */
+/* ---------------------------- */
+
+static void timer_walk_abort(struct nb_notif_walk_args *args);
+static void timer_walk_continue(struct event *event);
+static void timer_walk_done(struct nb_notif_walk_args *args);
+
+static struct op_change *__next_change(struct op_changes_group *group)
+{
+ struct op_change *next = RB_NEXT(op_changes, group->cur_change);
+
+ /* Remove and free current so retry works */
+ RB_REMOVE(op_changes, group->cur_changes, group->cur_change);
+ op_change_free(group->cur_change);
+ return next;
+}
+
+static struct op_changes_group *__next_group(struct op_changes_group *group)
+{
+ __dbg("done with oper-path collection for group");
+ op_changes_group_free(group);
+ return op_changes_group_next();
+}
+
+static enum nb_error oper_walk_done(const struct lyd_node *tree, void *arg, enum nb_error ret)
+{
+ struct nb_notif_walk_args *args = arg;
+ struct op_changes_group *group = args->group;
+ const char *path = group->cur_change->path;
+
+ /* we don't send batches when yielding as we need completed edit in any patch */
+ assert(ret != NB_YIELD);
+
+ if (ret == NB_ERR_NOT_FOUND) {
+ __dbg("Path not found while walking oper tree: %s", path);
+ ret = NB_OK;
+ } else if (ret != NB_OK) {
+error:
+ __log_err("Error notifying for datastore path: %s: %s", path, nb_err_name(ret));
+
+ timer_walk_abort(args);
+ goto done;
+ } else {
+ __dbg("Done with oper-path collection for path: %s", path);
+
+ /* Do we need this? */
+ while (tree->parent)
+ tree = lyd_parent(tree);
+
+ /* Send the add (replace) notification */
+ if (mgmt_be_send_ds_replace_notification(path, tree)) {
+ __log_err("Error sending notification message for path: %s", path);
+ ret = NB_ERR;
+ goto error;
+ }
+ }
+
+ /*
+ * Advance to next change.
+ */
+
+ group->cur_change = __next_change(group);
+ if (!group->cur_change) {
+ args->group = __next_group(group);
+ if (!args->group) {
+ timer_walk_done(args);
+ goto done;
+ }
+ }
+
+ /* Run next walk after giving other events a shot to run */
+ event_add_timer_msec(nb_notif_master, timer_walk_continue, args, 0, &nb_notif_timer);
+done:
+ /* Done with current walk and scheduled next one if there is more */
+ nb_notif_walk = NULL;
+
+ return ret;
+}
+
+static int nb_notify_delete_changes(struct nb_notif_walk_args *args)
+{
+ struct op_changes_group *group = args->group;
+
+ group->cur_change = RB_MIN(op_changes, group->cur_changes);
+ while (group->cur_change) {
+ if (mgmt_be_send_ds_delete_notification(group->cur_change->path)) {
+ __log_err("Error sending delete notification message for path: %s",
+ group->cur_change->path);
+ return 1;
+ }
+ group->cur_change = __next_change(group);
+ }
+ return 0;
+}
+
+static void timer_walk_continue(struct event *event)
+{
+ struct nb_notif_walk_args *args = EVENT_ARG(event);
+ struct op_changes_group *group = args->group;
+ const char *path;
+ int ret;
+
+ /*
+ * Notify about deletes until we have add changes to collect.
+ */
+ while (group->cur_changes == &group->dels) {
+ ret = nb_notify_delete_changes(args);
+ if (ret) {
+ timer_walk_abort(args);
+ return;
+ }
+
+ /* after deletes advance to adds */
+ group->cur_changes = &group->adds;
+ group->cur_change = RB_MIN(op_changes, group->cur_changes);
+ if (group->cur_change)
+ break;
+
+ args->group = __next_group(group);
+ if (!args->group) {
+ timer_walk_done(args);
+ return;
+ }
+ }
+
+ path = group->cur_change->path;
+ __dbg("starting next oper-path replace walk for path: %s", path);
+ nb_notif_walk = nb_oper_walk(path, NULL, 0, false, NULL, NULL, oper_walk_done, args);
+}
+
+static void timer_walk_start(struct event *event)
+{
+ struct op_changes_group *group;
+ struct nb_notif_walk_args *args;
+
+ __dbg("oper-state change notification timer fires");
+
+ group = op_changes_group_next();
+ if (!group) {
+ __dbg("no oper changes to notify");
+ return;
+ }
+
+ args = XCALLOC(MTYPE_NB_NOTIF_WALK_ARGS, sizeof(*args));
+ args->group = group;
+
+ EVENT_ARG(event) = args;
+ timer_walk_continue(event);
+}
+
+static void timer_walk_abort(struct nb_notif_walk_args *args)
+{
+ __dbg("Failed notifying datastore changes, will retry");
+
+ __dbg("oper-state notify setting retry timer to fire in: %d msec ", NB_NOTIF_TIMER_MSEC);
+ event_add_timer_msec(nb_notif_master, timer_walk_continue, args, NB_NOTIF_TIMER_MSEC,
+ &nb_notif_timer);
+}
+
+static void timer_walk_done(struct nb_notif_walk_args *args)
+{
+ __dbg("Finished notifying for all datastore changes");
+ assert(!args->group);
+ XFREE(MTYPE_NB_NOTIF_WALK_ARGS, args);
+}
+
+static void nb_notif_set_walk_timer(void)
+{
+ if (nb_notif_walk) {
+ __dbg("oper-state walk already in progress.");
+ return;
+ }
+ if (event_is_scheduled(nb_notif_timer)) {
+ __dbg("oper-state notification timer already set.");
+ return;
+ }
+
+ __dbg("oper-state notification setting timer to fire in: %d msec ", NB_NOTIF_TIMER_MSEC);
+ event_add_timer_msec(nb_notif_master, timer_walk_start, NULL, NB_NOTIF_TIMER_MSEC,
+ &nb_notif_timer);
+}
+
+void nb_notif_set_filters(const char **selectors, bool replace)
+{
+ const char **csp;
+
+ if (replace) {
+ darr_free_free(nb_notif_filters);
+ nb_notif_filters = selectors;
+ return;
+ }
+ darr_foreach_p (selectors, csp)
+ *darr_append(nb_notif_filters) = *csp;
+ darr_free(selectors);
+}
+
+void nb_notif_init(struct event_loop *tm)
+{
+ nb_notif_master = tm;
+ op_changes_queue_init(&op_changes_queue);
+}
+
+void nb_notif_terminate(void)
+{
+ struct nb_notif_walk_args *args = nb_notif_timer ? EVENT_ARG(nb_notif_timer) : NULL;
+ struct op_changes_group *group;
+
+ __dbg("terminating: timer: %p timer arg: %p walk %p", nb_notif_timer, args, nb_notif_walk);
+
+ EVENT_OFF(nb_notif_timer);
+
+ if (nb_notif_walk) {
+ /* Grab walk args from walk if active. */
+ args = nb_oper_walk_finish_arg(nb_notif_walk);
+ nb_oper_cancel_walk(nb_notif_walk);
+ nb_notif_walk = NULL;
+ }
+ if (args) {
+ op_changes_group_free(args->group);
+ XFREE(MTYPE_NB_NOTIF_WALK_ARGS, args);
+ }
+
+ while ((group = op_changes_group_next()))
+ op_changes_group_free(group);
+
+ darr_free_free(nb_notif_filters);
+}
diff --git a/lib/northbound_oper.c b/lib/northbound_oper.c
index c80cdc116..6336db502 100644
--- a/lib/northbound_oper.c
+++ b/lib/northbound_oper.c
@@ -35,6 +35,7 @@
* We must also process containers with lookup-next descendants last.
*/
+DEFINE_MTYPE_STATIC(LIB, NB_STATE, "Northbound State");
DEFINE_MTYPE_STATIC(LIB, NB_YIELD_STATE, "NB Yield State");
DEFINE_MTYPE_STATIC(LIB, NB_NODE_INFOS, "NB Node Infos");
@@ -1833,6 +1834,20 @@ bool nb_oper_is_yang_lib_query(const char *xpath)
return strlen(xpath) > liblen;
}
+void *nb_oper_walk_finish_arg(void *walk)
+{
+ struct nb_op_yield_state *ys = walk;
+
+ return ys->finish_arg;
+}
+
+void *nb_oper_walk_cb_arg(void *walk)
+{
+ struct nb_op_yield_state *ys = walk;
+
+ return ys->cb_arg;
+}
+
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)
@@ -1903,6 +1918,87 @@ enum nb_error nb_oper_iterate_legacy(const char *xpath,
return ret;
}
+static const char *__adjust_ptr(struct lysc_node_leaf *lsnode, const char *valuep, size_t *size)
+{
+ switch (lsnode->type->basetype) {
+ case LY_TYPE_INT8:
+ case LY_TYPE_UINT8:
+#ifdef BIG_ENDIAN
+ valuep += 7;
+#endif
+ *size = 1;
+ break;
+ case LY_TYPE_INT16:
+ case LY_TYPE_UINT16:
+#ifdef BIG_ENDIAN
+ valuep += 6;
+#endif
+ *size = 2;
+ break;
+ case LY_TYPE_INT32:
+ case LY_TYPE_UINT32:
+#ifdef BIG_ENDIAN
+ valuep += 4;
+#endif
+ *size = 4;
+ break;
+ case LY_TYPE_INT64:
+ case LY_TYPE_UINT64:
+ *size = 8;
+ break;
+ case LY_TYPE_UNKNOWN:
+ case LY_TYPE_BINARY:
+ case LY_TYPE_STRING:
+ case LY_TYPE_BITS:
+ case LY_TYPE_BOOL:
+ case LY_TYPE_DEC64:
+ case LY_TYPE_EMPTY:
+ case LY_TYPE_ENUM:
+ case LY_TYPE_IDENT:
+ case LY_TYPE_INST:
+ case LY_TYPE_LEAFREF:
+ case LY_TYPE_UNION:
+ default:
+ assert(0);
+ }
+ return valuep;
+}
+
+enum nb_error nb_oper_uint64_get(const struct nb_node *nb_node, const void *parent_list_entry,
+ struct lyd_node *parent)
+{
+ struct lysc_node_leaf *lsnode = (struct lysc_node_leaf *)nb_node->snode;
+ struct lysc_node *snode = &lsnode->node;
+ ssize_t offset = (ssize_t)nb_node->cbs.get_elem;
+ uint64_t ubigval = *(uint64_t *)((char *)parent_list_entry + offset);
+ const char *valuep;
+ size_t size;
+
+ valuep = __adjust_ptr(lsnode, (const char *)&ubigval, &size);
+ if (lyd_new_term_bin(parent, snode->module, snode->name, valuep, size, LYD_NEW_PATH_UPDATE,
+ NULL))
+ return NB_ERR_RESOURCE;
+ return NB_OK;
+}
+
+
+enum nb_error nb_oper_uint32_get(const struct nb_node *nb_node, const void *parent_list_entry,
+ struct lyd_node *parent)
+{
+ struct lysc_node_leaf *lsnode = (struct lysc_node_leaf *)nb_node->snode;
+ struct lysc_node *snode = &lsnode->node;
+ ssize_t offset = (ssize_t)nb_node->cbs.get_elem;
+ uint64_t ubigval = *(uint64_t *)((char *)parent_list_entry + offset);
+ const char *valuep;
+ size_t size;
+
+ valuep = __adjust_ptr(lsnode, (const char *)&ubigval, &size);
+ if (lyd_new_term_bin(parent, snode->module, snode->name, valuep, size, LYD_NEW_PATH_UPDATE,
+ NULL))
+ return NB_ERR_RESOURCE;
+ return NB_OK;
+}
+
void nb_oper_init(struct event_loop *loop)
{
event_loop = loop;
diff --git a/lib/plist.c b/lib/plist.c
index 6950ab576..713eee25e 100644
--- a/lib/plist.c
+++ b/lib/plist.c
@@ -1536,7 +1536,6 @@ int prefix_bgp_show_prefix_list(struct vty *vty, afi_t afi, char *name,
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);
@@ -1544,10 +1543,7 @@ int prefix_bgp_show_prefix_list(struct vty *vty, afi_t afi, char *name,
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_list = json_object_new_object();
json_object_int_add(json_list, "seq", pentry->seq);
json_object_string_add(json_list, "seqPrefixListType",
@@ -1560,7 +1556,7 @@ int prefix_bgp_show_prefix_list(struct vty *vty, afi_t afi, char *name,
json_object_int_add(json_list, "le",
pentry->le);
- json_object_object_add(json_prefix, buf_a, json_list);
+ json_object_object_addf(json_prefix, json_list, "%pFX", &pentry->prefix);
}
if (afi == AFI_IP)
json_object_object_add(json, "ipPrefixList",
diff --git a/lib/privs.c b/lib/privs.c
index 717a2e48d..b0809bf69 100644
--- a/lib/privs.c
+++ b/lib/privs.c
@@ -179,7 +179,7 @@ static pset_t *zcaps2sys(zebra_capabilities_t *zcaps, int num)
for (i = 0; i < num; i++)
count += cap_map[zcaps[i]].num;
- if ((syscaps = XCALLOC(MTYPE_PRIVS, (sizeof(pset_t) * num))) == NULL) {
+ if ((syscaps = XCALLOC(MTYPE_PRIVS, sizeof(pset_t))) == NULL) {
fprintf(stderr, "%s: could not allocate syscaps!", __func__);
return NULL;
}
diff --git a/lib/routemap.h b/lib/routemap.h
index 8dcc17ecc..1c0234831 100644
--- a/lib/routemap.h
+++ b/lib/routemap.h
@@ -310,6 +310,7 @@ DECLARE_QOBJ_TYPE(route_map);
(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_LIMIT(C) (strmatch(C, "frr-bgp-route-map:match-community-limit"))
#define IS_MATCH_COMMUNITY(C) \
(strmatch(C, "frr-bgp-route-map:match-community"))
#define IS_MATCH_LCOMMUNITY(C) \
diff --git a/lib/routemap_cli.c b/lib/routemap_cli.c
index 69b942064..eb0170970 100644
--- a/lib/routemap_cli.c
+++ b/lib/routemap_cli.c
@@ -810,6 +810,10 @@ void route_map_condition_show(struct vty *vty, const struct lyd_node *dnode,
yang_dnode_get_string(
dnode,
"./rmap-match-condition/frr-bgp-route-map:list-name"));
+ } else if (IS_MATCH_COMMUNITY_LIMIT(condition)) {
+ vty_out(vty, " match community-limit %s\n",
+ yang_dnode_get_string(dnode,
+ "./rmap-match-condition/frr-bgp-route-map:community-limit"));
} else if (IS_MATCH_COMMUNITY(condition)) {
vty_out(vty, " match community %s",
yang_dnode_get_string(
diff --git a/lib/srv6.h b/lib/srv6.h
index 9a041e3d8..7e4fb97ad 100644
--- a/lib/srv6.h
+++ b/lib/srv6.h
@@ -22,6 +22,8 @@
#define SRV6_SID_FORMAT_NAME_SIZE 512
+#define DEFAULT_SRV6_IFNAME "sr0"
+
#ifdef __cplusplus
extern "C" {
#endif
@@ -186,6 +188,42 @@ enum srv6_endpoint_behavior_codepoint {
SRV6_ENDPOINT_BEHAVIOR_OPAQUE = 0xFFFF,
};
+/*
+ * Convert SRv6 endpoint behavior codepoints to human-friendly string.
+ */
+static inline const char *
+srv6_endpoint_behavior_codepoint2str(enum srv6_endpoint_behavior_codepoint behavior)
+{
+ switch (behavior) {
+ case SRV6_ENDPOINT_BEHAVIOR_RESERVED:
+ return "Reserved";
+ case SRV6_ENDPOINT_BEHAVIOR_END:
+ return "End";
+ case SRV6_ENDPOINT_BEHAVIOR_END_X:
+ return "End.X";
+ case SRV6_ENDPOINT_BEHAVIOR_END_DT6:
+ return "End.DT6";
+ case SRV6_ENDPOINT_BEHAVIOR_END_DT4:
+ return "End.DT4";
+ case SRV6_ENDPOINT_BEHAVIOR_END_DT46:
+ return "End.DT46";
+ case SRV6_ENDPOINT_BEHAVIOR_END_NEXT_CSID:
+ return "uN";
+ case SRV6_ENDPOINT_BEHAVIOR_END_X_NEXT_CSID:
+ return "uA";
+ case SRV6_ENDPOINT_BEHAVIOR_END_DT6_USID:
+ return "uDT6";
+ case SRV6_ENDPOINT_BEHAVIOR_END_DT4_USID:
+ return "uDT4";
+ case SRV6_ENDPOINT_BEHAVIOR_END_DT46_USID:
+ return "uDT46";
+ case SRV6_ENDPOINT_BEHAVIOR_OPAQUE:
+ return "Opaque";
+ }
+
+ return "Unspec";
+}
+
struct nexthop_srv6 {
/* SRv6 localsid info for Endpoint-behaviour */
enum seg6local_action_t seg6local_action;
diff --git a/lib/subdir.am b/lib/subdir.am
index 4bcce9a2b..a975eb2fc 100644
--- a/lib/subdir.am
+++ b/lib/subdir.am
@@ -84,6 +84,7 @@ lib_libfrr_la_SOURCES = \
lib/northbound.c \
lib/northbound_cli.c \
lib/northbound_db.c \
+ lib/northbound_notif.c \
lib/northbound_oper.c \
lib/ntop.c \
lib/openbsd-tree.c \
@@ -144,6 +145,7 @@ lib_libfrr_la_SOURCES = \
nodist_lib_libfrr_la_SOURCES = \
yang/frr-affinity-map.yang.c \
+ yang/frr-backend.yang.c \
yang/frr-filter.yang.c \
yang/frr-if-rmap.yang.c \
yang/frr-interface.yang.c \
diff --git a/lib/vrf.c b/lib/vrf.c
index 31632a80d..0b39d9360 100644
--- a/lib/vrf.c
+++ b/lib/vrf.c
@@ -22,6 +22,9 @@
#include "northbound.h"
#include "northbound_cli.h"
+/* Set by the owner (zebra). */
+bool vrf_notify_oper_changes;
+
/* default VRF name value used when VRF backend is not NETNS */
#define VRF_DEFAULT_NAME_INTERNAL "default"
@@ -39,8 +42,7 @@ 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 int vrf_backend = VRF_BACKEND_VRF_LITE;
static char vrf_default_name[VRF_NAMSIZ] = VRF_DEFAULT_NAME_INTERNAL;
/*
@@ -105,6 +107,19 @@ int vrf_switchback_to_initial(void)
return ret;
}
+static void vrf_update_state(struct vrf *vrf)
+{
+ if (!vrf->state || !vrf_notify_oper_changes)
+ return;
+
+ /*
+ * Remove top level container update when we have patch support, for now
+ * this keeps us from generating 2 separate REPLACE messages though.
+ */
+ nb_op_updatef(vrf->state, "id", "%u", vrf->vrf_id);
+ nb_op_update(vrf->state, "active", CHECK_FLAG(vrf->status, VRF_ACTIVE) ? "true" : "false");
+}
+
/* Get a VRF. If not found, create one.
* Arg:
* name - The name of the vrf. May be NULL if unknown.
@@ -155,16 +170,32 @@ struct vrf *vrf_get(vrf_id_t vrf_id, const char *name)
/* Set name */
if (name && vrf->name[0] != '\0' && strcmp(name, vrf->name)) {
- /* update the vrf name */
+ /* vrf name has changed */
+ if (vrf_notify_oper_changes) {
+ nb_op_update_delete_pathf(NULL, "/frr-vrf:lib/vrf[name=\"%s\"]", vrf->name);
+ lyd_free_all(vrf->state);
+ }
RB_REMOVE(vrf_name_head, &vrfs_by_name, vrf);
- strlcpy(vrf->data.l.netns_name,
- name, NS_NAMSIZ);
+ 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);
+ /* New state with new name */
+ if (vrf_notify_oper_changes)
+ vrf->state = nb_op_update_pathf(NULL, "/frr-vrf:lib/vrf[name=\"%s\"]/state",
+ NULL, vrf->name);
} else if (name && vrf->name[0] == '\0') {
strlcpy(vrf->name, name, sizeof(vrf->name));
RB_INSERT(vrf_name_head, &vrfs_by_name, vrf);
+
+ /* We have a name now so we can have state */
+ if (vrf_notify_oper_changes)
+ vrf->state = nb_op_update_pathf(NULL, "/frr-vrf:lib/vrf[name=\"%s\"]/state",
+ NULL, vrf->name);
}
+ /* Update state before hook call */
+ if (vrf->state)
+ vrf_update_state(vrf);
+
if (new &&vrf_master.vrf_new_hook)
(*vrf_master.vrf_new_hook)(vrf);
@@ -208,6 +239,7 @@ struct vrf *vrf_update(vrf_id_t new_vrf_id, const char *name)
vrf->vrf_id = new_vrf_id;
RB_INSERT(vrf_id_head, &vrfs_by_id, vrf);
+ vrf_update_state(vrf);
} else {
/*
@@ -254,6 +286,11 @@ void vrf_delete(struct vrf *vrf)
if (vrf->name[0] != '\0')
RB_REMOVE(vrf_name_head, &vrfs_by_name, vrf);
+ if (vrf_notify_oper_changes) {
+ nb_op_update_delete_pathf(NULL, "/frr-vrf:lib/vrf[name=\"%s\"]", vrf->name);
+ lyd_free_all(vrf->state);
+ }
+
XFREE(MTYPE_VRF, vrf);
}
@@ -282,6 +319,8 @@ int vrf_enable(struct vrf *vrf)
SET_FLAG(vrf->status, VRF_ACTIVE);
+ vrf_update_state(vrf);
+
if (vrf_master.vrf_enable_hook)
(*vrf_master.vrf_enable_hook)(vrf);
@@ -307,6 +346,8 @@ void vrf_disable(struct vrf *vrf)
UNSET_FLAG(vrf->status, VRF_ACTIVE);
+ vrf_update_state(vrf);
+
if (debug_vrf)
zlog_debug("VRF %s(%u) is to be disabled.", vrf->name,
vrf->vrf_id);
@@ -540,15 +581,6 @@ void vrf_init(int (*create)(struct vrf *), int (*enable)(struct vrf *),
"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)) {
@@ -612,8 +644,6 @@ int vrf_is_backend_netns(void)
int vrf_get_backend(void)
{
- if (!vrf_backend_configured)
- return VRF_BACKEND_UNKNOWN;
return vrf_backend;
}
@@ -621,7 +651,6 @@ 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;
@@ -630,7 +659,6 @@ int vrf_configure_backend(enum vrf_backend_type backend)
}
vrf_backend = backend;
- vrf_backend_configured = 1;
return 0;
}
diff --git a/lib/vrf.h b/lib/vrf.h
index 3ebb6ddf5..46d72910c 100644
--- a/lib/vrf.h
+++ b/lib/vrf.h
@@ -80,6 +80,8 @@ struct vrf {
/* Back pointer to namespace context */
void *ns_ctxt;
+ struct lyd_node *state;
+
QOBJ_FIELDS;
};
RB_HEAD(vrf_id_head, vrf);
@@ -92,7 +94,6 @@ DECLARE_QOBJ_TYPE(vrf);
enum vrf_backend_type {
VRF_BACKEND_VRF_LITE,
VRF_BACKEND_NETNS,
- VRF_BACKEND_UNKNOWN,
VRF_BACKEND_MAX,
};
@@ -299,6 +300,7 @@ extern void vrf_disable(struct vrf *vrf);
extern int vrf_enable(struct vrf *vrf);
extern void vrf_delete(struct vrf *vrf);
+extern bool vrf_notify_oper_changes;
extern const struct frr_yang_module_info frr_vrf_info;
extern const struct frr_yang_module_info frr_vrf_cli_info;
diff --git a/lib/yang.c b/lib/yang.c
index b847b8b77..dd48d8861 100644
--- a/lib/yang.c
+++ b/lib/yang.c
@@ -14,6 +14,7 @@
#include <libyang/version.h>
#include "northbound.h"
#include "frrstr.h"
+#include "darr.h"
#include "lib/config_paths.h"
@@ -680,6 +681,116 @@ void yang_dnode_rpc_output_add(struct lyd_node *output, const char *xpath,
assert(err == LY_SUCCESS);
}
+struct lyd_node *yang_state_new(struct lyd_node *tree, const char *path, const char *value)
+{
+ struct lyd_node *dnode, *parent;
+ LY_ERR err;
+
+ err = lyd_new_path2(tree, ly_native_ctx, path, value, 0, 0, LYD_NEW_PATH_UPDATE, &parent,
+ &dnode);
+ assert(err == LY_SUCCESS);
+
+ /*
+ * If the node exists and isn't updated returned dnode will be NULL, so
+ * we need to find it. But even if returned it can be the first newly
+ * created node (could be container of path) not the actual path dnode.
+ * So we always find.
+ */
+ err = lyd_find_path(tree ?: parent, path, false, &dnode);
+ assert(err == LY_SUCCESS);
+
+ return dnode;
+}
+
+void yang_state_delete(struct lyd_node *tree, const char *path)
+{
+ LY_ERR err;
+
+ if (!tree)
+ return;
+
+ if (path) {
+ err = lyd_find_path(tree, path, false, &tree);
+ if (err != LY_SUCCESS) {
+ zlog_info("State %s has already been deleted", path);
+ return;
+ }
+ }
+ lyd_free_tree(tree);
+}
+
+PRINTFRR(2, 0)
+struct lyd_node *yang_state_new_vpathf(struct lyd_node *tree, const char *path_fmt,
+ const char *value, va_list ap)
+{
+ struct lyd_node *dnode;
+ char *path;
+
+ path = darr_vsprintf(path_fmt, ap);
+ dnode = yang_state_new(tree, path, value);
+ darr_free(path);
+
+ return dnode;
+}
+
+struct lyd_node *yang_state_new_pathf(struct lyd_node *tree, const char *path_fmt,
+ const char *value, ...)
+{
+ struct lyd_node *dnode;
+ va_list ap;
+
+ va_start(ap, value);
+ dnode = yang_state_new_vpathf(tree, path_fmt, value, ap);
+ va_end(ap);
+
+ return dnode;
+}
+
+PRINTFRR(2, 0)
+void yang_state_delete_vpathf(struct lyd_node *tree, const char *path_fmt, va_list ap)
+{
+ char *path;
+
+ path = darr_vsprintf(path_fmt, ap);
+ yang_state_delete(tree, path);
+ darr_free(path);
+}
+
+void yang_state_delete_pathf(struct lyd_node *tree, const char *path_fmt, ...)
+{
+ va_list ap;
+
+ va_start(ap, path_fmt);
+ yang_state_delete_vpathf(tree, path_fmt, ap);
+ va_end(ap);
+}
+
+PRINTFRR(3, 0)
+struct lyd_node *yang_state_vnewf(struct lyd_node *tree, const char *path, const char *val_fmt,
+ va_list ap)
+{
+ struct lyd_node *dnode;
+ char *value;
+
+ value = darr_vsprintf(val_fmt, ap);
+ dnode = yang_state_new(tree, path, value);
+ darr_free(value);
+
+ return dnode;
+}
+
+struct lyd_node *yang_state_newf(struct lyd_node *tree, const char *path, const char *val_fmt, ...)
+{
+ struct lyd_node *dnode;
+ va_list ap;
+
+ va_start(ap, val_fmt);
+ dnode = yang_state_vnewf(tree, path, val_fmt, ap);
+ va_end(ap);
+
+ return dnode;
+}
+
struct yang_data *yang_data_new(const char *xpath, const char *value)
{
struct yang_data *data;
@@ -763,6 +874,60 @@ static void ly_zlog_cb(LY_LOG_LEVEL level, const char *msg, const char *data_pat
zlog(priority, "libyang: %s", msg);
}
+LY_ERR yang_parse_data(const char *xpath, LYD_FORMAT format, bool as_subtree, bool is_oper,
+ bool validate, const char *data, struct lyd_node **tree)
+{
+ struct ly_in *in = NULL;
+ struct lyd_node *subtree = NULL;
+ uint32_t parse_options = LYD_PARSE_STRICT | LYD_PARSE_ONLY;
+ uint32_t validate_options = LYD_VALIDATE_PRESENT;
+ LY_ERR err;
+
+ err = ly_in_new_memory(data, &in);
+ if (err != LY_SUCCESS)
+ return err;
+
+ if (as_subtree) {
+ struct lyd_node *parent;
+
+ /*
+ * Create the subtree branch from root using the xpath. This
+ * will be used below to parse the data rooted at the subtree --
+ * a common YANG JSON technique (vs XML which starts all
+ * data trees from the root).
+ */
+ err = lyd_new_path2(NULL, ly_native_ctx, xpath, NULL, 0, 0, 0, &parent, &subtree);
+ if (err != LY_SUCCESS)
+ goto done;
+ err = lyd_find_path(parent, xpath, false, &subtree);
+ if (err != LY_SUCCESS)
+ goto done;
+ }
+
+ if (is_oper)
+ validate_options |= LYD_VALIDATE_OPERATIONAL;
+
+#ifdef LYD_VALIDATE_NOT_FINAL
+ if (!validate)
+ validate_options |= LYD_VALIDATE_NOT_FINAL;
+#endif
+
+ err = lyd_parse_data(ly_native_ctx, subtree, in, format, parse_options, validate_options,
+ tree);
+ if (err == LY_SUCCESS && subtree)
+ *tree = subtree;
+done:
+ ly_in_free(in, 0);
+ if (err != LY_SUCCESS) {
+ if (*tree)
+ lyd_free_all(*tree);
+ else if (subtree)
+ lyd_free_all(subtree);
+ *tree = NULL;
+ }
+ return err;
+}
+
LY_ERR yang_parse_notification(const char *xpath, LYD_FORMAT format,
const char *data, struct lyd_node **notif)
{
diff --git a/lib/yang.h b/lib/yang.h
index 52857ecf0..748f08903 100644
--- a/lib/yang.h
+++ b/lib/yang.h
@@ -535,6 +535,66 @@ extern struct lyd_node *yang_dnode_dup(const struct lyd_node *dnode);
*/
extern void yang_dnode_free(struct lyd_node *dnode);
+/**
+ * yang_state_new() - Create new state data.
+ * @tree: subtree @path is relative to or NULL in which case @path must be
+ * absolute.
+ * @path: The path of the state node to create.
+ * @value: The canonical value of the state.
+ *
+ * Return: The new libyang node.
+ */
+extern struct lyd_node *yang_state_new(struct lyd_node *tree, const char *path, const char *value);
+
+/**
+ * yang_state_delete() - Delete state data.
+ * @tree: subtree @path is relative to or NULL in which case @path must be
+ * absolute.
+ * @path: The path of the state node to delete, or NULL if @tree should just be
+ * deleted.
+ */
+extern void yang_state_delete(struct lyd_node *tree, const char *path);
+
+/**
+ * yang_state_new_pathf() - Create new state data.
+ * @tree: subtree @path_fmt is relative to or NULL in which case @path_fmt must
+ * be absolute.
+ * @path_fmt: The path format string of the state node to create.
+ * @value: The canonical value of the state.
+ * @...: The values to substitute into @path_fmt.
+ *
+ * Return: The new libyang node.
+ */
+extern struct lyd_node *yang_state_new_pathf(struct lyd_node *tree, const char *path_fmt,
+ const char *value, ...) PRINTFRR(2, 4);
+extern struct lyd_node *yang_state_new_vpathf(struct lyd_node *tree, const char *path_fmt,
+ const char *value, va_list ap);
+/**
+ * yang_state_delete_pathf() - Delete state data.
+ * @tree: subtree @path_fmt is relative to or NULL in which case @path_fmt must
+ * be absolute.
+ * @path: The path of the state node to delete.
+ * @...: The values to substitute into @path_fmt.
+ */
+extern void yang_state_delete_pathf(struct lyd_node *tree, const char *path_fmt, ...) PRINTFRR(2, 3);
+extern void yang_state_delete_vpathf(struct lyd_node *tree, const char *path_fmt, va_list ap);
+
+/**
+ * yang_state_newf() - Create new state data.
+ * @tree: subtree @path is relative to or NULL in which case @path must be
+ * absolute.
+ * @path: The path of the state node to create.
+ * @val_fmt: The value format string to set the canonical value of the state.
+ * @...: The values to substitute into @val_fmt.
+ *
+ * Return: The new libyang node.
+ */
+extern struct lyd_node *yang_state_newf(struct lyd_node *tree, const char *path,
+ const char *val_fmt, ...) PRINTFRR(3, 4);
+
+extern struct lyd_node *yang_state_vnewf(struct lyd_node *tree, const char *path,
+ const char *val_fmt, va_list ap);
+
/*
* Add a libyang data node to an RPC/action output container.
*
@@ -621,6 +681,25 @@ extern struct ly_ctx *yang_ctx_new_setup(bool embedded_modules, bool explicit_co
*/
extern void yang_debugging_set(bool enable);
+
+/*
+ * Parse YANG data.
+ *
+ * Args:
+ * xpath: xpath of the data.
+ * format: LYD_FORMAT of input data.
+ * as_subtree: parse the data as starting at the subtree identified by xpath.
+ * is_oper: parse as operational state allows for invalid (logs warning).
+ * validate: validate the data (otherwise treat as non-final).
+ * 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.
+ */
+LY_ERR yang_parse_data(const char *xpath, LYD_FORMAT format, bool as_subtree, bool is_oper,
+ bool validate, const char *data, struct lyd_node **tree);
+
/*
* Parse a YANG notification.
*
diff --git a/m4/ax_lua.m4 b/m4/ax_lua.m4
index f4236cf08..60bdf3da3 100644
--- a/m4/ax_lua.m4
+++ b/m4/ax_lua.m4
@@ -1,5 +1,5 @@
# ===========================================================================
-# http://www.gnu.org/software/autoconf-archive/ax_lua.html
+# https://www.gnu.org/software/autoconf-archive/ax_lua.html
# ===========================================================================
#
# SYNOPSIS
@@ -19,7 +19,7 @@
# 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).
+# 5.2, 5.3, and 5.4 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
@@ -49,6 +49,14 @@
# interpreter. If LUA is blank, the user's path is searched for an
# suitable interpreter.
#
+# Optionally a LUAJIT option may be set ahead of time to look for and
+# validate a LuaJIT install instead of PUC Lua. Usage might look like:
+#
+# AC_ARG_WITH(luajit, [AS_HELP_STRING([--with-luajit],
+# [Prefer LuaJIT over PUC Lua, even if the latter is newer. Default: no])
+# ])
+# AM_CONDITIONAL([LUAJIT], [test "x$with_luajit" != 'xno'])
+#
# 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
@@ -82,7 +90,7 @@
# 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-
+# 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, give ':' as an action.
#
@@ -152,6 +160,7 @@
#
# LICENSE
#
+# Copyright (c) 2023 Caleb Maclennan <caleb@alerque.com>
# Copyright (c) 2015 Reuben Thomas <rrt@sc3d.org>
# Copyright (c) 2014 Tim Perkins <tprk77@gmail.com>
#
@@ -166,7 +175,7 @@
# Public License for more details.
#
# You should have received a copy of the GNU General Public License along
-# with this program. If not, see <http://www.gnu.org/licenses/>.
+# with this program. If not, see <https://www.gnu.org/licenses/>.
#
# As a special exception, the respective Autoconf Macro's copyright owner
# gives unlimited permission to copy, distribute and modify the configure
@@ -181,7 +190,7 @@
# 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
+#serial 47
dnl =========================================================================
dnl AX_PROG_LUA([MINIMUM-VERSION], [TOO-BIG-VERSION],
@@ -197,13 +206,13 @@ AC_DEFUN([AX_PROG_LUA],
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_define_default([_ax_lua_interpreter_list],
+ [lua lua5.4 lua54 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], [:])])
+ [AC_PATH_PROGS([LUA], [_ax_lua_interpreter_list], [:])])
ax_display_LUA='lua'
AS_IF([test "x$LUA" != 'x:'],
@@ -242,7 +251,7 @@ AC_DEFUN([AX_PROG_LUA],
[_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
+ [ 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])
@@ -268,7 +277,7 @@ AC_DEFUN([AX_PROG_LUA],
ax_cv_lua_version=[`$LUA -e '
-- return a version number in X.Y format
local _, _, ver = string.find(_VERSION, "^Lua (%d+%.%d+)")
- print(ver)'`]
+ print(ver or "")'`]
])
AS_IF([test "x$ax_cv_lua_version" = 'x'],
[AC_MSG_ERROR([invalid Lua version number])])
@@ -469,7 +478,7 @@ AC_DEFUN([AX_LUA_HEADERS],
dnl Some default directories to search.
LUA_SHORT_VERSION=`echo "$LUA_VERSION" | $SED 's|\.||'`
- m4_define_default([_AX_LUA_INCLUDE_LIST],
+ m4_define_default([_ax_lua_include_list],
[ /usr/include/lua$LUA_VERSION \
/usr/include/lua-$LUA_VERSION \
/usr/include/lua/$LUA_VERSION \
@@ -488,9 +497,11 @@ AC_DEFUN([AX_LUA_HEADERS],
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'],
+ test "x$ac_cv_header_lua_h" != 'xyes' ||
+ test "x$with_luajit" != 'xno' &&
+ test "x$ac_cv_header_luajit_h" != 'xyes'],
[ dnl Try some common include paths.
- for _ax_include_path in _AX_LUA_INCLUDE_LIST; do
+ for _ax_include_path in _ax_lua_include_list; do
test ! -d "$_ax_include_path" && continue
AC_MSG_CHECKING([for Lua headers in])
@@ -500,6 +511,7 @@ AC_DEFUN([AX_LUA_HEADERS],
AS_UNSET([ac_cv_header_lualib_h])
AS_UNSET([ac_cv_header_lauxlib_h])
AS_UNSET([ac_cv_header_luaconf_h])
+ AS_UNSET([ac_cv_header_luajit_h])
_ax_lua_saved_cppflags=$CPPFLAGS
CPPFLAGS="$CPPFLAGS -I$_ax_include_path"
@@ -514,24 +526,42 @@ AC_DEFUN([AX_LUA_HEADERS],
])
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'
+ [ dnl Make a program to print LUA_VERSION defined in the header.
+ dnl TODO It would be really nice if we could do this without compiling a
+ dnl program, then it would work when cross compiling. But I'm not sure how
+ dnl to do this reliably. For now, assume versions match when cross compiling.
+
+ AS_IF([test "x$cross_compiling" != 'xyes'],
+ [ AC_CACHE_CHECK([for Lua header version],
+ [ax_cv_lua_header_version],
+ [ _ax_lua_saved_cppflags=$CPPFLAGS
+ CPPFLAGS="$CPPFLAGS $LUA_INCLUDE"
+ AC_COMPUTE_INT(ax_cv_lua_header_version_major,[LUA_VERSION_NUM/100],[AC_INCLUDES_DEFAULT
+#include <lua.h>
+],[ax_cv_lua_header_version_major=unknown])
+ AC_COMPUTE_INT(ax_cv_lua_header_version_minor,[LUA_VERSION_NUM%100],[AC_INCLUDES_DEFAULT
+#include <lua.h>
+],[ax_cv_lua_header_version_minor=unknown])
+ AS_IF([test "x$ax_cv_lua_header_version_major" = xunknown || test "x$ax_cv_lua_header_version_minor" = xunknown],[
+ ax_cv_lua_header_version=unknown
+ ],[
+ ax_cv_lua_header_version="$ax_cv_lua_header_version_major.$ax_cv_lua_header_version_minor"
+ ])
+ CPPFLAGS=$_ax_lua_saved_cppflags
+ ])
+
+ 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'
+ ])
],
- [ AC_MSG_RESULT([no])
- ax_header_version_match='no'
+ [ AC_MSG_WARN([cross compiling so assuming header version number matches])
+ ax_header_version_match='yes'
])
])
@@ -612,7 +642,7 @@ AC_DEFUN([AX_LUA_LIBS],
],
[_ax_found_lua_libs='yes'],
[_ax_found_lua_libs='no'],
- [$_ax_lua_extra_libs])
+ [$_ax_lua_extra_libs])])
LIBS=$_ax_lua_saved_libs
AS_IF([test "x$ac_cv_search_lua_load" != 'xno' &&
diff --git a/mgmtd/mgmt_be_adapter.c b/mgmtd/mgmt_be_adapter.c
index 45e154d83..8f7049037 100644
--- a/mgmtd/mgmt_be_adapter.c
+++ b/mgmtd/mgmt_be_adapter.c
@@ -77,12 +77,20 @@ static const char *const zebra_config_xpaths[] = {
};
static const char *const zebra_oper_xpaths[] = {
+ "/frr-backend:clients",
"/frr-interface:lib/interface",
"/frr-vrf:lib/vrf/frr-zebra:zebra",
"/frr-zebra:zebra",
NULL,
};
+#ifdef HAVE_MGMTD_TESTC
+static const char *const mgmtd_testc_oper_xpaths[] = {
+ "/frr-backend:clients",
+ NULL,
+};
+#endif
+
#ifdef HAVE_RIPD
static const char *const ripd_config_xpaths[] = {
"/frr-filter:lib",
@@ -94,6 +102,7 @@ static const char *const ripd_config_xpaths[] = {
NULL,
};
static const char *const ripd_oper_xpaths[] = {
+ "/frr-backend:clients",
"/frr-ripd:ripd",
"/ietf-key-chain:key-chains",
NULL,
@@ -114,6 +123,7 @@ static const char *const ripngd_config_xpaths[] = {
NULL,
};
static const char *const ripngd_oper_xpaths[] = {
+ "/frr-backend:clients",
"/frr-ripngd:ripngd",
NULL,
};
@@ -130,6 +140,11 @@ static const char *const staticd_config_xpaths[] = {
"/frr-routing:routing/control-plane-protocols/control-plane-protocol/frr-staticd:staticd",
NULL,
};
+
+static const char *const staticd_oper_xpaths[] = {
+ "/frr-backend:clients",
+ NULL,
+};
#endif
static const char *const *be_client_config_xpaths[MGMTD_BE_CLIENT_ID_MAX] = {
@@ -146,12 +161,18 @@ static const char *const *be_client_config_xpaths[MGMTD_BE_CLIENT_ID_MAX] = {
};
static const char *const *be_client_oper_xpaths[MGMTD_BE_CLIENT_ID_MAX] = {
+#ifdef HAVE_MGMTD_TESTC
+ [MGMTD_BE_CLIENT_ID_TESTC] = mgmtd_testc_oper_xpaths,
+#endif
#ifdef HAVE_RIPD
[MGMTD_BE_CLIENT_ID_RIPD] = ripd_oper_xpaths,
#endif
#ifdef HAVE_RIPNGD
[MGMTD_BE_CLIENT_ID_RIPNGD] = ripngd_oper_xpaths,
#endif
+#ifdef HAVE_STATICD
+ [MGMTD_BE_CLIENT_ID_STATICD] = staticd_oper_xpaths,
+#endif
[MGMTD_BE_CLIENT_ID_ZEBRA] = zebra_oper_xpaths,
};
@@ -320,7 +341,7 @@ static void mgmt_be_xpath_map_init(void)
__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 Notif XPath Maps: %u", darr_len(be_notif_xpath_map));
__dbg("Total RPC XPath Maps: %u", darr_len(be_rpc_xpath_map));
}
@@ -651,13 +672,17 @@ int mgmt_be_send_native(enum mgmt_be_client_id id, void *msg)
return mgmt_msg_native_send_msg(adapter->conn, msg, false);
}
+/*
+ * Send notification to back-ends that subscribed for them.
+ */
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;
+ struct nb_node *nb_node = NULL;
const char *notif;
+ bool is_root;
uint id, len;
if (!darr_len(be_notif_xpath_map))
@@ -669,28 +694,34 @@ static void mgmt_be_adapter_send_notify(struct mgmt_msg_notify_data *msg,
return;
}
- nb_node = nb_node_find(notif);
- if (!nb_node) {
- __log_err("No schema found for notification: %s", notif);
- return;
+ is_root = !strcmp(notif, "/");
+ if (!is_root) {
+ 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;
-
+ if (!is_root) {
+ 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
*/
@@ -735,6 +766,9 @@ static void be_adapter_handle_native_msg(struct mgmt_be_client_adapter *adapter,
mgmt_txn_notify_rpc_reply(adapter, rpc_msg, msg_len);
break;
case MGMT_MSG_CODE_NOTIFY:
+ /*
+ * Handle notify message from a back-end client
+ */
notify_msg = (typeof(notify_msg))msg;
__dbg("Got NOTIFY from '%s'", adapter->name);
mgmt_be_adapter_send_notify(notify_msg, msg_len);
diff --git a/mgmtd/mgmt_fe_adapter.c b/mgmtd/mgmt_fe_adapter.c
index 22656f189..8d5919880 100644
--- a/mgmtd/mgmt_fe_adapter.c
+++ b/mgmtd/mgmt_fe_adapter.c
@@ -9,6 +9,7 @@
#include <zebra.h>
#include "darr.h"
+#include "frrstr.h"
#include "sockopt.h"
#include "network.h"
#include "libfrr.h"
@@ -31,6 +32,7 @@
#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,
@@ -55,6 +57,22 @@ 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))
+/*
+ * A tree for storing unique notify-select strings.
+ */
+PREDECL_RBTREE_UNIQ(ns_string);
+struct ns_string {
+ struct ns_string_item link;
+ struct list *sessions;
+ char s[];
+};
+static uint32_t ns_string_compare(const struct ns_string *ns1, const struct ns_string *ns2);
+DECLARE_RBTREE_UNIQ(ns_string, struct ns_string, link, ns_string_compare);
+
+/* ---------------- */
+/* Global variables */
+/* ---------------- */
+
static struct event_loop *mgmt_loop;
static struct msg_server mgmt_fe_server = {.fd = -1};
@@ -63,6 +81,89 @@ static struct mgmt_fe_adapters_head mgmt_fe_adapters;
static struct hash *mgmt_fe_sessions;
static uint64_t mgmt_fe_next_session_id;
+static struct ns_string_head mgmt_fe_ns_strings;
+
+/* ------------------------------ */
+/* Notify select string functions */
+/* ------------------------------ */
+
+static uint32_t ns_string_compare(const struct ns_string *ns1, const struct ns_string *ns2)
+{
+ return strcmp(ns1->s, ns2->s);
+}
+
+static void mgmt_fe_free_ns_string(struct ns_string *ns)
+{
+ list_delete(&ns->sessions);
+ XFREE(MTYPE_MGMTD_XPATH, ns);
+}
+
+static void mgmt_fe_free_ns_strings(struct ns_string_head *head)
+{
+ struct ns_string *ns;
+
+ while ((ns = ns_string_pop(head)))
+ mgmt_fe_free_ns_string(ns);
+ ns_string_fini(head);
+}
+
+static uint64_t mgmt_fe_ns_string_remove_session(struct ns_string_head *head,
+ struct mgmt_fe_session_ctx *session)
+{
+ struct listnode *node;
+ struct ns_string *ns;
+ uint64_t clients = 0;
+
+ frr_each_safe (ns_string, head, ns) {
+ node = listnode_lookup(ns->sessions, session);
+ if (!node)
+ continue;
+ list_delete_node(ns->sessions, node);
+ clients |= mgmt_be_interested_clients(ns->s, MGMT_BE_XPATH_SUBSCR_TYPE_OPER);
+ if (list_isempty(ns->sessions)) {
+ ns_string_del(head, ns);
+ mgmt_fe_free_ns_string(ns);
+ }
+ }
+
+ return clients;
+}
+
+static uint64_t mgmt_fe_add_ns_string(struct ns_string_head *head, const char *path, size_t plen,
+ struct mgmt_fe_session_ctx *session)
+{
+ struct ns_string *e, *ns;
+ uint64_t clients = 0;
+
+ ns = XCALLOC(MTYPE_MGMTD_XPATH, sizeof(*ns) + plen + 1);
+ strlcpy(ns->s, path, plen + 1);
+
+ e = ns_string_add(head, ns);
+ if (!e) {
+ ns->sessions = list_new();
+ listnode_add(ns->sessions, session);
+ clients = mgmt_be_interested_clients(ns->s, MGMT_BE_XPATH_SUBSCR_TYPE_OPER);
+ } else {
+ XFREE(MTYPE_MGMTD_XPATH, ns);
+ if (!listnode_lookup(e->sessions, session))
+ listnode_add(e->sessions, session);
+ }
+
+ return clients;
+}
+
+char **mgmt_fe_get_all_selectors(void)
+{
+ char **selectors = NULL;
+ struct ns_string *ns;
+
+ frr_each (ns_string, &mgmt_fe_ns_strings, ns)
+ *darr_append(selectors) = darr_strdup(ns->s);
+
+ return selectors;
+}
+
+
/* Forward declarations */
static void
mgmt_fe_session_register_event(struct mgmt_fe_session_ctx *session,
@@ -190,6 +291,7 @@ static void mgmt_fe_cleanup_session(struct mgmt_fe_session_ctx **sessionp)
assert(session->adapter->refcount > 1);
mgmt_fe_adapter_unlock(&session->adapter);
}
+ mgmt_fe_ns_string_remove_session(&mgmt_fe_ns_strings, session);
darr_free_free(session->notify_xpaths);
hash_release(mgmt_fe_sessions, session);
XFREE(MTYPE_MGMTD_FE_SESSION, session);
@@ -1542,32 +1644,83 @@ static void fe_adapter_handle_edit(struct mgmt_fe_session_ctx *session,
* @__msg: the message data.
* @msg_len: the length of the message data.
*/
-static void fe_adapter_handle_notify_select(struct mgmt_fe_session_ctx *session,
- void *__msg, size_t msg_len)
+static void fe_adapter_handle_notify_select(struct mgmt_fe_session_ctx *session, void *__msg,
+ size_t msg_len)
{
struct mgmt_msg_notify_select *msg = __msg;
uint64_t req_id = msg->req_id;
const char **selectors = NULL;
const char **new;
+ const char **sp;
+ char *selstr = NULL;
+ uint64_t clients = 0;
+ uint ret;
if (msg_len >= sizeof(*msg)) {
- selectors = mgmt_msg_native_strings_decode(msg, msg_len,
- msg->selectors);
+ selectors = mgmt_msg_native_strings_decode(msg, msg_len, msg->selectors);
if (!selectors) {
- fe_adapter_send_error(session, req_id, false, -EINVAL,
- "Invalid message");
+ fe_adapter_send_error(session, req_id, false, -EINVAL, "Invalid message");
return;
}
}
+ if (DEBUG_MODE_CHECK(&mgmt_debug_fe, DEBUG_MODE_ALL)) {
+ selstr = frrstr_join(selectors, darr_len(selectors), ", ");
+ if (!selstr)
+ selstr = XSTRDUP(MTYPE_TMP, "");
+ }
+
if (msg->replace) {
+ clients = mgmt_fe_ns_string_remove_session(&mgmt_fe_ns_strings, session);
+ // [ ] Keep a local tree to optimize sending selectors to BE?
+ // [*] Or just KISS and fanout the original message to BEs?
+ // mgmt_remove_add_notify_selectors(session->notify_xpaths, selectors);
darr_free_free(session->notify_xpaths);
session->notify_xpaths = selectors;
} else if (selectors) {
- new = darr_append_nz(session->notify_xpaths,
- darr_len(selectors));
+ // [ ] Keep a local tree to optimize sending selectors to BE?
+ // [*] Or just KISS and fanout the original message to BEs?
+ // mgmt_remove_add_notify_selectors(session->notify_xpaths, selectors);
+ new = darr_append_nz(session->notify_xpaths, darr_len(selectors));
memcpy(new, selectors, darr_len(selectors) * sizeof(*selectors));
- darr_free(selectors);
+ } else {
+ __log_err("Invalid msg from session-id: %Lu: no selectors present in non-replace msg",
+ session->session_id);
+ darr_free_free(selectors);
+ selectors = NULL;
+ goto done;
}
+
+
+ if (session->notify_xpaths && DEBUG_MODE_CHECK(&mgmt_debug_fe, DEBUG_MODE_ALL)) {
+ const char **sel = session->notify_xpaths;
+ char *s = frrstr_join(sel, darr_len(sel), ", ");
+ __dbg("New NOTIF %d selectors '%s' (replace: %d) txn-id: %Lu for session-id: %Lu",
+ darr_len(sel), s, msg->replace, session->cfg_txn_id, session->session_id);
+ XFREE(MTYPE_TMP, s);
+ }
+
+ /* Add the new selectors to the global tree */
+ darr_foreach_p (selectors, sp)
+ clients |= mgmt_fe_add_ns_string(&mgmt_fe_ns_strings, *sp, darr_strlen(*sp),
+ session);
+
+ if (!clients) {
+ __dbg("No backends to newly notify for selectors: '%s' txn-id %Lu session-id: %Lu",
+ selstr, session->txn_id, session->session_id);
+ goto done;
+ }
+
+ /* We don't use a transaction for this, just send the message */
+ ret = mgmt_txn_send_notify_selectors(req_id, clients, msg->replace ? NULL : selectors);
+ if (ret) {
+ fe_adapter_send_error(session, req_id, false, -EINPROGRESS,
+ "Failed to create a NOTIFY_SELECT transaction");
+ }
+done:
+ if (session->notify_xpaths != selectors)
+ darr_free(selectors);
+ if (selstr)
+ XFREE(MTYPE_TMP, selstr);
}
/**
@@ -1758,10 +1911,11 @@ 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;
- struct nb_node *nb_node;
- const char **xpath_prefix;
+ struct nb_node *nb_node = NULL;
+ struct listnode *node;
+ struct ns_string *ns;
const char *notif;
- bool sendit;
+ bool is_root;
uint len;
assert(msg->refer_id == 0);
@@ -1772,36 +1926,48 @@ void mgmt_fe_adapter_send_notify(struct mgmt_msg_notify_data *msg, size_t msglen
return;
}
- /*
- * We need the nb_node to obtain a path which does not include any
- * specific list entry selectors
- */
- nb_node = nb_node_find(notif);
- if (!nb_node) {
- __log_err("No schema found for notification: %s", notif);
- return;
+ is_root = !strcmp(notif, "/");
+ if (!is_root) {
+ /*
+ * We need the nb_node to obtain a path which does not include any
+ * specific list entry selectors
+ */
+ nb_node = nb_node_find(notif);
+ if (!nb_node) {
+ __log_err("No schema found for notification: %s", notif);
+ return;
+ }
}
- FOREACH_ADAPTER_IN_LIST (adapter) {
- FOREACH_SESSION_IN_LIST (adapter, session) {
- /* If no selectors then always send */
- sendit = !session->notify_xpaths;
- darr_foreach_p (session->notify_xpaths, xpath_prefix) {
- len = strlen(*xpath_prefix);
- if (!strncmp(*xpath_prefix, notif, len) ||
- !strncmp(*xpath_prefix, nb_node->xpath,
- len)) {
- sendit = true;
- break;
- }
- }
- if (sendit) {
+ frr_each (ns_string, &mgmt_fe_ns_strings, ns) {
+ if (!is_root) {
+ len = strlen(ns->s);
+ if (strncmp(ns->s, notif, len) && strncmp(ns->s, nb_node->xpath, len))
+ continue;
+ }
+ for (ALL_LIST_ELEMENTS_RO(ns->sessions, node, session)) {
+ msg->refer_id = session->session_id;
+ (void)fe_adapter_send_native_msg(session->adapter, msg, msglen, false);
+ }
+ }
+
+ /*
+ * Send all YANG defined notifications to all sesisons with *no*
+ * selectors as well (i.e., original NETCONF/RESTCONF notification
+ * scheme).
+ */
+ if (!is_root && CHECK_FLAG(nb_node->snode->nodetype, LYS_NOTIF)) {
+ FOREACH_ADAPTER_IN_LIST (adapter) {
+ FOREACH_SESSION_IN_LIST (adapter, session) {
+ if (session->notify_xpaths)
+ continue;
msg->refer_id = session->session_id;
(void)fe_adapter_send_native_msg(adapter, msg,
msglen, false);
}
}
}
+
msg->refer_id = 0;
}
@@ -1810,9 +1976,10 @@ 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)
+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) {
@@ -1840,6 +2007,8 @@ void mgmt_fe_adapter_init(struct event_loop *tm)
hash_create(mgmt_fe_session_hash_key, mgmt_fe_session_hash_cmp,
"MGMT Frontend Sessions");
+ ns_string_init(&mgmt_fe_ns_strings);
+
snprintf(server_path, sizeof(server_path), MGMTD_FE_SOCK_NAME);
if (msg_server_init(&mgmt_fe_server, server_path, tm,
@@ -1869,10 +2038,13 @@ void mgmt_fe_adapter_destroy(void)
msg_server_cleanup(&mgmt_fe_server);
+
/* Deleting the adapters will delete all the sessions */
FOREACH_ADAPTER_IN_LIST (adapter)
mgmt_fe_adapter_delete(adapter);
+ mgmt_fe_free_ns_strings(&mgmt_fe_ns_strings);
+
hash_clean_and_free(&mgmt_fe_sessions, mgmt_fe_abort_if_session);
}
@@ -1885,8 +2057,7 @@ struct msg_conn *mgmt_fe_create_adapter(int conn_fd, union sockunion *from)
adapter = mgmt_fe_find_adapter_by_fd(conn_fd);
if (!adapter) {
- adapter = XCALLOC(MTYPE_MGMTD_FE_ADPATER,
- sizeof(struct mgmt_fe_client_adapter));
+ adapter = XCALLOC(MTYPE_MGMTD_FE_ADPATER, sizeof(struct mgmt_fe_client_adapter));
snprintf(adapter->name, sizeof(adapter->name), "Unknown-FD-%d",
conn_fd);
diff --git a/mgmtd/mgmt_fe_adapter.h b/mgmtd/mgmt_fe_adapter.h
index 4d94e7604..19a3d1634 100644
--- a/mgmtd/mgmt_fe_adapter.h
+++ b/mgmtd/mgmt_fe_adapter.h
@@ -225,6 +225,13 @@ extern int mgmt_fe_adapter_txn_error(uint64_t txn_id, uint64_t req_id,
const char *errstr);
+/**
+ * mgmt_fe_get_all_selectors() - Get all selectors for all frontend adapters.
+ *
+ * Returns: A darr array of all selectors for all frontend adapters.
+ */
+extern char **mgmt_fe_get_all_selectors(void);
+
/* Fetch frontend client session set-config stats */
extern struct mgmt_setcfg_stats *
mgmt_fe_get_session_setcfg_stats(uint64_t session_id);
diff --git a/mgmtd/mgmt_history.c b/mgmtd/mgmt_history.c
index c97cb7f0f..934748b1f 100644
--- a/mgmtd/mgmt_history.c
+++ b/mgmtd/mgmt_history.c
@@ -177,6 +177,7 @@ static bool mgmt_history_dump_cmt_record_index(void)
return false;
}
+ assert(cnt <= 10); /* silence bad CLANG SA warning */
ret = fwrite(&cmt_info_set, sizeof(struct mgmt_cmt_info_t), cnt, fp);
fclose(fp);
if (ret != cnt) {
diff --git a/mgmtd/mgmt_main.c b/mgmtd/mgmt_main.c
index 1880d9441..e3fc6b7f2 100644
--- a/mgmtd/mgmt_main.c
+++ b/mgmtd/mgmt_main.c
@@ -159,6 +159,12 @@ const struct frr_yang_module_info ietf_netconf_with_defaults_info = {
* 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 frr_backend_client_info = {
+ .name = "frr-backend",
+ .ignore_cfg_cbs = true,
+ .nodes = { { .xpath = NULL } },
+};
+
const struct frr_yang_module_info zebra_route_map_info = {
.name = "frr-zebra-route-map",
.ignore_cfg_cbs = true,
@@ -183,6 +189,7 @@ static const struct frr_yang_module_info *const mgmt_yang_modules[] = {
/*
* YANG module info used by backend clients get added here.
*/
+ &frr_backend_client_info,
&frr_zebra_cli_info,
&zebra_route_map_info,
@@ -231,10 +238,9 @@ int main(int argc, char **argv)
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");
+ 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 (deprecated, use -w)\n");
/* Command line argument treatment. */
while (1) {
@@ -257,6 +263,8 @@ int main(int argc, char **argv)
buffer_size = atoi(optarg);
break;
case 'n':
+ fprintf(stderr,
+ "The -n option is deprecated, please use global -w option instead.\n");
vrf_configure_backend(VRF_BACKEND_NETNS);
break;
default:
diff --git a/mgmtd/mgmt_testc.c b/mgmtd/mgmt_testc.c
index 8bb07ed06..ab8ea9a04 100644
--- a/mgmtd/mgmt_testc.c
+++ b/mgmtd/mgmt_testc.c
@@ -9,8 +9,10 @@
#include <zebra.h>
#include <lib/version.h>
#include "darr.h"
+#include "debug.h"
#include "libfrr.h"
#include "mgmt_be_client.h"
+#include "mgmt_msg_native.h"
#include "northbound.h"
/* ---------------- */
@@ -43,15 +45,15 @@ struct zebra_privs_t __privs = {
.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 }
-};
+#define OPTION_DATASTORE 2000
+#define OPTION_LISTEN 2001
+#define OPTION_NOTIF_COUNT 2002
+#define OPTION_TIMEOUT 2003
+const struct option longopts[] = { { "datastore", no_argument, NULL, OPTION_DATASTORE },
+ { "listen", no_argument, NULL, OPTION_LISTEN },
+ { "notify-count", required_argument, NULL, OPTION_NOTIF_COUNT },
+ { "timeout", required_argument, NULL, OPTION_TIMEOUT },
+ { 0 } };
/* Master of threads. */
@@ -79,6 +81,20 @@ struct frr_signal_t __signals[] = {
#define MGMTD_TESTC_VTY_PORT 2624
/* clang-format off */
+static const struct frr_yang_module_info frr_if_info = {
+ .name = "frr-interface",
+ .ignore_cfg_cbs = true,
+ .nodes = {
+ {
+ .xpath = "/frr-interface:lib/interface",
+ .cbs.notify = async_notification,
+ },
+ {
+ .xpath = NULL,
+ }
+ }
+};
+
static const struct frr_yang_module_info frr_ripd_info = {
.name = "frr-ripd",
.ignore_cfg_cbs = true,
@@ -98,6 +114,8 @@ static const struct frr_yang_module_info frr_ripd_info = {
};
static const struct frr_yang_module_info *const mgmt_yang_modules[] = {
+ &frr_backend_info,
+ &frr_if_info,
&frr_ripd_info,
};
@@ -123,6 +141,7 @@ const char **__rpc_xpaths;
struct mgmt_be_client_cbs __client_cbs = {};
struct event *event_timeout;
+int f_datastore;
int o_notif_count = 1;
int o_timeout;
@@ -165,10 +184,56 @@ static void success(struct event *event)
quit(0);
}
-static void async_notification(struct nb_cb_notify_args *args)
+static void __ds_notification(struct nb_cb_notify_args *args)
{
- zlog_notice("Received YANG notification");
+ uint8_t *output = NULL;
+
+ zlog_notice("Received YANG datastore notification: op %u", args->op);
+
+ if (args->op == NOTIFY_OP_NOTIFICATION) {
+ zlog_warn("ignoring non-datastore op notification: %s", args->xpath);
+ return;
+ }
+
+ /* datastore notification */
+ switch (args->op) {
+ case NOTIFY_OP_DS_REPLACE:
+ printfrr("#OP=REPLACE: %s\n", args->xpath);
+ break;
+ case NOTIFY_OP_DS_DELETE:
+ printfrr("#OP=DELETE: %s\n", args->xpath);
+ break;
+ case NOTIFY_OP_DS_PATCH:
+ printfrr("#OP=PATCH: %s\n", args->xpath);
+ break;
+ default:
+ printfrr("#OP=%u: unknown notify op\n", args->op);
+ quit(1);
+ }
+ if (args->dnode && args->op != NOTIFY_OP_DS_DELETE) {
+ output = yang_print_tree(args->dnode, LYD_JSON, LYD_PRINT_SHRINK);
+ if (output) {
+ printfrr("%s\n", output);
+ darr_free(output);
+ }
+ }
+ fflush(stdout);
+
+ if (o_notif_count && !--o_notif_count)
+ quit(0);
+}
+
+static void __notification(struct nb_cb_notify_args *args)
+{
+ zlog_notice("Received YANG notification: op: %u", args->op);
+
+ if (args->op != NOTIFY_OP_NOTIFICATION) {
+ zlog_warn("ignoring datastore notification: op: %u: path %s", args->op, args->xpath);
+ return;
+ }
+
+ /* bogus, we should print the actual data */
printf("{\"frr-ripd:authentication-failure\": {\"interface-name\": \"%s\"}}\n",
yang_dnode_get_string(args->dnode, "interface-name"));
@@ -176,6 +241,14 @@ static void async_notification(struct nb_cb_notify_args *args)
quit(0);
}
+static void async_notification(struct nb_cb_notify_args *args)
+{
+ if (f_datastore)
+ __ds_notification(args);
+ else
+ __notification(args);
+}
+
static int rpc_callback(struct nb_cb_rpc_args *args)
{
const char *vrf = NULL;
@@ -210,6 +283,9 @@ int main(int argc, char **argv)
break;
switch (opt) {
+ case OPTION_DATASTORE:
+ f_datastore = 1;
+ break;
case OPTION_LISTEN:
f_listen = 1;
break;
@@ -228,6 +304,9 @@ int main(int argc, char **argv)
master = frr_init();
+ mgmt_be_client_lib_vty_init();
+ mgmt_dbg_be_client.flags = DEBUG_MODE_ALL;
+
/*
* Setup notification listen
*/
diff --git a/mgmtd/mgmt_txn.c b/mgmtd/mgmt_txn.c
index ccfdd7539..4afab389b 100644
--- a/mgmtd/mgmt_txn.c
+++ b/mgmtd/mgmt_txn.c
@@ -237,6 +237,7 @@ struct mgmt_txn_ctx {
struct event *clnup;
/* List of backend adapters involved in this transaction */
+ /* XXX reap this */
struct mgmt_txn_badapters_head be_adapters;
int refcount;
@@ -2651,6 +2652,52 @@ int mgmt_txn_send_rpc(uint64_t txn_id, uint64_t req_id, uint64_t clients,
return 0;
}
+int mgmt_txn_send_notify_selectors(uint64_t req_id, uint64_t clients, const char **selectors)
+{
+ struct mgmt_msg_notify_select *msg;
+ char **all_selectors = NULL;
+ uint64_t id;
+ int ret;
+ uint i;
+
+ msg = mgmt_msg_native_alloc_msg(struct mgmt_msg_notify_select, 0,
+ MTYPE_MSG_NATIVE_NOTIFY_SELECT);
+ msg->refer_id = MGMTD_TXN_ID_NONE;
+ msg->req_id = req_id;
+ msg->code = MGMT_MSG_CODE_NOTIFY_SELECT;
+ msg->replace = selectors == NULL;
+
+ if (selectors == NULL) {
+ /* Get selectors for all sessions */
+ all_selectors = mgmt_fe_get_all_selectors();
+ selectors = (const char **)all_selectors;
+ }
+
+ darr_foreach_i (selectors, i)
+ mgmt_msg_native_add_str(msg, selectors[i]);
+
+ assert(clients);
+ FOREACH_BE_CLIENT_BITS (id, clients) {
+ /* make sure the backend is running/connected */
+ if (!mgmt_be_get_adapter_by_id(id))
+ continue;
+ ret = mgmt_be_send_native(id, msg);
+ if (ret) {
+ __log_err("Could not send notify-select message to backend client %s",
+ mgmt_be_client_id2name(id));
+ continue;
+ }
+
+ __dbg("Sent notify-select req to backend client %s", mgmt_be_client_id2name(id));
+ }
+ mgmt_msg_native_free_msg(msg);
+
+ if (all_selectors)
+ darr_free_free(all_selectors);
+
+ return 0;
+}
+
/*
* Error reply from the backend client.
*/
diff --git a/mgmtd/mgmt_txn.h b/mgmtd/mgmt_txn.h
index 37dadc017..879d20517 100644
--- a/mgmtd/mgmt_txn.h
+++ b/mgmtd/mgmt_txn.h
@@ -297,6 +297,16 @@ 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);
+/**
+ * mgmt_txn_send_notify_selectors() - Send NOTIFY SELECT request.
+ * @req_id: FE client request identifier.
+ * @clients: Bitmask of clients to send RPC to.
+ * @selectors: Array of selectors or NULL to resend all selectors to BE clients.
+ *
+ * Returns 0 on success.
+ */
+extern int mgmt_txn_send_notify_selectors(uint64_t req_id, uint64_t clients, const char **selectors);
+
/*
* Notifiy backend adapter on connection.
*/
diff --git a/ospf6d/ospf6_lsdb.c b/ospf6d/ospf6_lsdb.c
index e5de30484..3215d51a7 100644
--- a/ospf6d/ospf6_lsdb.c
+++ b/ospf6d/ospf6_lsdb.c
@@ -258,7 +258,8 @@ struct ospf6_lsa *ospf6_lsdb_lookup_next(uint16_t type, uint32_t id,
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);
+ if (OSPF6_LSA_DEBUG)
+ zlog_debug("lsdb_lookup_next: key: %pFX", &key);
node = route_table_get_next(lsdb->table, &key);
@@ -398,7 +399,9 @@ int ospf6_lsdb_maxage_remover(struct ospf6_lsdb *lsdb)
EVENT_OFF(lsa->refresh);
event_execute(master, ospf6_lsa_refresh, lsa, 0, NULL);
} else {
- zlog_debug("calling ospf6_lsdb_remove %s", lsa->name);
+ if (IS_OSPF6_DEBUG_LSA_TYPE(lsa->header->type))
+ zlog_debug("calling ospf6_lsdb_remove %s", lsa->name);
+
ospf6_lsdb_remove(lsa, lsdb);
}
}
diff --git a/ospfd/ospf_asbr.c b/ospfd/ospf_asbr.c
index aa1146702..978a6fcc1 100644
--- a/ospfd/ospf_asbr.c
+++ b/ospfd/ospf_asbr.c
@@ -1145,8 +1145,7 @@ static void ospf_external_aggr_timer(struct ospf *ospf,
aggr->action = operation;
if (ospf->t_external_aggr) {
- if (ospf->aggr_action == OSPF_ROUTE_AGGR_ADD) {
-
+ if (ospf->aggr_action == OSPF_ROUTE_AGGR_ADD || operation != OSPF_ROUTE_AGGR_ADD) {
if (IS_DEBUG_OSPF(lsa, EXTNL_LSA_AGGR))
zlog_debug("%s: Not required to restart timer,set is already added.",
__func__);
diff --git a/pimd/pim_bsm.c b/pimd/pim_bsm.c
index 6c4d64923..50fe543b2 100644
--- a/pimd/pim_bsm.c
+++ b/pimd/pim_bsm.c
@@ -354,24 +354,29 @@ static void pim_on_g2rp_timer(struct event *t)
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;
+ pim = bsgrp_node->scope->pim;
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);
+ /*
+ * Update elapse for all bsrp nodes except on the BSR itself.
+ * The timer is meant to remove any bsr RPs learned from the BSR that
+ * we don't hear from anymore. on the BSR itself, no need to do this.
+ */
+ if (pim->global_scope.state != BSR_ELECTED) {
+ /* elapse time is the hold time of expired node */
+ elapse = bsrp->rp_holdtime;
+ 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) {
@@ -386,7 +391,7 @@ static void pim_on_g2rp_timer(struct event *t)
return;
}
- if (rp_info->rp_src != RP_SRC_STATIC) {
+ if (rp_info->rp_src == RP_SRC_BSR) {
/* If new rp available, change it else delete the existing */
if (bsrp) {
pim_g2rp_timer_start(
@@ -2165,6 +2170,7 @@ static void cand_addrsel_config_write(struct vty *vty,
int pim_cand_config_write(struct pim_instance *pim, struct vty *vty)
{
struct bsm_scope *scope = &pim->global_scope;
+ struct cand_rp_group *group;
int ret = 0;
if (scope->cand_rp_addrsel.cfg_enable) {
@@ -2176,14 +2182,11 @@ int pim_cand_config_write(struct pim_instance *pim, struct vty *vty)
cand_addrsel_config_write(vty, &scope->cand_rp_addrsel);
vty_out(vty, "\n");
ret++;
+ }
- struct cand_rp_group *group;
-
- frr_each (cand_rp_groups, scope->cand_rp_groups, group) {
- vty_out(vty, " bsr candidate-rp group %pFX\n",
- &group->p);
- ret++;
- }
+ frr_each (cand_rp_groups, scope->cand_rp_groups, group) {
+ vty_out(vty, " bsr candidate-rp group %pFX\n", &group->p);
+ ret++;
}
if (scope->bsr_addrsel.cfg_enable) {
diff --git a/pimd/pim_bsr_rpdb.c b/pimd/pim_bsr_rpdb.c
index 02e7a69ff..860009312 100644
--- a/pimd/pim_bsr_rpdb.c
+++ b/pimd/pim_bsr_rpdb.c
@@ -417,7 +417,7 @@ void pim_crp_nht_update(struct pim_instance *pim, struct pim_nexthop_cache *pnc)
rp = bsr_crp_rps_find(scope->ebsr_rps, &ref);
assertf(rp, "addr=%pPA", &ref.addr);
- ok = pim_nht_pnc_is_valid(pim, pnc);
+ ok = pim_nht_pnc_is_valid(pim, pnc, PIMADDR_ANY);
if (ok == rp->nht_ok)
return;
diff --git a/pimd/pim_cmd.c b/pimd/pim_cmd.c
index a34fb344f..a1ad26186 100644
--- a/pimd/pim_cmd.c
+++ b/pimd/pim_cmd.c
@@ -3296,7 +3296,7 @@ DEFUN (show_ip_rib,
return CMD_WARNING;
}
- if (!pim_nht_lookup(vrf->info, &nexthop, addr, 0)) {
+ if (!pim_nht_lookup(vrf->info, &nexthop, addr, PIMADDR_ANY, false)) {
vty_out(vty,
"Failure querying RIB nexthop for unicast address %s\n",
addr_str);
@@ -8878,21 +8878,31 @@ done:
}
DEFPY_YANG(pim_rpf_lookup_mode, pim_rpf_lookup_mode_cmd,
- "[no] rpf-lookup-mode ![urib-only|mrib-only|mrib-then-urib|lower-distance|longer-prefix]$mode",
+ "[no] rpf-lookup-mode\
+ ![urib-only|mrib-only|mrib-then-urib|lower-distance|longer-prefix]$mode\
+ [{group-list PREFIX_LIST$grp_list|source-list PREFIX_LIST$src_list}]",
NO_STR
"RPF lookup behavior\n"
"Lookup in unicast RIB only\n"
"Lookup in multicast RIB only\n"
"Try multicast RIB first, fall back to unicast RIB\n"
"Lookup both, use entry with lower distance\n"
- "Lookup both, use entry with longer prefix\n")
-{
- if (no)
- nb_cli_enqueue_change(vty, "./mcast-rpf-lookup", NB_OP_DESTROY, NULL);
- else
- nb_cli_enqueue_change(vty, "./mcast-rpf-lookup", NB_OP_MODIFY, mode);
+ "Lookup both, use entry with longer prefix\n"
+ "Set a specific mode matching group\n"
+ "Multicast group prefix list\n"
+ "Set a specific mode matching source address\n"
+ "Source address prefix list\n")
+{
+ if (no) {
+ nb_cli_enqueue_change(vty, "./mode", NB_OP_DESTROY, NULL);
+ nb_cli_enqueue_change(vty, ".", NB_OP_DESTROY, NULL);
+ } else {
+ nb_cli_enqueue_change(vty, ".", NB_OP_CREATE, NULL);
+ nb_cli_enqueue_change(vty, "./mode", NB_OP_MODIFY, mode);
+ }
- return nb_cli_apply_changes(vty, NULL);
+ return nb_cli_apply_changes(vty, "./mcast-rpf-lookup[group-list='%s'][source-list='%s']",
+ (grp_list ? grp_list : ""), (src_list ? src_list : ""));
}
struct cmd_node pim_node = {
diff --git a/pimd/pim_igmp_mtrace.c b/pimd/pim_igmp_mtrace.c
index ad6f26510..f0fbb2bbf 100644
--- a/pimd/pim_igmp_mtrace.c
+++ b/pimd/pim_igmp_mtrace.c
@@ -59,7 +59,8 @@ static bool mtrace_fwd_info_weak(struct pim_instance *pim,
memset(&nexthop, 0, sizeof(nexthop));
- if (!pim_nht_lookup(pim, &nexthop, mtracep->src_addr, 1)) {
+ /* TODO Is there any valid group address to use for lookup? */
+ if (!pim_nht_lookup(pim, &nexthop, mtracep->src_addr, PIMADDR_ANY, true)) {
if (PIM_DEBUG_MTRACE)
zlog_debug("mtrace not found neighbor");
return false;
@@ -354,7 +355,8 @@ static int mtrace_un_forward_packet(struct pim_instance *pim, struct ip *ip_hdr,
if (interface == NULL) {
memset(&nexthop, 0, sizeof(nexthop));
- if (!pim_nht_lookup(pim, &nexthop, ip_hdr->ip_dst, 0)) {
+ /* TODO Is there any valid group address to use for lookup? */
+ if (!pim_nht_lookup(pim, &nexthop, ip_hdr->ip_dst, PIMADDR_ANY, false)) {
if (PIM_DEBUG_MTRACE)
zlog_debug(
"Dropping mtrace packet, no route to destination");
@@ -535,8 +537,11 @@ static int mtrace_send_response(struct pim_instance *pim,
zlog_debug("mtrace response to RP");
} else {
memset(&nexthop, 0, sizeof(nexthop));
- /* TODO: should use unicast rib lookup */
- if (!pim_nht_lookup(pim, &nexthop, mtracep->rsp_addr, 1)) {
+ /* TODO: should use unicast rib lookup
+ * NEB 10/30/24 - Not sure why this needs the unicast rib...right now it will look up per the rpf mode
+ * Are any of the igmp_mtrace addresses a valid group address to use for lookups??
+ */
+ if (!pim_nht_lookup(pim, &nexthop, mtracep->rsp_addr, PIMADDR_ANY, true)) {
if (PIM_DEBUG_MTRACE)
zlog_debug(
"Dropped response qid=%ud, no route to response address",
diff --git a/pimd/pim_instance.h b/pimd/pim_instance.h
index 7f022111b..7aa9d857d 100644
--- a/pimd/pim_instance.h
+++ b/pimd/pim_instance.h
@@ -18,6 +18,7 @@
#include "pim_upstream.h"
#include "pim_mroute.h"
#include "pim_autorp.h"
+#include "pim_nht.h"
enum pim_spt_switchover {
PIM_SPT_IMMEDIATE,
@@ -116,7 +117,7 @@ struct pim_instance {
char *register_plist;
struct hash *nht_hash;
- enum pim_rpf_lookup_mode rpf_mode;
+ struct pim_lookup_mode_head rpf_mode;
void *ssm_info; /* per-vrf SSM configuration */
diff --git a/pimd/pim_mroute.c b/pimd/pim_mroute.c
index 93bdd8dac..6c13e1324 100644
--- a/pimd/pim_mroute.c
+++ b/pimd/pim_mroute.c
@@ -567,7 +567,8 @@ int pim_mroute_msg_wrvifwhole(int fd, struct interface *ifp, const char *buf,
* setting the SPTBIT to true
*/
if (!(pim_addr_is_any(up->upstream_register)) &&
- pim_nht_lookup(pim_ifp->pim, &source, up->upstream_register, 0)) {
+ pim_nht_lookup(pim_ifp->pim, &source, up->upstream_register, up->sg.grp,
+ false)) {
pim_register_stop_send(source.interface, &sg,
pim_ifp->primary_address,
up->upstream_register);
@@ -580,7 +581,8 @@ int pim_mroute_msg_wrvifwhole(int fd, struct interface *ifp, const char *buf,
__func__);
} else {
if (I_am_RP(pim_ifp->pim, up->sg.grp)) {
- if (pim_nht_lookup(pim_ifp->pim, &source, up->upstream_register, 0))
+ if (pim_nht_lookup(pim_ifp->pim, &source, up->upstream_register,
+ up->sg.grp, false))
pim_register_stop_send(
source.interface, &sg,
pim_ifp->primary_address,
diff --git a/pimd/pim_msdp.c b/pimd/pim_msdp.c
index 5e5ee5e91..46d5f4881 100644
--- a/pimd/pim_msdp.c
+++ b/pimd/pim_msdp.c
@@ -706,7 +706,7 @@ bool pim_msdp_peer_rpf_check(struct pim_msdp_peer *mp, struct in_addr rp)
}
/* check if the MSDP peer is the nexthop for the RP */
- if (pim_nht_lookup(mp->pim, &nexthop, rp, 0) &&
+ if (pim_nht_lookup(mp->pim, &nexthop, rp, PIMADDR_ANY, false) &&
nexthop.mrib_nexthop_addr.s_addr == mp->peer.s_addr) {
return true;
}
diff --git a/pimd/pim_nb.c b/pimd/pim_nb.c
index b55541b81..ea9ce3cec 100644
--- a/pimd/pim_nb.c
+++ b/pimd/pim_nb.c
@@ -266,7 +266,14 @@ const struct frr_yang_module_info frr_pim_info = {
{
.xpath = "/frr-routing:routing/control-plane-protocols/control-plane-protocol/frr-pim:pim/address-family/mcast-rpf-lookup",
.cbs = {
- .modify = routing_control_plane_protocols_control_plane_protocol_pim_address_family_mcast_rpf_lookup_modify,
+ .create = routing_control_plane_protocols_control_plane_protocol_pim_address_family_mcast_rpf_lookup_create,
+ .destroy = routing_control_plane_protocols_control_plane_protocol_pim_address_family_mcast_rpf_lookup_destroy,
+ }
+ },
+ {
+ .xpath = "/frr-routing:routing/control-plane-protocols/control-plane-protocol/frr-pim:pim/address-family/mcast-rpf-lookup/mode",
+ .cbs = {
+ .modify = routing_control_plane_protocols_control_plane_protocol_pim_address_family_mcast_rpf_lookup_mode_modify
}
},
{
diff --git a/pimd/pim_nb.h b/pimd/pim_nb.h
index a5ef6ad60..a15c6e6d9 100644
--- a/pimd/pim_nb.h
+++ b/pimd/pim_nb.h
@@ -102,7 +102,11 @@ int routing_control_plane_protocols_control_plane_protocol_pim_address_family_re
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 routing_control_plane_protocols_control_plane_protocol_pim_address_family_mcast_rpf_lookup_modify(
+int routing_control_plane_protocols_control_plane_protocol_pim_address_family_mcast_rpf_lookup_create(
+ struct nb_cb_create_args *args);
+int routing_control_plane_protocols_control_plane_protocol_pim_address_family_mcast_rpf_lookup_destroy(
+ struct nb_cb_destroy_args *args);
+int routing_control_plane_protocols_control_plane_protocol_pim_address_family_mcast_rpf_lookup_mode_modify(
struct nb_cb_modify_args *args);
int lib_interface_pim_address_family_dr_priority_modify(
struct nb_cb_modify_args *args);
diff --git a/pimd/pim_nb_config.c b/pimd/pim_nb_config.c
index b55d08bab..51f061588 100644
--- a/pimd/pim_nb_config.c
+++ b/pimd/pim_nb_config.c
@@ -1895,12 +1895,25 @@ int routing_control_plane_protocols_control_plane_protocol_pim_address_family_re
/*
* XPath: /frr-routing:routing/control-plane-protocols/control-plane-protocol/frr-pim:pim/address-family/mcast-rpf-lookup
*/
-int routing_control_plane_protocols_control_plane_protocol_pim_address_family_mcast_rpf_lookup_modify(
- struct nb_cb_modify_args *args)
+int routing_control_plane_protocols_control_plane_protocol_pim_address_family_mcast_rpf_lookup_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_mcast_rpf_lookup_destroy(
+ struct nb_cb_destroy_args *args)
{
struct vrf *vrf;
struct pim_instance *pim;
- enum pim_rpf_lookup_mode old_mode;
switch (args->event) {
case NB_EV_VALIDATE:
@@ -1910,15 +1923,37 @@ int routing_control_plane_protocols_control_plane_protocol_pim_address_family_mc
case NB_EV_APPLY:
vrf = nb_running_get_entry(args->dnode, NULL, true);
pim = vrf->info;
- old_mode = pim->rpf_mode;
- pim->rpf_mode = yang_dnode_get_enum(args->dnode, NULL);
+ pim_nht_change_rpf_mode(pim, yang_dnode_get_string(args->dnode, "group-list"),
+ yang_dnode_get_string(args->dnode, "source-list"),
+ MCAST_NO_CONFIG);
+ break;
+ }
- if (pim->rpf_mode != old_mode &&
- /* MCAST_MIX_MRIB_FIRST is the default if not configured */
- (old_mode != MCAST_NO_CONFIG && pim->rpf_mode != MCAST_MIX_MRIB_FIRST)) {
- pim_nht_mode_changed(pim);
- }
+ return NB_OK;
+}
+
+/*
+ * XPath: /frr-routing:routing/control-plane-protocols/control-plane-protocol/frr-pim:pim/address-family/mcast-rpf-lookup/mode
+ */
+int routing_control_plane_protocols_control_plane_protocol_pim_address_family_mcast_rpf_lookup_mode_modify(
+ struct nb_cb_modify_args *args)
+{
+ struct vrf *vrf;
+ struct pim_instance *pim;
+ enum pim_rpf_lookup_mode mode = MCAST_NO_CONFIG;
+ 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;
+ mode = yang_dnode_get_enum(args->dnode, NULL);
+ pim_nht_change_rpf_mode(pim, yang_dnode_get_string(args->dnode, "../group-list"),
+ yang_dnode_get_string(args->dnode, "../source-list"), mode);
break;
}
diff --git a/pimd/pim_nht.c b/pimd/pim_nht.c
index 00ab46b4c..1e9ea24b2 100644
--- a/pimd/pim_nht.c
+++ b/pimd/pim_nht.c
@@ -34,6 +34,176 @@
#include "pim_register.h"
#include "pim_vxlan.h"
+DEFINE_MTYPE_STATIC(PIMD, PIM_LOOKUP_MODE, "PIM RPF lookup mode");
+DEFINE_MTYPE_STATIC(PIMD, PIM_LOOKUP_MODE_STR, "PIM RPF lookup mode prefix list string");
+
+static void pim_update_rp_nh(struct pim_instance *pim, struct pim_nexthop_cache *pnc);
+static int pim_update_upstream_nh(struct pim_instance *pim, struct pim_nexthop_cache *pnc);
+
+static int pim_lookup_mode_cmp(const struct pim_lookup_mode *l, const struct pim_lookup_mode *r)
+{
+ /* Let's just sort anything with both lists set above those with only one list set,
+ * which is above the global where neither are set
+ */
+
+ /* Both are set on right, either lower or equal */
+ if (l->grp_plist != NULL && l->src_plist != NULL)
+ return (r->grp_plist == NULL || r->src_plist == NULL) ? -1 : 0;
+
+ /* Only one set on the left */
+ if (!(l->grp_plist == NULL && l->src_plist == NULL)) {
+ /* Lower only if both are not set on right */
+ if (r->grp_plist == NULL && r->src_plist == NULL)
+ return -1;
+ /* Higher only if both are set on right */
+ if (r->grp_plist != NULL && r->src_plist != NULL)
+ return 1;
+ /* Otherwise both sides have at least one set, so equal */
+ return 0;
+ }
+
+ /* Neither set on left, so equal if neither set on right also */
+ if (r->grp_plist == NULL && r->src_plist == NULL)
+ return 0;
+
+ /* Otherwise higher */
+ return 1;
+}
+
+DECLARE_SORTLIST_NONUNIQ(pim_lookup_mode, struct pim_lookup_mode, list, pim_lookup_mode_cmp);
+
+static void pim_lookup_mode_free(struct pim_lookup_mode *m)
+{
+ if (m->grp_plist)
+ XFREE(MTYPE_PIM_LOOKUP_MODE_STR, m->grp_plist);
+ if (m->src_plist)
+ XFREE(MTYPE_PIM_LOOKUP_MODE_STR, m->src_plist);
+ XFREE(MTYPE_PIM_LOOKUP_MODE, m);
+}
+
+static void pim_lookup_mode_list_free(struct pim_lookup_mode_head *head)
+{
+ struct pim_lookup_mode *m;
+
+ while ((m = pim_lookup_mode_pop(head)))
+ pim_lookup_mode_free(m);
+}
+
+enum pim_rpf_lookup_mode pim_get_lookup_mode(struct pim_instance *pim, pim_addr group,
+ pim_addr source)
+{
+ struct pim_lookup_mode *m;
+ struct prefix_list *plist;
+ struct prefix p;
+
+ frr_each_safe (pim_lookup_mode, &(pim->rpf_mode), m) {
+ if (!pim_addr_is_any(group) && m->grp_plist) {
+ /* Match group against plist, continue if no match */
+ plist = prefix_list_lookup(PIM_AFI, m->grp_plist);
+ if (plist == NULL)
+ continue;
+ pim_addr_to_prefix(&p, group);
+ if (prefix_list_apply(plist, &p) == PREFIX_DENY)
+ continue;
+ }
+
+ if (!pim_addr_is_any(source) && m->src_plist) {
+ /* Match source against plist, continue if no match */
+ plist = prefix_list_lookup(PIM_AFI, m->src_plist);
+ if (plist == NULL)
+ continue;
+ pim_addr_to_prefix(&p, source);
+ if (prefix_list_apply(plist, &p) == PREFIX_DENY)
+ continue;
+ }
+
+ /* If lookup mode has a group list, but no group is provided, don't match it */
+ if (pim_addr_is_any(group) && m->grp_plist)
+ continue;
+
+ /* If lookup mode has a source list, but no source is provided, don't match it */
+ if (pim_addr_is_any(source) && m->src_plist)
+ continue;
+
+ /* Match found */
+ return m->mode;
+ }
+
+ /* This shouldn't happen since we have the global mode, but if it's gone,
+ * just return the default of no config
+ */
+ if (PIM_DEBUG_PIM_NHT)
+ zlog_debug("%s: No RPF lookup matched for given group %pPA and source %pPA",
+ __func__, &group, &source);
+
+ return MCAST_NO_CONFIG;
+}
+
+static bool pim_rpf_mode_changed(enum pim_rpf_lookup_mode old, enum pim_rpf_lookup_mode new)
+{
+ if (old != new) {
+ /* These two are equivalent, so don't update in that case */
+ if (old == MCAST_NO_CONFIG && new == MCAST_MIX_MRIB_FIRST)
+ return false;
+ if (old == MCAST_MIX_MRIB_FIRST && new == MCAST_NO_CONFIG)
+ return false;
+ return true;
+ }
+ return false;
+}
+
+struct pnc_mode_update_hash_walk_data {
+ struct pim_instance *pim;
+ struct prefix_list *grp_plist;
+ struct prefix_list *src_plist;
+};
+
+static int pim_nht_hash_mode_update_helper(struct hash_bucket *bucket, void *arg)
+{
+ struct pim_nexthop_cache *pnc = bucket->data;
+ struct pnc_mode_update_hash_walk_data *pwd = arg;
+ struct pim_instance *pim = pwd->pim;
+ struct prefix p;
+
+ pim_addr_to_prefix(&p, pnc->addr);
+
+ /* Make sure this pnc entry matches the prefix lists */
+ /* TODO: For now, pnc only has the source address, so we can only check that */
+ if (pwd->src_plist &&
+ (pim_addr_is_any(pnc->addr) || prefix_list_apply(pwd->src_plist, &p) == PREFIX_DENY))
+ return HASHWALK_CONTINUE;
+
+ /* Otherwise the address is any, or matches the prefix list, or no prefix list to match, so do the updates */
+ /* TODO for RP, there are groups....but I don't think we'd want to use those */
+ if (listcount(pnc->rp_list))
+ pim_update_rp_nh(pim, pnc);
+
+ /* TODO for upstream, there is an S,G key...can/should we use that group?? */
+ if (pnc->upstream_hash->count)
+ pim_update_upstream_nh(pim, pnc);
+
+ if (pnc->candrp_count)
+ pim_crp_nht_update(pim, pnc);
+
+ return HASHWALK_CONTINUE;
+}
+
+static void pim_rpf_mode_changed_update(struct pim_instance *pim, const char *group_plist,
+ const char *source_plist)
+{
+ struct pnc_mode_update_hash_walk_data pwd;
+
+ /* Update the refresh time to force new lookups if needed */
+ pim_rpf_set_refresh_time(pim);
+
+ /* Force update the registered RP and upstreams for all cache entries */
+ pwd.pim = pim;
+ pwd.grp_plist = prefix_list_lookup(PIM_AFI, group_plist);
+ pwd.src_plist = prefix_list_lookup(PIM_AFI, source_plist);
+
+ hash_walk(pim->nht_hash, pim_nht_hash_mode_update_helper, &pwd);
+}
+
/**
* pim_sendmsg_zebra_rnh -- Format and send a nexthop register/Unregister
* command to Zebra.
@@ -106,9 +276,10 @@ static struct pim_nexthop_cache *pim_nexthop_cache_add(struct pim_instance *pim,
return pnc;
}
-static bool pim_nht_pnc_has_answer(struct pim_instance *pim, struct pim_nexthop_cache *pnc)
+static bool pim_nht_pnc_has_answer(struct pim_instance *pim, struct pim_nexthop_cache *pnc,
+ pim_addr group)
{
- switch (pim->rpf_mode) {
+ switch (pim_get_lookup_mode(pim, group, pnc->addr)) {
case MCAST_MRIB_ONLY:
return CHECK_FLAG(pnc->mrib.flags, PIM_NEXTHOP_ANSWER_RECEIVED);
@@ -133,25 +304,28 @@ static bool pim_nht_pnc_has_answer(struct pim_instance *pim, struct pim_nexthop_
}
static struct pim_nexthop_cache_rib *pim_pnc_get_rib(struct pim_instance *pim,
- struct pim_nexthop_cache *pnc)
+ struct pim_nexthop_cache *pnc, pim_addr group)
{
struct pim_nexthop_cache_rib *pnc_rib = NULL;
+ enum pim_rpf_lookup_mode mode;
- if (pim->rpf_mode == MCAST_MRIB_ONLY)
+ mode = pim_get_lookup_mode(pim, group, pnc->addr);
+
+ if (mode == MCAST_MRIB_ONLY)
pnc_rib = &pnc->mrib;
- else if (pim->rpf_mode == MCAST_URIB_ONLY)
+ else if (mode == MCAST_URIB_ONLY)
pnc_rib = &pnc->urib;
- else if (pim->rpf_mode == MCAST_MIX_MRIB_FIRST || pim->rpf_mode == MCAST_NO_CONFIG) {
+ else if (mode == MCAST_MIX_MRIB_FIRST || mode == MCAST_NO_CONFIG) {
if (pnc->mrib.nexthop_num > 0)
pnc_rib = &pnc->mrib;
else
pnc_rib = &pnc->urib;
- } else if (pim->rpf_mode == MCAST_MIX_DISTANCE) {
+ } else if (mode == MCAST_MIX_DISTANCE) {
if (pnc->mrib.distance <= pnc->urib.distance)
pnc_rib = &pnc->mrib;
else
pnc_rib = &pnc->urib;
- } else if (pim->rpf_mode == MCAST_MIX_PFXLEN) {
+ } else if (mode == MCAST_MIX_PFXLEN) {
if (pnc->mrib.prefix_len >= pnc->urib.prefix_len)
pnc_rib = &pnc->mrib;
else
@@ -161,9 +335,151 @@ static struct pim_nexthop_cache_rib *pim_pnc_get_rib(struct pim_instance *pim,
return pnc_rib;
}
-bool pim_nht_pnc_is_valid(struct pim_instance *pim, struct pim_nexthop_cache *pnc)
+void pim_nht_change_rpf_mode(struct pim_instance *pim, const char *group_plist,
+ const char *source_plist, enum pim_rpf_lookup_mode mode)
+{
+ struct pim_lookup_mode *m;
+ bool found = false;
+ bool update = false;
+ const char *glist = NULL;
+ const char *slist = NULL;
+
+ /* Prefix lists may be passed in as empty string, leave them NULL instead */
+ if (group_plist && strlen(group_plist))
+ glist = group_plist;
+ if (source_plist && strlen(source_plist))
+ slist = source_plist;
+
+ frr_each_safe (pim_lookup_mode, &(pim->rpf_mode), m) {
+ if ((m->grp_plist && glist && strmatch(m->grp_plist, glist)) &&
+ (m->src_plist && slist && strmatch(m->src_plist, slist))) {
+ /* Group and source plists are both set and matched */
+ found = true;
+ if (mode == MCAST_NO_CONFIG) {
+ /* MCAST_NO_CONFIG means we should remove this lookup mode
+ * We don't know what other modes might match, or if only the global, so we need to
+ * update all lookups
+ */
+ pim_lookup_mode_del(&pim->rpf_mode, m);
+ pim_lookup_mode_free(m);
+ glist = NULL;
+ slist = NULL;
+ update = true;
+ } else {
+ /* Just changing mode */
+ update = pim_rpf_mode_changed(m->mode, mode);
+ m->mode = mode; /* Always make sure the mode is set, even if not updating */
+ }
+
+ if (update)
+ pim_rpf_mode_changed_update(pim, glist, slist);
+ break;
+ }
+
+ if ((m->grp_plist && glist && strmatch(m->grp_plist, glist)) &&
+ (!m->src_plist && !slist)) {
+ /* Only group list set and matched */
+ found = true;
+ if (mode == MCAST_NO_CONFIG) {
+ /* MCAST_NO_CONFIG means we should remove this lookup mode
+ * We don't know what other modes might match, or if only the global, so we need to
+ * update all lookups
+ */
+ pim_lookup_mode_del(&pim->rpf_mode, m);
+ pim_lookup_mode_free(m);
+ glist = NULL;
+ slist = NULL;
+ update = true;
+ } else {
+ /* Just changing mode */
+ update = pim_rpf_mode_changed(m->mode, mode);
+ m->mode = mode; /* Always make sure the mode is set, even if not updating */
+ }
+
+ if (update)
+ pim_rpf_mode_changed_update(pim, glist, slist);
+ break;
+ }
+
+ if ((!m->grp_plist && !glist) &&
+ (m->src_plist && slist && strmatch(m->src_plist, slist))) {
+ /* Only source list set and matched */
+ found = true;
+ if (mode == MCAST_NO_CONFIG) {
+ /* MCAST_NO_CONFIG means we should remove this lookup mode
+ * We don't know what other modes might match, or if only the global, so we need to
+ * update all lookups
+ */
+ pim_lookup_mode_del(&pim->rpf_mode, m);
+ pim_lookup_mode_free(m);
+ glist = NULL;
+ slist = NULL;
+ update = true;
+ } else {
+ /* Just changing mode */
+ update = pim_rpf_mode_changed(m->mode, mode);
+ m->mode = mode; /* Always make sure the mode is set, even if not updating */
+ }
+
+ if (update)
+ pim_rpf_mode_changed_update(pim, glist, slist);
+ break;
+ }
+
+ if (!m->grp_plist && !glist && !m->src_plist && !slist) {
+ /* No prefix lists set, so this is the global mode */
+ /* We never delete this mode, even when set back to MCAST_NO_CONFIG */
+ update = pim_rpf_mode_changed(m->mode, mode);
+ m->mode = mode; /* Always make sure the mode is set, even if not updating */
+ if (update)
+ pim_rpf_mode_changed_update(pim, glist, slist);
+ found = true;
+ break;
+ }
+ }
+
+ if (!found) {
+ /* Adding a new lookup mode with unique prefix lists, add it */
+ m = XCALLOC(MTYPE_PIM_LOOKUP_MODE, sizeof(struct pim_lookup_mode));
+ m->grp_plist = XSTRDUP(MTYPE_PIM_LOOKUP_MODE_STR, glist);
+ m->src_plist = XSTRDUP(MTYPE_PIM_LOOKUP_MODE_STR, slist);
+ m->mode = mode;
+ pim_lookup_mode_add(&(pim->rpf_mode), m);
+ pim_rpf_mode_changed_update(pim, glist, slist);
+ }
+}
+
+int pim_lookup_mode_write(struct pim_instance *pim, struct vty *vty)
{
- switch (pim->rpf_mode) {
+ int writes = 0;
+ struct pim_lookup_mode *m;
+
+ frr_each_safe (pim_lookup_mode, &(pim->rpf_mode), m) {
+ if (m->mode == MCAST_NO_CONFIG)
+ continue;
+
+ ++writes;
+ vty_out(vty, " rpf-lookup-mode %s",
+ m->mode == MCAST_URIB_ONLY ? "urib-only"
+ : m->mode == MCAST_MRIB_ONLY ? "mrib-only"
+ : m->mode == MCAST_MIX_MRIB_FIRST ? "mrib-then-urib"
+ : m->mode == MCAST_MIX_DISTANCE ? "lower-distance"
+ : "longer-prefix");
+
+ if (m->grp_plist)
+ vty_out(vty, " group-list %s", m->grp_plist);
+
+ if (m->src_plist)
+ vty_out(vty, " source-list %s", m->src_plist);
+
+ vty_out(vty, "\n");
+ }
+ return writes;
+}
+
+bool pim_nht_pnc_is_valid(struct pim_instance *pim, struct pim_nexthop_cache *pnc, pim_addr group)
+{
+ switch (pim_get_lookup_mode(pim, group, pnc->addr)) {
case MCAST_MRIB_ONLY:
return CHECK_FLAG(pnc->mrib.flags, PIM_NEXTHOP_VALID);
@@ -275,6 +591,7 @@ bool pim_nht_find_or_track(struct pim_instance *pim, pim_addr addr, struct pim_u
{
struct pim_nexthop_cache *pnc;
struct listnode *ch_node = NULL;
+ pim_addr group = PIMADDR_ANY;
/* This will find the entry and add it to tracking if not found */
pnc = pim_nht_get(pim, addr);
@@ -289,10 +606,12 @@ bool pim_nht_find_or_track(struct pim_instance *pim, pim_addr addr, struct pim_u
}
/* Store the upstream if provided and not currently in the list */
- if (up != NULL)
+ if (up != NULL) {
(void)hash_get(pnc->upstream_hash, up, hash_alloc_intern);
+ group = up->sg.grp;
+ }
- if (pim_nht_pnc_is_valid(pim, pnc)) {
+ if (pim_nht_pnc_is_valid(pim, pnc, group)) {
if (out_pnc)
memcpy(out_pnc, pnc, sizeof(struct pim_nexthop_cache));
return true;
@@ -315,7 +634,7 @@ bool pim_nht_candrp_add(struct pim_instance *pim, pim_addr addr)
pnc = pim_nht_get(pim, addr);
pnc->candrp_count++;
- return pim_nht_pnc_is_valid(pim, pnc);
+ return pim_nht_pnc_is_valid(pim, pnc, PIMADDR_ANY);
}
static void pim_nht_drop_maybe(struct pim_instance *pim, struct pim_nexthop_cache *pnc)
@@ -448,7 +767,7 @@ bool pim_nht_bsr_rpf_check(struct pim_instance *pim, pim_addr bsr_addr,
lookup.addr = bsr_addr;
pnc = hash_lookup(pim->nht_hash, &lookup);
- if (!pnc || !pim_nht_pnc_has_answer(pim, pnc)) {
+ if (!pnc || !pim_nht_pnc_has_answer(pim, pnc, PIMADDR_ANY)) {
/* 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
@@ -465,9 +784,8 @@ bool pim_nht_bsr_rpf_check(struct pim_instance *pim, pim_addr bsr_addr,
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);
+ num_ifindex = zclient_lookup_nexthop(pim, nexthop_tab, router->multipath, bsr_addr,
+ PIMADDR_ANY, PIM_NEXTHOP_LOOKUP_MAX);
if (num_ifindex <= 0)
return false;
@@ -507,7 +825,7 @@ bool pim_nht_bsr_rpf_check(struct pim_instance *pim, pim_addr bsr_addr,
return false;
}
- if (pim_nht_pnc_is_valid(pim, pnc)) {
+ if (pim_nht_pnc_is_valid(pim, pnc, PIMADDR_ANY)) {
/* 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
@@ -515,7 +833,7 @@ bool pim_nht_bsr_rpf_check(struct pim_instance *pim, pim_addr bsr_addr,
*
* so... only accept the first (IPv4) valid nexthop as source.
*/
- struct pim_nexthop_cache_rib *rib = pim_pnc_get_rib(pim, pnc);
+ struct pim_nexthop_cache_rib *rib = pim_pnc_get_rib(pim, pnc, PIMADDR_ANY);
for (nh = rib->nexthop; nh; nh = nh->next) {
pim_addr nhaddr;
@@ -754,14 +1072,17 @@ static bool pim_ecmp_nexthop_search(struct pim_instance *pim, struct pim_nexthop
pim_addr nh_addr;
pim_addr grp_addr;
struct pim_nexthop_cache_rib *rib;
+ pim_addr group;
+
+ group = pim_addr_from_prefix(grp);
/* Early return if required parameters aren't provided */
- if (!pim || !pnc || !pim_nht_pnc_is_valid(pim, pnc) || !nexthop || !grp)
+ if (!pim || !pnc || !pim_nht_pnc_is_valid(pim, pnc, group) || !nexthop || !grp)
return false;
nh_addr = nexthop->mrib_nexthop_addr;
grp_addr = pim_addr_from_prefix(grp);
- rib = pim_pnc_get_rib(pim, pnc);
+ rib = pim_pnc_get_rib(pim, pnc, group);
/* Current Nexthop is VALID, check to stay on the current path. */
if (nexthop->interface && nexthop->interface->info &&
@@ -934,6 +1255,9 @@ bool pim_nht_lookup_ecmp(struct pim_instance *pim, struct pim_nexthop *nexthop,
uint32_t hash_val = 0;
uint32_t mod_val = 0;
uint32_t num_nbrs = 0;
+ pim_addr group;
+
+ group = pim_addr_from_prefix(grp);
if (PIM_DEBUG_PIM_NHT_DETAIL)
zlog_debug("%s: Looking up: %pPA(%s), last lookup time: %lld", __func__, &src,
@@ -941,12 +1265,12 @@ bool pim_nht_lookup_ecmp(struct pim_instance *pim, struct pim_nexthop *nexthop,
pnc = pim_nexthop_cache_find(pim, src);
if (pnc) {
- if (pim_nht_pnc_has_answer(pim, pnc))
+ if (pim_nht_pnc_has_answer(pim, pnc, group))
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,
+ num_ifindex = zclient_lookup_nexthop(pim, nexthop_tab, router->multipath, src, group,
PIM_NEXTHOP_LOOKUP_MAX);
if (num_ifindex < 1) {
if (PIM_DEBUG_PIM_NHT)
@@ -1051,7 +1375,7 @@ bool pim_nht_lookup_ecmp(struct pim_instance *pim, struct pim_nexthop *nexthop,
}
bool pim_nht_lookup(struct pim_instance *pim, struct pim_nexthop *nexthop, pim_addr addr,
- int neighbor_needed)
+ pim_addr group, bool neighbor_needed)
{
struct pim_zlookup_nexthop nexthop_tab[router->multipath];
struct pim_neighbor *nbr = NULL;
@@ -1087,7 +1411,7 @@ bool pim_nht_lookup(struct pim_instance *pim, struct pim_nexthop *nexthop, pim_a
&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,
+ num_ifindex = zclient_lookup_nexthop(pim, nexthop_tab, router->multipath, addr, group,
PIM_NEXTHOP_LOOKUP_MAX);
if (num_ifindex < 1) {
if (PIM_DEBUG_PIM_NHT)
@@ -1349,36 +1673,6 @@ void pim_nexthop_update(struct vrf *vrf, struct prefix *match, struct zapi_route
pim_crp_nht_update(pim, pnc);
}
-static int pim_nht_hash_mode_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;
-
- if (listcount(pnc->rp_list))
- pim_update_rp_nh(pim, pnc);
-
- if (pnc->upstream_hash->count)
- pim_update_upstream_nh(pim, pnc);
-
- if (pnc->candrp_count)
- pim_crp_nht_update(pim, pnc);
-
- return HASHWALK_CONTINUE;
-}
-
-void pim_nht_mode_changed(struct pim_instance *pim)
-{
- struct pnc_hash_walk_data pwd;
-
- /* Update the refresh time to force new lookups if needed */
- pim_rpf_set_refresh_time(pim);
-
- /* Force update the registered RP and upstreams for all cache entries */
- pwd.pim = pim;
- hash_walk(pim->nht_hash, pim_nht_hash_mode_update_helper, &pwd);
-}
-
/* Cleanup pim->nht_hash each node data */
static void pim_nht_hash_clean(void *data)
{
@@ -1418,11 +1712,19 @@ static bool pim_nht_equal(const void *arg1, const void *arg2)
void pim_nht_init(struct pim_instance *pim)
{
char hash_name[64];
+ struct pim_lookup_mode *global_mode;
snprintf(hash_name, sizeof(hash_name), "PIM %s NHT Hash", pim->vrf->name);
pim->nht_hash = hash_create_size(256, pim_nht_hash_key, pim_nht_equal, hash_name);
- pim->rpf_mode = MCAST_NO_CONFIG;
+ pim_lookup_mode_init(&(pim->rpf_mode));
+
+ /* Add the default global mode */
+ global_mode = XCALLOC(MTYPE_PIM_LOOKUP_MODE, sizeof(*global_mode));
+ global_mode->grp_plist = NULL;
+ global_mode->src_plist = NULL;
+ global_mode->mode = MCAST_NO_CONFIG;
+ pim_lookup_mode_add(&(pim->rpf_mode), global_mode);
if (PIM_DEBUG_ZEBRA)
zlog_debug("%s: NHT hash init: %s ", __func__, hash_name);
@@ -1432,4 +1734,7 @@ void pim_nht_terminate(struct pim_instance *pim)
{
/* Traverse and cleanup nht_hash */
hash_clean_and_free(&pim->nht_hash, (void *)pim_nht_hash_clean);
+
+ pim_lookup_mode_list_free(&(pim->rpf_mode));
+ pim_lookup_mode_fini(&(pim->rpf_mode));
}
diff --git a/pimd/pim_nht.h b/pimd/pim_nht.h
index 144139f40..671fa8720 100644
--- a/pimd/pim_nht.h
+++ b/pimd/pim_nht.h
@@ -16,6 +16,15 @@
#include "pim_rp.h"
#include "pim_rpf.h"
+PREDECL_SORTLIST_NONUNIQ(pim_lookup_mode);
+
+struct pim_lookup_mode {
+ char *grp_plist;
+ char *src_plist;
+ enum pim_rpf_lookup_mode mode;
+ struct pim_lookup_mode_item list;
+};
+
/* PIM nexthop cache value structure. */
struct pim_nexthop_cache_rib {
/* IGP route's metric. */
@@ -54,8 +63,22 @@ struct pnc_hash_walk_data {
struct interface *ifp;
};
+/* Find the right lookup mode for the given group and/or source
+ * either may be ANY (although source should realistically always be provided)
+ * Find the lookup mode that has matching group and/or source prefix lists, or the global mode.
+ */
+enum pim_rpf_lookup_mode pim_get_lookup_mode(struct pim_instance *pim, pim_addr group,
+ pim_addr source);
+
+/* Change the RPF lookup config, may trigger updates to RP's and Upstreams registered for matching cache entries */
+void pim_nht_change_rpf_mode(struct pim_instance *pim, const char *group_plist,
+ const char *source_plist, enum pim_rpf_lookup_mode mode);
+
+/* Write the rpf lookup mode configuration */
+int pim_lookup_mode_write(struct pim_instance *pim, struct vty *vty);
+
/* Verify that we have nexthop information in the cache entry */
-bool pim_nht_pnc_is_valid(struct pim_instance *pim, struct pim_nexthop_cache *pnc);
+bool pim_nht_pnc_is_valid(struct pim_instance *pim, struct pim_nexthop_cache *pnc, pim_addr group);
/* Get (or add) the NH cache entry for the given address */
struct pim_nexthop_cache *pim_nht_get(struct pim_instance *pim, pim_addr addr);
@@ -109,7 +132,7 @@ bool pim_nht_lookup_ecmp(struct pim_instance *pim, struct pim_nexthop *nexthop,
* a synchronous lookup. No ECMP decision is made.
*/
bool pim_nht_lookup(struct pim_instance *pim, struct pim_nexthop *nexthop, pim_addr addr,
- int neighbor_needed);
+ pim_addr group, bool neighbor_needed);
/* Performs a pim_nht_lookup_ecmp and returns the mroute VIF index of the nexthop interface */
int pim_nht_lookup_ecmp_if_vif_index(struct pim_instance *pim, pim_addr src, struct prefix *grp);
@@ -117,9 +140,6 @@ int pim_nht_lookup_ecmp_if_vif_index(struct pim_instance *pim, pim_addr src, str
/* Tracked nexthop update from zebra */
void pim_nexthop_update(struct vrf *vrf, struct prefix *match, struct zapi_route *nhr);
-/* RPF lookup mode changed via configuration */
-void pim_nht_mode_changed(struct pim_instance *pim);
-
/* NHT init and finish funcitons */
void pim_nht_init(struct pim_instance *pim);
void pim_nht_terminate(struct pim_instance *pim);
diff --git a/pimd/pim_vty.c b/pimd/pim_vty.c
index 974cf30cf..a972a38c7 100644
--- a/pimd/pim_vty.c
+++ b/pimd/pim_vty.c
@@ -29,6 +29,7 @@
#include "pim_bfd.h"
#include "pim_bsm.h"
#include "pim_vxlan.h"
+#include "pim_nht.h"
#include "pim6_mld.h"
int pim_debug_config_write(struct vty *vty)
@@ -275,15 +276,7 @@ int pim_global_config_write_worker(struct pim_instance *pim, struct vty *vty)
}
}
- if (pim->rpf_mode != MCAST_NO_CONFIG) {
- ++writes;
- vty_out(vty, " rpf-lookup-mode %s\n",
- pim->rpf_mode == MCAST_URIB_ONLY ? "urib-only"
- : pim->rpf_mode == MCAST_MRIB_ONLY ? "mrib-only"
- : pim->rpf_mode == MCAST_MIX_MRIB_FIRST ? "mrib-then-urib"
- : pim->rpf_mode == MCAST_MIX_DISTANCE ? "lower-distance"
- : "longer-prefix");
- }
+ writes += pim_lookup_mode_write(pim, vty);
return writes;
}
diff --git a/pimd/pim_zlookup.c b/pimd/pim_zlookup.c
index febc595ad..4ffb5bac1 100644
--- a/pimd/pim_zlookup.c
+++ b/pimd/pim_zlookup.c
@@ -375,12 +375,16 @@ static int zclient_rib_lookup(struct pim_instance *pim, struct pim_zlookup_nexth
static int zclient_lookup_nexthop_once(struct pim_instance *pim,
struct pim_zlookup_nexthop nexthop_tab[], const int tab_size,
- pim_addr addr)
+ pim_addr addr, pim_addr group)
{
- if (pim->rpf_mode == MCAST_MRIB_ONLY)
+ enum pim_rpf_lookup_mode mode;
+
+ mode = pim_get_lookup_mode(pim, group, addr);
+
+ if (mode == MCAST_MRIB_ONLY)
return zclient_rib_lookup(pim, nexthop_tab, tab_size, addr, SAFI_MULTICAST);
- if (pim->rpf_mode == MCAST_URIB_ONLY)
+ if (mode == MCAST_URIB_ONLY)
return zclient_rib_lookup(pim, nexthop_tab, tab_size, addr, SAFI_UNICAST);
/* All other modes require looking up both tables and making a choice */
@@ -420,15 +424,14 @@ static int zclient_lookup_nexthop_once(struct pim_instance *pim,
/* Both tables have results, so compare them. Distance and prefix length are the same for all
* nexthops, so only compare the first in the list
*/
- if (pim->rpf_mode == MCAST_MIX_DISTANCE &&
+ if (mode == MCAST_MIX_DISTANCE &&
mrib_tab[0].protocol_distance > urib_tab[0].protocol_distance) {
if (PIM_DEBUG_PIM_NHT_DETAIL)
zlog_debug("%s: addr=%pPAs(%s), URIB has shortest distance", __func__,
&addr, pim->vrf->name);
memcpy(nexthop_tab, urib_tab, sizeof(struct pim_zlookup_nexthop) * tab_size);
return urib_num;
- } else if (pim->rpf_mode == MCAST_MIX_PFXLEN &&
- mrib_tab[0].prefix_len < urib_tab[0].prefix_len) {
+ } else if (mode == MCAST_MIX_PFXLEN && mrib_tab[0].prefix_len < urib_tab[0].prefix_len) {
if (PIM_DEBUG_PIM_NHT_DETAIL)
zlog_debug("%s: addr=%pPAs(%s), URIB has lengthest prefix length", __func__,
&addr, pim->vrf->name);
@@ -459,15 +462,13 @@ void zclient_lookup_read_pipe(struct event *thread)
return;
}
- zclient_lookup_nexthop_once(pim, nexthop_tab, 10, l);
+ zclient_lookup_nexthop_once(pim, nexthop_tab, 10, l, PIMADDR_ANY);
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 zclient_lookup_nexthop(struct pim_instance *pim, struct pim_zlookup_nexthop nexthop_tab[],
+ const int tab_size, pim_addr addr, pim_addr group, int max_lookup)
{
int lookup;
uint32_t route_metric = 0xFFFFFFFF;
@@ -480,8 +481,7 @@ int zclient_lookup_nexthop(struct pim_instance *pim,
int first_ifindex;
pim_addr nexthop_addr;
- num_ifindex = zclient_lookup_nexthop_once(pim, nexthop_tab,
- tab_size, addr);
+ num_ifindex = zclient_lookup_nexthop_once(pim, nexthop_tab, tab_size, addr, group);
if (num_ifindex < 1) {
if (PIM_DEBUG_PIM_NHT_DETAIL)
zlog_debug(
diff --git a/pimd/pim_zlookup.h b/pimd/pim_zlookup.h
index c9461eb7e..720cc4fca 100644
--- a/pimd/pim_zlookup.h
+++ b/pimd/pim_zlookup.h
@@ -27,10 +27,8 @@ struct pim_zlookup_nexthop {
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);
+int zclient_lookup_nexthop(struct pim_instance *pim, struct pim_zlookup_nexthop nexthop_tab[],
+ const int tab_size, pim_addr addr, pim_addr group, int max_lookup);
void pim_zlookup_show_ip_multicast(struct vty *vty);
diff --git a/ripd/rip_main.c b/ripd/rip_main.c
index 67469f5fe..cfe4a7e43 100644
--- a/ripd/rip_main.c
+++ b/ripd/rip_main.c
@@ -127,6 +127,7 @@ static struct frr_signal_t ripd_signals[] = {
};
static const struct frr_yang_module_info *const ripd_yang_modules[] = {
+ &frr_backend_info,
&frr_filter_info,
&frr_interface_info,
&frr_ripd_info,
diff --git a/ripngd/ripng_main.c b/ripngd/ripng_main.c
index ada9ad4e7..b3584b9c3 100644
--- a/ripngd/ripng_main.c
+++ b/ripngd/ripng_main.c
@@ -120,6 +120,7 @@ struct frr_signal_t ripng_signals[] = {
};
static const struct frr_yang_module_info *const ripngd_yang_modules[] = {
+ &frr_backend_info,
&frr_filter_info,
&frr_interface_info,
&frr_ripngd_info,
diff --git a/staticd/static_debug.c b/staticd/static_debug.c
index 618ba91d1..b30886042 100644
--- a/staticd/static_debug.c
+++ b/staticd/static_debug.c
@@ -22,6 +22,7 @@
struct debug static_dbg_events = {0, "debug static events", "Staticd events"};
struct debug static_dbg_route = {0, "debug static route", "Staticd route"};
struct debug static_dbg_bfd = {0, "debug static bfd", "Staticd bfd"};
+struct debug static_dbg_srv6 = {0, "debug static srv6", "Staticd srv6"};
/* clang-format on */
/*
@@ -37,8 +38,7 @@ struct debug static_dbg_bfd = {0, "debug static bfd", "Staticd bfd"};
* Debug general internal events
*
*/
-void static_debug_set(int vtynode, bool onoff, bool events, bool route,
- bool bfd)
+void static_debug_set(int vtynode, bool onoff, bool events, bool route, bool bfd, bool srv6)
{
uint32_t mode = DEBUG_NODE2MODE(vtynode);
@@ -50,6 +50,8 @@ void static_debug_set(int vtynode, bool onoff, bool events, bool route,
DEBUG_MODE_SET(&static_dbg_bfd, mode, onoff);
bfd_protocol_integration_set_debug(onoff);
}
+ if (srv6)
+ DEBUG_MODE_SET(&static_dbg_srv6, mode, onoff);
}
/*
@@ -61,4 +63,5 @@ void static_debug_init(void)
debug_install(&static_dbg_events);
debug_install(&static_dbg_route);
debug_install(&static_dbg_bfd);
+ debug_install(&static_dbg_srv6);
}
diff --git a/staticd/static_debug.h b/staticd/static_debug.h
index b990f7bcc..a16e398eb 100644
--- a/staticd/static_debug.h
+++ b/staticd/static_debug.h
@@ -20,6 +20,7 @@ extern "C" {
extern struct debug static_dbg_events;
extern struct debug static_dbg_route;
extern struct debug static_dbg_bfd;
+extern struct debug static_dbg_srv6;
/*
* Initialize staticd debugging.
@@ -41,8 +42,7 @@ void static_debug_init(void);
* Debug general internal events
*
*/
-void static_debug_set(int vtynode, bool onoff, bool events, bool route,
- bool bfd);
+void static_debug_set(int vtynode, bool onoff, bool events, bool route, bool bfd, bool srv6);
#ifdef __cplusplus
}
diff --git a/staticd/static_main.c b/staticd/static_main.c
index 9468a98b8..3b59ca6a7 100644
--- a/staticd/static_main.c
+++ b/staticd/static_main.c
@@ -26,6 +26,7 @@
#include "static_zebra.h"
#include "static_debug.h"
#include "static_nb.h"
+#include "static_srv6.h"
#include "mgmt_be_client.h"
@@ -76,6 +77,10 @@ static void sigint(void)
static_vrf_terminate();
static_zebra_stop();
+
+ /* clean up SRv6 data structures */
+ static_srv6_cleanup();
+
frr_fini();
exit(0);
@@ -107,6 +112,7 @@ struct frr_signal_t static_signals[] = {
};
static const struct frr_yang_module_info *const staticd_yang_modules[] = {
+ &frr_backend_info,
&frr_interface_info,
&frr_vrf_info,
&frr_routing_info,
@@ -160,6 +166,9 @@ int main(int argc, char **argv, char **envp)
static_debug_init();
static_vrf_init();
+ /* initialize SRv6 data structures */
+ static_srv6_init();
+
static_zebra_init();
static_vty_init();
diff --git a/staticd/static_nb.c b/staticd/static_nb.c
index e6aa71a77..356324126 100644
--- a/staticd/static_nb.c
+++ b/staticd/static_nb.c
@@ -225,6 +225,35 @@ const struct frr_yang_module_info frr_staticd_info = {
}
},
{
+ .xpath = "/frr-routing:routing/control-plane-protocols/control-plane-protocol/frr-staticd:staticd/segment-routing/srv6/static-sids/sid",
+ .cbs = {
+ .apply_finish = routing_control_plane_protocols_control_plane_protocol_staticd_segment_routing_srv6_local_sids_sid_apply_finish,
+ .create = routing_control_plane_protocols_control_plane_protocol_staticd_segment_routing_srv6_local_sids_sid_create,
+ .destroy = routing_control_plane_protocols_control_plane_protocol_staticd_segment_routing_srv6_local_sids_sid_destroy,
+ }
+ },
+ {
+ .xpath = "/frr-routing:routing/control-plane-protocols/control-plane-protocol/frr-staticd:staticd/segment-routing/srv6/static-sids/sid/behavior",
+ .cbs = {
+ .modify = routing_control_plane_protocols_control_plane_protocol_staticd_segment_routing_srv6_local_sids_sid_behavior_modify,
+ .destroy = routing_control_plane_protocols_control_plane_protocol_staticd_segment_routing_srv6_local_sids_sid_behavior_destroy,
+ }
+ },
+ {
+ .xpath = "/frr-routing:routing/control-plane-protocols/control-plane-protocol/frr-staticd:staticd/segment-routing/srv6/static-sids/sid/vrf-name",
+ .cbs = {
+ .modify = routing_control_plane_protocols_control_plane_protocol_staticd_segment_routing_srv6_local_sids_sid_vrf_name_modify,
+ .destroy = routing_control_plane_protocols_control_plane_protocol_staticd_segment_routing_srv6_local_sids_sid_vrf_name_destroy,
+ }
+ },
+ {
+ .xpath = "/frr-routing:routing/control-plane-protocols/control-plane-protocol/frr-staticd:staticd/segment-routing/srv6/static-sids/sid/locator-name",
+ .cbs = {
+ .modify = routing_control_plane_protocols_control_plane_protocol_staticd_segment_routing_srv6_local_sids_sid_locator_name_modify,
+ .destroy = routing_control_plane_protocols_control_plane_protocol_staticd_segment_routing_srv6_local_sids_sid_locator_name_destroy,
+ }
+ },
+ {
.xpath = NULL,
},
}
diff --git a/staticd/static_nb.h b/staticd/static_nb.h
index be75d9d38..d11bf5363 100644
--- a/staticd/static_nb.h
+++ b/staticd/static_nb.h
@@ -118,6 +118,34 @@ int routing_control_plane_protocols_control_plane_protocol_staticd_route_list_sr
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);
+int routing_control_plane_protocols_control_plane_protocol_staticd_segment_routing_create(
+ struct nb_cb_create_args *args);
+int routing_control_plane_protocols_control_plane_protocol_staticd_segment_routing_destroy(
+ struct nb_cb_destroy_args *args);
+int routing_control_plane_protocols_control_plane_protocol_staticd_segment_routing_srv6_create(
+ struct nb_cb_create_args *args);
+int routing_control_plane_protocols_control_plane_protocol_staticd_segment_routing_srv6_destroy(
+ struct nb_cb_destroy_args *args);
+int routing_control_plane_protocols_control_plane_protocol_staticd_segment_routing_srv6_local_sids_create(
+ struct nb_cb_create_args *args);
+int routing_control_plane_protocols_control_plane_protocol_staticd_segment_routing_srv6_local_sids_destroy(
+ struct nb_cb_destroy_args *args);
+int routing_control_plane_protocols_control_plane_protocol_staticd_segment_routing_srv6_local_sids_sid_create(
+ struct nb_cb_create_args *args);
+int routing_control_plane_protocols_control_plane_protocol_staticd_segment_routing_srv6_local_sids_sid_destroy(
+ struct nb_cb_destroy_args *args);
+int routing_control_plane_protocols_control_plane_protocol_staticd_segment_routing_srv6_local_sids_sid_behavior_modify(
+ struct nb_cb_modify_args *args);
+int routing_control_plane_protocols_control_plane_protocol_staticd_segment_routing_srv6_local_sids_sid_behavior_destroy(
+ struct nb_cb_destroy_args *args);
+int routing_control_plane_protocols_control_plane_protocol_staticd_segment_routing_srv6_local_sids_sid_vrf_name_modify(
+ struct nb_cb_modify_args *args);
+int routing_control_plane_protocols_control_plane_protocol_staticd_segment_routing_srv6_local_sids_sid_vrf_name_destroy(
+ struct nb_cb_destroy_args *args);
+int routing_control_plane_protocols_control_plane_protocol_staticd_segment_routing_srv6_local_sids_sid_locator_name_modify(
+ struct nb_cb_modify_args *args);
+int routing_control_plane_protocols_control_plane_protocol_staticd_segment_routing_srv6_local_sids_sid_locator_name_destroy(
+ struct nb_cb_destroy_args *args);
/* Optional 'apply_finish' callbacks. */
@@ -125,6 +153,8 @@ void routing_control_plane_protocols_control_plane_protocol_staticd_route_list_p
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);
+void routing_control_plane_protocols_control_plane_protocol_staticd_segment_routing_srv6_local_sids_sid_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(
@@ -206,6 +236,24 @@ int routing_control_plane_protocols_name_validate(
FRR_S_ROUTE_SRC_INFO_KEY_NO_DISTANCE_XPATH \
FRR_STATIC_ROUTE_NH_KEY_XPATH
+/* srv6 */
+#define FRR_STATIC_SRV6_INFO_KEY_XPATH \
+ "/frr-routing:routing/control-plane-protocols/" \
+ "control-plane-protocol[type='%s'][name='%s'][vrf='%s']/" \
+ "frr-staticd:staticd/segment-routing/srv6"
+
+/* srv6/static-sids */
+#define FRR_STATIC_SRV6_SID_KEY_XPATH \
+ FRR_STATIC_SRV6_INFO_KEY_XPATH \
+ "/static-sids/" \
+ "sid[sid='%s']"
+
+#define FRR_STATIC_SRV6_SID_BEHAVIOR_XPATH "/behavior"
+
+#define FRR_STATIC_SRV6_SID_VRF_NAME_XPATH "/vrf-name"
+
+#define FRR_STATIC_SRV6_SID_LOCATOR_NAME_XPATH "/locator-name"
+
#ifdef __cplusplus
}
#endif
diff --git a/staticd/static_nb_config.c b/staticd/static_nb_config.c
index 7de5f0474..51de05c2e 100644
--- a/staticd/static_nb_config.c
+++ b/staticd/static_nb_config.c
@@ -20,6 +20,9 @@
#include "static_nb.h"
#include "static_zebra.h"
+#include "static_srv6.h"
+#include "static_debug.h"
+
static int static_path_list_create(struct nb_cb_create_args *args)
{
@@ -1367,3 +1370,222 @@ int routing_control_plane_protocols_control_plane_protocol_staticd_route_list_sr
return NB_OK;
}
+
+/*
+ * XPath:
+ * /frr-routing:routing/control-plane-protocols/control-plane-protocol/frr-staticd:staticd/segment-routing
+ */
+int routing_control_plane_protocols_control_plane_protocol_staticd_segment_routing_create(
+ struct nb_cb_create_args *args)
+{
+ return NB_OK;
+}
+
+int routing_control_plane_protocols_control_plane_protocol_staticd_segment_routing_destroy(
+ struct nb_cb_destroy_args *args)
+{
+ return NB_OK;
+}
+
+/*
+ * XPath:
+ * /frr-routing:routing/control-plane-protocols/control-plane-protocol/frr-staticd:staticd/segment-routing/srv6
+ */
+int routing_control_plane_protocols_control_plane_protocol_staticd_segment_routing_srv6_create(
+ struct nb_cb_create_args *args)
+{
+ return NB_OK;
+}
+
+int routing_control_plane_protocols_control_plane_protocol_staticd_segment_routing_srv6_destroy(
+ struct nb_cb_destroy_args *args)
+{
+ return NB_OK;
+}
+
+/*
+ * XPath:
+ * /frr-routing:routing/control-plane-protocols/control-plane-protocol/frr-staticd:staticd/segment-routing/srv6/static-sids
+ */
+int routing_control_plane_protocols_control_plane_protocol_staticd_segment_routing_srv6_local_sids_create(
+ struct nb_cb_create_args *args)
+{
+ return NB_OK;
+}
+
+int routing_control_plane_protocols_control_plane_protocol_staticd_segment_routing_srv6_local_sids_destroy(
+ struct nb_cb_destroy_args *args)
+{
+ return NB_OK;
+}
+
+/*
+ * XPath:
+ * /frr-routing:routing/control-plane-protocols/control-plane-protocol/frr-staticd:staticd/segment-routing/srv6/locators/locator/static-sids/sid
+ */
+int routing_control_plane_protocols_control_plane_protocol_staticd_segment_routing_srv6_local_sids_sid_create(
+ struct nb_cb_create_args *args)
+{
+ struct static_srv6_sid *sid;
+ struct prefix_ipv6 sid_value;
+
+ if (args->event != NB_EV_APPLY)
+ return NB_OK;
+
+ yang_dnode_get_ipv6p(&sid_value, args->dnode, "sid");
+ sid = static_srv6_sid_alloc(&sid_value);
+ nb_running_set_entry(args->dnode, sid);
+
+ return NB_OK;
+}
+
+int routing_control_plane_protocols_control_plane_protocol_staticd_segment_routing_srv6_local_sids_sid_destroy(
+ struct nb_cb_destroy_args *args)
+{
+ struct static_srv6_sid *sid;
+
+ if (args->event != NB_EV_APPLY)
+ return NB_OK;
+
+ sid = nb_running_unset_entry(args->dnode);
+ listnode_delete(srv6_sids, sid);
+ static_srv6_sid_del(sid);
+
+ return NB_OK;
+}
+
+void routing_control_plane_protocols_control_plane_protocol_staticd_segment_routing_srv6_local_sids_sid_apply_finish(
+ struct nb_cb_apply_finish_args *args)
+{
+ struct static_srv6_sid *sid;
+ struct static_srv6_locator *locator;
+
+ sid = nb_running_get_entry(args->dnode, NULL, true);
+
+ locator = static_srv6_locator_lookup(sid->locator_name);
+ if (!locator) {
+ DEBUGD(&static_dbg_srv6,
+ "%s: Locator %s not found, trying to get locator information from zebra",
+ __func__, sid->locator_name);
+ static_zebra_srv6_manager_get_locator(sid->locator_name);
+ listnode_add(srv6_sids, sid);
+ return;
+ }
+
+ sid->locator = locator;
+
+ listnode_add(srv6_sids, sid);
+ static_zebra_request_srv6_sid(sid);
+}
+
+/*
+ * XPath:
+ * /frr-routing:routing/control-plane-protocols/control-plane-protocol/frr-staticd:staticd/segment-routing/srv6/locators/locator/static-sids/sid/behavior
+ */
+int routing_control_plane_protocols_control_plane_protocol_staticd_segment_routing_srv6_local_sids_sid_behavior_modify(
+ struct nb_cb_modify_args *args)
+{
+ struct static_srv6_sid *sid;
+
+ if (args->event != NB_EV_APPLY)
+ return NB_OK;
+
+ sid = nb_running_get_entry(args->dnode, NULL, true);
+
+ /* Release and uninstall existing SID, if any, before requesting the new one */
+ if (CHECK_FLAG(sid->flags, STATIC_FLAG_SRV6_SID_VALID)) {
+ static_zebra_release_srv6_sid(sid);
+ UNSET_FLAG(sid->flags, STATIC_FLAG_SRV6_SID_VALID);
+ }
+
+ if (CHECK_FLAG(sid->flags, STATIC_FLAG_SRV6_SID_SENT_TO_ZEBRA)) {
+ static_zebra_srv6_sid_uninstall(sid);
+ UNSET_FLAG(sid->flags, STATIC_FLAG_SRV6_SID_SENT_TO_ZEBRA);
+ }
+
+ sid->behavior = yang_dnode_get_enum(args->dnode, "../behavior");
+
+ return NB_OK;
+}
+
+int routing_control_plane_protocols_control_plane_protocol_staticd_segment_routing_srv6_local_sids_sid_behavior_destroy(
+ struct nb_cb_destroy_args *args)
+{
+ return NB_OK;
+}
+
+/*
+ * XPath:
+ * /frr-routing:routing/control-plane-protocols/control-plane-protocol/frr-staticd:staticd/segment-routing/srv6/locators/locator/static-sids/sid/vrf-name
+ */
+int routing_control_plane_protocols_control_plane_protocol_staticd_segment_routing_srv6_local_sids_sid_vrf_name_modify(
+ struct nb_cb_modify_args *args)
+{
+ struct static_srv6_sid *sid;
+ const char *vrf_name;
+
+ if (args->event != NB_EV_APPLY)
+ return NB_OK;
+
+ sid = nb_running_get_entry(args->dnode, NULL, true);
+
+ /* Release and uninstall existing SID, if any, before requesting the new one */
+ if (CHECK_FLAG(sid->flags, STATIC_FLAG_SRV6_SID_VALID)) {
+ static_zebra_release_srv6_sid(sid);
+ UNSET_FLAG(sid->flags, STATIC_FLAG_SRV6_SID_VALID);
+ }
+
+ if (CHECK_FLAG(sid->flags, STATIC_FLAG_SRV6_SID_SENT_TO_ZEBRA)) {
+ static_zebra_srv6_sid_uninstall(sid);
+ UNSET_FLAG(sid->flags, STATIC_FLAG_SRV6_SID_SENT_TO_ZEBRA);
+ }
+
+ vrf_name = yang_dnode_get_string(args->dnode, "../vrf-name");
+ snprintf(sid->attributes.vrf_name, sizeof(sid->attributes.vrf_name), "%s", vrf_name);
+
+ return NB_OK;
+}
+
+int routing_control_plane_protocols_control_plane_protocol_staticd_segment_routing_srv6_local_sids_sid_vrf_name_destroy(
+ struct nb_cb_destroy_args *args)
+{
+ return NB_OK;
+}
+
+/*
+ * XPath:
+ * /frr-routing:routing/control-plane-protocols/control-plane-protocol/frr-staticd:staticd/segment-routing/srv6/locators/locator/static-sids/sid/vrf-name
+ */
+int routing_control_plane_protocols_control_plane_protocol_staticd_segment_routing_srv6_local_sids_sid_locator_name_modify(
+ struct nb_cb_modify_args *args)
+{
+ struct static_srv6_sid *sid;
+ const char *loc_name;
+
+ if (args->event != NB_EV_APPLY)
+ return NB_OK;
+
+ sid = nb_running_get_entry(args->dnode, NULL, true);
+
+ /* Release and uninstall existing SID, if any, before requesting the new one */
+ if (CHECK_FLAG(sid->flags, STATIC_FLAG_SRV6_SID_VALID)) {
+ static_zebra_release_srv6_sid(sid);
+ UNSET_FLAG(sid->flags, STATIC_FLAG_SRV6_SID_VALID);
+ }
+
+ if (CHECK_FLAG(sid->flags, STATIC_FLAG_SRV6_SID_SENT_TO_ZEBRA)) {
+ static_zebra_srv6_sid_uninstall(sid);
+ UNSET_FLAG(sid->flags, STATIC_FLAG_SRV6_SID_SENT_TO_ZEBRA);
+ }
+
+ loc_name = yang_dnode_get_string(args->dnode, "../locator-name");
+ snprintf(sid->locator_name, sizeof(sid->locator_name), "%s", loc_name);
+
+ return NB_OK;
+}
+
+int routing_control_plane_protocols_control_plane_protocol_staticd_segment_routing_srv6_local_sids_sid_locator_name_destroy(
+ struct nb_cb_destroy_args *args)
+{
+ return NB_OK;
+}
diff --git a/staticd/static_srv6.c b/staticd/static_srv6.c
new file mode 100644
index 000000000..032bb9de9
--- /dev/null
+++ b/staticd/static_srv6.c
@@ -0,0 +1,174 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * STATICd - Segment Routing over IPv6 (SRv6) code
+ * Copyright (C) 2025 Alibaba Inc.
+ * Yuqing Zhao
+ * Lingyu Zhang
+ */
+#include <zebra.h>
+
+#include "vrf.h"
+#include "nexthop.h"
+
+#include "static_routes.h"
+#include "static_srv6.h"
+#include "static_vrf.h"
+#include "static_zebra.h"
+#include "static_debug.h"
+
+/*
+ * List of SRv6 SIDs.
+ */
+struct list *srv6_locators;
+struct list *srv6_sids;
+
+DEFINE_MTYPE_STATIC(STATIC, STATIC_SRV6_LOCATOR, "Static SRv6 locator");
+DEFINE_MTYPE_STATIC(STATIC, STATIC_SRV6_SID, "Static SRv6 SID");
+
+/*
+ * When an interface is enabled in the kernel, go through all the static SRv6 SIDs in
+ * the system that use this interface and install/remove them in the zebra RIB.
+ *
+ * ifp - The interface being enabled
+ * is_up - Whether the interface is up or down
+ */
+void static_ifp_srv6_sids_update(struct interface *ifp, bool is_up)
+{
+ struct static_srv6_sid *sid;
+ struct listnode *node;
+
+ if (!srv6_sids || !ifp)
+ return;
+
+ DEBUGD(&static_dbg_srv6, "%s: Interface %s %s. %s SIDs that depend on the interface",
+ __func__, (is_up) ? "enabled" : "disabled", (is_up) ? "Removing" : "disabled",
+ ifp->name);
+
+ /*
+ * iterate over the list of SRv6 SIDs and remove the SIDs that use this
+ * VRF from the zebra RIB
+ */
+ for (ALL_LIST_ELEMENTS_RO(srv6_sids, node, sid)) {
+ if ((strcmp(sid->attributes.vrf_name, ifp->name) == 0) ||
+ (strncmp(ifp->name, DEFAULT_SRV6_IFNAME, sizeof(ifp->name)) == 0 &&
+ (sid->behavior == SRV6_ENDPOINT_BEHAVIOR_END ||
+ sid->behavior == SRV6_ENDPOINT_BEHAVIOR_END_NEXT_CSID))) {
+ if (is_up) {
+ static_zebra_srv6_sid_install(sid);
+ SET_FLAG(sid->flags, STATIC_FLAG_SRV6_SID_SENT_TO_ZEBRA);
+ } else {
+ static_zebra_srv6_sid_uninstall(sid);
+ UNSET_FLAG(sid->flags, STATIC_FLAG_SRV6_SID_SENT_TO_ZEBRA);
+ }
+ }
+ }
+}
+
+/*
+ * Allocate an SRv6 SID object and initialize the fields common to all the
+ * behaviors (i.e., SID address and behavor).
+ */
+struct static_srv6_sid *static_srv6_sid_alloc(struct prefix_ipv6 *addr)
+{
+ struct static_srv6_sid *sid = NULL;
+
+ sid = XCALLOC(MTYPE_STATIC_SRV6_SID, sizeof(struct static_srv6_sid));
+ sid->addr = *addr;
+
+ return sid;
+}
+
+void static_srv6_sid_free(struct static_srv6_sid *sid)
+{
+ XFREE(MTYPE_STATIC_SRV6_SID, sid);
+}
+
+struct static_srv6_locator *static_srv6_locator_lookup(const char *name)
+{
+ struct static_srv6_locator *locator;
+ struct listnode *node;
+
+ for (ALL_LIST_ELEMENTS_RO(srv6_locators, node, locator))
+ if (!strncmp(name, locator->name, SRV6_LOCNAME_SIZE))
+ return locator;
+ return NULL;
+}
+
+/*
+ * Look-up an SRv6 SID in the list of SRv6 SIDs.
+ */
+struct static_srv6_sid *static_srv6_sid_lookup(struct prefix_ipv6 *sid_addr)
+{
+ struct static_srv6_sid *sid;
+ struct listnode *node;
+
+ for (ALL_LIST_ELEMENTS_RO(srv6_sids, node, sid))
+ if (memcmp(&sid->addr, sid_addr, sizeof(struct prefix_ipv6)) == 0)
+ return sid;
+
+ return NULL;
+}
+
+struct static_srv6_locator *static_srv6_locator_alloc(const char *name)
+{
+ struct static_srv6_locator *locator = NULL;
+
+ locator = XCALLOC(MTYPE_STATIC_SRV6_LOCATOR, sizeof(struct static_srv6_locator));
+ strlcpy(locator->name, name, sizeof(locator->name));
+
+ return locator;
+}
+
+void static_srv6_locator_free(struct static_srv6_locator *locator)
+{
+ XFREE(MTYPE_STATIC_SRV6_LOCATOR, locator);
+}
+
+void delete_static_srv6_locator(void *val)
+{
+ static_srv6_locator_free((struct static_srv6_locator *)val);
+}
+
+/*
+ * Remove an SRv6 SID from the zebra RIB (if it was previously installed) and
+ * release the memory previously allocated for the SID.
+ */
+void static_srv6_sid_del(struct static_srv6_sid *sid)
+{
+ if (CHECK_FLAG(sid->flags, STATIC_FLAG_SRV6_SID_VALID)) {
+ static_zebra_release_srv6_sid(sid);
+ UNSET_FLAG(sid->flags, STATIC_FLAG_SRV6_SID_VALID);
+ }
+
+ if (CHECK_FLAG(sid->flags, STATIC_FLAG_SRV6_SID_SENT_TO_ZEBRA)) {
+ static_zebra_srv6_sid_uninstall(sid);
+ UNSET_FLAG(sid->flags, STATIC_FLAG_SRV6_SID_SENT_TO_ZEBRA);
+ }
+
+ XFREE(MTYPE_STATIC_SRV6_SID, sid);
+}
+
+void delete_static_srv6_sid(void *val)
+{
+ static_srv6_sid_free((struct static_srv6_sid *)val);
+}
+
+/*
+ * Initialize SRv6 data structures.
+ */
+void static_srv6_init(void)
+{
+ srv6_locators = list_new();
+ srv6_locators->del = delete_static_srv6_locator;
+ srv6_sids = list_new();
+ srv6_sids->del = delete_static_srv6_sid;
+}
+
+/*
+ * Clean up all the SRv6 data structures.
+ */
+void static_srv6_cleanup(void)
+{
+ list_delete(&srv6_locators);
+ list_delete(&srv6_sids);
+}
diff --git a/staticd/static_srv6.h b/staticd/static_srv6.h
new file mode 100644
index 000000000..48986092a
--- /dev/null
+++ b/staticd/static_srv6.h
@@ -0,0 +1,108 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * STATICd - Segment Routing over IPv6 (SRv6) header
+ * Copyright (C) 2025 Alibaba Inc.
+ * Yuqing Zhao
+ * Lingyu Zhang
+ */
+#ifndef __STATIC_SRV6_H__
+#define __STATIC_SRV6_H__
+
+#include "vrf.h"
+#include "srv6.h"
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+/* Attributes for an SRv6 SID */
+struct static_srv6_sid_attributes {
+ /* VRF name */
+ char vrf_name[VRF_NAMSIZ];
+ char ifname[IFNAMSIZ];
+ struct in6_addr nh6;
+};
+
+/* Static SRv6 SID */
+struct static_srv6_sid {
+ /* SRv6 SID address */
+ struct prefix_ipv6 addr;
+ /* behavior bound to the SRv6 SID */
+ enum srv6_endpoint_behavior_codepoint behavior;
+ /* SID attributes */
+ struct static_srv6_sid_attributes attributes;
+
+ /* SRv6 SID flags */
+ uint8_t flags;
+/*
+ * this SRv6 SID has been allocated by SID Manager
+ * and can be installed in the zebra RIB
+ */
+#define STATIC_FLAG_SRV6_SID_VALID (1 << 0)
+/* this SRv6 SID has been installed in the zebra RIB */
+#define STATIC_FLAG_SRV6_SID_SENT_TO_ZEBRA (1 << 1)
+
+ char locator_name[SRV6_LOCNAME_SIZE];
+ struct static_srv6_locator *locator;
+};
+
+struct static_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;
+
+ uint8_t flags;
+};
+
+/* List of SRv6 SIDs. */
+extern struct list *srv6_locators;
+extern struct list *srv6_sids;
+
+/*
+ * Allocate an SRv6 SID object and initialize its fields, SID address and
+ * behavor.
+ */
+extern struct static_srv6_sid *static_srv6_sid_alloc(struct prefix_ipv6 *addr);
+extern void static_srv6_sid_free(struct static_srv6_sid *sid);
+/* Look-up an SRv6 SID in the list of SRv6 SIDs. */
+extern struct static_srv6_sid *static_srv6_sid_lookup(struct prefix_ipv6 *sid_addr);
+/*
+ * Remove an SRv6 SID from the zebra RIB (if it was previously installed) and
+ * release the memory previously allocated for the SID.
+ */
+extern void static_srv6_sid_del(struct static_srv6_sid *sid);
+
+/* Initialize SRv6 data structures. */
+extern void static_srv6_init(void);
+/* Clean up all the SRv6 data structures. */
+extern void static_srv6_cleanup(void);
+
+/*
+ * When an interface is enabled in the kernel, go through all the static SRv6 SIDs in
+ * the system that use this interface and install/remove them in the zebra RIB.
+ *
+ * ifp - The interface being enabled
+ * is_up - Whether the interface is up or down
+ */
+void static_ifp_srv6_sids_update(struct interface *ifp, bool is_up);
+
+struct static_srv6_locator *static_srv6_locator_alloc(const char *name);
+void static_srv6_locator_free(struct static_srv6_locator *locator);
+struct static_srv6_locator *static_srv6_locator_lookup(const char *name);
+
+void delete_static_srv6_sid(void *val);
+void delete_static_srv6_locator(void *val);
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* __STATIC_SRV6_H__ */
diff --git a/staticd/static_vty.c b/staticd/static_vty.c
index 07b8bc3d2..2fadc1f0d 100644
--- a/staticd/static_vty.c
+++ b/staticd/static_vty.c
@@ -27,6 +27,8 @@
#include "static_debug.h"
#include "staticd/static_vty_clippy.c"
#include "static_nb.h"
+#include "static_srv6.h"
+#include "static_zebra.h"
#define STATICD_STR "Static route daemon\n"
@@ -1201,8 +1203,167 @@ DEFPY_YANG(ipv6_route_vrf, ipv6_route_vrf_cmd,
return static_route_nb_run(vty, &args);
}
+DEFUN_NOSH (static_segment_routing, static_segment_routing_cmd,
+ "segment-routing",
+ "Segment Routing\n")
+{
+ VTY_PUSH_CONTEXT_NULL(SEGMENT_ROUTING_NODE);
+ return CMD_SUCCESS;
+}
+
+DEFUN_NOSH (static_srv6, static_srv6_cmd,
+ "srv6",
+ "Segment Routing SRv6\n")
+{
+ VTY_PUSH_CONTEXT_NULL(SRV6_NODE);
+ return CMD_SUCCESS;
+}
+
+DEFUN_YANG_NOSH (no_static_srv6, no_static_srv6_cmd,
+ "no srv6",
+ NO_STR
+ "Segment Routing SRv6\n")
+{
+ char xpath[XPATH_MAXLEN];
+
+ snprintf(xpath, sizeof(xpath), FRR_STATIC_SRV6_INFO_KEY_XPATH, "frr-staticd:staticd",
+ "staticd", VRF_DEFAULT_NAME);
+
+ nb_cli_enqueue_change(vty, xpath, NB_OP_DESTROY, NULL);
+
+ return nb_cli_apply_changes(vty, "%s", xpath);
+}
+
+DEFUN_NOSH (static_srv6_sids, static_srv6_sids_cmd,
+ "static-sids",
+ "Segment Routing SRv6 SIDs\n")
+{
+ VTY_PUSH_CONTEXT_NULL(SRV6_SIDS_NODE);
+ return CMD_SUCCESS;
+}
+
+DEFPY_YANG(srv6_sid, srv6_sid_cmd,
+ "sid X:X::X:X/M locator NAME$locator_name behavior <uN | uDT6 vrf VIEWVRFNAME | uDT4 vrf VIEWVRFNAME | uDT46 vrf VIEWVRFNAME>",
+ "Configure SRv6 SID\n"
+ "Specify SRv6 SID\n"
+ "Locator name\n"
+ "Specify Locator name\n"
+ "Specify SRv6 SID behavior\n"
+ "Apply the code to a uN SID\n"
+ "Apply the code to an uDT6 SID\n"
+ "Configure VRF name\n"
+ "Specify VRF name\n"
+ "Apply the code to an uDT4 SID\n"
+ "Configure VRF name\n"
+ "Specify VRF name\n"
+ "Apply the code to an uDT46 SID\n"
+ "Configure VRF name\n"
+ "Specify VRF name\n")
+{
+ enum srv6_endpoint_behavior_codepoint behavior = SRV6_ENDPOINT_BEHAVIOR_RESERVED;
+ int idx = 0;
+ const char *vrf_name = NULL;
+ char xpath_srv6[XPATH_MAXLEN];
+ char xpath_sid[XPATH_MAXLEN];
+ char xpath_behavior[XPATH_MAXLEN];
+ char xpath_vrf_name[XPATH_MAXLEN];
+ char xpath_locator_name[XPATH_MAXLEN];
+
+ if (argv_find(argv, argc, "uN", &idx)) {
+ behavior = SRV6_ENDPOINT_BEHAVIOR_END_NEXT_CSID;
+ } else if (argv_find(argv, argc, "uDT6", &idx)) {
+ behavior = SRV6_ENDPOINT_BEHAVIOR_END_DT6_USID;
+ vrf_name = argv[idx + 2]->arg;
+ } else if (argv_find(argv, argc, "uDT4", &idx)) {
+ behavior = SRV6_ENDPOINT_BEHAVIOR_END_DT4_USID;
+ vrf_name = argv[idx + 2]->arg;
+ } else if (argv_find(argv, argc, "uDT46", &idx)) {
+ behavior = SRV6_ENDPOINT_BEHAVIOR_END_DT46_USID;
+ vrf_name = argv[idx + 2]->arg;
+ }
+
+ snprintf(xpath_srv6, sizeof(xpath_srv6), FRR_STATIC_SRV6_INFO_KEY_XPATH,
+ "frr-staticd:staticd", "staticd", VRF_DEFAULT_NAME);
+
+ snprintf(xpath_sid, sizeof(xpath_sid), FRR_STATIC_SRV6_SID_KEY_XPATH, "frr-staticd:staticd",
+ "staticd", VRF_DEFAULT_NAME, sid_str);
+
+ strlcpy(xpath_behavior, xpath_sid, sizeof(xpath_behavior));
+ strlcat(xpath_behavior, FRR_STATIC_SRV6_SID_BEHAVIOR_XPATH, sizeof(xpath_behavior));
+
+ nb_cli_enqueue_change(vty, xpath_sid, NB_OP_CREATE, sid_str);
+
+ nb_cli_enqueue_change(vty, xpath_behavior, NB_OP_MODIFY,
+ srv6_endpoint_behavior_codepoint2str(behavior));
+
+ if (vrf_name) {
+ strlcpy(xpath_vrf_name, xpath_sid, sizeof(xpath_vrf_name));
+ strlcat(xpath_vrf_name, FRR_STATIC_SRV6_SID_VRF_NAME_XPATH, sizeof(xpath_vrf_name));
+
+ nb_cli_enqueue_change(vty, xpath_vrf_name, NB_OP_MODIFY, vrf_name);
+ }
+
+ strlcpy(xpath_locator_name, xpath_sid, sizeof(xpath_locator_name));
+ strlcat(xpath_locator_name, FRR_STATIC_SRV6_SID_LOCATOR_NAME_XPATH,
+ sizeof(xpath_locator_name));
+
+ nb_cli_enqueue_change(vty, xpath_locator_name, NB_OP_MODIFY, locator_name);
+
+ return nb_cli_apply_changes(vty, "%s", xpath_sid);
+}
+
+DEFPY_YANG(no_srv6_sid, no_srv6_sid_cmd,
+ "no sid X:X::X:X/M [locator NAME$locator_name] [behavior <uN | uDT6 vrf VIEWVRFNAME | uDT4 vrf VIEWVRFNAME | uDT46 vrf VIEWVRFNAME>]",
+ NO_STR
+ "Configure SRv6 SID\n"
+ "Specify SRv6 SID\n"
+ "Locator name\n"
+ "Specify Locator name\n"
+ "Specify SRv6 SID behavior\n"
+ "Apply the code to a uN SID\n"
+ "Apply the code to an uDT6 SID\n"
+ "Configure VRF name\n"
+ "Specify VRF name\n"
+ "Apply the code to an uDT4 SID\n"
+ "Configure VRF name\n"
+ "Specify VRF name\n"
+ "Apply the code to an uDT46 SID\n"
+ "Configure VRF name\n"
+ "Specify VRF name\n")
+{
+ char xpath[XPATH_MAXLEN + 37];
+
+ snprintf(xpath, sizeof(xpath), FRR_STATIC_SRV6_INFO_KEY_XPATH, "frr-staticd:staticd",
+ "staticd", VRF_DEFAULT_NAME);
+
+ nb_cli_enqueue_change(vty, xpath, NB_OP_DESTROY, NULL);
+
+ return nb_cli_apply_changes(vty, NULL);
+}
+
#ifdef INCLUDE_MGMTD_CMDDEFS_ONLY
+static struct cmd_node sr_node = {
+ .name = "sr",
+ .node = SEGMENT_ROUTING_NODE,
+ .parent_node = CONFIG_NODE,
+ .prompt = "%s(config-sr)# ",
+};
+
+static struct cmd_node srv6_node = {
+ .name = "srv6",
+ .node = SRV6_NODE,
+ .parent_node = SEGMENT_ROUTING_NODE,
+ .prompt = "%s(config-srv6)# ",
+};
+
+static struct cmd_node srv6_sids_node = {
+ .name = "srv6-sids",
+ .node = SRV6_SIDS_NODE,
+ .parent_node = SRV6_NODE,
+ .prompt = "%s(config-srv6-sids)# ",
+};
+
static void static_cli_show(struct vty *vty, const struct lyd_node *dnode,
bool show_defaults)
{
@@ -1545,6 +1706,100 @@ static int static_path_list_cli_cmp(const struct lyd_node *dnode1,
return (int)distance1 - (int)distance2;
}
+static void static_segment_routing_cli_show(struct vty *vty, const struct lyd_node *dnode,
+ bool show_defaults)
+{
+ vty_out(vty, "segment-routing\n");
+}
+
+static void static_segment_routing_cli_show_end(struct vty *vty, const struct lyd_node *dnode)
+{
+ vty_out(vty, "exit\n");
+ vty_out(vty, "!\n");
+}
+
+static void static_srv6_cli_show(struct vty *vty, const struct lyd_node *dnode, bool show_defaults)
+{
+ vty_out(vty, " srv6\n");
+}
+
+static void static_srv6_cli_show_end(struct vty *vty, const struct lyd_node *dnode)
+{
+ vty_out(vty, " exit\n");
+ vty_out(vty, " !\n");
+}
+
+static void static_sids_cli_show(struct vty *vty, const struct lyd_node *dnode, bool show_defaults)
+{
+ vty_out(vty, " static-sids\n");
+}
+
+static void static_sids_cli_show_end(struct vty *vty, const struct lyd_node *dnode)
+{
+ vty_out(vty, " exit\n");
+ vty_out(vty, " !\n");
+}
+
+static void srv6_sid_cli_show(struct vty *vty, const struct lyd_node *sid, bool show_defaults)
+{
+ enum srv6_endpoint_behavior_codepoint srv6_behavior;
+ struct prefix_ipv6 sid_value;
+
+ yang_dnode_get_ipv6p(&sid_value, sid, "sid");
+
+ vty_out(vty, " sid %pFX", &sid_value);
+ vty_out(vty, " locator %s", yang_dnode_get_string(sid, "locator-name"));
+
+ srv6_behavior = yang_dnode_get_enum(sid, "behavior");
+ switch (srv6_behavior) {
+ case SRV6_ENDPOINT_BEHAVIOR_END:
+ vty_out(vty, " behavior End");
+ break;
+ case SRV6_ENDPOINT_BEHAVIOR_END_X:
+ vty_out(vty, " behavior End.X");
+ break;
+ case SRV6_ENDPOINT_BEHAVIOR_END_DT6:
+ vty_out(vty, " behavior End.DT6");
+ break;
+ case SRV6_ENDPOINT_BEHAVIOR_END_DT4:
+ vty_out(vty, " behavior End.DT4");
+ break;
+ case SRV6_ENDPOINT_BEHAVIOR_END_DT46:
+ vty_out(vty, " behavior End.DT46");
+ break;
+ case SRV6_ENDPOINT_BEHAVIOR_END_NEXT_CSID:
+ vty_out(vty, " behavior uN");
+ break;
+ case SRV6_ENDPOINT_BEHAVIOR_END_X_NEXT_CSID:
+ vty_out(vty, " behavior uA");
+ break;
+ case SRV6_ENDPOINT_BEHAVIOR_END_DT6_USID:
+ vty_out(vty, " behavior uDT6");
+ break;
+ case SRV6_ENDPOINT_BEHAVIOR_END_DT4_USID:
+ vty_out(vty, " behavior uDT4");
+ break;
+ case SRV6_ENDPOINT_BEHAVIOR_END_DT46_USID:
+ vty_out(vty, " behavior uDT46");
+ break;
+ case SRV6_ENDPOINT_BEHAVIOR_RESERVED:
+ case SRV6_ENDPOINT_BEHAVIOR_OPAQUE:
+ vty_out(vty, " behavior unknown");
+ break;
+ }
+
+ if (yang_dnode_exists(sid, "vrf-name"))
+ vty_out(vty, " vrf %s", yang_dnode_get_string(sid, "vrf-name"));
+
+ vty_out(vty, "\n");
+}
+
+static void static_srv6_sid_cli_show(struct vty *vty, const struct lyd_node *dnode,
+ bool show_defaults)
+{
+ srv6_sid_cli_show(vty, dnode, show_defaults);
+}
+
const struct frr_yang_module_info frr_staticd_cli_info = {
.name = "frr-staticd",
.ignore_cfg_cbs = true,
@@ -1595,6 +1850,33 @@ const struct frr_yang_module_info frr_staticd_cli_info = {
}
},
{
+ .xpath = "/frr-routing:routing/control-plane-protocols/control-plane-protocol/frr-staticd:staticd/segment-routing",
+ .cbs = {
+ .cli_show = static_segment_routing_cli_show,
+ .cli_show_end = static_segment_routing_cli_show_end,
+ }
+ },
+ {
+ .xpath = "/frr-routing:routing/control-plane-protocols/control-plane-protocol/frr-staticd:staticd/segment-routing/srv6",
+ .cbs = {
+ .cli_show = static_srv6_cli_show,
+ .cli_show_end = static_srv6_cli_show_end,
+ }
+ },
+ {
+ .xpath = "/frr-routing:routing/control-plane-protocols/control-plane-protocol/frr-staticd:staticd/segment-routing/srv6/static-sids",
+ .cbs = {
+ .cli_show = static_sids_cli_show,
+ .cli_show_end = static_sids_cli_show_end,
+ }
+ },
+ {
+ .xpath = "/frr-routing:routing/control-plane-protocols/control-plane-protocol/frr-staticd:staticd/segment-routing/srv6/static-sids/sid",
+ .cbs = {
+ .cli_show = static_srv6_sid_cli_show,
+ }
+ },
+ {
.xpath = NULL,
},
}
@@ -1603,17 +1885,18 @@ const struct frr_yang_module_info frr_staticd_cli_info = {
#else /* ifdef INCLUDE_MGMTD_CMDDEFS_ONLY */
DEFPY_YANG(debug_staticd, debug_staticd_cmd,
- "[no] debug static [{events$events|route$route|bfd$bfd}]",
+ "[no] debug static [{events$events|route$route|bfd$bfd|srv6$srv6}]",
NO_STR DEBUG_STR STATICD_STR
"Debug events\n"
"Debug route\n"
- "Debug bfd\n")
+ "Debug bfd\n"
+ "Debug srv6\n")
{
/* If no specific category, change all */
if (strmatch(argv[argc - 1]->text, "static"))
- static_debug_set(vty->node, !no, true, true, true);
+ static_debug_set(vty->node, !no, true, true, true, true);
else
- static_debug_set(vty->node, !no, !!events, !!route, !!bfd);
+ static_debug_set(vty->node, !no, !!events, !!route, !!bfd, !!srv6);
return CMD_SUCCESS;
}
@@ -1669,6 +1952,21 @@ void static_vty_init(void)
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);
+
+ install_node(&sr_node);
+ install_node(&srv6_node);
+ install_node(&srv6_sids_node);
+ install_default(SEGMENT_ROUTING_NODE);
+ install_default(SRV6_NODE);
+ install_default(SRV6_SIDS_NODE);
+
+ install_element(CONFIG_NODE, &static_segment_routing_cmd);
+ install_element(SEGMENT_ROUTING_NODE, &static_srv6_cmd);
+ install_element(SEGMENT_ROUTING_NODE, &no_static_srv6_cmd);
+ install_element(SRV6_NODE, &static_srv6_sids_cmd);
+ install_element(SRV6_SIDS_NODE, &srv6_sid_cmd);
+ install_element(SRV6_SIDS_NODE, &no_srv6_sid_cmd);
+
#endif /* ifndef INCLUDE_MGMTD_CMDDEFS_ONLY */
#ifndef INCLUDE_MGMTD_CMDDEFS_ONLY
diff --git a/staticd/static_zebra.c b/staticd/static_zebra.c
index d76befc13..e87eaed00 100644
--- a/staticd/static_zebra.c
+++ b/staticd/static_zebra.c
@@ -30,6 +30,9 @@
#include "static_nht.h"
#include "static_vty.h"
#include "static_debug.h"
+#include "zclient.h"
+#include "static_srv6.h"
+#include "lib_errors.h"
DEFINE_MTYPE_STATIC(STATIC, STATIC_NHT_DATA, "Static Nexthop tracking data");
PREDECL_HASH(static_nht_hash);
@@ -113,6 +116,8 @@ static int static_ifp_up(struct interface *ifp)
{
static_ifindex_update(ifp, true);
+ static_ifp_srv6_sids_update(ifp, true);
+
return 0;
}
@@ -120,6 +125,8 @@ static int static_ifp_down(struct interface *ifp)
{
static_ifindex_update(ifp, false);
+ static_ifp_srv6_sids_update(ifp, false);
+
return 0;
}
@@ -530,10 +537,660 @@ extern void static_zebra_route_add(struct static_path *pn, bool install)
zclient, &api);
}
+/**
+ * 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 static_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;
+ }
+
+ DEBUGD(&static_dbg_srv6, "%s: |- %s SRv6 SID %pI6 behavior %s", __func__,
+ 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 = ZEBRA_ROUTE_STATIC;
+ 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 sid SRv6 SID
+ */
+void static_zebra_srv6_sid_install(struct static_srv6_sid *sid)
+{
+ enum seg6local_action_t action = ZEBRA_SEG6_LOCAL_ACTION_UNSPEC;
+ struct seg6local_context ctx = {};
+ struct interface *ifp = NULL;
+ struct vrf *vrf;
+
+ if (!sid)
+ return;
+
+ if (CHECK_FLAG(sid->flags, STATIC_FLAG_SRV6_SID_SENT_TO_ZEBRA))
+ return;
+
+ if (!sid->locator) {
+ zlog_err("Failed to install SID %pFX: missing locator information", &sid->addr);
+ return;
+ }
+
+ switch (sid->behavior) {
+ case SRV6_ENDPOINT_BEHAVIOR_END:
+ action = ZEBRA_SEG6_LOCAL_ACTION_END;
+ break;
+ case SRV6_ENDPOINT_BEHAVIOR_END_NEXT_CSID:
+ action = ZEBRA_SEG6_LOCAL_ACTION_END;
+ 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_DT6:
+ case SRV6_ENDPOINT_BEHAVIOR_END_DT6_USID:
+ action = ZEBRA_SEG6_LOCAL_ACTION_END_DT6;
+ vrf = vrf_lookup_by_name(sid->attributes.vrf_name);
+ if (!vrf_is_enabled(vrf)) {
+ zlog_warn("Failed to install SID %pFX: VRF %s is inactive", &sid->addr,
+ sid->attributes.vrf_name);
+ return;
+ }
+ ctx.table = vrf->data.l.table_id;
+ ifp = if_get_vrf_loopback(vrf->vrf_id);
+ if (!ifp) {
+ zlog_warn("Failed to install SID %pFX: failed to get loopback for vrf %s",
+ &sid->addr, sid->attributes.vrf_name);
+ return;
+ }
+ break;
+ case SRV6_ENDPOINT_BEHAVIOR_END_DT4:
+ case SRV6_ENDPOINT_BEHAVIOR_END_DT4_USID:
+ action = ZEBRA_SEG6_LOCAL_ACTION_END_DT4;
+ vrf = vrf_lookup_by_name(sid->attributes.vrf_name);
+ if (!vrf_is_enabled(vrf)) {
+ zlog_warn("Failed to install SID %pFX: VRF %s is inactive", &sid->addr,
+ sid->attributes.vrf_name);
+ return;
+ }
+ ctx.table = vrf->data.l.table_id;
+ ifp = if_get_vrf_loopback(vrf->vrf_id);
+ if (!ifp) {
+ zlog_warn("Failed to install SID %pFX: failed to get loopback for vrf %s",
+ &sid->addr, sid->attributes.vrf_name);
+ return;
+ }
+ break;
+ case SRV6_ENDPOINT_BEHAVIOR_END_DT46:
+ case SRV6_ENDPOINT_BEHAVIOR_END_DT46_USID:
+ action = ZEBRA_SEG6_LOCAL_ACTION_END_DT46;
+ vrf = vrf_lookup_by_name(sid->attributes.vrf_name);
+ if (!vrf_is_enabled(vrf)) {
+ zlog_warn("Failed to install SID %pFX: VRF %s is inactive", &sid->addr,
+ sid->attributes.vrf_name);
+ return;
+ }
+ ctx.table = vrf->data.l.table_id;
+ ifp = if_get_vrf_loopback(vrf->vrf_id);
+ if (!ifp) {
+ zlog_warn("Failed to install SID %pFX: failed to get loopback for vrf %s",
+ &sid->addr, sid->attributes.vrf_name);
+ return;
+ }
+ break;
+ case SRV6_ENDPOINT_BEHAVIOR_END_X:
+ case SRV6_ENDPOINT_BEHAVIOR_END_X_NEXT_CSID:
+ case SRV6_ENDPOINT_BEHAVIOR_OPAQUE:
+ case SRV6_ENDPOINT_BEHAVIOR_RESERVED:
+ zlog_warn("unsupported behavior: %u", sid->behavior);
+ break;
+ }
+
+ ctx.block_len = sid->locator->block_bits_length;
+ ctx.node_len = sid->locator->node_bits_length;
+ ctx.function_len = sid->locator->function_bits_length;
+ ctx.argument_len = sid->locator->argument_bits_length;
+
+ /* Attach the SID to the SRv6 interface */
+ if (!ifp) {
+ ifp = if_lookup_by_name(DEFAULT_SRV6_IFNAME, VRF_DEFAULT);
+ if (!ifp) {
+ zlog_warn("Failed to install SRv6 SID %pFX: %s interface not found",
+ &sid->addr, DEFAULT_SRV6_IFNAME);
+ return;
+ }
+ }
+
+ /* Send the SID to zebra */
+ static_zebra_send_localsid(ZEBRA_ROUTE_ADD, &sid->addr.prefix, sid->addr.prefixlen,
+ ifp->ifindex, action, &ctx);
+
+ SET_FLAG(sid->flags, STATIC_FLAG_SRV6_SID_SENT_TO_ZEBRA);
+}
+
+void static_zebra_srv6_sid_uninstall(struct static_srv6_sid *sid)
+{
+ enum seg6local_action_t action = ZEBRA_SEG6_LOCAL_ACTION_UNSPEC;
+ struct interface *ifp = NULL;
+ struct seg6local_context ctx = {};
+ struct vrf *vrf;
+
+ if (!sid)
+ return;
+
+ if (!CHECK_FLAG(sid->flags, STATIC_FLAG_SRV6_SID_SENT_TO_ZEBRA))
+ return;
+
+ if (!sid->locator) {
+ zlog_err("Failed to uninstall SID %pFX: missing locator information", &sid->addr);
+ return;
+ }
+
+ switch (sid->behavior) {
+ case SRV6_ENDPOINT_BEHAVIOR_END:
+ case SRV6_ENDPOINT_BEHAVIOR_END_NEXT_CSID:
+ break;
+ case SRV6_ENDPOINT_BEHAVIOR_END_DT6:
+ case SRV6_ENDPOINT_BEHAVIOR_END_DT6_USID:
+ vrf = vrf_lookup_by_name(sid->attributes.vrf_name);
+ if (!vrf_is_enabled(vrf)) {
+ zlog_warn("Failed to install SID %pFX: VRF %s is inactive", &sid->addr,
+ sid->attributes.vrf_name);
+ return;
+ }
+ ifp = if_get_vrf_loopback(vrf->vrf_id);
+ if (!ifp) {
+ zlog_warn("Failed to install SID %pFX: failed to get loopback for vrf %s",
+ &sid->addr, sid->attributes.vrf_name);
+ return;
+ }
+ break;
+ case SRV6_ENDPOINT_BEHAVIOR_END_DT4:
+ case SRV6_ENDPOINT_BEHAVIOR_END_DT4_USID:
+ vrf = vrf_lookup_by_name(sid->attributes.vrf_name);
+ if (!vrf_is_enabled(vrf)) {
+ zlog_warn("Failed to install SID %pFX: VRF %s is inactive", &sid->addr,
+ sid->attributes.vrf_name);
+ return;
+ }
+ ifp = if_get_vrf_loopback(vrf->vrf_id);
+ if (!ifp) {
+ zlog_warn("Failed to install SID %pFX: failed to get loopback for vrf %s",
+ &sid->addr, sid->attributes.vrf_name);
+ return;
+ }
+ break;
+ case SRV6_ENDPOINT_BEHAVIOR_END_DT46:
+ case SRV6_ENDPOINT_BEHAVIOR_END_DT46_USID:
+ vrf = vrf_lookup_by_name(sid->attributes.vrf_name);
+ if (!vrf_is_enabled(vrf)) {
+ zlog_warn("Failed to install SID %pFX: VRF %s is inactive", &sid->addr,
+ sid->attributes.vrf_name);
+ return;
+ }
+ ifp = if_get_vrf_loopback(vrf->vrf_id);
+ if (!ifp) {
+ zlog_warn("Failed to install SID %pFX: failed to get loopback for vrf %s",
+ &sid->addr, sid->attributes.vrf_name);
+ return;
+ }
+ break;
+ case SRV6_ENDPOINT_BEHAVIOR_END_X:
+ case SRV6_ENDPOINT_BEHAVIOR_END_X_NEXT_CSID:
+ case SRV6_ENDPOINT_BEHAVIOR_OPAQUE:
+ case SRV6_ENDPOINT_BEHAVIOR_RESERVED:
+ zlog_warn("unsupported behavior: %u", sid->behavior);
+ break;
+ }
+
+ /* The SID is attached to the SRv6 interface */
+ if (!ifp) {
+ ifp = if_lookup_by_name(DEFAULT_SRV6_IFNAME, VRF_DEFAULT);
+ if (!ifp) {
+ zlog_warn("%s interface not found: nothing to uninstall",
+ DEFAULT_SRV6_IFNAME);
+ return;
+ }
+ }
+
+ ctx.block_len = sid->locator->block_bits_length;
+ ctx.node_len = sid->locator->node_bits_length;
+ ctx.function_len = sid->locator->function_bits_length;
+ ctx.argument_len = sid->locator->argument_bits_length;
+
+ static_zebra_send_localsid(ZEBRA_ROUTE_DELETE, &sid->addr.prefix, sid->addr.prefixlen,
+ ifp->ifindex, action, &ctx);
+
+ UNSET_FLAG(sid->flags, STATIC_FLAG_SRV6_SID_SENT_TO_ZEBRA);
+}
+
+extern void static_zebra_request_srv6_sid(struct static_srv6_sid *sid)
+{
+ struct srv6_sid_ctx ctx = {};
+ int ret = 0;
+ struct vrf *vrf;
+
+ if (!sid)
+ return;
+
+ /* convert `srv6_endpoint_behavior_codepoint` to `seg6local_action_t` */
+ switch (sid->behavior) {
+ case SRV6_ENDPOINT_BEHAVIOR_END:
+ case SRV6_ENDPOINT_BEHAVIOR_END_NEXT_CSID:
+ ctx.behavior = ZEBRA_SEG6_LOCAL_ACTION_END;
+ break;
+ case SRV6_ENDPOINT_BEHAVIOR_END_DT6:
+ case SRV6_ENDPOINT_BEHAVIOR_END_DT6_USID:
+ ctx.behavior = ZEBRA_SEG6_LOCAL_ACTION_END_DT6;
+ /* process SRv6 SID attributes */
+ /* generate table ID from the VRF name, if configured */
+ if (sid->attributes.vrf_name[0] != '\0') {
+ vrf = vrf_lookup_by_name(sid->attributes.vrf_name);
+ if (!vrf_is_enabled(vrf))
+ return;
+ ctx.vrf_id = vrf->vrf_id;
+ }
+
+ break;
+ case SRV6_ENDPOINT_BEHAVIOR_END_DT4:
+ case SRV6_ENDPOINT_BEHAVIOR_END_DT4_USID:
+ ctx.behavior = ZEBRA_SEG6_LOCAL_ACTION_END_DT4;
+ /* process SRv6 SID attributes */
+ /* generate table ID from the VRF name, if configured */
+ if (sid->attributes.vrf_name[0] != '\0') {
+ vrf = vrf_lookup_by_name(sid->attributes.vrf_name);
+ if (!vrf_is_enabled(vrf))
+ return;
+ ctx.vrf_id = vrf->vrf_id;
+ }
+
+ break;
+ case SRV6_ENDPOINT_BEHAVIOR_END_DT46:
+ case SRV6_ENDPOINT_BEHAVIOR_END_DT46_USID:
+ ctx.behavior = ZEBRA_SEG6_LOCAL_ACTION_END_DT46;
+ /* process SRv6 SID attributes */
+ /* generate table ID from the VRF name, if configured */
+ if (sid->attributes.vrf_name[0] != '\0') {
+ vrf = vrf_lookup_by_name(sid->attributes.vrf_name);
+ if (!vrf_is_enabled(vrf))
+ return;
+ ctx.vrf_id = vrf->vrf_id;
+ }
+
+ break;
+ case SRV6_ENDPOINT_BEHAVIOR_END_X:
+ case SRV6_ENDPOINT_BEHAVIOR_END_X_NEXT_CSID:
+ case SRV6_ENDPOINT_BEHAVIOR_OPAQUE:
+ case SRV6_ENDPOINT_BEHAVIOR_RESERVED:
+ zlog_warn("unsupported behavior: %u", sid->behavior);
+ return;
+ }
+
+ /* Request SRv6 SID from SID Manager */
+ ret = srv6_manager_get_sid(zclient, &ctx, &sid->addr.prefix, sid->locator->name, NULL);
+ if (ret < 0)
+ zlog_warn("%s: error getting SRv6 SID!", __func__);
+}
+
+extern void static_zebra_release_srv6_sid(struct static_srv6_sid *sid)
+{
+ struct srv6_sid_ctx ctx = {};
+ struct vrf *vrf;
+ int ret = 0;
+
+ if (!sid || !CHECK_FLAG(sid->flags, STATIC_FLAG_SRV6_SID_VALID))
+ return;
+
+ /* convert `srv6_endpoint_behavior_codepoint` to `seg6local_action_t` */
+ switch (sid->behavior) {
+ case SRV6_ENDPOINT_BEHAVIOR_END:
+ case SRV6_ENDPOINT_BEHAVIOR_END_NEXT_CSID:
+ ctx.behavior = ZEBRA_SEG6_LOCAL_ACTION_END;
+ break;
+ case SRV6_ENDPOINT_BEHAVIOR_END_DT6:
+ case SRV6_ENDPOINT_BEHAVIOR_END_DT6_USID:
+ ctx.behavior = ZEBRA_SEG6_LOCAL_ACTION_END_DT6;
+ /* process SRv6 SID attributes */
+ /* generate table ID from the VRF name, if configured */
+ if (sid->attributes.vrf_name[0] != '\0') {
+ vrf = vrf_lookup_by_name(sid->attributes.vrf_name);
+ if (!vrf_is_enabled(vrf))
+ return;
+ ctx.vrf_id = vrf->vrf_id;
+ }
+
+ break;
+ case SRV6_ENDPOINT_BEHAVIOR_END_DT4:
+ case SRV6_ENDPOINT_BEHAVIOR_END_DT4_USID:
+ ctx.behavior = ZEBRA_SEG6_LOCAL_ACTION_END_DT4;
+ /* process SRv6 SID attributes */
+ /* generate table ID from the VRF name, if configured */
+ if (sid->attributes.vrf_name[0] != '\0') {
+ vrf = vrf_lookup_by_name(sid->attributes.vrf_name);
+ if (!vrf_is_enabled(vrf))
+ return;
+ ctx.vrf_id = vrf->vrf_id;
+ }
+
+ break;
+ case SRV6_ENDPOINT_BEHAVIOR_END_DT46:
+ case SRV6_ENDPOINT_BEHAVIOR_END_DT46_USID:
+ ctx.behavior = ZEBRA_SEG6_LOCAL_ACTION_END_DT46;
+ /* process SRv6 SID attributes */
+ /* generate table ID from the VRF name, if configured */
+ if (sid->attributes.vrf_name[0] != '\0') {
+ vrf = vrf_lookup_by_name(sid->attributes.vrf_name);
+ if (!vrf_is_enabled(vrf))
+ return;
+ ctx.vrf_id = vrf->vrf_id;
+ }
+
+ break;
+ case SRV6_ENDPOINT_BEHAVIOR_END_X:
+ case SRV6_ENDPOINT_BEHAVIOR_END_X_NEXT_CSID:
+ case SRV6_ENDPOINT_BEHAVIOR_OPAQUE:
+ case SRV6_ENDPOINT_BEHAVIOR_RESERVED:
+ zlog_warn("unsupported behavior: %u", sid->behavior);
+ return;
+ }
+
+ /* remove the SRv6 SID from the zebra RIB */
+ ret = srv6_manager_release_sid(zclient, &ctx);
+ if (ret == ZCLIENT_SEND_FAILURE)
+ flog_err(EC_LIB_ZAPI_SOCKET, "zclient_send_get_srv6_sid() delete failed: %s",
+ safe_strerror(errno));
+}
+
+/**
+ * Ask the SRv6 Manager (zebra) about a specific locator
+ *
+ * @param name Locator name
+ * @return 0 on success, -1 otherwise
+ */
+int static_zebra_srv6_manager_get_locator(const char *name)
+{
+ if (!name)
+ return -1;
+
+ /*
+ * Send the Get Locator request to the SRv6 Manager and return the
+ * result
+ */
+ return srv6_manager_get_locator(zclient, name);
+}
+
+static void request_srv6_sids(struct static_srv6_locator *locator)
+{
+ struct static_srv6_sid *sid;
+ struct listnode *node;
+
+ for (ALL_LIST_ELEMENTS_RO(srv6_sids, node, sid)) {
+ if (sid->locator == locator)
+ static_zebra_request_srv6_sid(sid);
+ }
+}
+
+/**
+ * Internal function to process an SRv6 locator
+ *
+ * @param locator The locator to be processed
+ */
+static int static_zebra_process_srv6_locator_internal(struct srv6_locator *locator)
+{
+ struct static_srv6_locator *loc;
+ struct listnode *node;
+ struct static_srv6_sid *sid;
+
+ if (!locator)
+ return -1;
+
+ DEBUGD(&static_dbg_srv6,
+ "%s: Received SRv6 locator %s %pFX, loc-block-len=%u, loc-node-len=%u func-len=%u, arg-len=%u",
+ __func__, locator->name, &locator->prefix, locator->block_bits_length,
+ locator->node_bits_length, locator->function_bits_length,
+ locator->argument_bits_length);
+
+ /* If we are already aware about the locator, nothing to do */
+ loc = static_srv6_locator_lookup(locator->name);
+ if (loc)
+ return 0;
+
+ loc = static_srv6_locator_alloc(locator->name);
+
+ DEBUGD(&static_dbg_srv6, "%s: SRv6 locator (locator %s, prefix %pFX) set", __func__,
+ locator->name, &locator->prefix);
+
+ /* Store the locator prefix */
+ loc->prefix = locator->prefix;
+ loc->block_bits_length = locator->block_bits_length;
+ loc->node_bits_length = locator->node_bits_length;
+ loc->function_bits_length = locator->function_bits_length;
+ loc->argument_bits_length = locator->argument_bits_length;
+ loc->flags = locator->flags;
+
+ listnode_add(srv6_locators, loc);
+
+ for (ALL_LIST_ELEMENTS_RO(srv6_sids, node, sid)) {
+ if (strncmp(sid->locator_name, loc->name, sizeof(loc->name)) == 0)
+ sid->locator = loc;
+ }
+
+ /* Request SIDs from the locator */
+ request_srv6_sids(loc);
+
+ return 0;
+}
+
+/**
+ * Callback to process an SRv6 locator received from SRv6 Manager (zebra).
+ *
+ * @result 0 on success, -1 otherwise
+ */
+static int static_zebra_process_srv6_locator_add(ZAPI_CALLBACK_ARGS)
+{
+ struct srv6_locator loc = {};
+
+ if (!srv6_locators)
+ return -1;
+
+ /* Decode the SRv6 locator */
+ if (zapi_srv6_locator_decode(zclient->ibuf, &loc) < 0)
+ return -1;
+
+ return static_zebra_process_srv6_locator_internal(&loc);
+}
+
+/**
+ * Callback to process a notification from SRv6 Manager (zebra) of an SRv6
+ * locator deleted.
+ *
+ * @result 0 on success, -1 otherwise
+ */
+static int static_zebra_process_srv6_locator_delete(ZAPI_CALLBACK_ARGS)
+{
+ struct srv6_locator loc = {};
+ struct listnode *node2, *nnode2;
+ struct static_srv6_sid *sid;
+ struct static_srv6_locator *locator;
+
+ if (!srv6_locators)
+ return -1;
+
+ /* Decode the received zebra message */
+ if (zapi_srv6_locator_decode(zclient->ibuf, &loc) < 0)
+ return -1;
+
+ DEBUGD(&static_dbg_srv6,
+ "%s: SRv6 locator deleted in zebra: name %s, prefix %pFX, block_len %u, node_len %u, func_len %u, arg_len %u",
+ __func__, loc.name, &loc.prefix, loc.block_bits_length, loc.node_bits_length,
+ loc.function_bits_length, loc.argument_bits_length);
+
+ locator = static_srv6_locator_lookup(loc.name);
+ if (!locator)
+ return 0;
+
+ DEBUGD(&static_dbg_srv6, "%s: Deleting srv6 sids from locator %s", __func__, locator->name);
+
+ /* Delete SRv6 SIDs */
+ for (ALL_LIST_ELEMENTS(srv6_sids, node2, nnode2, sid)) {
+ if (sid->locator != locator)
+ continue;
+
+
+ DEBUGD(&static_dbg_srv6, "%s: Deleting SRv6 SID (locator %s, sid %pFX)", __func__,
+ locator->name, &sid->addr);
+
+ /*
+ * Uninstall the SRv6 SID from the forwarding plane
+ * through Zebra
+ */
+ if (CHECK_FLAG(sid->flags, STATIC_FLAG_SRV6_SID_SENT_TO_ZEBRA)) {
+ static_zebra_srv6_sid_uninstall(sid);
+ UNSET_FLAG(sid->flags, STATIC_FLAG_SRV6_SID_SENT_TO_ZEBRA);
+ }
+ }
+
+ listnode_delete(srv6_locators, locator);
+ static_srv6_locator_free(locator);
+
+ return 0;
+}
+
+static int static_zebra_srv6_sid_notify(ZAPI_CALLBACK_ARGS)
+{
+ struct srv6_sid_ctx ctx;
+ struct in6_addr sid_addr;
+ enum zapi_srv6_sid_notify note;
+ uint32_t sid_func;
+ struct listnode *node;
+ char buf[256];
+ struct static_srv6_sid *sid = NULL;
+ char *loc_name;
+ bool found = false;
+
+ if (!srv6_locators)
+ return -1;
+
+ /* Decode the received notification message */
+ if (!zapi_srv6_sid_notify_decode(zclient->ibuf, &ctx, &sid_addr, &sid_func, NULL, &note,
+ &loc_name)) {
+ zlog_err("%s : error in msg decode", __func__);
+ return -1;
+ }
+
+ DEBUGD(&static_dbg_srv6,
+ "%s: received SRv6 SID notify: ctx %s sid_value %pI6 sid_func %u note %s", __func__,
+ srv6_sid_ctx2str(buf, sizeof(buf), &ctx), &sid_addr, sid_func,
+ zapi_srv6_sid_notify2str(note));
+
+ /* Handle notification */
+ switch (note) {
+ case ZAPI_SRV6_SID_ALLOCATED:
+
+ DEBUGD(&static_dbg_srv6, "%s: SRv6 SID %pI6 %s ALLOCATED", __func__, &sid_addr,
+ srv6_sid_ctx2str(buf, sizeof(buf), &ctx));
+
+ for (ALL_LIST_ELEMENTS_RO(srv6_sids, node, sid)) {
+ if (IPV6_ADDR_SAME(&sid->addr.prefix, &sid_addr)) {
+ found = true;
+ break;
+ }
+ }
+
+ if (!found || !sid) {
+ zlog_err("SRv6 SID %pI6 %s: not found", &sid_addr,
+ srv6_sid_ctx2str(buf, sizeof(buf), &ctx));
+ return 0;
+ }
+
+ SET_FLAG(sid->flags, STATIC_FLAG_SRV6_SID_VALID);
+
+ /*
+ * Install the new SRv6 End SID in the forwarding plane through
+ * Zebra
+ */
+ static_zebra_srv6_sid_install(sid);
+
+ SET_FLAG(sid->flags, STATIC_FLAG_SRV6_SID_SENT_TO_ZEBRA);
+
+ break;
+ case ZAPI_SRV6_SID_RELEASED:
+
+ DEBUGD(&static_dbg_srv6, "%s: SRv6 SID %pI6 %s: RELEASED", __func__, &sid_addr,
+ srv6_sid_ctx2str(buf, sizeof(buf), &ctx));
+
+ UNSET_FLAG(sid->flags, STATIC_FLAG_SRV6_SID_VALID);
+
+ break;
+ case ZAPI_SRV6_SID_FAIL_ALLOC:
+ zlog_err("SRv6 SID %pI6 %s: Failed to allocate", &sid_addr,
+ srv6_sid_ctx2str(buf, sizeof(buf), &ctx));
+
+ /* Error will be logged by zebra module */
+ break;
+ case ZAPI_SRV6_SID_FAIL_RELEASE:
+ zlog_err("%s: SRv6 SID %pI6 %s failure to release", __func__, &sid_addr,
+ srv6_sid_ctx2str(buf, sizeof(buf), &ctx));
+
+ /* Error will be logged by zebra module */
+ break;
+ }
+
+ return 0;
+}
+
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,
+ [ZEBRA_SRV6_LOCATOR_ADD] = static_zebra_process_srv6_locator_add,
+ [ZEBRA_SRV6_LOCATOR_DELETE] = static_zebra_process_srv6_locator_delete,
+ [ZEBRA_SRV6_SID_NOTIFY] = static_zebra_srv6_sid_notify,
};
void static_zebra_init(void)
diff --git a/staticd/static_zebra.h b/staticd/static_zebra.h
index c4f4ebdcb..2a94c6dad 100644
--- a/staticd/static_zebra.h
+++ b/staticd/static_zebra.h
@@ -7,6 +7,8 @@
#ifndef __STATIC_ZEBRA_H__
#define __STATIC_ZEBRA_H__
+#include "static_srv6.h"
+
#ifdef __cplusplus
extern "C" {
#endif
@@ -22,6 +24,14 @@ extern void static_zebra_stop(void);
extern void static_zebra_vrf_register(struct vrf *vrf);
extern void static_zebra_vrf_unregister(struct vrf *vrf);
+extern int static_zebra_srv6_manager_get_locator(const char *name);
+
+extern void static_zebra_request_srv6_sid(struct static_srv6_sid *sid);
+extern void static_zebra_release_srv6_sid(struct static_srv6_sid *sid);
+
+extern void static_zebra_srv6_sid_install(struct static_srv6_sid *sid);
+extern void static_zebra_srv6_sid_uninstall(struct static_srv6_sid *sid);
+
#ifdef __cplusplus
}
#endif
diff --git a/staticd/subdir.am b/staticd/subdir.am
index 07ebe3c02..bdbacbdd6 100644
--- a/staticd/subdir.am
+++ b/staticd/subdir.am
@@ -19,6 +19,7 @@ staticd_libstatic_a_SOURCES = \
staticd/static_vty.c \
staticd/static_nb.c \
staticd/static_nb_config.c \
+ staticd/static_srv6.c \
# end
noinst_HEADERS += \
@@ -29,6 +30,7 @@ noinst_HEADERS += \
staticd/static_vty.h \
staticd/static_vrf.h \
staticd/static_nb.h \
+ staticd/static_srv6.h \
# end
clippy_scan += \
diff --git a/tests/topotests/bfd_vrf_topo1/test_bfd_vrf_topo1.py b/tests/topotests/bfd_vrf_topo1/test_bfd_vrf_topo1.py
index f6adff61d..f00af34e3 100644
--- a/tests/topotests/bfd_vrf_topo1/test_bfd_vrf_topo1.py
+++ b/tests/topotests/bfd_vrf_topo1/test_bfd_vrf_topo1.py
@@ -84,11 +84,9 @@ def setup_module(mod):
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.use_netns_vrf()
router.load_config(
- TopoRouter.RD_ZEBRA,
- os.path.join(CWD, "{}/zebra.conf".format(rname)),
- "--vrfwnetns",
+ 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))
diff --git a/tests/topotests/bgp_bmp/bmp1import/bmp-update-loc-rib-step1.json b/tests/topotests/bgp_bmp/bmp1import/bmp-update-loc-rib-step1.json
new file mode 100644
index 000000000..3542f4e49
--- /dev/null
+++ b/tests/topotests/bgp_bmp/bmp1import/bmp-update-loc-rib-step1.json
@@ -0,0 +1,34 @@
+{
+ "loc-rib": {
+ "update": {
+ "172.31.0.77/32": {
+ "as_path": "",
+ "bgp_nexthop": "192.168.1.3",
+ "bmp_log_type": "update",
+ "ip_prefix": "172.31.0.77/32",
+ "is_filtered": false,
+ "origin": "IGP",
+ "peer_asn": 65501,
+ "peer_bgp_id": "192.168.0.1",
+ "peer_distinguisher": "444:1",
+ "peer_type": "loc-rib instance",
+ "policy": "loc-rib"
+ },
+ "2001::1125/128": {
+ "afi": 2,
+ "as_path": "",
+ "bmp_log_type": "update",
+ "ip_prefix": "2001::1125/128",
+ "is_filtered": false,
+ "nxhp_ip": "192:167::3",
+ "origin": "IGP",
+ "peer_asn": 65501,
+ "peer_bgp_id": "192.168.0.1",
+ "peer_distinguisher": "555:1",
+ "peer_type": "loc-rib instance",
+ "policy": "loc-rib",
+ "safi": 1
+ }
+ }
+ }
+}
diff --git a/tests/topotests/bgp_bmp/bmp1import/bmp-update-loc-rib-step2.json b/tests/topotests/bgp_bmp/bmp1import/bmp-update-loc-rib-step2.json
new file mode 100644
index 000000000..60066d502
--- /dev/null
+++ b/tests/topotests/bgp_bmp/bmp1import/bmp-update-loc-rib-step2.json
@@ -0,0 +1,34 @@
+{
+ "loc-rib": {
+ "update": {
+ "172.31.0.77/32": {
+ "as_path": "",
+ "bgp_nexthop": "192.168.1.3",
+ "bmp_log_type": "update",
+ "ip_prefix": "172.31.0.77/32",
+ "is_filtered": false,
+ "origin": "IGP",
+ "peer_asn": 65501,
+ "peer_bgp_id": "192.168.0.1",
+ "peer_distinguisher": "666:22",
+ "peer_type": "loc-rib instance",
+ "policy": "loc-rib"
+ },
+ "2001::1125/128": {
+ "afi": 2,
+ "as_path": "",
+ "bmp_log_type": "update",
+ "ip_prefix": "2001::1125/128",
+ "is_filtered": false,
+ "nxhp_ip": "192:167::3",
+ "origin": "IGP",
+ "peer_asn": 65501,
+ "peer_bgp_id": "192.168.0.1",
+ "peer_distinguisher": "666:22",
+ "peer_type": "loc-rib instance",
+ "policy": "loc-rib",
+ "safi": 1
+ }
+ }
+ }
+}
diff --git a/tests/topotests/bgp_bmp/bmp1import/bmp-update-post-policy-step1.json b/tests/topotests/bgp_bmp/bmp1import/bmp-update-post-policy-step1.json
new file mode 100644
index 000000000..cf71f2048
--- /dev/null
+++ b/tests/topotests/bgp_bmp/bmp1import/bmp-update-post-policy-step1.json
@@ -0,0 +1,36 @@
+{
+ "post-policy": {
+ "update": {
+ "172.31.0.77/32": {
+ "as_path": "",
+ "bgp_nexthop": "192.168.1.3",
+ "bmp_log_type": "update",
+ "ip_prefix": "172.31.0.77/32",
+ "ipv6": false,
+ "origin": "IGP",
+ "peer_asn": 65501,
+ "peer_bgp_id": "192.168.1.3",
+ "peer_distinguisher": "444:1",
+ "peer_ip": "192.168.1.3",
+ "peer_type": "route distinguisher instance",
+ "policy": "post-policy"
+ },
+ "2001::1125/128": {
+ "afi": 2,
+ "as_path": "",
+ "bmp_log_type": "update",
+ "ip_prefix": "2001::1125/128",
+ "ipv6": true,
+ "nxhp_ip": "192:167::3",
+ "origin": "IGP",
+ "peer_asn": 65501,
+ "peer_bgp_id": "192.168.1.3",
+ "peer_distinguisher": "555:1",
+ "peer_ip": "192:167::3",
+ "peer_type": "route distinguisher instance",
+ "policy": "post-policy",
+ "safi": 1
+ }
+ }
+ }
+}
diff --git a/tests/topotests/bgp_bmp/bmp1import/bmp-update-post-policy-step2.json b/tests/topotests/bgp_bmp/bmp1import/bmp-update-post-policy-step2.json
new file mode 100644
index 000000000..b555c2a37
--- /dev/null
+++ b/tests/topotests/bgp_bmp/bmp1import/bmp-update-post-policy-step2.json
@@ -0,0 +1,36 @@
+{
+ "post-policy": {
+ "update": {
+ "172.31.0.77/32": {
+ "as_path": "",
+ "bgp_nexthop": "192.168.1.3",
+ "bmp_log_type": "update",
+ "ip_prefix": "172.31.0.77/32",
+ "ipv6": false,
+ "origin": "IGP",
+ "peer_asn": 65501,
+ "peer_bgp_id": "192.168.1.3",
+ "peer_distinguisher": "666:22",
+ "peer_ip": "192.168.1.3",
+ "peer_type": "route distinguisher instance",
+ "policy": "post-policy"
+ },
+ "2001::1125/128": {
+ "afi": 2,
+ "as_path": "",
+ "bmp_log_type": "update",
+ "ip_prefix": "2001::1125/128",
+ "ipv6": true,
+ "nxhp_ip": "192:167::3",
+ "origin": "IGP",
+ "peer_asn": 65501,
+ "peer_bgp_id": "192.168.1.3",
+ "peer_distinguisher": "666:22",
+ "peer_ip": "192:167::3",
+ "peer_type": "route distinguisher instance",
+ "policy": "post-policy",
+ "safi": 1
+ }
+ }
+ }
+}
diff --git a/tests/topotests/bgp_bmp/bmp1import/bmp-update-pre-policy-step1.json b/tests/topotests/bgp_bmp/bmp1import/bmp-update-pre-policy-step1.json
new file mode 100644
index 000000000..43273cc93
--- /dev/null
+++ b/tests/topotests/bgp_bmp/bmp1import/bmp-update-pre-policy-step1.json
@@ -0,0 +1,36 @@
+{
+ "pre-policy": {
+ "update": {
+ "172.31.0.77/32": {
+ "as_path": "",
+ "bgp_nexthop": "192.168.1.3",
+ "bmp_log_type": "update",
+ "ip_prefix": "172.31.0.77/32",
+ "ipv6": false,
+ "origin": "IGP",
+ "peer_asn": 65501,
+ "peer_bgp_id": "192.168.1.3",
+ "peer_distinguisher": "444:1",
+ "peer_ip": "192.168.1.3",
+ "peer_type": "route distinguisher instance",
+ "policy": "pre-policy"
+ },
+ "2001::1125/128": {
+ "afi": 2,
+ "as_path": "",
+ "bmp_log_type": "update",
+ "ip_prefix": "2001::1125/128",
+ "ipv6": true,
+ "nxhp_ip": "192:167::3",
+ "origin": "IGP",
+ "peer_asn": 65501,
+ "peer_bgp_id": "192.168.1.3",
+ "peer_distinguisher": "555:1",
+ "peer_ip": "192:167::3",
+ "peer_type": "route distinguisher instance",
+ "policy": "pre-policy",
+ "safi": 1
+ }
+ }
+ }
+}
diff --git a/tests/topotests/bgp_bmp/bmp1import/bmp-update-pre-policy-step2.json b/tests/topotests/bgp_bmp/bmp1import/bmp-update-pre-policy-step2.json
new file mode 100644
index 000000000..20549926d
--- /dev/null
+++ b/tests/topotests/bgp_bmp/bmp1import/bmp-update-pre-policy-step2.json
@@ -0,0 +1,36 @@
+{
+ "pre-policy": {
+ "update": {
+ "172.31.0.77/32": {
+ "as_path": "",
+ "bgp_nexthop": "192.168.1.3",
+ "bmp_log_type": "update",
+ "ip_prefix": "172.31.0.77/32",
+ "ipv6": false,
+ "origin": "IGP",
+ "peer_asn": 65501,
+ "peer_bgp_id": "192.168.1.3",
+ "peer_distinguisher": "666:22",
+ "peer_ip": "192.168.1.3",
+ "peer_type": "route distinguisher instance",
+ "policy": "pre-policy"
+ },
+ "2001::1125/128": {
+ "afi": 2,
+ "as_path": "",
+ "bmp_log_type": "update",
+ "ip_prefix": "2001::1125/128",
+ "ipv6": true,
+ "nxhp_ip": "192:167::3",
+ "origin": "IGP",
+ "peer_asn": 65501,
+ "peer_bgp_id": "192.168.1.3",
+ "peer_distinguisher": "666:22",
+ "peer_ip": "192:167::3",
+ "peer_type": "route distinguisher instance",
+ "policy": "pre-policy",
+ "safi": 1
+ }
+ }
+ }
+}
diff --git a/tests/topotests/bgp_bmp/bmp1import/bmp-withdraw-loc-rib-step1.json b/tests/topotests/bgp_bmp/bmp1import/bmp-withdraw-loc-rib-step1.json
new file mode 100644
index 000000000..fcf518390
--- /dev/null
+++ b/tests/topotests/bgp_bmp/bmp1import/bmp-withdraw-loc-rib-step1.json
@@ -0,0 +1,28 @@
+{
+ "loc-rib": {
+ "withdraw": {
+ "172.31.0.77/32": {
+ "bmp_log_type": "withdraw",
+ "ip_prefix": "172.31.0.77/32",
+ "is_filtered": false,
+ "peer_asn": 65501,
+ "peer_bgp_id": "192.168.0.1",
+ "peer_distinguisher": "444:1",
+ "peer_type": "loc-rib instance",
+ "policy": "loc-rib"
+ },
+ "2001::1125/128": {
+ "afi": 2,
+ "bmp_log_type": "withdraw",
+ "ip_prefix": "2001::1125/128",
+ "is_filtered": false,
+ "peer_asn": 65501,
+ "peer_bgp_id": "192.168.0.1",
+ "peer_distinguisher": "555:1",
+ "peer_type": "loc-rib instance",
+ "policy": "loc-rib",
+ "safi": 1
+ }
+ }
+ }
+}
diff --git a/tests/topotests/bgp_bmp/bmp1import/bmp-withdraw-loc-rib-step2.json b/tests/topotests/bgp_bmp/bmp1import/bmp-withdraw-loc-rib-step2.json
new file mode 100644
index 000000000..1e5040ba6
--- /dev/null
+++ b/tests/topotests/bgp_bmp/bmp1import/bmp-withdraw-loc-rib-step2.json
@@ -0,0 +1,34 @@
+{
+ "loc-rib": {
+ "withdraw": {
+ "172.31.0.15/32": {
+ "afi": 1,
+ "bmp_log_type": "withdraw",
+ "ip_prefix": "172.31.0.15/32",
+ "is_filtered": false,
+ "label": 0,
+ "peer_asn": 65501,
+ "peer_bgp_id": "192.168.0.1",
+ "peer_distinguisher": "0:0",
+ "peer_type": "loc-rib instance",
+ "policy": "loc-rib",
+ "rd": "444:2",
+ "safi": 128
+ },
+ "2001::1111/128": {
+ "afi": 2,
+ "bmp_log_type": "withdraw",
+ "ip_prefix": "2001::1111/128",
+ "is_filtered": false,
+ "label": 0,
+ "peer_asn": 65501,
+ "peer_bgp_id": "192.168.0.1",
+ "peer_distinguisher": "0:0",
+ "peer_type": "loc-rib instance",
+ "policy": "loc-rib",
+ "rd": "555:2",
+ "safi": 128
+ }
+ }
+ }
+}
diff --git a/tests/topotests/bgp_bmp/bmp1import/bmp-withdraw-post-policy-step1.json b/tests/topotests/bgp_bmp/bmp1import/bmp-withdraw-post-policy-step1.json
new file mode 100644
index 000000000..6626e9136
--- /dev/null
+++ b/tests/topotests/bgp_bmp/bmp1import/bmp-withdraw-post-policy-step1.json
@@ -0,0 +1,30 @@
+{
+ "post-policy": {
+ "withdraw": {
+ "172.31.0.77/32": {
+ "bmp_log_type": "withdraw",
+ "ip_prefix": "172.31.0.77/32",
+ "ipv6": false,
+ "peer_asn": 65501,
+ "peer_bgp_id": "192.168.1.3",
+ "peer_distinguisher": "444:1",
+ "peer_ip": "192.168.1.3",
+ "peer_type": "route distinguisher instance",
+ "policy": "post-policy"
+ },
+ "2001::1125/128": {
+ "afi": 2,
+ "bmp_log_type": "withdraw",
+ "ip_prefix": "2001::1125/128",
+ "ipv6": true,
+ "peer_asn": 65501,
+ "peer_bgp_id": "192.168.1.3",
+ "peer_distinguisher": "555:1",
+ "peer_ip": "192:167::3",
+ "peer_type": "route distinguisher instance",
+ "policy": "post-policy",
+ "safi": 1
+ }
+ }
+ }
+}
diff --git a/tests/topotests/bgp_bmp/bmp1import/bmp-withdraw-pre-policy-step1.json b/tests/topotests/bgp_bmp/bmp1import/bmp-withdraw-pre-policy-step1.json
new file mode 100644
index 000000000..d3fb1b7ba
--- /dev/null
+++ b/tests/topotests/bgp_bmp/bmp1import/bmp-withdraw-pre-policy-step1.json
@@ -0,0 +1,30 @@
+{
+ "pre-policy": {
+ "withdraw": {
+ "172.31.0.77/32": {
+ "bmp_log_type": "withdraw",
+ "ip_prefix": "172.31.0.77/32",
+ "ipv6": false,
+ "peer_asn": 65501,
+ "peer_bgp_id": "192.168.1.3",
+ "peer_distinguisher": "444:1",
+ "peer_ip": "192.168.1.3",
+ "peer_type": "route distinguisher instance",
+ "policy": "pre-policy"
+ },
+ "2001::1125/128": {
+ "afi": 2,
+ "bmp_log_type": "withdraw",
+ "ip_prefix": "2001::1125/128",
+ "ipv6": true,
+ "peer_asn": 65501,
+ "peer_bgp_id": "192.168.1.3",
+ "peer_distinguisher": "555:1",
+ "peer_ip": "192:167::3",
+ "peer_type": "route distinguisher instance",
+ "policy": "pre-policy",
+ "safi": 1
+ }
+ }
+ }
+}
diff --git a/tests/topotests/bgp_bmp/r1import/frr.conf b/tests/topotests/bgp_bmp/r1import/frr.conf
new file mode 100644
index 000000000..bec4eb01c
--- /dev/null
+++ b/tests/topotests/bgp_bmp/r1import/frr.conf
@@ -0,0 +1,73 @@
+interface r1import-eth0
+ ip address 192.0.2.1/24
+!
+interface r1import-eth1
+ ip address 192.168.0.1/24
+ ipv6 address 192:168::1/64
+!
+interface r1import-eth2
+ ip address 192.168.1.1/24
+ ipv6 address 192:167::1/64
+!
+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
+ bmp monitor ipv4 unicast pre-policy
+ bmp monitor ipv6 unicast pre-policy
+ bmp monitor ipv4 unicast post-policy
+ bmp monitor ipv6 unicast post-policy
+ bmp monitor ipv4 unicast loc-rib
+ bmp monitor ipv6 unicast loc-rib
+ bmp import-vrf-view vrf1
+ 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 65501 vrf vrf1
+ bgp router-id 192.168.0.1
+ bgp log-neighbor-changes
+ neighbor 192.168.1.3 remote-as 65501
+ neighbor 192:167::3 remote-as 65501
+ address-family ipv4 unicast
+ neighbor 192.168.1.3 activate
+ neighbor 192.168.1.3 soft-reconfiguration inbound
+ no neighbor 192:167::3 activate
+ 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
+ neighbor 192:167::3 activate
+ neighbor 192:167::3 soft-reconfiguration inbound
+ 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/r1import/show-bgp-vrf1-ipv4-update-step1.json b/tests/topotests/bgp_bmp/r1import/show-bgp-vrf1-ipv4-update-step1.json
new file mode 100644
index 000000000..c21a586c3
--- /dev/null
+++ b/tests/topotests/bgp_bmp/r1import/show-bgp-vrf1-ipv4-update-step1.json
@@ -0,0 +1,21 @@
+{
+ "routes": {
+ "172.31.0.77/32": [
+ {
+ "bestpath": true,
+ "pathFrom": "internal",
+ "path": "",
+ "origin": "IGP",
+ "nexthops": [
+ {
+ "ip": "192.168.1.3",
+ "hostname": "r3",
+ "afi": "ipv4",
+ "used": true
+ }
+ ]
+ }
+ ]
+ }
+}
+
diff --git a/tests/topotests/bgp_bmp/r1import/show-bgp-vrf1-ipv4-withdraw-step1.json b/tests/topotests/bgp_bmp/r1import/show-bgp-vrf1-ipv4-withdraw-step1.json
new file mode 100644
index 000000000..154bef799
--- /dev/null
+++ b/tests/topotests/bgp_bmp/r1import/show-bgp-vrf1-ipv4-withdraw-step1.json
@@ -0,0 +1,6 @@
+{
+ "routes": {
+ "172.31.0.77/32": null
+ }
+}
+
diff --git a/tests/topotests/bgp_bmp/r1import/show-bgp-vrf1-ipv6-update-step1.json b/tests/topotests/bgp_bmp/r1import/show-bgp-vrf1-ipv6-update-step1.json
new file mode 100644
index 000000000..14df5ec93
--- /dev/null
+++ b/tests/topotests/bgp_bmp/r1import/show-bgp-vrf1-ipv6-update-step1.json
@@ -0,0 +1,27 @@
+{
+ "routes": {
+ "2001::1125/128": [
+ {
+ "bestpath": true,
+ "pathFrom": "internal",
+ "path": "",
+ "origin": "IGP",
+ "nexthops": [
+ {
+ "ip": "192:167::3",
+ "hostname": "r3",
+ "afi": "ipv6",
+ "scope": "global"
+ },
+ {
+ "hostname": "r3",
+ "afi": "ipv6",
+ "scope": "link-local",
+ "used": true
+ }
+ ]
+ }
+ ]
+ }
+}
+
diff --git a/tests/topotests/bgp_bmp/r1import/show-bgp-vrf1-ipv6-withdraw-step1.json b/tests/topotests/bgp_bmp/r1import/show-bgp-vrf1-ipv6-withdraw-step1.json
new file mode 100644
index 000000000..7c7a95e33
--- /dev/null
+++ b/tests/topotests/bgp_bmp/r1import/show-bgp-vrf1-ipv6-withdraw-step1.json
@@ -0,0 +1,6 @@
+{
+ "routes": {
+ "2001::1125/128": null
+ }
+}
+
diff --git a/tests/topotests/bgp_bmp/r3/frr.conf b/tests/topotests/bgp_bmp/r3/frr.conf
new file mode 100644
index 000000000..145e156b1
--- /dev/null
+++ b/tests/topotests/bgp_bmp/r3/frr.conf
@@ -0,0 +1,18 @@
+interface r3-eth0
+ ip address 192.168.1.3/24
+ ipv6 address 192:167::3/64
+!
+router bgp 65501
+ bgp router-id 192.168.1.3
+ bgp log-neighbor-changes
+ no bgp network import-check
+ neighbor 192.168.1.1 remote-as 65501
+ neighbor 192:167::1 remote-as 65501
+ address-family ipv4 unicast
+ neighbor 192.168.1.1 activate
+ no neighbor 192:167::1 activate
+ exit-address-family
+ address-family ipv6 unicast
+ neighbor 192:167::1 activate
+ exit-address-family
+exit
diff --git a/tests/topotests/bgp_bmp/test_bgp_bmp_3.py b/tests/topotests/bgp_bmp/test_bgp_bmp_3.py
new file mode 100644
index 000000000..212cf9e69
--- /dev/null
+++ b/tests/topotests/bgp_bmp/test_bgp_bmp_3.py
@@ -0,0 +1,567 @@
+#!/usr/bin/env python
+# SPDX-License-Identifier: ISC
+
+# Copyright 2024 6WIND S.A.
+#
+
+"""
+test_bgp_bmp.py_3: Test BGP BMP functionalities
+
+ +------+ +------+ +------+
+ | | | | | |
+ | BMP1 |------------| R1 |---------------| R2 |
+ | | | | | |
+ +------+ +--+---+ +------+
+ |
+ +--+---+
+ | |
+ | R3 |
+ | |
+ +------+
+
+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
+import json
+import os
+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.bgp import bgp_configure_prefixes
+from .bgpbmp import (
+ bmp_check_for_prefixes,
+ bmp_check_for_peer_message,
+ bmp_update_seq,
+ bmp_reset_seq,
+)
+from lib.topogen import Topogen, TopoRouter, get_topogen
+from lib.topolog import logger
+
+pytestmark = [pytest.mark.bgpd]
+
+PRE_POLICY = "pre-policy"
+POST_POLICY = "post-policy"
+LOC_RIB = "loc-rib"
+
+UPDATE_EXPECTED_JSON = False
+DEBUG_PCAP = False
+
+
+def build_topo(tgen):
+ tgen.add_router("r1import")
+ tgen.add_router("r2")
+ tgen.add_router("r3") # CPE behind r1
+
+ tgen.add_bmp_server("bmp1import", ip="192.0.2.10", defaultRoute="via 192.0.2.1")
+
+ switch = tgen.add_switch("s1")
+ switch.add_link(tgen.gears["r1import"])
+ switch.add_link(tgen.gears["bmp1import"])
+
+ tgen.add_link(tgen.gears["r1import"], tgen.gears["r2"], "r1import-eth1", "r2-eth0")
+ tgen.add_link(tgen.gears["r1import"], tgen.gears["r3"], "r1import-eth2", "r3-eth0")
+
+
+def setup_module(mod):
+ tgen = Topogen(build_topo, mod.__name__)
+ tgen.start_topology()
+
+ tgen.net["r1import"].cmd(
+ """
+ip link add vrf1 type vrf table 10
+ip link set vrf1 up
+ip link set r1import-eth2 master vrf1
+ """
+ )
+
+ bmp_reset_seq()
+ if DEBUG_PCAP:
+ tgen.gears["r1import"].run("rm /tmp/bmp.pcap")
+ tgen.gears["r1import"].run(
+ "tcpdump -nni r1import-eth0 -s 0 -w /tmp/bmp.pcap &", stdout=None
+ )
+
+ for rname, router in tgen.routers().items():
+ logger.info("Loading router %s" % rname)
+ router.load_frr_config(
+ os.path.join(CWD, "{}/frr.conf".format(rname)),
+ [(TopoRouter.RD_ZEBRA, None), (TopoRouter.RD_BGP, "-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="r1import")
+ assert result is True, "BGP is not converging"
+
+
+def _test_prefixes_syncro(policy, vrf=None, step=1):
+ """
+ Check that the given policy has syncronised the previously received BGP
+ updates.
+ """
+ tgen = get_topogen()
+
+ prefixes = ["172.31.0.77/32", "2001::1125/128"]
+ # check
+ test_func = partial(
+ bmp_check_for_prefixes,
+ prefixes,
+ "update",
+ policy,
+ step,
+ tgen.gears["bmp1import"],
+ os.path.join(tgen.logdir, "bmp1import"),
+ tgen.gears["r1import"],
+ f"{CWD}/bmp1import",
+ UPDATE_EXPECTED_JSON,
+ LOC_RIB,
+ )
+ success, res = topotest.run_and_expect(test_func, None, count=30, wait=1)
+ assert success, "Checking the updated prefixes has failed ! %s" % res
+
+
+def _test_prefixes(policy, vrf=None, step=0):
+ """
+ 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()
+
+ safi = "vpn" if vrf else "unicast"
+
+ prefixes = ["172.31.0.77/32", "2001::1125/128"]
+
+ for type in ("update", "withdraw"):
+ bmp_update_seq(
+ tgen.gears["bmp1import"], os.path.join(tgen.logdir, "bmp1import", "bmp.log")
+ )
+
+ bgp_configure_prefixes(
+ tgen.gears["r3"],
+ 65501,
+ "unicast",
+ prefixes,
+ vrf=None,
+ update=(type == "update"),
+ )
+
+ logger.info(f"checking for prefixes {type}")
+
+ for ipver in [4, 6]:
+ if UPDATE_EXPECTED_JSON:
+ continue
+ ref_file = "{}/r1import/show-bgp-{}-ipv{}-{}-step{}.json".format(
+ CWD, vrf, ipver, type, step
+ )
+ expected = json.loads(open(ref_file).read())
+
+ test_func = partial(
+ topotest.router_json_cmp,
+ tgen.gears["r1import"],
+ f"show bgp vrf {vrf} ipv{ipver} json",
+ expected,
+ )
+ _, res = topotest.run_and_expect(test_func, None, count=30, wait=1)
+ assertmsg = f"r1: BGP IPv{ipver} convergence failed"
+ assert res is None, assertmsg
+
+ # check
+ test_func = partial(
+ bmp_check_for_prefixes,
+ prefixes,
+ type,
+ policy,
+ step,
+ tgen.gears["bmp1import"],
+ os.path.join(tgen.logdir, "bmp1import"),
+ tgen.gears["r1import"],
+ f"{CWD}/bmp1import",
+ UPDATE_EXPECTED_JSON,
+ LOC_RIB,
+ )
+ success, res = topotest.run_and_expect(test_func, None, count=30, wait=1)
+ assert success, "Checking the updated prefixes has failed ! %s" % res
+
+
+def _test_peer_up(check_locrib=True):
+ """
+ Checking for BMP peers up messages
+ """
+
+ tgen = get_topogen()
+ if check_locrib:
+ peers = ["0.0.0.0", "192.168.1.3", "192:167::3"]
+ else:
+ peers = ["192.168.1.3", "192:167::3"]
+
+ logger.info("checking for BMP peers up messages")
+
+ test_func = partial(
+ bmp_check_for_peer_message,
+ peers,
+ "peer up",
+ tgen.gears["bmp1import"],
+ os.path.join(tgen.logdir, "bmp1import", "bmp.log"),
+ is_rd_instance=True,
+ )
+ success, _ = topotest.run_and_expect(test_func, True, count=30, wait=1)
+ assert success, "Checking the updated 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["bmp1import"].run(
+ "ls {}".format(os.path.join(tgen.logdir, "bmp1import"))
+ )
+ if "bmp.log" not in output:
+ return False
+ return True
+
+ success, _ = topotest.run_and_expect(check_for_log_file, True, count=30, wait=1)
+ assert success, "The BMP server is not logging"
+
+
+def test_bmp_peer_up_start():
+ _test_peer_up()
+
+
+def test_bmp_bgp_unicast():
+ """
+ Add/withdraw bgp unicast prefixes and check the bmp logs.
+ """
+ logger.info("*** Unicast prefixes pre-policy logging ***")
+ _test_prefixes(PRE_POLICY, vrf="vrf1", step=1)
+ logger.info("*** Unicast prefixes post-policy logging ***")
+ _test_prefixes(POST_POLICY, vrf="vrf1", step=1)
+ logger.info("*** Unicast prefixes loc-rib logging ***")
+ _test_prefixes(LOC_RIB, vrf="vrf1", step=1)
+
+
+def test_peer_down():
+ """
+ Checking for BMP peers down messages
+ """
+ tgen = get_topogen()
+
+ tgen.gears["r3"].vtysh_cmd("clear bgp *")
+
+ peers = ["192.168.1.3", "192:167::3"]
+
+ logger.info("checking for BMP peers down messages")
+
+ test_func = partial(
+ bmp_check_for_peer_message,
+ peers,
+ "peer down",
+ tgen.gears["bmp1import"],
+ os.path.join(tgen.logdir, "bmp1import", "bmp.log"),
+ )
+ success, _ = topotest.run_and_expect(test_func, True, count=30, wait=1)
+ assert success, "Checking the updated prefixes has been failed !."
+
+
+def test_reconfigure_prefixes():
+ """
+ Reconfigured BGP networks from R3. Check for BGP VRF update messages
+ """
+
+ tgen = get_topogen()
+
+ prefixes = ["172.31.0.77/32", "2001::1125/128"]
+ bgp_configure_prefixes(
+ tgen.gears["r3"],
+ 65501,
+ "unicast",
+ prefixes,
+ vrf=None,
+ update=True,
+ )
+
+ for ipver in [4, 6]:
+ ref_file = "{}/r1import/show-bgp-{}-ipv{}-{}-step{}.json".format(
+ CWD, "vrf1", ipver, "update", 1
+ )
+ expected = json.loads(open(ref_file).read())
+
+ test_func = partial(
+ topotest.router_json_cmp,
+ tgen.gears["r1import"],
+ f"show bgp vrf vrf1 ipv{ipver} json",
+ expected,
+ )
+ _, res = topotest.run_and_expect(test_func, None, count=30, wait=1)
+ assertmsg = f"r1: BGP IPv{ipver} convergence failed"
+ assert res is None, assertmsg
+
+
+def test_monitor_syncro():
+ """
+ Checking for BMP peers down messages
+ """
+ tgen = get_topogen()
+
+ tgen.gears["r1import"].vtysh_cmd(
+ """
+ configure terminal
+ router bgp 65501
+ bmp targets bmp1
+ bmp import-vrf-view vrf1
+ """
+ )
+
+ logger.info("*** Unicast prefixes pre-policy logging ***")
+ _test_prefixes_syncro(PRE_POLICY, vrf="vrf1")
+ logger.info("*** Unicast prefixes post-policy logging ***")
+ _test_prefixes_syncro(POST_POLICY, vrf="vrf1")
+ logger.info("*** Unicast prefixes loc-rib logging ***")
+ _test_prefixes_syncro(LOC_RIB, vrf="vrf1")
+
+
+def test_reconfigure_route_distinguisher_vrf1():
+ """
+ Checking for BMP peers down messages
+ """
+ tgen = get_topogen()
+
+ bmp_update_seq(
+ tgen.gears["bmp1import"], os.path.join(tgen.logdir, "bmp1import", "bmp.log")
+ )
+ peers = ["0.0.0.0"]
+
+ tgen.gears["r1import"].vtysh_cmd(
+ """
+ configure terminal
+ router bgp 65501 vrf vrf1
+ address-family ipv4 unicast
+ rd vpn export 666:22
+ exit-address-family
+ address-family ipv6 unicast
+ rd vpn export 666:22
+ """
+ )
+ logger.info(
+ "Checking for BMP peer down LOC-RIB message with route-distinguisher set to 444:1"
+ )
+ test_func = partial(
+ bmp_check_for_peer_message,
+ peers,
+ "peer down",
+ tgen.gears["bmp1import"],
+ os.path.join(tgen.logdir, "bmp1import", "bmp.log"),
+ is_rd_instance=True,
+ peer_distinguisher="444:1",
+ )
+ success, _ = topotest.run_and_expect(test_func, True, count=30, wait=1)
+ assert (
+ success
+ ), "Checking the BMP peer down LOC-RIB message with route-distinguisher set to 444:1 failed !."
+
+ logger.info(
+ "Checking for BMP peer up LOC-RIB messages with route-distinguisher set to 666:22"
+ )
+ test_func = partial(
+ bmp_check_for_peer_message,
+ peers,
+ "peer up",
+ tgen.gears["bmp1import"],
+ os.path.join(tgen.logdir, "bmp1import", "bmp.log"),
+ is_rd_instance=True,
+ peer_distinguisher="666:22",
+ )
+ success, _ = topotest.run_and_expect(test_func, True, count=30, wait=1)
+ assert (
+ success
+ ), "Checking the BMP peer up LOC-RIB message with route-distinguisher set to 666:22 failed !."
+
+ logger.info(
+ "Checking for BMP peer up messages with route-distinguisher set to 666:22"
+ )
+ peers = ["192.168.1.3", "192:167::3"]
+ test_func = partial(
+ bmp_check_for_peer_message,
+ peers,
+ "peer up",
+ tgen.gears["bmp1import"],
+ os.path.join(tgen.logdir, "bmp1import", "bmp.log"),
+ is_rd_instance=True,
+ peer_distinguisher="666:22",
+ )
+ success, _ = topotest.run_and_expect(test_func, True, count=30, wait=1)
+ assert (
+ success
+ ), "Checking the BMP peer up messages with route-distinguisher set to 666:22 failed !."
+
+ logger.info("*** Unicast prefixes pre-policy logging ***")
+ _test_prefixes_syncro(PRE_POLICY, vrf="vrf1", step=2)
+ logger.info("*** Unicast prefixes post-policy logging ***")
+ _test_prefixes_syncro(POST_POLICY, vrf="vrf1", step=2)
+ logger.info("*** Unicast prefixes loc-rib logging ***")
+ _test_prefixes_syncro(LOC_RIB, vrf="vrf1", step=2)
+
+
+def test_bgp_routerid_changed():
+ """
+ Checking for BGP loc-rib up messages with new router-id
+ """
+ tgen = get_topogen()
+
+ tgen.gears["r1import"].vtysh_cmd(
+ """
+ configure terminal
+ router bgp 65501 vrf vrf1
+ bgp router-id 192.168.1.77
+ """
+ )
+
+ peers = ["0.0.0.0"]
+
+ logger.info(
+ "checking for BMP peer down LOC-RIB message with router-id set to 192.168.0.1."
+ )
+ test_func = partial(
+ bmp_check_for_peer_message,
+ peers,
+ "peer down",
+ tgen.gears["bmp1import"],
+ os.path.join(tgen.logdir, "bmp1import", "bmp.log"),
+ is_rd_instance=True,
+ peer_bgp_id="192.168.0.1",
+ )
+ success, _ = topotest.run_and_expect(test_func, True, count=30, wait=1)
+ assert (
+ success
+ ), "Checking the BMP peer down LOC-RIB message with router-id set to 192.168.0.1 failed !."
+
+ logger.info(
+ "checking for BMP peer up LOC-RIB message with router-id set to 192.168.1.77."
+ )
+ test_func = partial(
+ bmp_check_for_peer_message,
+ peers,
+ "peer up",
+ tgen.gears["bmp1import"],
+ os.path.join(tgen.logdir, "bmp1import", "bmp.log"),
+ is_rd_instance=True,
+ peer_bgp_id="192.168.1.77",
+ )
+ success, _ = topotest.run_and_expect(test_func, True, count=30, wait=1)
+ assert (
+ success
+ ), "Checking the BMP peer up LOC-RIB message with router-id set to 192.168.1.77 failed !."
+
+
+def test_bgp_instance_flapping():
+ """
+ Checking for BGP loc-rib up messages
+ """
+ tgen = get_topogen()
+
+ # create flapping at BMP
+ # note: only peer up are handled at BMP level today
+ tgen.net["r1import"].cmd("ip link set dev vrf1 down")
+
+ peers = ["0.0.0.0"]
+
+ logger.info("checking for BMP peer down LOC-RIB message.")
+ test_func = partial(
+ bmp_check_for_peer_message,
+ peers,
+ "peer down",
+ tgen.gears["bmp1import"],
+ os.path.join(tgen.logdir, "bmp1import", "bmp.log"),
+ is_rd_instance=True,
+ )
+ success, _ = topotest.run_and_expect(test_func, True, count=30, wait=1)
+ assert success, "Checking the BMP peer down LOC-RIB message failed !."
+
+ tgen.net["r1import"].cmd("ip link set dev vrf1 up")
+
+ logger.info("checking for BMP peer up LOC-RIB message.")
+ test_func = partial(
+ bmp_check_for_peer_message,
+ peers,
+ "peer up",
+ tgen.gears["bmp1import"],
+ os.path.join(tgen.logdir, "bmp1import", "bmp.log"),
+ is_rd_instance=True,
+ )
+ success, _ = topotest.run_and_expect(test_func, True, count=30, wait=1)
+ assert success, "Checking the BMP peer up LOC-RIB message failed !."
+
+
+def test_peer_up_after_flush():
+ """
+ Checking for BMP peers down messages
+ """
+ _test_peer_up(check_locrib=False)
+
+
+def test_peer_down_locrib():
+ """
+ Checking for BMP peers down loc-rib messages
+ """
+ tgen = get_topogen()
+
+ tgen.gears["r1import"].vtysh_cmd(
+ """
+ configure terminal
+ router bgp 65501
+ bmp targets bmp1
+ no bmp import-vrf-view vrf1
+ """
+ )
+
+ peers = ["0.0.0.0"]
+
+ logger.info("checking for BMP peers down messages")
+
+ test_func = partial(
+ bmp_check_for_peer_message,
+ peers,
+ "peer down",
+ tgen.gears["bmp1import"],
+ os.path.join(tgen.logdir, "bmp1import", "bmp.log"),
+ )
+ success, _ = topotest.run_and_expect(test_func, True, count=30, wait=1)
+ assert success, "Checking the BMP peer down message has failed !."
+
+
+if __name__ == "__main__":
+ args = ["-s"] + sys.argv[1:]
+ sys.exit(pytest.main(args))
diff --git a/tests/topotests/bgp_comm_list_match/r1/bgpd.conf b/tests/topotests/bgp_comm_list_match/r1/bgpd.conf
index bac841208..d7d58d22a 100644
--- a/tests/topotests/bgp_comm_list_match/r1/bgpd.conf
+++ b/tests/topotests/bgp_comm_list_match/r1/bgpd.conf
@@ -12,6 +12,8 @@ router bgp 65001
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
+ip prefix-list p5 seq 5 permit 172.16.255.5/32
+ip prefix-list p6 seq 5 permit 172.16.255.6/32
!
route-map r2 permit 10
match ip address prefix-list p1
@@ -24,5 +26,13 @@ route-map r2 permit 30
set community 65001:10 65001:12 65001:13
exit
route-map r2 permit 40
+ match ip address prefix-list p5
+ set community 65001:13 65001:14
+exit
+route-map r2 permit 50
+ match ip address prefix-list p6
+ set community 65001:16 65001:17 65001:18 65001:19
+exit
+route-map r2 permit 60
exit
!
diff --git a/tests/topotests/bgp_comm_list_match/r1/zebra.conf b/tests/topotests/bgp_comm_list_match/r1/zebra.conf
index 4219a7ca3..1b19a4a12 100644
--- a/tests/topotests/bgp_comm_list_match/r1/zebra.conf
+++ b/tests/topotests/bgp_comm_list_match/r1/zebra.conf
@@ -4,6 +4,8 @@ interface lo
ip address 172.16.255.2/32
ip address 172.16.255.3/32
ip address 172.16.255.4/32
+ ip address 172.16.255.5/32
+ ip address 172.16.255.6/32
!
interface r1-eth0
ip address 192.168.0.1/24
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
index d0cab26e1..c14ef6b8c 100644
--- 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
@@ -133,6 +133,70 @@ def test_bgp_comm_list_match_any():
assert result is None, "Failed to filter BGP UPDATES with community-list on R3"
+def test_bgp_comm_list_limit_match():
+ tgen = get_topogen()
+
+ if tgen.routers_have_failure():
+ pytest.skip(tgen.errors)
+
+ router = tgen.gears["r3"]
+ router.vtysh_cmd(
+ """
+ configure terminal
+ route-map r1 permit 20
+ match community-limit 3
+ """
+ )
+
+ def _bgp_count():
+ output = json.loads(router.vtysh_cmd("show bgp ipv4 json"))
+ expected = {
+ "vrfName": "default",
+ "routerId": "192.168.1.3",
+ "localAS": 65003,
+ "totalRoutes": 3,
+ "totalPaths": 3,
+ }
+ return topotest.json_cmp(output, expected)
+
+ step("Check that 3 routes have been received on R3")
+ test_func = functools.partial(_bgp_count)
+ _, result = topotest.run_and_expect(test_func, None, count=60, wait=0.5)
+ assert result is None, "Failed to check that 3 routes have been received on R3"
+
+
+def test_bgp_comm_list_reset_limit_match():
+ tgen = get_topogen()
+
+ if tgen.routers_have_failure():
+ pytest.skip(tgen.errors)
+
+ router = tgen.gears["r3"]
+ router.vtysh_cmd(
+ """
+ configure terminal
+ route-map r1 permit 20
+ no match community-limit
+ """
+ )
+
+ def _bgp_count_two():
+ output = json.loads(router.vtysh_cmd("show bgp ipv4 json"))
+ expected = {
+ "vrfName": "default",
+ "routerId": "192.168.1.3",
+ "localAS": 65003,
+ "totalRoutes": 4,
+ "totalPaths": 4,
+ }
+ return topotest.json_cmp(output, expected)
+
+ step("Check that 4 routes have been received on R3")
+ test_func = functools.partial(_bgp_count_two)
+ _, result = topotest.run_and_expect(test_func, None, count=60, wait=0.5)
+ assert result is None, "Failed to check that 4 routes have been received on R3"
+
+
if __name__ == "__main__":
args = ["-s"] + sys.argv[1:]
sys.exit(pytest.main(args))
diff --git a/tests/topotests/bgp_dynamic_capability/r1/frr.conf b/tests/topotests/bgp_dynamic_capability/r1/frr.conf
index c9594626f..d91913e15 100644
--- a/tests/topotests/bgp_dynamic_capability/r1/frr.conf
+++ b/tests/topotests/bgp_dynamic_capability/r1/frr.conf
@@ -3,6 +3,7 @@
!
int r1-eth0
ip address 192.168.1.1/24
+ ipv6 address 2001:db8::1/64
!
router bgp 65001
no bgp ebgp-requires-policy
@@ -12,11 +13,19 @@ router bgp 65001
neighbor 192.168.1.2 timers 1 3
neighbor 192.168.1.2 timers connect 1
neighbor 192.168.1.2 capability dynamic
+ neighbor 2001:db8::2 remote-as external
+ neighbor 2001:db8::2 timers 1 3
+ neighbor 2001:db8::2 timers connect 1
+ neighbor 2001:db8::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
+ !
+ address-family ipv6 unicast
+ neighbor 2001:db8::2 activate
+ 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
index 3cc1f1fc3..621e9381e 100644
--- a/tests/topotests/bgp_dynamic_capability/r2/frr.conf
+++ b/tests/topotests/bgp_dynamic_capability/r2/frr.conf
@@ -7,6 +7,7 @@ int lo
!
int r2-eth0
ip address 192.168.1.2/24
+ ipv6 address 2001:db8::2/64
!
router bgp 65002
bgp graceful-restart
@@ -16,9 +17,20 @@ router bgp 65002
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 capability extended-nexthop
neighbor 192.168.1.1 addpath-rx-paths-limit 20
+ neighbor 2001:db8::1 remote-as external
+ neighbor 2001:db8::1 timers 1 3
+ neighbor 2001:db8::1 timers connect 1
+ neighbor 2001:db8::1 capability dynamic
+ neighbor 2001:db8::1 capability extended-nexthop
!
address-family ipv4 unicast
redistribute connected
exit-address-family
+ !
+ address-family ipv6 unicast
+ redistribute connected
+ neighbor 2001:db8::1 activate
+ exit-address-family
!
diff --git a/tests/topotests/bgp_dynamic_capability/test_bgp_dynamic_capability_enhe.py b/tests/topotests/bgp_dynamic_capability/test_bgp_dynamic_capability_enhe.py
new file mode 100644
index 000000000..fd467b8c3
--- /dev/null
+++ b/tests/topotests/bgp_dynamic_capability/test_bgp_dynamic_capability_enhe.py
@@ -0,0 +1,189 @@
+#!/usr/bin/env python
+# SPDX-License-Identifier: ISC
+
+# Copyright (c) 2024 by
+# Donatas Abraitis <donatas@opensourcerouting.org>
+#
+
+"""
+Test if extended nexthop capability is exchanged dynamically.
+"""
+
+import os
+import sys
+import json
+import pytest
+import functools
+import time
+
+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
+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_enhe():
+ 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 2001:db8::2 json"))
+ expected = {
+ "2001:db8::2": {
+ "bgpState": "Established",
+ "localRole": "undefined",
+ "remoteRole": "undefined",
+ "neighborCapabilities": {
+ "dynamic": "advertisedAndReceived",
+ "extendedNexthop": "received",
+ },
+ }
+ }
+ 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"
+
+ def _bgp_check_nexthop():
+ output = json.loads(r1.vtysh_cmd("show ip route 10.10.10.10/32 json"))
+ expected = {
+ "10.10.10.10/32": [
+ {
+ "protocol": "bgp",
+ "selected": True,
+ "installed": True,
+ "nexthops": [
+ {
+ "fib": True,
+ "ip": "192.168.1.2",
+ "afi": "ipv4",
+ "interfaceName": "r1-eth0",
+ "active": True,
+ },
+ {
+ "duplicate": True,
+ "ip": "192.168.1.2",
+ "afi": "ipv4",
+ "interfaceName": "r1-eth0",
+ "active": True,
+ },
+ ],
+ }
+ ]
+ }
+ return topotest.json_cmp(output, expected)
+
+ test_func = functools.partial(
+ _bgp_check_nexthop,
+ )
+ _, result = topotest.run_and_expect(test_func, None, count=30, wait=1)
+ assert result is None, "Can't see 10.10.10.10/32 with IPv4 only nexthops"
+
+ step("Enable ENHE capability")
+
+ # Clear message stats to check if we receive a notification or not after we
+ # change the role.
+ r2.vtysh_cmd("clear bgp 2001:db8::1 message-stats")
+ r1.vtysh_cmd(
+ """
+ configure terminal
+ router bgp
+ neighbor 2001:db8::2 capability extended-nexthop
+ """
+ )
+
+ def _bgp_check_if_session_not_reset():
+ output = json.loads(r2.vtysh_cmd("show bgp neighbor 2001:db8::1 json"))
+ expected = {
+ "2001:db8::1": {
+ "bgpState": "Established",
+ "neighborCapabilities": {
+ "dynamic": "advertisedAndReceived",
+ "extendedNexthop": "advertisedAndReceived",
+ "extendedNexthopFamililesByPeer": {
+ "ipv4Unicast": "recieved",
+ },
+ },
+ "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 ENHE capability"
+
+ def _bgp_check_nexthop_enhe():
+ output = json.loads(r1.vtysh_cmd("show ip route 10.10.10.10/32 json"))
+ expected = {
+ "10.10.10.10/32": [
+ {
+ "protocol": "bgp",
+ "selected": True,
+ "installed": True,
+ "nexthops": [
+ {
+ "fib": True,
+ "ip": "192.168.1.2",
+ "afi": "ipv4",
+ "interfaceName": "r1-eth0",
+ "active": True,
+ },
+ {
+ "fib": True,
+ "afi": "ipv6",
+ "interfaceName": "r1-eth0",
+ "active": True,
+ },
+ ],
+ }
+ ]
+ }
+ return topotest.json_cmp(output, expected)
+
+ test_func = functools.partial(
+ _bgp_check_nexthop_enhe,
+ )
+ _, result = topotest.run_and_expect(test_func, None, count=30, wait=1)
+ assert result is None, "Can't see 10.10.10.10/32 with IPv4 only nexthops"
+
+
+if __name__ == "__main__":
+ args = ["-s"] + sys.argv[1:]
+ sys.exit(pytest.main(args))
diff --git a/tests/topotests/bgp_evpn_rt5/test_bgp_evpn.py b/tests/topotests/bgp_evpn_rt5/test_bgp_evpn.py
index a9636a92f..c874cbed6 100644
--- a/tests/topotests/bgp_evpn_rt5/test_bgp_evpn.py
+++ b/tests/topotests/bgp_evpn_rt5/test_bgp_evpn.py
@@ -136,11 +136,9 @@ def setup_module(mod):
for rname, router in router_list.items():
if rname == "r1":
- router.load_config(TopoRouter.RD_MGMTD, None, "--vrfwnetns")
+ router.use_netns_vrf()
router.load_config(
- TopoRouter.RD_ZEBRA,
- os.path.join(CWD, "{}/zebra.conf".format(rname)),
- "--vrfwnetns",
+ TopoRouter.RD_ZEBRA, os.path.join(CWD, "{}/zebra.conf".format(rname))
)
else:
router.load_config(
diff --git a/tests/topotests/bgp_ipv6_rtadv/r1/bgp_ipv4_routes.json b/tests/topotests/bgp_ipv6_rtadv/r1/bgp_ipv4_routes.json
new file mode 100644
index 000000000..affe5cf8d
--- /dev/null
+++ b/tests/topotests/bgp_ipv6_rtadv/r1/bgp_ipv4_routes.json
@@ -0,0 +1,35 @@
+{
+ "vrfId": 0,
+ "vrfName": "default",
+ "routerId": "10.254.254.1",
+ "defaultLocPrf": 100,
+ "localAS": 101,
+ "routes": {
+ "10.254.254.2/32": [{
+ "valid": true,
+ "bestpath": true,
+ "selectionReason":"First path received",
+ "pathFrom":"external",
+ "prefix":"10.254.254.2",
+ "prefixLen":32,
+ "network":"10.254.254.2/32",
+ "metric":0,
+ "weight":0,
+ "path":"102",
+ "origin":"incomplete",
+ "nexthops":[{
+ "ip":"2001:db8:1::2",
+ "hostname":"r2",
+ "afi":"ipv6",
+ "scope":"global"
+ },{
+ "interface":"r1-eth0",
+ "hostname":"r2",
+ "afi":"ipv6",
+ "scope":"link-local",
+ "used":true
+ }]}]
+ },
+ "totalRoutes": 2,
+ "totalPaths": 2
+}
diff --git a/tests/topotests/bgp_ipv6_rtadv/r1/bgp_ipv6_routes.json b/tests/topotests/bgp_ipv6_rtadv/r1/bgp_ipv6_routes.json
new file mode 100644
index 000000000..bccfb4577
--- /dev/null
+++ b/tests/topotests/bgp_ipv6_rtadv/r1/bgp_ipv6_routes.json
@@ -0,0 +1,35 @@
+{
+ "vrfId": 0,
+ "vrfName": "default",
+ "routerId": "10.254.254.1",
+ "defaultLocPrf": 100,
+ "localAS": 101,
+ "routes": {
+ "2001:db8:1::/64": [{
+ "valid":true,
+ "bestpath":true,
+ "selectionReason":"First path received",
+ "pathFrom":"external",
+ "prefix":"2001:db8:1::",
+ "prefixLen":64,
+ "network":"2001:db8:1::/64",
+ "metric":0,
+ "weight":0,
+ "path":"102",
+ "origin":"incomplete",
+ "nexthops":[{
+ "ip":"2001:db8:1::2",
+ "hostname":"r2",
+ "afi":"ipv6",
+ "scope":"global"
+ },{
+ "interface":"r1-eth0",
+ "hostname":"r2",
+ "afi":"ipv6",
+ "scope":"link-local",
+ "used":true
+ }]}]
+ },
+ "totalRoutes": 1,
+ "totalPaths": 1
+}
diff --git a/tests/topotests/bgp_ipv6_rtadv/test_bgp_ipv6_rtadv.py b/tests/topotests/bgp_ipv6_rtadv/test_bgp_ipv6_rtadv.py
index 045ac91fc..5992c3011 100644
--- a/tests/topotests/bgp_ipv6_rtadv/test_bgp_ipv6_rtadv.py
+++ b/tests/topotests/bgp_ipv6_rtadv/test_bgp_ipv6_rtadv.py
@@ -75,6 +75,45 @@ def teardown_module(_mod):
def test_protocols_convergence():
"""
+ Assert that BGP protocol has converged
+ by checking the incoming BGP updates have been received.
+ """
+ tgen = get_topogen()
+ if tgen.routers_have_failure():
+ pytest.skip(tgen.errors)
+
+ # Check BGP IPv4 routing table.
+ logger.info("Checking BGP IPv4 routes for convergence")
+ router = tgen.gears["r1"]
+
+ json_file = "{}/{}/bgp_ipv4_routes.json".format(CWD, router.name)
+ expected = json.loads(open(json_file).read())
+ test_func = partial(
+ topotest.router_json_cmp,
+ router,
+ "show bgp ipv4 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 BGP IPv6 routing table.
+ json_file = "{}/{}/bgp_ipv6_routes.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=160, wait=0.5)
+ assertmsg = '"{}" JSON output mismatches'.format(router.name)
+ assert result is None, assertmsg
+
+
+def test_route_convergence():
+ """
Assert that all protocols have converged
statuses as they depend on it.
"""
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
index f8385401c..cf590ad01 100755
--- 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
@@ -99,7 +99,7 @@ def teardown_module(mod):
def test_ping():
tgen = get_topogen()
- check_ping("c11", "192.168.2.1", True, 10, 1)
+ check_ping("c11", "192.168.2.1", True, 120, 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)
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
index bba006185..530537646 100644
--- 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
@@ -95,7 +95,7 @@ def open_json_file(filename):
assert False, "Could not read file {}".format(filename)
-def check_rib(name, cmd, expected_file):
+def check_rib(name, cmd, expected_file, count=30, wait=0.5):
def _check(name, dest_addr, match):
logger.info("polling")
tgen = get_topogen()
@@ -107,12 +107,12 @@ def check_rib(name, cmd, expected_file):
logger.info('[+] check {} "{}" {}'.format(name, cmd, expected_file))
tgen = get_topogen()
func = functools.partial(_check, name, cmd, expected_file)
- _, result = topotest.run_and_expect(func, None, count=10, wait=0.5)
+ _, result = topotest.run_and_expect(func, None, count, wait)
assert result is None, "Failed"
def test_rib():
- check_rib("r1", "show bgp ipv4 vpn json", "r1/vpnv4_rib.json")
+ check_rib("r1", "show bgp ipv4 vpn json", "r1/vpnv4_rib.json", 120, 1)
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")
diff --git a/tests/topotests/bgp_vpnv4_import_allowas_in_between_vrf/__init__.py b/tests/topotests/bgp_vpnv4_import_allowas_in_between_vrf/__init__.py
new file mode 100644
index 000000000..e69de29bb
--- /dev/null
+++ b/tests/topotests/bgp_vpnv4_import_allowas_in_between_vrf/__init__.py
diff --git a/tests/topotests/bgp_vpnv4_import_allowas_in_between_vrf/r1/frr.conf b/tests/topotests/bgp_vpnv4_import_allowas_in_between_vrf/r1/frr.conf
new file mode 100644
index 000000000..428b1d992
--- /dev/null
+++ b/tests/topotests/bgp_vpnv4_import_allowas_in_between_vrf/r1/frr.conf
@@ -0,0 +1,35 @@
+!
+interface r1-eth0
+ ip address 192.168.179.4/24
+exit
+!
+router bgp 65001
+!
+router bgp 65001 vrf CUSTOMER-A
+ bgp router-id 192.168.179.4
+ no bgp ebgp-requires-policy
+ no bgp network import-check
+ neighbor 192.168.179.5 remote-as external
+!
+ address-family ipv4 unicast
+ neighbor 192.168.179.5 next-hop-self
+ neighbor 192.168.179.5 allowas-in 10
+ label vpn export auto
+ rd vpn export 100:1
+ rt vpn both 100:1 100:2
+ export vpn
+ import vpn
+ exit-address-family
+!
+router bgp 65001 vrf CUSTOMER-B
+ bgp router-id 192.168.0.1
+ no bgp ebgp-requires-policy
+ no bgp network import-check
+!
+ address-family ipv4 unicast
+ label vpn export auto
+ rd vpn export 100:2
+ rt vpn import 100:1 100:2
+ export vpn
+ import vpn
+ exit-address-family
diff --git a/tests/topotests/bgp_vpnv4_import_allowas_in_between_vrf/r2/frr.conf b/tests/topotests/bgp_vpnv4_import_allowas_in_between_vrf/r2/frr.conf
new file mode 100644
index 000000000..58e63d6cf
--- /dev/null
+++ b/tests/topotests/bgp_vpnv4_import_allowas_in_between_vrf/r2/frr.conf
@@ -0,0 +1,48 @@
+!
+interface lo
+ ip address 10.10.10.10/32
+!
+interface r2-eth0
+ ip address 192.168.179.5/24
+exit
+!
+interface r2-eth1
+ ip address 192.168.2.2/24
+exit
+!
+router bgp 65002
+!
+router bgp 65002 vrf CUSTOMER-A
+ bgp router-id 192.168.179.5
+ no bgp ebgp-requires-policy
+ no bgp network import-check
+ neighbor 192.168.179.4 remote-as external
+!
+ address-family ipv4 unicast
+ neighbor 192.168.179.4 next-hop-self
+ neighbor 192.168.179.4 route-map r1 out
+ label vpn export auto
+ rd vpn export 100:1
+ rt vpn import 100:1 100:2
+ export vpn
+ import vpn
+ exit-address-family
+!
+router bgp 65002 vrf CUSTOMER-B
+ bgp router-id 192.168.0.2
+ no bgp ebgp-requires-policy
+ no bgp network import-check
+!
+ address-family ipv4 unicast
+ redistribute connected
+ network 10.10.10.10/32
+ label vpn export auto
+ rd vpn export 100:2
+ rt vpn both 100:2
+ export vpn
+ import vpn
+ exit-address-family
+!
+route-map r1 permit 10
+ set as-path prepend 65001
+!
diff --git a/tests/topotests/bgp_vpnv4_import_allowas_in_between_vrf/test_bgp_vpnv4_import_allowas_in_between_vrf.py b/tests/topotests/bgp_vpnv4_import_allowas_in_between_vrf/test_bgp_vpnv4_import_allowas_in_between_vrf.py
new file mode 100644
index 000000000..23325c7a1
--- /dev/null
+++ b/tests/topotests/bgp_vpnv4_import_allowas_in_between_vrf/test_bgp_vpnv4_import_allowas_in_between_vrf.py
@@ -0,0 +1,142 @@
+#!/usr/bin/env python
+# SPDX-License-Identifier: ISC
+
+#
+# Copyright (c) 2024 by
+# Donatas Abraitis <donatas@opensourcerouting.org>
+#
+
+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, get_topogen
+
+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"])
+
+ 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):
+ tgen = Topogen(build_topo, mod.__name__)
+ tgen.start_topology()
+
+ r1 = tgen.gears["r1"]
+ r2 = tgen.gears["r2"]
+
+ r1.run("ip link add CUSTOMER-A type vrf table 1001")
+ r1.run("ip link set up dev CUSTOMER-A")
+ r1.run("ip link set r1-eth0 master CUSTOMER-A")
+
+ r1.run("ip link add CUSTOMER-B type vrf table 1002")
+ r1.run("ip link set up dev CUSTOMER-B")
+ r1.run("ip link set r1-eth1 master CUSTOMER-B")
+
+ r2.run("ip link add CUSTOMER-A type vrf table 1001")
+ r2.run("ip link set up dev CUSTOMER-A")
+ r2.run("ip link set r2-eth0 master CUSTOMER-A")
+
+ r2.run("ip link add CUSTOMER-B type vrf table 1002")
+ r2.run("ip link set up dev CUSTOMER-B")
+ r2.run("ip link set r2-eth1 master CUSTOMER-B")
+
+ 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_vpnv4_import_allowas_in_between_vrf():
+ 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 vrf CUSTOMER-A ipv4 unicast 10.10.10.10/32 json")
+ )
+ expected = {
+ "paths": [
+ {
+ "aspath": {
+ "string": "65002 65001",
+ },
+ "valid": True,
+ }
+ ]
+ }
+ 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 to see 10.10.10.10/32 with a valid next-hop"
+
+ def _vrf_route_imported_to_vrf():
+ output = json.loads(
+ r1.vtysh_cmd("show ip route vrf CUSTOMER-B 10.10.10.10/32 json")
+ )
+ expected = {
+ "10.10.10.10/32": [
+ {
+ "protocol": "bgp",
+ "vrfName": "CUSTOMER-B",
+ "selected": True,
+ "installed": True,
+ "table": 1002,
+ "internalNextHopNum": 1,
+ "internalNextHopActiveNum": 1,
+ "nexthops": [
+ {
+ "fib": True,
+ "ip": "192.168.179.5",
+ "afi": "ipv4",
+ "interfaceName": "r1-eth0",
+ "vrf": "CUSTOMER-A",
+ "active": True,
+ }
+ ],
+ }
+ ]
+ }
+ return topotest.json_cmp(output, expected)
+
+ test_func = functools.partial(_vrf_route_imported_to_vrf)
+ _, result = topotest.run_and_expect(test_func, None, count=30, wait=1)
+ assert (
+ result is None
+ ), "Failed to see 10.10.10.10/32 to be imported into CUSTOMER-B VRF (Zebra)"
+
+
+if __name__ == "__main__":
+ args = ["-s"] + sys.argv[1:]
+ sys.exit(pytest.main(args))
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
index 028bc3535..4aa440413 100644
--- a/tests/topotests/bgp_vrf_netns/test_bgp_vrf_netns_topo.py
+++ b/tests/topotests/bgp_vrf_netns/test_bgp_vrf_netns_topo.py
@@ -94,11 +94,9 @@ def setup_module(module):
router.net.set_intf_netns("r1-eth0", ns, up=True)
# run daemons
- router.load_config(TopoRouter.RD_MGMTD, None, "--vrfwnetns")
+ router.use_netns_vrf()
router.load_config(
- TopoRouter.RD_ZEBRA,
- os.path.join(CWD, "{}/zebra.conf".format("r1")),
- "--vrfwnetns",
+ 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"))
diff --git a/tests/topotests/conftest.py b/tests/topotests/conftest.py
index dafd19c28..117ff74e4 100755
--- a/tests/topotests/conftest.py
+++ b/tests/topotests/conftest.py
@@ -31,7 +31,7 @@ from lib import topolog, topotest
try:
# Used by munet native tests
- from munet.testing.fixtures import unet # pylint: disable=all # noqa
+ from munet.testing.fixtures import stepf, unet # pylint: disable=all # noqa
@pytest.fixture(scope="module")
def rundir_module(pytestconfig):
diff --git a/tests/topotests/grpc_basic/test_basic_grpc.py b/tests/topotests/grpc_basic/test_basic_grpc.py
index 5ff2894fd..e82436c39 100644
--- a/tests/topotests/grpc_basic/test_basic_grpc.py
+++ b/tests/topotests/grpc_basic/test_basic_grpc.py
@@ -108,7 +108,7 @@ def test_capabilities(tgen):
logging.debug("grpc output: %s", output)
modules = sorted(re.findall('name: "([^"]+)"', output))
- expected = ["frr-interface", "frr-routing", "frr-staticd", "frr-vrf"]
+ expected = ["frr-backend", "frr-interface", "frr-routing", "frr-staticd", "frr-vrf"]
assert modules == expected
encodings = sorted(re.findall("supported_encodings: (.*)", output))
@@ -145,15 +145,10 @@ def test_get_config(tgen):
"ip": "192.168.1.1",
"prefix-length": 24
}
- ],
- "evpn-mh": {},
- "ipv6-router-advertisements": {}
+ ]
}
}
]
- },
- "frr-zebra:zebra": {
- "import-kernel-table": {}
}
} """
)
diff --git a/tests/topotests/ldp_snmp/test_ldp_snmp_topo1.py b/tests/topotests/ldp_snmp/test_ldp_snmp_topo1.py
index ea404beae..db2657e52 100644
--- a/tests/topotests/ldp_snmp/test_ldp_snmp_topo1.py
+++ b/tests/topotests/ldp_snmp/test_ldp_snmp_topo1.py
@@ -107,6 +107,11 @@ def build_topo(tgen):
def setup_module(mod):
"Sets up the pytest environment"
+
+ if not os.path.isfile("/usr/sbin/snmpd"):
+ error_msg = "SNMP not installed - skipping"
+ pytest.skip(error_msg)
+
tgen = Topogen(build_topo, mod.__name__)
tgen.start_topology()
diff --git a/tests/topotests/lib/topogen.py b/tests/topotests/lib/topogen.py
index 0a9a84a4b..8b1bd6e1a 100644
--- a/tests/topotests/lib/topogen.py
+++ b/tests/topotests/lib/topogen.py
@@ -819,6 +819,12 @@ class TopoRouter(TopoGear):
gear += " TopoRouter<>"
return gear
+ def use_netns_vrf(self):
+ """
+ Use netns as VRF backend.
+ """
+ self.net.useNetnsVRF()
+
def check_capability(self, daemon, param):
"""
Checks a capability daemon against an argument option
diff --git a/tests/topotests/lib/topotest.py b/tests/topotests/lib/topotest.py
index ca6723aec..e2c70cdcc 100644
--- a/tests/topotests/lib/topotest.py
+++ b/tests/topotests/lib/topotest.py
@@ -1467,6 +1467,7 @@ class Router(Node):
self.daemons_options = {"zebra": ""}
self.reportCores = True
self.version = None
+ self.use_netns_vrf = False
self.ns_cmd = "sudo nsenter -a -t {} ".format(self.pid)
try:
@@ -1622,6 +1623,9 @@ class Router(Node):
# breakpoint()
# assert False, "can't remove IPs %s" % str(ex)
+ def useNetnsVRF(self):
+ self.use_netns_vrf = True
+
def checkCapability(self, daemon, param):
if param is not None:
daemon_path = os.path.join(self.daemondir, daemon)
@@ -1908,6 +1912,8 @@ class Router(Node):
def start_daemon(daemon, instance=None):
daemon_opts = self.daemons_options.get(daemon, "")
+ if self.use_netns_vrf:
+ daemon_opts += " -w"
# get pid and vty filenames and remove the files
m = re.match(r"(.* |^)-n (\d+)( ?.*|$)", daemon_opts)
diff --git a/tests/topotests/mgmt_notif/r1/frr.conf b/tests/topotests/mgmt_notif/r1/frr.conf
index 47e73956c..36981c94d 100644
--- a/tests/topotests/mgmt_notif/r1/frr.conf
+++ b/tests/topotests/mgmt_notif/r1/frr.conf
@@ -4,7 +4,7 @@ log file frr.log
no debug memstats-at-exit
debug northbound notifications
-debug northbound libyang
+!! debug northbound libyang
debug northbound events
debug northbound callbacks
diff --git a/tests/topotests/mgmt_notif/r2/frr.conf b/tests/topotests/mgmt_notif/r2/frr.conf
index cd052011e..540961a0e 100644
--- a/tests/topotests/mgmt_notif/r2/frr.conf
+++ b/tests/topotests/mgmt_notif/r2/frr.conf
@@ -16,7 +16,7 @@ ip route 22.22.22.22/32 lo
interface r2-eth0
ip address 1.1.1.2/24
- ip rip authentication string bar
+ ip rip authentication string foo
ip rip authentication mode text
exit
diff --git a/tests/topotests/mgmt_notif/test_ds_notify.py b/tests/topotests/mgmt_notif/test_ds_notify.py
new file mode 100644
index 000000000..1759bf8df
--- /dev/null
+++ b/tests/topotests/mgmt_notif/test_ds_notify.py
@@ -0,0 +1,238 @@
+# -*- coding: utf-8 eval: (blacken-mode 1) -*-
+#
+# January 14 2025, Christian Hopps <chopps@labn.net>
+#
+# Copyright (c) 2025, LabN Consulting, L.L.C.
+#
+"""
+Test YANG Datastore Notifications
+"""
+import json
+import logging
+import os
+import re
+import time
+
+import pytest
+from lib.topogen import Topogen
+from lib.topotest import json_cmp
+from munet.testing.util import waitline
+from oper import check_kernel_32
+
+pytestmark = [pytest.mark.ripd, pytest.mark.staticd, pytest.mark.mgmtd]
+
+CWD = os.path.dirname(os.path.realpath(__file__))
+FE_CLIENT = CWD + "/../lib/fe_client.py"
+
+
+@pytest.fixture(scope="module")
+def tgen(request):
+ "Setup/Teardown the environment and provide tgen argument to tests"
+
+ topodef = {
+ "s1": ("r1", "r2"),
+ }
+
+ tgen = Topogen(topodef, request.module.__name__)
+ tgen.start_topology()
+
+ router_list = tgen.routers()
+ for _, router in router_list.items():
+ router.load_frr_config("frr.conf")
+
+ tgen.start_router()
+ yield tgen
+ tgen.stop_topology()
+
+
+def get_op_and_json(output):
+ op = ""
+ path = ""
+ data = ""
+ for line in output.split("\n"):
+ if not line:
+ continue
+ if not op:
+ m = re.match("#OP=([A-Z]*): (.*)", line)
+ if m:
+ op = m.group(1)
+ path = m.group(2)
+ continue
+ data += line + "\n"
+ if not op:
+ assert False, f"No notifcation op present in:\n{output}"
+ return op, path, data
+
+
+def test_frontend_datastore_notification(tgen):
+ if tgen.routers_have_failure():
+ pytest.skip(tgen.errors)
+
+ r1 = tgen.gears["r1"].net
+
+ check_kernel_32(r1, "11.11.11.11", 1, "")
+
+ rc, _, _ = r1.cmd_status(FE_CLIENT + " --help")
+
+ if rc:
+ pytest.skip("No protoc or present cannot run test")
+
+ # Start our FE client in the background
+ p = r1.popen(
+ [FE_CLIENT, "--datastore", "--listen=/frr-interface:lib/interface/state"]
+ )
+ assert waitline(p.stderr, "Connected", timeout=10)
+
+ r1.cmd_raises("ip link set r1-eth0 mtu 1200")
+
+ # {"frr-interface:lib":{"interface":[{"name":"r1-eth0","state":{"if-index":2,"mtu":1200,"mtu6":1200,"speed":10000,"metric":0,"phy-address":"ba:fd:de:b5:8b:90"}}]}}
+
+ try:
+ # Wait for FE client to exit
+ output, error = p.communicate(timeout=10)
+ op, path, data = get_op_and_json(output)
+
+ assert op == "REPLACE"
+ assert path.startswith("/frr-interface:lib/interface[name='r1-eth0']/state")
+
+ jsout = json.loads(data)
+ expected = json.loads(
+ '{"frr-interface:lib":{"interface":[{"name":"r1-eth0","state":{"mtu":1200}}]}}'
+ )
+ result = json_cmp(jsout, expected)
+ assert result is None
+ finally:
+ p.kill()
+ r1.cmd_raises("ip link set r1-eth0 mtu 1500")
+
+
+def test_backend_datastore_update(tgen):
+ if tgen.routers_have_failure():
+ pytest.skip(tgen.errors)
+
+ r1 = tgen.gears["r1"].net
+
+ check_kernel_32(r1, "11.11.11.11", 1, "")
+
+ be_client_path = "/usr/lib/frr/mgmtd_testc"
+ rc, _, _ = r1.cmd_status(be_client_path + " --help")
+
+ if rc:
+ pytest.skip("No mgmtd_testc")
+
+ # Start our BE client in the background
+ p = r1.popen(
+ [
+ be_client_path,
+ "--timeout=20",
+ "--log=file:/dev/stderr",
+ "--datastore",
+ "--listen",
+ "/frr-interface:lib/interface",
+ ]
+ )
+ assert waitline(p.stderr, "Got SUBSCR_REPLY success 1", timeout=10)
+
+ r1.cmd_raises("ip link set r1-eth0 mtu 1200")
+ try:
+ expected = json.loads(
+ '{"frr-interface:lib":{"interface":[{"name":"r1-eth0","state":{"mtu":1200}}]}}'
+ )
+
+ output, error = p.communicate(timeout=10)
+ op, path, data = get_op_and_json(output)
+ jsout = json.loads(data)
+ result = json_cmp(jsout, expected)
+ assert result is None
+ finally:
+ p.kill()
+ r1.cmd_raises("ip link set r1-eth0 mtu 1500")
+
+
+def test_backend_datastore_add_delete(tgen):
+ if tgen.routers_have_failure():
+ pytest.skip(tgen.errors)
+
+ r1 = tgen.gears["r1"].net
+
+ check_kernel_32(r1, "11.11.11.11", 1, "")
+
+ be_client_path = "/usr/lib/frr/mgmtd_testc"
+ rc, _, _ = r1.cmd_status(be_client_path + " --help")
+
+ if rc:
+ pytest.skip("No mgmtd_testc")
+
+ # Start our BE client in the background
+ p = r1.popen(
+ [
+ be_client_path,
+ "--timeout=20",
+ "--log=file:/dev/stderr",
+ "--notify-count=2",
+ "--datastore",
+ "--listen",
+ "/frr-interface:lib/interface",
+ ]
+ )
+ assert waitline(p.stderr, "Got SUBSCR_REPLY success 1", timeout=10)
+
+ r1.cmd_raises('vtysh -c "conf t" -c "int foobar"')
+ try:
+ assert waitline(
+ p.stdout,
+ re.escape('#OP=REPLACE: /frr-interface:lib/interface[name="foobar"]/state'),
+ timeout=2,
+ )
+
+ r1.cmd_raises('vtysh -c "conf t" -c "no int foobar"')
+ assert waitline(
+ p.stdout,
+ re.escape('#OP=DELETE: /frr-interface:lib/interface[name="foobar"]/state'),
+ timeout=2,
+ )
+ finally:
+ p.kill()
+ r1.cmd_raises('vtysh -c "conf t" -c "no int foobar"')
+
+
+def test_datastore_backend_filters(tgen):
+ if tgen.routers_have_failure():
+ pytest.skip(tgen.errors)
+
+ r1 = tgen.gears["r1"].net
+
+ check_kernel_32(r1, "11.11.11.11", 1, "")
+
+ rc, _, _ = r1.cmd_status(FE_CLIENT + " --help")
+ if rc:
+ pytest.skip("No protoc or present cannot run test")
+
+ # Start our FE client in the background
+ p = r1.popen(
+ [FE_CLIENT, "--datastore", "--listen=/frr-interface:lib/interface/state"]
+ )
+ assert waitline(p.stderr, "Connected", timeout=10)
+ time.sleep(1)
+
+ try:
+ output = r1.cmd_raises(
+ 'vtysh -c "show mgmt get-data /frr-backend:clients/client/state/notify-selectors"'
+ )
+ jsout = json.loads(output)
+
+ #
+ # Verify only zebra has the notify selector as it's the only provider currently
+ #
+ state = {"notify-selectors": ["/frr-interface:lib/interface/state"]}
+ expected = {
+ "frr-backend:clients": {"client": [{"name": "zebra", "state": state}]}
+ }
+
+ result = json_cmp(jsout, expected, exact=True)
+ assert result is None
+ except Exception as error:
+ logging.error("got exception: %s", error)
+ raise
+ finally:
+ p.kill()
diff --git a/tests/topotests/mgmt_notif/test_notif.py b/tests/topotests/mgmt_notif/test_notif.py
index e5286faae..f3c7c8bc8 100644
--- a/tests/topotests/mgmt_notif/test_notif.py
+++ b/tests/topotests/mgmt_notif/test_notif.py
@@ -5,9 +5,8 @@
#
# Copyright (c) 2024, LabN Consulting, L.L.C.
#
-
"""
-Test YANG Notifications
+Test Traditional YANG Notifications
"""
import json
import os
@@ -50,33 +49,101 @@ def test_frontend_notification(tgen):
check_kernel_32(r1, "11.11.11.11", 1, "")
- fe_client_path = CWD + "/../lib/fe_client.py --verbose"
+ fe_client_path = CWD + "/../lib/fe_client.py"
rc, _, _ = r1.cmd_status(fe_client_path + " --help")
if rc:
pytest.skip("No protoc or present cannot run test")
- # The first notifications is a frr-ripd:authentication-type-failure
- # So we filter to avoid that, all the rest are frr-ripd:authentication-failure
- # making our test deterministic
- output = r1.cmd_raises(
- fe_client_path + " --listen /frr-ripd:authentication-failure"
- )
- jsout = json.loads(output)
+ # Update config to non-matching authentication.
+ conf = """
+ conf t
+ interface r1-eth0
+ ip rip authentication string bar
+ """
+ r1.cmd_raises("vtysh", stdin=conf)
+
+ try:
+ output = r1.cmd_raises(
+ fe_client_path + " --listen /frr-ripd:authentication-failure"
+ )
+
+ jsout = json.loads(output)
+ expected = {"frr-ripd:authentication-failure": {"interface-name": "r1-eth0"}}
+ result = json_cmp(jsout, expected)
+ assert result is None
+
+ output = r1.cmd_raises(
+ fe_client_path + " --use-protobuf --listen /frr-ripd:authentication-failure"
+ )
+ jsout = json.loads(output)
+ expected = {"frr-ripd:authentication-failure": {"interface-name": "r1-eth0"}}
+ result = json_cmp(jsout, expected)
+ assert result is None
+ finally:
+ # Update config to matching authentication.
+ conf = """
+ conf t
+ interface r1-eth0
+ ip rip authentication string foo
+ """
+ r1.cmd_raises("vtysh", stdin=conf)
+
+
+def test_frontend_all_notification(tgen):
+ if tgen.routers_have_failure():
+ pytest.skip(tgen.errors)
- expected = {"frr-ripd:authentication-failure": {"interface-name": "r1-eth0"}}
- result = json_cmp(jsout, expected)
- assert result is None
+ r1 = tgen.gears["r1"].net
- output = r1.cmd_raises(fe_client_path + " --use-protobuf --listen")
- jsout = json.loads(output)
+ check_kernel_32(r1, "11.11.11.11", 1, "")
- expected = {"frr-ripd:authentication-failure": {"interface-name": "r1-eth0"}}
- result = json_cmp(jsout, expected)
- assert result is None
+ fe_client_path = CWD + "/../lib/fe_client.py"
+ rc, _, _ = r1.cmd_status(fe_client_path + " --help")
+ if rc:
+ pytest.skip("No protoc or present cannot run test")
-def test_backend_notification(tgen):
+ # Update config to non-matching authentication.
+ conf = """
+ conf t
+ interface r1-eth0
+ ip rip authentication string bar
+ """
+ r1.cmd_raises("vtysh", stdin=conf)
+
+ try:
+ # The first notifications is a frr-ripd:authentication-type-failure
+ # All the rest are frr-ripd:authentication-failure so we check for both.
+ output = r1.cmd_raises(fe_client_path + " --listen /")
+ jsout = json.loads(output)
+ expected = {
+ "frr-ripd:authentication-type-failure": {"interface-name": "r1-eth0"}
+ }
+ result = json_cmp(jsout, expected)
+ if result is not None:
+ expected = {
+ "frr-ripd:authentication-failure": {"interface-name": "r1-eth0"}
+ }
+ result = json_cmp(jsout, expected)
+ assert result is None
+
+ output = r1.cmd_raises(fe_client_path + " --use-protobuf --listen /")
+ jsout = json.loads(output)
+ expected = {"frr-ripd:authentication-failure": {"interface-name": "r1-eth0"}}
+ result = json_cmp(jsout, expected)
+ assert result is None
+ finally:
+ # Update config to matching authentication.
+ conf = """
+ conf t
+ interface r1-eth0
+ ip rip authentication string foo
+ """
+ r1.cmd_raises("vtysh", stdin=conf)
+
+
+def test_backend_yang_notification(tgen):
if tgen.routers_have_failure():
pytest.skip(tgen.errors)
@@ -90,12 +157,28 @@ def test_backend_notification(tgen):
if rc:
pytest.skip("No mgmtd_testc")
- output = r1.cmd_raises(
- be_client_path + " --timeout 20 --log file:mgmt_testc.log --listen /frr-ripd"
- )
-
- jsout = json.loads(output)
-
- expected = {"frr-ripd:authentication-failure": {"interface-name": "r1-eth0"}}
- result = json_cmp(jsout, expected)
- assert result is None
+ # Update config to non-matching authentication.
+ conf = """
+ conf t
+ interface r1-eth0
+ ip rip authentication string bar
+ """
+ r1.cmd_raises("vtysh", stdin=conf)
+
+ try:
+ output = r1.cmd_raises(
+ be_client_path
+ + " --timeout 20 --log file:mgmt_testc.log --listen /frr-ripd"
+ )
+ jsout = json.loads(output)
+ expected = {"frr-ripd:authentication-failure": {"interface-name": "r1-eth0"}}
+ result = json_cmp(jsout, expected)
+ assert result is None
+ finally:
+ # Update config to matching authentication.
+ conf = """
+ conf t
+ interface r1-eth0
+ ip rip authentication string foo
+ """
+ r1.cmd_raises("vtysh", stdin=conf)
diff --git a/tests/topotests/msdp_topo3/r1/frr.conf b/tests/topotests/msdp_topo3/r1/frr.conf
index d5b10bf8a..161f0008d 100644
--- a/tests/topotests/msdp_topo3/r1/frr.conf
+++ b/tests/topotests/msdp_topo3/r1/frr.conf
@@ -27,5 +27,6 @@ router pim
msdp originator-id 10.254.254.1
msdp log sa-events
msdp peer 192.168.1.2 source 192.168.1.1
+ msdp timers 10 20 3
rp 192.168.1.1
! \ No newline at end of file
diff --git a/tests/topotests/msdp_topo3/r2/frr.conf b/tests/topotests/msdp_topo3/r2/frr.conf
index 245c06187..b7a20d4b7 100644
--- a/tests/topotests/msdp_topo3/r2/frr.conf
+++ b/tests/topotests/msdp_topo3/r2/frr.conf
@@ -24,5 +24,6 @@ router bgp 65200
router pim
msdp log sa-events
msdp peer 192.168.1.1 source 192.168.1.2
+ msdp timers 10 20 3
rp 192.168.1.2
! \ No newline at end of file
diff --git a/tests/topotests/msdp_topo3/test_msdp_topo3.py b/tests/topotests/msdp_topo3/test_msdp_topo3.py
index 9393ae7ff..4e3b18f7c 100644
--- a/tests/topotests/msdp_topo3/test_msdp_topo3.py
+++ b/tests/topotests/msdp_topo3/test_msdp_topo3.py
@@ -121,6 +121,29 @@ def test_bgp_convergence():
expect_loopback_route("r2", "ip", "10.254.254.1/32", "bgp")
+def test_msdp_connect():
+ "Test that the MSDP peers have connected."
+ tgen = get_topogen()
+ if tgen.routers_have_failure():
+ pytest.skip(tgen.errors)
+
+ def msdp_is_connected(router, peer):
+ logger.info(f"waiting MSDP peer {peer} in router {router}")
+ expected = {peer: {"state": "established"}}
+ test_func = partial(
+ topotest.router_json_cmp,
+ tgen.gears[router],
+ "show ip msdp peer json",
+ expected,
+ )
+ _, result = topotest.run_and_expect(test_func, None, count=40, wait=2)
+ assertmsg = '"{}" convergence failure'.format(router)
+ assert result is None, assertmsg
+
+ msdp_is_connected("r1", "192.168.1.2")
+ msdp_is_connected("r2", "192.168.1.1")
+
+
def test_sa_learn():
"""
Test that the learned SA uses the configured originator ID instead
@@ -145,10 +168,10 @@ def test_sa_learn():
"local": "no",
}
}
- }
+ },
)
- _, result = topotest.run_and_expect(test_func, None, count=100, wait=1)
- assert result is None, 'r2 SA convergence failure'
+ _, result = topotest.run_and_expect(test_func, None, count=80, wait=2)
+ assert result is None, "r2 SA convergence failure"
def test_memory_leak():
diff --git a/tests/topotests/munet/base.py b/tests/topotests/munet/base.py
index e77eb15dc..e9410d442 100644
--- a/tests/topotests/munet/base.py
+++ b/tests/topotests/munet/base.py
@@ -332,6 +332,10 @@ class Commander: # pylint: disable=R0904
self.last = None
self.exec_paths = {}
+ # For running commands one time only (deals with asyncio)
+ self.cmd_once_done = {}
+ self.cmd_once_locks = {}
+
if not logger:
logname = f"munet.{self.__class__.__name__.lower()}.{name}"
self.logger = logging.getLogger(logname)
@@ -1189,7 +1193,7 @@ class Commander: # pylint: disable=R0904
return stdout
# Run a command in a new window (gnome-terminal, screen, tmux, xterm)
- def run_in_window(
+ def run_in_window( # pylint: disable=too-many-positional-arguments
self,
cmd,
wait_for=False,
@@ -1205,7 +1209,7 @@ class Commander: # pylint: disable=R0904
Args:
cmd: string to execute.
- wait_for: True to wait for exit from command or `str` as channel neme to
+ wait_for: True to wait for exit from command or `str` as channel name to
signal on exit, otherwise False
background: Do not change focus to new window.
title: Title for new pane (tmux) or window (xterm).
@@ -1405,6 +1409,26 @@ class Commander: # pylint: disable=R0904
return pane_info
+ async def async_cmd_raises_once(self, cmd, **kwargs):
+ if cmd in self.cmd_once_done:
+ return self.cmd_once_done[cmd]
+
+ if cmd not in self.cmd_once_locks:
+ self.cmd_once_locks[cmd] = asyncio.Lock()
+
+ async with self.cmd_once_locks[cmd]:
+ if cmd not in self.cmd_once_done:
+ self.logger.info("Running command once: %s", cmd)
+ self.cmd_once_done[cmd] = await commander.async_cmd_raises(
+ cmd, **kwargs
+ )
+ return self.cmd_once_done[cmd]
+
+ def cmd_raises_once(self, cmd, **kwargs):
+ if cmd not in self.cmd_once_done:
+ self.cmd_once_done[cmd] = commander.cmd_raises(cmd, **kwargs)
+ return self.cmd_once_done[cmd]
+
def delete(self):
"""Calls self.async_delete within an exec loop."""
asyncio.run(self.async_delete())
diff --git a/tests/topotests/munet/munet-schema.json b/tests/topotests/munet/munet-schema.json
index 6ebc368dc..44453cb44 100644
--- a/tests/topotests/munet/munet-schema.json
+++ b/tests/topotests/munet/munet-schema.json
@@ -117,6 +117,12 @@
"bios": {
"type": "string"
},
+ "cloud-init": {
+ "type": "boolean"
+ },
+ "cloud-init-disk": {
+ "type": "string"
+ },
"disk": {
"type": "string"
},
@@ -129,7 +135,7 @@
"initial-cmd": {
"type": "string"
},
- "kerenel": {
+ "kernel": {
"type": "string"
},
"initrd": {
@@ -373,6 +379,12 @@
"networks-autonumber": {
"type": "boolean"
},
+ "initial-setup-cmd": {
+ "type": "string"
+ },
+ "initial-setup-host-cmd": {
+ "type": "string"
+ },
"networks": {
"type": "array",
"items": {
@@ -452,6 +464,12 @@
"bios": {
"type": "string"
},
+ "cloud-init": {
+ "type": "boolean"
+ },
+ "cloud-init-disk": {
+ "type": "string"
+ },
"disk": {
"type": "string"
},
@@ -464,7 +482,7 @@
"initial-cmd": {
"type": "string"
},
- "kerenel": {
+ "kernel": {
"type": "string"
},
"initrd": {
diff --git a/tests/topotests/munet/mutest/userapi.py b/tests/topotests/munet/mutest/userapi.py
index abc63af36..e367e65a1 100644
--- a/tests/topotests/munet/mutest/userapi.py
+++ b/tests/topotests/munet/mutest/userapi.py
@@ -180,7 +180,7 @@ class TestCase:
# sum_hfmt = "{:5.5s} {:4.4s} {:>6.6s} {}"
# sum_dfmt = "{:5s} {:4.4s} {:^6.6s} {}"
- sum_fmt = "%-8.8s %4.4s %{}s %6s %s"
+ sum_fmt = "%-10s %4.4s %{}s %6s %s"
def __init__(
self,
diff --git a/tests/topotests/munet/native.py b/tests/topotests/munet/native.py
index e3b782396..4e29fe91b 100644
--- a/tests/topotests/munet/native.py
+++ b/tests/topotests/munet/native.py
@@ -24,6 +24,13 @@ import time
from pathlib import Path
+
+try:
+ # We only want to require yaml for the gen cloud image feature
+ import yaml
+except ImportError:
+ pass
+
from . import cli
from .base import BaseMunet
from .base import Bridge
@@ -749,9 +756,11 @@ class L3NodeMixin(NodeMixin):
# Disable IPv6
self.cmd_raises("sysctl -w net.ipv6.conf.all.autoconf=0")
self.cmd_raises("sysctl -w net.ipv6.conf.all.disable_ipv6=1")
+ self.cmd_raises("sysctl -w net.ipv6.conf.all.forwarding=0")
else:
self.cmd_raises("sysctl -w net.ipv6.conf.all.autoconf=1")
self.cmd_raises("sysctl -w net.ipv6.conf.all.disable_ipv6=0")
+ self.cmd_raises("sysctl -w net.ipv6.conf.all.forwarding=1")
self.next_p2p_network = ipaddress.ip_network(f"10.254.{self.id}.0/31")
self.next_p2p_network6 = ipaddress.ip_network(f"fcff:ffff:{self.id:02x}::/127")
@@ -2265,6 +2274,164 @@ class L3QemuVM(L3NodeMixin, LinuxNamespace):
tid = self.cpu_thread_map[i]
self.cmd_raises_nsonly(f"taskset -cp {aff} {tid}")
+ def _gen_network_config(self):
+ intfs = sorted(self.intfs)
+ if not intfs:
+ return ""
+
+ self.logger.debug("Generating cloud-init interface config")
+ config = {}
+ config["version"] = 2
+ enets = config["ethernets"] = {}
+
+ for ifname in sorted(self.intfs):
+ self.logger.debug("Interface %s", ifname)
+ conn = find_with_kv(self.config["connections"], "name", ifname)
+
+ index = self.config["connections"].index(conn)
+ to = conn["to"]
+ switch = self.unet.switches.get(to)
+ mtu = conn.get("mtu")
+ if not mtu and switch:
+ mtu = switch.config.get("mtu")
+
+ devaddr = conn.get("physical", "")
+ # Eventually we should get the MAC from /sys
+ if not devaddr:
+ mac = self.tapmacs.get(ifname, f"02:aa:aa:aa:{index:02x}:{self.id:02x}")
+ nic = {
+ "match": {"macaddress": str(mac)},
+ "set-name": ifname,
+ }
+ if mtu:
+ nic["mtu"] = str(mtu)
+ enets[f"nic-{ifname}"] = nic
+
+ ifaddr4 = self.get_intf_addr(ifname, ipv6=False)
+ ifaddr6 = self.get_intf_addr(ifname, ipv6=True)
+ if not ifaddr4 and not ifaddr6:
+ continue
+ net = {
+ "dhcp4": False,
+ "dhcp6": False,
+ "accept-ra": False,
+ "addresses": [],
+ }
+ if ifaddr4:
+ net["addresses"].append(str(ifaddr4))
+ if ifaddr6:
+ net["addresses"].append(str(ifaddr6))
+ if switch and hasattr(switch, "is_nat") and switch.is_nat:
+ net["nameservers"] = {"addresses": []}
+ nameservers = net["nameservers"]["addresses"]
+ if hasattr(switch, "ip6_address"):
+ net["gateway6"] = str(switch.ip6_address)
+ nameservers.append("2001:4860:4860::8888")
+ if switch.ip_address:
+ net["gateway4"] = str(switch.ip_address)
+ nameservers.append("8.8.8.8")
+ enets[ifname] = net
+
+ return yaml.safe_dump(config)
+
+ def _gen_cloud_init(self):
+ qc = self.qemu_config
+ cc = qc.get("console", {})
+ cipath = self.rundir.joinpath("cloud-init.img")
+
+ geniso = get_exec_path_host("genisoimage")
+ if not geniso:
+ mfbin = get_exec_path_host("mkfs.vfat")
+ mcbin = get_exec_path_host("mcopy")
+ assert (
+ mfbin and mcbin
+ ), "genisoimage or mkfs.vfat,mcopy needed to gen cloud-init disk"
+
+ #
+ # cloud-init: meta-data
+ #
+ mdata = f"""
+instance-id: "munet-{self.id}"
+local-hostname: "{self.name}"
+"""
+ #
+ # cloud-init: user-data
+ #
+ ssh_auth_s = ""
+ if bool(self.ssh_keyfile):
+ pubkey = commander.cmd_raises(f"ssh-keygen -y -f {self.ssh_keyfile}")
+ assert pubkey, f"Can't extract public key from {self.ssh_keyfile}"
+ pubkey = pubkey.strip()
+ ssh_auth_s = f'ssh_authorized_keys: ["{pubkey}"]'
+
+ user = cc.get("user", "root")
+ password = cc.get("password", "admin")
+ if user != "root":
+ root_password = "admin"
+ else:
+ root_password = password
+
+ udata = f"""#cloud-config
+disable_root: 0
+ssh_pwauth: 1
+hostname: {self.name}
+runcmd:
+ - systemctl enable serial-getty@ttyS1.service
+ - systemctl start serial-getty@ttyS1.service
+ - systemctl enable serial-getty@ttyS2.service
+ - systemctl start serial-getty@ttyS2.service
+ - systemctl enable serial-getty@hvc0.service
+ - systemctl start serial-getty@hvc0.service
+ - systemctl enable serial-getty@hvc1.service
+ - systemctl start serial-getty@hvc1.service
+users:
+ - name: root
+ lock_passwd: false
+ plain_text_passwd: "{root_password}"
+ {ssh_auth_s}
+"""
+ if user != "root":
+ udata += """
+ - name: {user}
+ lock_passwd: false
+ plain_text_passwd: "{password}"
+ {ssh_auth_s}
+"""
+ #
+ # cloud-init: network-config
+ #
+ ndata = self._gen_network_config()
+
+ #
+ # Generate cloud-init files
+ #
+ cidir = self.rundir.joinpath("ci-data")
+ commander.cmd_raises(f"mkdir -p {cidir}")
+
+ with open(cidir.joinpath("meta-data"), "w+", encoding="utf-8") as f:
+ f.write(mdata)
+ with open(cidir.joinpath("user-data"), "w+", encoding="utf-8") as f:
+ f.write(udata)
+ files = "meta-data user-data"
+ if ndata:
+ files += " network-config"
+ with open(cidir.joinpath("network-config"), "w+", encoding="utf-8") as f:
+ f.write(ndata)
+ if geniso:
+ commander.cmd_raises(
+ f"cd {cidir} && "
+ f'genisoimage -output "{cipath}" -volid cidata'
+ f" -joliet -rock {files}"
+ )
+ else:
+ commander.cmd_raises(f'cd {cidir} && mkfs.vfat -n cidata "{cipath}"')
+ commander.cmd_raises(f'cd {cidir} && mcopy -oi "{cipath}" {files}')
+
+ #
+ # Generate cloud-init disk
+ #
+ return cipath
+
async def launch(self):
"""Launch qemu."""
self.logger.info("%s: Launch Qemu", self)
@@ -2367,11 +2534,21 @@ class L3QemuVM(L3NodeMixin, LinuxNamespace):
diskpath = os.path.join(self.unet.config_dirname, diskpath)
if dtpl and (not disk or not os.path.exists(diskpath)):
+ basename = os.path.basename(dtpl)
+ confdir = self.unet.config_dirname
+ if re.match("(https|http|ftp|tftp):.*", dtpl):
+ await self.unet.async_cmd_raises_once(
+ f"cd {confdir} && (test -e {basename} || curl -fLO {dtpl})"
+ )
+ dtplpath = os.path.join(confdir, basename)
+
if not disk:
- disk = qc["disk"] = f"{self.name}-{os.path.basename(dtpl)}"
+ disk = qc["disk"] = f"{self.name}-{basename}"
diskpath = os.path.join(self.rundir, disk)
+
if self.path_exists(diskpath):
logging.debug("Disk '%s' file exists, using.", diskpath)
+
else:
if dtplpath[0] != "/":
dtplpath = os.path.join(self.unet.config_dirname, dtpl)
@@ -2392,11 +2569,15 @@ class L3QemuVM(L3NodeMixin, LinuxNamespace):
args.extend(["-device", "ahci,id=ahci"])
args.extend(["-device", "ide-hd,bus=ahci.0,drive=sata-disk0"])
- cidiskpath = qc.get("cloud-init-disk")
- if cidiskpath:
- if cidiskpath[0] != "/":
- cidiskpath = os.path.join(self.unet.config_dirname, cidiskpath)
- args.extend(["-drive", f"file={cidiskpath},if=virtio,format=qcow2"])
+ if qc.get("cloud-init"):
+ cidiskpath = qc.get("cloud-init-disk")
+ if cidiskpath:
+ if cidiskpath[0] != "/":
+ cidiskpath = os.path.join(self.unet.config_dirname, cidiskpath)
+ else:
+ cidiskpath = self._gen_cloud_init()
+ diskfmt = "qcow2" if str(cidiskpath).endswith("qcow2") else "raw"
+ args.extend(["-drive", f"file={cidiskpath},if=virtio,format={diskfmt}"])
# args.extend(["-display", "vnc=0.0.0.0:40"])
@@ -2488,7 +2669,7 @@ class L3QemuVM(L3NodeMixin, LinuxNamespace):
if use_cmdcon:
confiles.append("_cmdcon")
- password = cc.get("password", "")
+ password = cc.get("password", "admin")
if self.disk_created:
password = cc.get("initial-password", password)
@@ -2764,9 +2945,11 @@ ff02::2\tip6-allrouters
# Disable IPv6
self.cmd_raises("sysctl -w net.ipv6.conf.all.autoconf=0")
self.cmd_raises("sysctl -w net.ipv6.conf.all.disable_ipv6=1")
+ self.cmd_raises("sysctl -w net.ipv6.conf.all.forwarding=0")
else:
self.cmd_raises("sysctl -w net.ipv6.conf.all.autoconf=1")
self.cmd_raises("sysctl -w net.ipv6.conf.all.disable_ipv6=0")
+ self.cmd_raises("sysctl -w net.ipv6.conf.all.forwarding=1")
# we really need overlay, but overlay-layers (used by overlay-images)
# counts on things being present in overlay so this temp stuff doesn't work.
@@ -2774,6 +2957,24 @@ ff02::2\tip6-allrouters
# # Let's hide podman details
# self.tmpfs_mount("/var/lib/containers/storage/overlay-containers")
+ def run_init_cmds(unet, key, on_host):
+ cmds = unet.topoconf.get(key, "")
+ cmds = cmds.replace("%CONFIGDIR%", str(unet.config_dirname))
+ cmds = cmds.replace("%RUNDIR%", str(unet.rundir))
+ cmds = cmds.strip()
+ if not cmds:
+ return
+
+ cmds += "\n"
+ c = commander if on_host else unet
+ o = c.cmd_raises(cmds)
+ self.logger.debug(
+ "run_init_cmds (on-host: %s): %s", on_host, cmd_error(0, o, "")
+ )
+
+ run_init_cmds(self, "initial-setup-host-cmd", True)
+ run_init_cmds(self, "initial-setup-cmd", False)
+
shellopt = self.cfgopt.getoption("--shell")
shellopt = shellopt if shellopt else ""
if shellopt == "all" or "." in shellopt.split(","):
@@ -3061,7 +3262,8 @@ done"""
if not rc:
continue
logging.info("Pulling missing image %s", image)
- aw = self.rootcmd.async_cmd_raises(f"podman pull {image}")
+
+ aw = self.rootcmd.async_cmd_raises_once(f"podman pull {image}")
tasks.append(asyncio.create_task(aw))
if not tasks:
return
diff --git a/tests/topotests/munet/testing/util.py b/tests/topotests/munet/testing/util.py
index 99687c0a8..02ff9bd69 100644
--- a/tests/topotests/munet/testing/util.py
+++ b/tests/topotests/munet/testing/util.py
@@ -8,12 +8,17 @@
"""Utility functions useful when using munet testing functionailty in pytest."""
import asyncio
import datetime
+import fcntl
import functools
import logging
+import os
+import re
+import select
import sys
import time
from ..base import BaseMunet
+from ..base import Timeout
from ..cli import async_cli
@@ -23,6 +28,7 @@ from ..cli import async_cli
async def async_pause_test(desc=""):
+ """Pause the running of a test offering options for CLI or PDB."""
isatty = sys.stdout.isatty()
if not isatty:
desc = f" for {desc}" if desc else ""
@@ -49,11 +55,12 @@ async def async_pause_test(desc=""):
def pause_test(desc=""):
+ """Pause the running of a test offering options for CLI or PDB."""
asyncio.run(async_pause_test(desc))
def retry(retry_timeout, initial_wait=0, retry_sleep=2, expected=True):
- """decorator: retry while functions return is not None or raises an exception.
+ """Retry decorated function until it returns None, raises an exception, or timeout.
* `retry_timeout`: Retry for at least this many seconds; after waiting
initial_wait seconds
@@ -116,3 +123,91 @@ def retry(retry_timeout, initial_wait=0, retry_sleep=2, expected=True):
return func_retry
return _retry
+
+
+def readline(f, timeout=None):
+ """Read a line or timeout.
+
+ This function will take over the file object, the file object should not be used
+ outside of calling this function once you begin.
+
+ Return: A line, remaining buffer if EOF (subsequent calls will return ""), or None
+ for timeout.
+ """
+ fd = f.fileno()
+ if not hasattr(f, "munet_non_block_set"):
+ flags = fcntl.fcntl(fd, fcntl.F_GETFL)
+ fcntl.fcntl(fd, fcntl.F_SETFL, flags | os.O_NONBLOCK)
+ f.munet_non_block_set = True
+ f.munet_lines = []
+ f.munet_buf = ""
+
+ if f.munet_lines:
+ return f.munet_lines.pop(0)
+
+ timeout = Timeout(timeout)
+ remaining = timeout.remaining()
+ while remaining > 0:
+ ready, _, _ = select.select([fd], [], [], remaining)
+ if not ready:
+ return None
+
+ c = f.read()
+ if c is None:
+ logging.error("munet readline: unexpected None during read")
+ return None
+
+ if not c:
+ logging.debug("munet readline: got eof")
+ c = f.munet_buf
+ f.munet_buf = ""
+ return c
+
+ f.munet_buf += c
+ while "\n" in f.munet_buf:
+ a, f.munet_buf = f.munet_buf.split("\n", 1)
+ f.munet_lines.append(a + "\n")
+
+ if f.munet_lines:
+ return f.munet_lines.pop(0)
+
+ remaining = timeout.remaining()
+ return None
+
+
+def waitline(f, regex, timeout=120):
+ """Match a regex within lines from a file with a timeout.
+
+ This function will take over the file object (by calling `readline` above), the file
+ object should not be used outside of calling these functions once you begin.
+
+ Return: the match object or None.
+ """
+ timeo = Timeout(timeout)
+ while not timeo.is_expired():
+ line = readline(f, timeo.remaining())
+ if line is None:
+ break
+
+ if line == "":
+ logging.warning("waitline: got eof while matching '%s'", regex)
+ return None
+
+ assert line[-1] == "\n"
+ line = line[:-1]
+ if not line:
+ continue
+
+ logging.debug("waitline: searching: '%s' for '%s'", line, regex)
+ m = re.search(regex, line)
+ if m:
+ logging.debug("waitline: matched '%s'", m.group(0))
+ return m
+
+ logging.warning(
+ "Timeout while getting output matching '%s' within %ss (actual %ss)",
+ regex,
+ timeout,
+ timeo.elapsed(),
+ )
+ return None
diff --git a/tests/topotests/ospf_metric_propagation/r1/frr.conf b/tests/topotests/ospf_metric_propagation/r1/frr.conf
index 082f7df51..09ae6e8d1 100644
--- a/tests/topotests/ospf_metric_propagation/r1/frr.conf
+++ b/tests/topotests/ospf_metric_propagation/r1/frr.conf
@@ -1,6 +1,10 @@
!
hostname r1
!
+vrf green
+ ip route 10.48.48.0/24 10.0.91.2
+exit
+!
interface r1-eth0
ip address 10.0.1.1/24
ip ospf cost 100
@@ -61,6 +65,7 @@ router bgp 99 vrf green
address-family ipv4 unicast
redistribute connected
redistribute ospf
+ redistribute static
import vrf route-map rmap
import vrf default
import vrf blue
@@ -75,7 +80,7 @@ ip prefix-list min seq 5 permit 10.0.80.0/24
route-map costmax permit 20
set metric-type type-1
set metric +1
- set metric-min 713
+ set min-metric 713
match ip address prefix-list min
exit
!
@@ -83,7 +88,7 @@ ip prefix-list max seq 10 permit 10.0.70.0/24
route-map costplus permit 30
set metric-type type-1
set metric +1
- set metric-max 13
+ set max-metric 13
match ip address prefix-list max
exit
!
diff --git a/tests/topotests/ospf_metric_propagation/r1/show_ip_route_static.json b/tests/topotests/ospf_metric_propagation/r1/show_ip_route_static.json
index 628a556c6..6060e8bd6 100644
--- a/tests/topotests/ospf_metric_propagation/r1/show_ip_route_static.json
+++ b/tests/topotests/ospf_metric_propagation/r1/show_ip_route_static.json
@@ -3,44 +3,23 @@
{
"prefix":"10.48.48.0/24",
"prefixLen":24,
- "protocol":"ospf",
- "vrfId":0,
- "vrfName":"default",
- "distance":20,
- "metric":134,
- "table":254,
- "nexthops":[
- {
- "flags":3,
- "fib":true,
- "ip":"10.0.1.2",
- "afi":"ipv4",
- "interfaceName":"r1-eth0",
- "active":true,
- "weight":1
- }
- ]
- },
- {
- "prefix":"10.48.48.0/24",
- "prefixLen":24,
"protocol":"bgp",
"vrfId":0,
"vrfName":"default",
"selected":true,
"destSelected":true,
- "distance":20,
- "metric":34,
+ "distance":1,
+ "metric":1,
"installed":true,
"table":254,
"nexthops":[
{
"flags":3,
"fib":true,
- "ip":"10.0.10.5",
+ "ip":"10.0.91.2",
"afi":"ipv4",
- "interfaceName":"r1-eth1",
- "vrf":"blue",
+ "interfaceName":"r1-eth2",
+ "vrf":"green",
"active":true,
"weight":1
}
diff --git a/tests/topotests/ospf_metric_propagation/r4/frr.conf b/tests/topotests/ospf_metric_propagation/r4/frr.conf
index d9832d80b..b02ae18fc 100644
--- a/tests/topotests/ospf_metric_propagation/r4/frr.conf
+++ b/tests/topotests/ospf_metric_propagation/r4/frr.conf
@@ -1,10 +1,6 @@
!
hostname r4
!
-vrf green
- ip route 10.48.48.0/24 10.0.94.2
-exit
-
interface r4-eth0
ip address 10.0.3.4/24
ip ospf cost 100
@@ -63,7 +59,6 @@ router bgp 99 vrf green
address-family ipv4 unicast
redistribute connected
redistribute ospf
- redistribute static
import vrf route-map rmap
import vrf default
import vrf blue
diff --git a/tests/topotests/ospf_metric_propagation/test_ospf_metric_propagation.py b/tests/topotests/ospf_metric_propagation/test_ospf_metric_propagation.py
index 4639a1e26..f574dac4e 100644
--- a/tests/topotests/ospf_metric_propagation/test_ospf_metric_propagation.py
+++ b/tests/topotests/ospf_metric_propagation/test_ospf_metric_propagation.py
@@ -190,8 +190,8 @@ def test_all_links_up():
assert result is None, assertmsg
-def test_static_remote():
- "Test static route at R1 configured on R4"
+def test_static():
+ "Test static route at R1 leaked from VRF green"
tgen = get_topogen()
if tgen.routers_have_failure():
@@ -201,7 +201,7 @@ def test_static_remote():
json_file = "{}/r1/show_ip_route_static.json".format(CWD)
expected = json.loads(open(json_file).read())
test_func = partial(
- topotest.router_json_cmp, r1, "show ip route 10.48.48.2 json", expected
+ topotest.router_json_cmp, r1, "show ip route 10.48.48.0/24 json", expected
)
_, result = topotest.run_and_expect(test_func, None, count=60, wait=1)
diff --git a/tests/topotests/ospf_netns_vrf/test_ospf_netns_vrf.py b/tests/topotests/ospf_netns_vrf/test_ospf_netns_vrf.py
index 718445f01..c0a4689d4 100644
--- a/tests/topotests/ospf_netns_vrf/test_ospf_netns_vrf.py
+++ b/tests/topotests/ospf_netns_vrf/test_ospf_netns_vrf.py
@@ -87,11 +87,9 @@ def setup_module(mod):
router.net.set_intf_netns(rname + "-eth0", ns, up=True)
router.net.set_intf_netns(rname + "-eth1", ns, up=True)
- router.load_config(TopoRouter.RD_MGMTD, None, "--vrfwnetns")
+ router.use_netns_vrf()
router.load_config(
- TopoRouter.RD_ZEBRA,
- os.path.join(CWD, "{}/zebra.conf".format(rname)),
- "--vrfwnetns",
+ 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))
diff --git a/tests/topotests/pim_boundary_acl/test_pim_boundary_acl.py b/tests/topotests/pim_boundary_acl/test_pim_boundary_acl.py
index 1488e610c..2a77c3b22 100644
--- a/tests/topotests/pim_boundary_acl/test_pim_boundary_acl.py
+++ b/tests/topotests/pim_boundary_acl/test_pim_boundary_acl.py
@@ -135,12 +135,10 @@ def test_pim_asm_igmp_join_acl():
expected = {
"r1-eth0":{
"name":"r1-eth0",
- "224.0.1.40":"*",
"229.1.1.1":None
},
"r1-eth2":{
"name":"r1-eth2",
- "224.0.1.40":"*",
"229.1.1.1":None
}
}
@@ -166,9 +164,7 @@ def test_pim_asm_igmp_join_acl():
"sources":[
{
"source":"*",
- "timer":"--:--",
"forwarded":False,
- "uptime":"*"
}
]
}
@@ -227,8 +223,6 @@ def test_pim_asm_igmp_join_acl():
"source":"*",
"group":"229.1.1.1",
"primaryAddr":"10.0.20.2",
- "sockFd":"*",
- "upTime":"*"
}
]
}
@@ -286,13 +280,11 @@ def test_pim_ssm_igmp_join_acl():
expected = {
"r1-eth0":{
"name":"r1-eth0",
- "224.0.1.40":"*",
"229.1.1.1":None,
"232.1.1.1":None
},
"r1-eth2":{
"name":"r1-eth2",
- "224.0.1.40":"*",
"229.1.1.1":None,
"232.1.1.1":None
}
@@ -319,9 +311,7 @@ def test_pim_ssm_igmp_join_acl():
"sources":[
{
"source":"10.0.20.2",
- "timer":"*",
"forwarded":False,
- "uptime":"*"
}
]
}
@@ -397,9 +387,7 @@ def test_pim_ssm_igmp_join_acl():
"sources":[
{
"source":"10.0.20.2",
- "timer":"*",
"forwarded":False,
- "uptime":"*"
}
]
}
@@ -422,8 +410,6 @@ def test_pim_ssm_igmp_join_acl():
"source":"10.0.20.2",
"group":"232.1.1.1",
"primaryAddr":"10.0.20.2",
- "sockFd":"*",
- "upTime":"*"
}
]
}
@@ -491,9 +477,7 @@ def test_pim_ssm_igmp_join_acl():
"sources":[
{
"source":"10.0.40.4",
- "timer":"*",
"forwarded":False,
- "uptime":"*"
}
]
}
diff --git a/tests/topotests/pim_mrib/r1/frr.conf b/tests/topotests/pim_mrib/r1/frr.conf
index 28cf2b2c4..7c9d27c60 100644
--- a/tests/topotests/pim_mrib/r1/frr.conf
+++ b/tests/topotests/pim_mrib/r1/frr.conf
@@ -20,9 +20,10 @@ interface r1-eth1
ip forwarding
!
ip route 10.0.2.0/24 10.0.0.2 50
-ip route 10.0.3.0/24 10.0.1.3 50
+ip route 10.0.3.0/24 10.0.0.2 50
!
router pim
rpf-lookup-mode mrib-then-urib
rp 10.0.0.1 224.0.0.0/4
+ rp 10.0.1.1 225.0.0.0/24
! \ No newline at end of file
diff --git a/tests/topotests/pim_mrib/r2/frr.conf b/tests/topotests/pim_mrib/r2/frr.conf
index 3e647f679..260b6b0f7 100644
--- a/tests/topotests/pim_mrib/r2/frr.conf
+++ b/tests/topotests/pim_mrib/r2/frr.conf
@@ -25,4 +25,5 @@ ip route 10.0.3.0/24 10.0.2.4 50
router pim
rpf-lookup-mode mrib-then-urib
rp 10.0.0.1 224.0.0.0/4
+ rp 10.0.1.1 225.0.0.0/24
! \ No newline at end of file
diff --git a/tests/topotests/pim_mrib/r3/frr.conf b/tests/topotests/pim_mrib/r3/frr.conf
index 9815484d0..5966ae0e8 100644
--- a/tests/topotests/pim_mrib/r3/frr.conf
+++ b/tests/topotests/pim_mrib/r3/frr.conf
@@ -25,4 +25,5 @@ ip route 10.0.2.0/24 10.0.3.4 50
router pim
rpf-lookup-mode mrib-then-urib
rp 10.0.0.1 224.0.0.0/4
+ rp 10.0.1.1 225.0.0.0/24
! \ No newline at end of file
diff --git a/tests/topotests/pim_mrib/r4/frr.conf b/tests/topotests/pim_mrib/r4/frr.conf
index 8432a7a35..8d9d8f7e2 100644
--- a/tests/topotests/pim_mrib/r4/frr.conf
+++ b/tests/topotests/pim_mrib/r4/frr.conf
@@ -18,12 +18,24 @@ interface r4-eth1
ip igmp
ip pim
!
+interface r4-dum0
+ ip address 10.10.0.4/24
+ ip igmp
+ ip pim
+ ip pim passive
+!
ip forwarding
!
ip route 10.0.0.0/24 10.0.2.2 50
-ip route 10.0.1.0/24 10.0.3.3 50
+ip route 10.0.1.0/24 10.0.2.2 50
+!
+ip prefix-list SRCPLIST permit 10.0.0.1/32
+ip prefix-list SRCPLIST2 permit 10.0.1.1/32
+ip prefix-list GRPPLIST permit 239.1.1.1/32
+ip prefix-list GRPPLIST2 permit 239.2.2.2/32
!
router pim
rpf-lookup-mode mrib-then-urib
rp 10.0.0.1 224.0.0.0/4
+ rp 10.0.1.1 225.0.0.0/24
! \ No newline at end of file
diff --git a/tests/topotests/pim_mrib/test_pim_mrib.py b/tests/topotests/pim_mrib/test_pim_mrib.py
index 355c503e3..2a391fa57 100644
--- a/tests/topotests/pim_mrib/test_pim_mrib.py
+++ b/tests/topotests/pim_mrib/test_pim_mrib.py
@@ -20,6 +20,8 @@ from lib.topogen import Topogen, get_topogen
from lib.topolog import logger
from lib.pim import (
verify_pim_rp_info,
+ verify_upstream_iif,
+ McastTesterHelper,
)
from lib.common_config import step, write_test_header
@@ -29,6 +31,8 @@ test_pim_mrib.py: Test PIM MRIB overrides and RPF modes
TOPOLOGY = """
Test PIM MRIB overrides and RPF modes
+ Static routes installed that uses R2 to get between R1 and R4.
+ Tests will install MRIB override through R3
+---+---+ +---+---+
| | 10.0.0.0/24 | |
@@ -42,7 +46,7 @@ TOPOLOGY = """
.3 | r3-eth0 r4-eth0 | .4
+---+---+ r3-eth1 r4-eth1 +---+---+
| | .3 .4 | |
- + R3 +----------------------+ R4 |
+ + R3 +----------------------+ R4 |---r4-dum0 10.10.0.4/24
| | 10.0.3.0/24 | |
+---+---+ +---+---+
"""
@@ -54,9 +58,12 @@ sys.path.append(os.path.join(CWD, "../"))
# Required to instantiate the topology builder class.
pytestmark = [pytest.mark.pimd]
+GROUP1 = "239.1.1.1"
+GROUP2 = "239.2.2.2"
+
def build_topo(tgen):
- '''Build function'''
+ """Build function"""
# Create routers
tgen.add_router("r1")
@@ -70,6 +77,8 @@ def build_topo(tgen):
tgen.add_link(tgen.gears["r2"], tgen.gears["r4"], "r2-eth1", "r4-eth0")
tgen.add_link(tgen.gears["r3"], tgen.gears["r4"], "r3-eth1", "r4-eth1")
+ tgen.gears["r4"].run("ip link add r4-dum0 type dummy")
+
def setup_module(mod):
logger.info("PIM MRIB/RPF functionality:\n {}".format(TOPOLOGY))
@@ -87,13 +96,13 @@ def setup_module(mod):
def teardown_module(mod):
- '''Teardown the pytest environment'''
+ """Teardown the pytest environment"""
tgen = get_topogen()
tgen.stop_topology()
def test_pim_mrib_init(request):
- '''Test boot in MRIB-than-URIB with the default MRIB'''
+ """Test boot in MRIB-than-URIB with the default MRIB"""
tgen = get_topogen()
tc_name = request.node.name
write_test_header(tc_name)
@@ -116,8 +125,23 @@ def test_pim_mrib_init(request):
)
assert result is True, "Testcase {} :Failed \n Error: {}".format(tc_name, result)
+ result = verify_pim_rp_info(
+ tgen,
+ None,
+ "r4",
+ "225.0.0.0/24",
+ "r4-eth0",
+ "10.0.1.1",
+ "Static",
+ False,
+ "ipv4",
+ True,
+ )
+ assert result is True, "Testcase {} :Failed \n Error: {}".format(tc_name, result)
+
+
def test_pim_mrib_override(request):
- '''Test MRIB override nexthop'''
+ """Test MRIB override nexthop"""
tgen = get_topogen()
tc_name = request.node.name
write_test_header(tc_name)
@@ -128,10 +152,11 @@ def test_pim_mrib_override(request):
# Install a MRIB route that has a shorter prefix length and lower cost.
# In MRIB-than-URIB mode, it should use this route
tgen.routers()["r4"].vtysh_cmd(
- '''
+ """
conf term
ip mroute 10.0.0.0/16 10.0.3.3 25
- '''
+ exit
+ """
)
step("Verify rp-info using MRIB nexthop")
@@ -149,8 +174,23 @@ def test_pim_mrib_override(request):
)
assert result is True, "Testcase {} :Failed \n Error: {}".format(tc_name, result)
+ result = verify_pim_rp_info(
+ tgen,
+ None,
+ "r4",
+ "225.0.0.0/24",
+ "r4-eth1",
+ "10.0.1.1",
+ "Static",
+ False,
+ "ipv4",
+ True,
+ )
+ assert result is True, "Testcase {} :Failed \n Error: {}".format(tc_name, result)
+
+
def test_pim_mrib_prefix_mode(request):
- '''Test longer prefix lookup mode'''
+ """Test longer prefix lookup mode"""
tgen = get_topogen()
tc_name = request.node.name
write_test_header(tc_name)
@@ -161,11 +201,13 @@ def test_pim_mrib_prefix_mode(request):
# Switch to longer prefix match, should switch back to the URIB route
# even with the lower cost, the longer prefix match will win because of the mode
tgen.routers()["r4"].vtysh_cmd(
- '''
+ """
conf term
router pim
rpf-lookup-mode longer-prefix
- '''
+ exit
+ exit
+ """
)
step("Verify rp-info using URIB nexthop")
@@ -183,8 +225,23 @@ def test_pim_mrib_prefix_mode(request):
)
assert result is True, "Testcase {} :Failed \n Error: {}".format(tc_name, result)
+ result = verify_pim_rp_info(
+ tgen,
+ None,
+ "r4",
+ "225.0.0.0/24",
+ "r4-eth0",
+ "10.0.1.1",
+ "Static",
+ False,
+ "ipv4",
+ True,
+ )
+ assert result is True, "Testcase {} :Failed \n Error: {}".format(tc_name, result)
+
+
def test_pim_mrib_dist_mode(request):
- '''Test lower distance lookup mode'''
+ """Test lower distance lookup mode"""
tgen = get_topogen()
tc_name = request.node.name
write_test_header(tc_name)
@@ -194,11 +251,13 @@ def test_pim_mrib_dist_mode(request):
# Switch to lower distance match, should switch back to the MRIB route
tgen.routers()["r4"].vtysh_cmd(
- '''
+ """
conf term
router pim
rpf-lookup-mode lower-distance
- '''
+ exit
+ exit
+ """
)
step("Verify rp-info using MRIB nexthop")
@@ -216,8 +275,23 @@ def test_pim_mrib_dist_mode(request):
)
assert result is True, "Testcase {} :Failed \n Error: {}".format(tc_name, result)
+ result = verify_pim_rp_info(
+ tgen,
+ None,
+ "r4",
+ "225.0.0.0/24",
+ "r4-eth1",
+ "10.0.1.1",
+ "Static",
+ False,
+ "ipv4",
+ True,
+ )
+ assert result is True, "Testcase {} :Failed \n Error: {}".format(tc_name, result)
+
+
def test_pim_mrib_urib_mode(request):
- '''Test URIB only lookup mode'''
+ """Test URIB only lookup mode"""
tgen = get_topogen()
tc_name = request.node.name
write_test_header(tc_name)
@@ -227,11 +301,13 @@ def test_pim_mrib_urib_mode(request):
# Switch to urib only match, should switch back to the URIB route
tgen.routers()["r4"].vtysh_cmd(
- '''
+ """
conf term
router pim
rpf-lookup-mode urib-only
- '''
+ exit
+ exit
+ """
)
step("Verify rp-info using URIB nexthop")
@@ -249,8 +325,23 @@ def test_pim_mrib_urib_mode(request):
)
assert result is True, "Testcase {} :Failed \n Error: {}".format(tc_name, result)
+ result = verify_pim_rp_info(
+ tgen,
+ None,
+ "r4",
+ "225.0.0.0/24",
+ "r4-eth0",
+ "10.0.1.1",
+ "Static",
+ False,
+ "ipv4",
+ True,
+ )
+ assert result is True, "Testcase {} :Failed \n Error: {}".format(tc_name, result)
+
+
def test_pim_mrib_mrib_mode(request):
- '''Test MRIB only lookup mode'''
+ """Test MRIB only lookup mode"""
tgen = get_topogen()
tc_name = request.node.name
write_test_header(tc_name)
@@ -260,11 +351,13 @@ def test_pim_mrib_mrib_mode(request):
# Switch to mrib only match, should switch back to the MRIB route
tgen.routers()["r4"].vtysh_cmd(
- '''
+ """
conf term
router pim
rpf-lookup-mode mrib-only
- '''
+ exit
+ exit
+ """
)
step("Verify rp-info using MRIB nexthop")
@@ -282,8 +375,23 @@ def test_pim_mrib_mrib_mode(request):
)
assert result is True, "Testcase {} :Failed \n Error: {}".format(tc_name, result)
+ result = verify_pim_rp_info(
+ tgen,
+ None,
+ "r4",
+ "225.0.0.0/24",
+ "r4-eth1",
+ "10.0.1.1",
+ "Static",
+ False,
+ "ipv4",
+ True,
+ )
+ assert result is True, "Testcase {} :Failed \n Error: {}".format(tc_name, result)
+
+
def test_pim_mrib_mrib_mode_no_route(request):
- '''Test MRIB only with no route'''
+ """Test MRIB only with no route"""
tgen = get_topogen()
tc_name = request.node.name
write_test_header(tc_name)
@@ -293,10 +401,11 @@ def test_pim_mrib_mrib_mode_no_route(request):
# Remove the MRIB route, in mrib-only mode, it should switch to no path for the RP
tgen.routers()["r4"].vtysh_cmd(
- '''
+ """
conf term
no ip mroute 10.0.0.0/16 10.0.3.3 25
- '''
+ exit
+ """
)
step("Verify rp-info with Unknown next hop")
@@ -314,8 +423,818 @@ def test_pim_mrib_mrib_mode_no_route(request):
)
assert result is True, "Testcase {} :Failed \n Error: {}".format(tc_name, result)
+ result = verify_pim_rp_info(
+ tgen,
+ None,
+ "r4",
+ "225.0.0.0/24",
+ "Unknown",
+ "10.0.1.1",
+ "Static",
+ False,
+ "ipv4",
+ True,
+ )
+ assert result is True, "Testcase {} :Failed \n Error: {}".format(tc_name, result)
+
+
+def test_pim_mrib_rpf_lookup_source_list_init(request):
+ """Test RPF lookup source list with initial setup"""
+ tgen = get_topogen()
+ tc_name = request.node.name
+ write_test_header(tc_name)
+
+ if tgen.routers_have_failure():
+ pytest.skip("skipped because of router(s) failure")
+
+ # Reset back to mrib then urib mode
+ # Also add mode using SRCPLIST(10.0.0.1) and SRCPLIST2(10.0.1.1)
+ tgen.routers()["r4"].vtysh_cmd(
+ """
+ conf term
+ router pim
+ rpf-lookup-mode mrib-then-urib
+ rpf-lookup-mode mrib-then-urib source-list SRCPLIST
+ rpf-lookup-mode mrib-then-urib source-list SRCPLIST2
+ exit
+ exit
+ """
+ )
+
+ step("Verify rp-info with default next hop")
+ result = verify_pim_rp_info(
+ tgen,
+ None,
+ "r4",
+ "224.0.0.0/4",
+ "r4-eth0",
+ "10.0.0.1",
+ "Static",
+ False,
+ "ipv4",
+ True,
+ )
+ assert result is True, "Testcase {} :Failed \n Error: {}".format(tc_name, result)
+
+ result = verify_pim_rp_info(
+ tgen,
+ None,
+ "r4",
+ "225.0.0.0/24",
+ "r4-eth0",
+ "10.0.1.1",
+ "Static",
+ False,
+ "ipv4",
+ True,
+ )
+ assert result is True, "Testcase {} :Failed \n Error: {}".format(tc_name, result)
+
+
+def test_pim_mrib_rpf_lookup_source_list_add_mroute(request):
+ """Test RPF lookup source list with MRIB route on alternate path"""
+ tgen = get_topogen()
+ tc_name = request.node.name
+ write_test_header(tc_name)
+
+ if tgen.routers_have_failure():
+ pytest.skip("skipped because of router(s) failure")
+
+ # Add a MRIB route through r4-eth1 that is better distance but worse prefix
+ tgen.routers()["r4"].vtysh_cmd(
+ """
+ conf term
+ ip mroute 10.0.0.0/16 10.0.3.3 25
+ exit
+ """
+ )
+
+ step("Verify rp-info with MRIB next hop")
+ result = verify_pim_rp_info(
+ tgen,
+ None,
+ "r4",
+ "224.0.0.0/4",
+ "r4-eth1",
+ "10.0.0.1",
+ "Static",
+ False,
+ "ipv4",
+ True,
+ )
+ assert result is True, "Testcase {} :Failed \n Error: {}".format(tc_name, result)
+
+ result = verify_pim_rp_info(
+ tgen,
+ None,
+ "r4",
+ "225.0.0.0/24",
+ "r4-eth1",
+ "10.0.1.1",
+ "Static",
+ False,
+ "ipv4",
+ True,
+ )
+ assert result is True, "Testcase {} :Failed \n Error: {}".format(tc_name, result)
+
+
+def test_pim_mrib_rpf_lookup_source_list_src1_prefix_mode(request):
+ """Test RPF lookup source list src1 longer prefix mode"""
+ tgen = get_topogen()
+ tc_name = request.node.name
+ write_test_header(tc_name)
+
+ if tgen.routers_have_failure():
+ pytest.skip("skipped because of router(s) failure")
+
+ # Switch just source 1 to longest prefix
+ tgen.routers()["r4"].vtysh_cmd(
+ """
+ conf term
+ router pim
+ rpf-lookup-mode longer-prefix source-list SRCPLIST
+ exit
+ exit
+ """
+ )
+
+ step("Verify rp-info with URIB next hop for source 1 and MRIB for source 2")
+ result = verify_pim_rp_info(
+ tgen,
+ None,
+ "r4",
+ "224.0.0.0/4",
+ "r4-eth0",
+ "10.0.0.1",
+ "Static",
+ False,
+ "ipv4",
+ True,
+ )
+ assert result is True, "Testcase {} :Failed \n Error: {}".format(tc_name, result)
+
+ result = verify_pim_rp_info(
+ tgen,
+ None,
+ "r4",
+ "225.0.0.0/24",
+ "r4-eth1",
+ "10.0.1.1",
+ "Static",
+ False,
+ "ipv4",
+ True,
+ )
+ assert result is True, "Testcase {} :Failed \n Error: {}".format(tc_name, result)
+
+
+def test_pim_mrib_rpf_lookup_source_list_src1_dist_src2_prefix_mode(request):
+ """Test RPF lookup source list src1 lower distance mode and src2 longer prefix mode"""
+ tgen = get_topogen()
+ tc_name = request.node.name
+ write_test_header(tc_name)
+
+ if tgen.routers_have_failure():
+ pytest.skip("skipped because of router(s) failure")
+
+ # Switch source 1 to shortest distance, source 2 to longest prefix
+ tgen.routers()["r4"].vtysh_cmd(
+ """
+ conf term
+ router pim
+ rpf-lookup-mode lower-distance source-list SRCPLIST
+ rpf-lookup-mode longer-prefix source-list SRCPLIST2
+ exit
+ exit
+ """
+ )
+
+ step("Verify rp-info with MRIB next hop for source 1 and URIB for source 2")
+ result = verify_pim_rp_info(
+ tgen,
+ None,
+ "r4",
+ "224.0.0.0/4",
+ "r4-eth1",
+ "10.0.0.1",
+ "Static",
+ False,
+ "ipv4",
+ True,
+ )
+ assert result is True, "Testcase {} :Failed \n Error: {}".format(tc_name, result)
+
+ result = verify_pim_rp_info(
+ tgen,
+ None,
+ "r4",
+ "225.0.0.0/24",
+ "r4-eth0",
+ "10.0.1.1",
+ "Static",
+ False,
+ "ipv4",
+ True,
+ )
+ assert result is True, "Testcase {} :Failed \n Error: {}".format(tc_name, result)
+
+
+def test_pim_mrib_rpf_lookup_source_list_src1_urib_src2_dist_mode(request):
+ """Test RPF lookup source list src1 urib mode and src2 lower distance mode"""
+ tgen = get_topogen()
+ tc_name = request.node.name
+ write_test_header(tc_name)
+
+ if tgen.routers_have_failure():
+ pytest.skip("skipped because of router(s) failure")
+
+ # Switch source 1 to urib only, source 2 to shorter distance
+ tgen.routers()["r4"].vtysh_cmd(
+ """
+ conf term
+ router pim
+ rpf-lookup-mode urib-only source-list SRCPLIST
+ rpf-lookup-mode lower-distance source-list SRCPLIST2
+ exit
+ exit
+ """
+ )
+
+ step("Verify rp-info with URIB next hop for source 1 and MRIB for source 2")
+ result = verify_pim_rp_info(
+ tgen,
+ None,
+ "r4",
+ "224.0.0.0/4",
+ "r4-eth0",
+ "10.0.0.1",
+ "Static",
+ False,
+ "ipv4",
+ True,
+ )
+ assert result is True, "Testcase {} :Failed \n Error: {}".format(tc_name, result)
+
+ result = verify_pim_rp_info(
+ tgen,
+ None,
+ "r4",
+ "225.0.0.0/24",
+ "r4-eth1",
+ "10.0.1.1",
+ "Static",
+ False,
+ "ipv4",
+ True,
+ )
+ assert result is True, "Testcase {} :Failed \n Error: {}".format(tc_name, result)
+
+
+def test_pim_mrib_rpf_lookup_source_list_src1_mrib_src2_urib_mode(request):
+ """Test RPF lookup source list src1 mrib mode and src2 urib mode"""
+ tgen = get_topogen()
+ tc_name = request.node.name
+ write_test_header(tc_name)
+
+ if tgen.routers_have_failure():
+ pytest.skip("skipped because of router(s) failure")
+
+ # Switch source 1 to mrib only, source 2 to urib only
+ tgen.routers()["r4"].vtysh_cmd(
+ """
+ conf term
+ router pim
+ rpf-lookup-mode mrib-only source-list SRCPLIST
+ rpf-lookup-mode urib-only source-list SRCPLIST2
+ exit
+ exit
+ """
+ )
+
+ step("Verify rp-info with MRIB next hop for source 1 and URIB for source 2")
+ result = verify_pim_rp_info(
+ tgen,
+ None,
+ "r4",
+ "224.0.0.0/4",
+ "r4-eth1",
+ "10.0.0.1",
+ "Static",
+ False,
+ "ipv4",
+ True,
+ )
+ assert result is True, "Testcase {} :Failed \n Error: {}".format(tc_name, result)
+
+ result = verify_pim_rp_info(
+ tgen,
+ None,
+ "r4",
+ "225.0.0.0/24",
+ "r4-eth0",
+ "10.0.1.1",
+ "Static",
+ False,
+ "ipv4",
+ True,
+ )
+ assert result is True, "Testcase {} :Failed \n Error: {}".format(tc_name, result)
+
+
+def test_pim_mrib_rpf_lookup_source_list_removed(request):
+ """Test RPF lookup source list removed"""
+ tgen = get_topogen()
+ tc_name = request.node.name
+ write_test_header(tc_name)
+
+ if tgen.routers_have_failure():
+ pytest.skip("skipped because of router(s) failure")
+
+ # Remove both special modes, both should switch to MRIB route
+ tgen.routers()["r4"].vtysh_cmd(
+ """
+ conf term
+ router pim
+ no rpf-lookup-mode mrib-only source-list SRCPLIST
+ no rpf-lookup-mode urib-only source-list SRCPLIST2
+ exit
+ exit
+ """
+ )
+
+ step("Verify rp-info with MRIB next hop for both sources")
+ result = verify_pim_rp_info(
+ tgen,
+ None,
+ "r4",
+ "224.0.0.0/4",
+ "r4-eth1",
+ "10.0.0.1",
+ "Static",
+ False,
+ "ipv4",
+ True,
+ )
+ assert result is True, "Testcase {} :Failed \n Error: {}".format(tc_name, result)
+
+ result = verify_pim_rp_info(
+ tgen,
+ None,
+ "r4",
+ "225.0.0.0/24",
+ "r4-eth1",
+ "10.0.1.1",
+ "Static",
+ False,
+ "ipv4",
+ True,
+ )
+ assert result is True, "Testcase {} :Failed \n Error: {}".format(tc_name, result)
+
+
+def test_pim_mrib_rpf_lookup_source_list_del_mroute(request):
+ """Test RPF lookup source list delete mroute"""
+ tgen = get_topogen()
+ tc_name = request.node.name
+ write_test_header(tc_name)
+
+ if tgen.routers_have_failure():
+ pytest.skip("skipped because of router(s) failure")
+
+ # Remove the MRIB route, both should switch to URIB
+ tgen.routers()["r4"].vtysh_cmd(
+ """
+ conf term
+ no ip mroute 10.0.0.0/16 10.0.3.3 25
+ exit
+ """
+ )
+
+ step("Verify rp-info with URIB next hop for both sources")
+ result = verify_pim_rp_info(
+ tgen,
+ None,
+ "r4",
+ "224.0.0.0/4",
+ "r4-eth0",
+ "10.0.0.1",
+ "Static",
+ False,
+ "ipv4",
+ True,
+ )
+ assert result is True, "Testcase {} :Failed \n Error: {}".format(tc_name, result)
+
+ result = verify_pim_rp_info(
+ tgen,
+ None,
+ "r4",
+ "225.0.0.0/24",
+ "r4-eth0",
+ "10.0.1.1",
+ "Static",
+ False,
+ "ipv4",
+ True,
+ )
+ assert result is True, "Testcase {} :Failed \n Error: {}".format(tc_name, result)
+
+
+def test_pim_mrib_rpf_lookup_group_list(request):
+ """Test RPF lookup group list"""
+ tgen = get_topogen()
+ tc_name = request.node.name
+ write_test_header(tc_name)
+
+ if tgen.routers_have_failure():
+ pytest.skip("skipped because of router(s) failure")
+
+ with McastTesterHelper(tgen) as apphelper:
+ step(
+ ("Send multicast traffic from R1 to dense groups {}, {}").format(
+ GROUP1, GROUP2
+ )
+ )
+ result = apphelper.run_traffic("r1", [GROUP1, GROUP2], bind_intf="r1-eth1")
+ assert result is True, "Testcase {} : Failed Error: {}".format(tc_name, result)
+
+ # Reset back to mrib then urib mode
+ # Also add mode using GRPPLIST(239.1.1.1) and GRPPLIST2(239.2.2.2)
+ # And do an igmp join to both groups on r4-eth2
+ tgen.routers()["r4"].vtysh_cmd(
+ """
+ conf term
+ router pim
+ rpf-lookup-mode mrib-then-urib
+ rpf-lookup-mode mrib-then-urib group-list GRPPLIST
+ rpf-lookup-mode mrib-then-urib group-list GRPPLIST2
+ exit
+ int r4-dum0
+ ip igmp join-group {}
+ ip igmp join-group {}
+ exit
+ exit
+ """.format(
+ GROUP1, GROUP2
+ )
+ )
+
+ step("Verify upstream iif with default next hop")
+ result = verify_upstream_iif(tgen, "r4", "r4-eth0", "10.0.1.1", GROUP1)
+ assert result is True, "Testcase {} :Failed \n Error: {}".format(
+ tc_name, result
+ )
+
+ result = verify_upstream_iif(tgen, "r4", "r4-eth0", "10.0.1.1", GROUP2)
+ assert result is True, "Testcase {} :Failed \n Error: {}".format(
+ tc_name, result
+ )
+
+ step("Add MRIB route through alternate path")
+ tgen.routers()["r4"].vtysh_cmd(
+ """
+ conf term
+ ip mroute 10.0.0.0/16 10.0.3.3 25
+ exit
+ """
+ )
+
+ step("Verify upstream iif with alternate next hop")
+ result = verify_upstream_iif(tgen, "r4", "r4-eth1", "10.0.1.1", GROUP1)
+ assert result is True, "Testcase {} :Failed \n Error: {}".format(
+ tc_name, result
+ )
+
+ result = verify_upstream_iif(tgen, "r4", "r4-eth1", "10.0.1.1", GROUP2)
+ assert result is True, "Testcase {} :Failed \n Error: {}".format(
+ tc_name, result
+ )
+
+ step("Switch group1 to longer prefix match (URIB)")
+ tgen.routers()["r4"].vtysh_cmd(
+ """
+ conf term
+ router pim
+ rpf-lookup-mode longer-prefix group-list GRPPLIST
+ exit
+ exit
+ """.format(
+ GROUP1, GROUP2
+ )
+ )
+
+ step("Verify upstream iif of group1 is URIB, group2 is MRIB")
+ result = verify_upstream_iif(tgen, "r4", "r4-eth0", "10.0.1.1", GROUP1)
+ assert result is True, "Testcase {} :Failed \n Error: {}".format(
+ tc_name, result
+ )
+
+ result = verify_upstream_iif(tgen, "r4", "r4-eth1", "10.0.1.1", GROUP2)
+ assert result is True, "Testcase {} :Failed \n Error: {}".format(
+ tc_name, result
+ )
+
+ step(
+ "Switch group1 to lower distance match (MRIB), and group2 to longer prefix (URIB)"
+ )
+ tgen.routers()["r4"].vtysh_cmd(
+ """
+ conf term
+ router pim
+ rpf-lookup-mode lower-distance group-list GRPPLIST
+ rpf-lookup-mode longer-prefix group-list GRPPLIST2
+ exit
+ exit
+ """.format(
+ GROUP1, GROUP2
+ )
+ )
+
+ step("Verify upstream iif of group1 is MRIB, group2 is URIB")
+ result = verify_upstream_iif(tgen, "r4", "r4-eth1", "10.0.1.1", GROUP1)
+ assert result is True, "Testcase {} :Failed \n Error: {}".format(
+ tc_name, result
+ )
+
+ result = verify_upstream_iif(tgen, "r4", "r4-eth0", "10.0.1.1", GROUP2)
+ assert result is True, "Testcase {} :Failed \n Error: {}".format(
+ tc_name, result
+ )
+
+ step("Switch group1 to urib match only, and group2 to lower distance (URIB)")
+ tgen.routers()["r4"].vtysh_cmd(
+ """
+ conf term
+ router pim
+ rpf-lookup-mode urib-only group-list GRPPLIST
+ rpf-lookup-mode lower-distance group-list GRPPLIST2
+ exit
+ exit
+ """.format(
+ GROUP1, GROUP2
+ )
+ )
+
+ step("Verify upstream iif of group1 is URIB, group2 is MRIB")
+ result = verify_upstream_iif(tgen, "r4", "r4-eth0", "10.0.1.1", GROUP1)
+ assert result is True, "Testcase {} :Failed \n Error: {}".format(
+ tc_name, result
+ )
+
+ result = verify_upstream_iif(tgen, "r4", "r4-eth1", "10.0.1.1", GROUP2)
+ assert result is True, "Testcase {} :Failed \n Error: {}".format(
+ tc_name, result
+ )
+
+ step("Switch group1 to mrib match only, and group2 to urib match only")
+ tgen.routers()["r4"].vtysh_cmd(
+ """
+ conf term
+ router pim
+ rpf-lookup-mode mrib-only group-list GRPPLIST
+ rpf-lookup-mode urib-only group-list GRPPLIST2
+ exit
+ exit
+ """.format(
+ GROUP1, GROUP2
+ )
+ )
+
+ step("Verify upstream iif of group1 is MRIB, group2 is URIB")
+ result = verify_upstream_iif(tgen, "r4", "r4-eth1", "10.0.1.1", GROUP1)
+ assert result is True, "Testcase {} :Failed \n Error: {}".format(
+ tc_name, result
+ )
+
+ result = verify_upstream_iif(tgen, "r4", "r4-eth0", "10.0.1.1", GROUP2)
+ assert result is True, "Testcase {} :Failed \n Error: {}".format(
+ tc_name, result
+ )
+
+ step("Delete MRIB route")
+ tgen.routers()["r4"].vtysh_cmd(
+ """
+ conf term
+ no ip mroute 10.0.0.0/16 10.0.3.3 25
+ exit
+ """.format(
+ GROUP1, GROUP2
+ )
+ )
+
+ step("Verify upstream iif of group1 is Unknown, group2 is URIB")
+ result = verify_upstream_iif(
+ tgen, "r4", "Unknown", "10.0.1.1", GROUP1, "NotJoined"
+ )
+ assert result is True, "Testcase {} :Failed \n Error: {}".format(
+ tc_name, result
+ )
+
+ result = verify_upstream_iif(tgen, "r4", "r4-eth0", "10.0.1.1", GROUP2)
+ assert result is True, "Testcase {} :Failed \n Error: {}".format(
+ tc_name, result
+ )
+
+
+def test_pim_mrib_rpf_lookup_source_group_lists(request):
+ """Test RPF lookup source and group lists"""
+ tgen = get_topogen()
+ tc_name = request.node.name
+ write_test_header(tc_name)
+
+ if tgen.routers_have_failure():
+ pytest.skip("skipped because of router(s) failure")
+
+ with McastTesterHelper(tgen) as apphelper:
+ step(
+ ("Send multicast traffic from R1 to dense groups {}, {}").format(
+ GROUP1, GROUP2
+ )
+ )
+ result = apphelper.run_traffic("r1", [GROUP1, GROUP2], bind_intf="r1-eth1")
+ assert result is True, "Testcase {} : Failed Error: {}".format(tc_name, result)
+
+ # Reset back to mrib then urib mode
+ # Also add mode using GRPPLIST(239.1.1.1) and GRPPLIST2(239.2.2.2), both using SRCPLIST2
+ # And do an igmp join to both groups on r4-eth2
+ tgen.routers()["r4"].vtysh_cmd(
+ """
+ conf term
+ router pim
+ rpf-lookup-mode mrib-then-urib
+ rpf-lookup-mode mrib-then-urib group-list GRPPLIST source-list SRCPLIST2
+ rpf-lookup-mode mrib-then-urib group-list GRPPLIST2 source-list SRCPLIST2
+ exit
+ int r4-dum0
+ ip igmp join-group {}
+ ip igmp join-group {}
+ exit
+ exit
+ """.format(
+ GROUP1, GROUP2
+ )
+ )
+
+ step("Verify upstream iif with default next hop")
+ result = verify_upstream_iif(tgen, "r4", "r4-eth0", "10.0.1.1", GROUP1)
+ assert result is True, "Testcase {} :Failed \n Error: {}".format(
+ tc_name, result
+ )
+
+ result = verify_upstream_iif(tgen, "r4", "r4-eth0", "10.0.1.1", GROUP2)
+ assert result is True, "Testcase {} :Failed \n Error: {}".format(
+ tc_name, result
+ )
+
+ step("Add MRIB route through alternate path")
+ tgen.routers()["r4"].vtysh_cmd(
+ """
+ conf term
+ ip mroute 10.0.0.0/16 10.0.3.3 25
+ exit
+ """
+ )
+
+ step("Verify upstream iif with alternate next hop")
+ result = verify_upstream_iif(tgen, "r4", "r4-eth1", "10.0.1.1", GROUP1)
+ assert result is True, "Testcase {} :Failed \n Error: {}".format(
+ tc_name, result
+ )
+
+ result = verify_upstream_iif(tgen, "r4", "r4-eth1", "10.0.1.1", GROUP2)
+ assert result is True, "Testcase {} :Failed \n Error: {}".format(
+ tc_name, result
+ )
+
+ step("Switch group1 to longer prefix match (URIB)")
+ tgen.routers()["r4"].vtysh_cmd(
+ """
+ conf term
+ router pim
+ rpf-lookup-mode longer-prefix group-list GRPPLIST source-list SRCPLIST2
+ exit
+ exit
+ """.format(
+ GROUP1, GROUP2
+ )
+ )
+
+ step("Verify upstream iif of group1 is URIB, group2 is MRIB")
+ result = verify_upstream_iif(tgen, "r4", "r4-eth0", "10.0.1.1", GROUP1)
+ assert result is True, "Testcase {} :Failed \n Error: {}".format(
+ tc_name, result
+ )
+
+ result = verify_upstream_iif(tgen, "r4", "r4-eth1", "10.0.1.1", GROUP2)
+ assert result is True, "Testcase {} :Failed \n Error: {}".format(
+ tc_name, result
+ )
+
+ step(
+ "Switch group1 to lower distance match (MRIB), and group2 to longer prefix (URIB)"
+ )
+ tgen.routers()["r4"].vtysh_cmd(
+ """
+ conf term
+ router pim
+ rpf-lookup-mode lower-distance group-list GRPPLIST source-list SRCPLIST2
+ rpf-lookup-mode longer-prefix group-list GRPPLIST2 source-list SRCPLIST2
+ exit
+ exit
+ """.format(
+ GROUP1, GROUP2
+ )
+ )
+
+ step("Verify upstream iif of group1 is MRIB, group2 is URIB")
+ result = verify_upstream_iif(tgen, "r4", "r4-eth1", "10.0.1.1", GROUP1)
+ assert result is True, "Testcase {} :Failed \n Error: {}".format(
+ tc_name, result
+ )
+
+ result = verify_upstream_iif(tgen, "r4", "r4-eth0", "10.0.1.1", GROUP2)
+ assert result is True, "Testcase {} :Failed \n Error: {}".format(
+ tc_name, result
+ )
+
+ step("Switch group1 to urib match only, and group2 to lower distance (URIB)")
+ tgen.routers()["r4"].vtysh_cmd(
+ """
+ conf term
+ router pim
+ rpf-lookup-mode urib-only group-list GRPPLIST source-list SRCPLIST2
+ rpf-lookup-mode lower-distance group-list GRPPLIST2 source-list SRCPLIST2
+ exit
+ exit
+ """.format(
+ GROUP1, GROUP2
+ )
+ )
+
+ step("Verify upstream iif of group1 is URIB, group2 is MRIB")
+ result = verify_upstream_iif(tgen, "r4", "r4-eth0", "10.0.1.1", GROUP1)
+ assert result is True, "Testcase {} :Failed \n Error: {}".format(
+ tc_name, result
+ )
+
+ result = verify_upstream_iif(tgen, "r4", "r4-eth1", "10.0.1.1", GROUP2)
+ assert result is True, "Testcase {} :Failed \n Error: {}".format(
+ tc_name, result
+ )
+
+ step("Switch group1 to mrib match only, and group2 to urib match only")
+ tgen.routers()["r4"].vtysh_cmd(
+ """
+ conf term
+ router pim
+ rpf-lookup-mode mrib-only group-list GRPPLIST source-list SRCPLIST2
+ rpf-lookup-mode urib-only group-list GRPPLIST2 source-list SRCPLIST2
+ exit
+ exit
+ """.format(
+ GROUP1, GROUP2
+ )
+ )
+
+ step("Verify upstream iif of group1 is MRIB, group2 is URIB")
+ result = verify_upstream_iif(tgen, "r4", "r4-eth1", "10.0.1.1", GROUP1)
+ assert result is True, "Testcase {} :Failed \n Error: {}".format(
+ tc_name, result
+ )
+
+ result = verify_upstream_iif(tgen, "r4", "r4-eth0", "10.0.1.1", GROUP2)
+ assert result is True, "Testcase {} :Failed \n Error: {}".format(
+ tc_name, result
+ )
+
+ step("Delete MRIB route")
+ tgen.routers()["r4"].vtysh_cmd(
+ """
+ conf term
+ no ip mroute 10.0.0.0/16 10.0.3.3 25
+ exit
+ """.format(
+ GROUP1, GROUP2
+ )
+ )
+
+ step("Verify upstream iif of group1 is Unknown, group2 is URIB")
+ result = verify_upstream_iif(
+ tgen, "r4", "Unknown", "10.0.1.1", GROUP1, "NotJoined"
+ )
+ assert result is True, "Testcase {} :Failed \n Error: {}".format(
+ tc_name, result
+ )
+
+ result = verify_upstream_iif(tgen, "r4", "r4-eth0", "10.0.1.1", GROUP2)
+ assert result is True, "Testcase {} :Failed \n Error: {}".format(
+ tc_name, result
+ )
+
+
def test_memory_leak():
- '''Run the memory leak test and report results.'''
+ """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")
diff --git a/tests/topotests/static_srv6_sids/__init__.py b/tests/topotests/static_srv6_sids/__init__.py
new file mode 100644
index 000000000..e69de29bb
--- /dev/null
+++ b/tests/topotests/static_srv6_sids/__init__.py
diff --git a/tests/topotests/static_srv6_sids/expected_srv6_sids.json b/tests/topotests/static_srv6_sids/expected_srv6_sids.json
new file mode 100644
index 000000000..e1a2a16af
--- /dev/null
+++ b/tests/topotests/static_srv6_sids/expected_srv6_sids.json
@@ -0,0 +1,107 @@
+{
+ "fcbb:bbbb:1:fe10::/64": [
+ {
+ "prefix": "fcbb:bbbb:1:fe10::/64",
+ "prefixLen": 64,
+ "protocol": "static",
+ "vrfId": 0,
+ "vrfName": "default",
+ "selected": true,
+ "destSelected": true,
+ "distance": 1,
+ "metric": 0,
+ "installed": true,
+ "table": 254,
+ "internalStatus": 16,
+ "internalFlags": 9,
+ "internalNextHopNum": 1,
+ "internalNextHopActiveNum": 1,
+ "nexthops": [
+ {
+ "flags": 3,
+ "fib": true,
+ "directlyConnected": true,
+ "interfaceName": "Vrf10",
+ "active": true,
+ "weight": 1,
+ "seg6local": {
+ "action": "End.DT4"
+ },
+ "seg6localContext": {
+ "table": 10
+ }
+ }
+ ]
+ }
+ ],
+ "fcbb:bbbb:1:fe20::/64": [
+ {
+ "prefix": "fcbb:bbbb:1:fe20::/64",
+ "prefixLen": 64,
+ "protocol": "static",
+ "vrfId": 0,
+ "vrfName": "default",
+ "selected": true,
+ "destSelected": true,
+ "distance": 1,
+ "metric": 0,
+ "installed": true,
+ "table": 254,
+ "internalStatus": 16,
+ "internalFlags": 9,
+ "internalNextHopNum": 1,
+ "internalNextHopActiveNum": 1,
+ "nexthops": [
+ {
+ "flags": 3,
+ "fib": true,
+ "directlyConnected": true,
+ "interfaceName": "Vrf20",
+ "active": true,
+ "weight": 1,
+ "seg6local": {
+ "action": "End.DT6"
+ },
+ "seg6localContext": {
+ "table": 20
+ }
+ }
+ ]
+ }
+ ],
+ "fcbb:bbbb:1:fe30::/64": [
+ {
+ "prefix": "fcbb:bbbb:1:fe30::/64",
+ "prefixLen": 64,
+ "protocol": "static",
+ "vrfId": 0,
+ "vrfName": "default",
+ "selected": true,
+ "destSelected": true,
+ "distance": 1,
+ "metric": 0,
+ "installed": true,
+ "table": 254,
+ "internalStatus": 16,
+ "internalFlags": 9,
+ "internalNextHopNum": 1,
+ "internalNextHopActiveNum": 1,
+ "nexthops": [
+ {
+ "flags": 3,
+ "fib": true,
+ "directlyConnected": true,
+ "interfaceName": "Vrf30",
+ "active": true,
+ "weight": 1,
+ "seg6local": {
+ "action": "End.DT46"
+ },
+ "seg6localContext": {
+ "table": 30
+ }
+ }
+ ]
+ }
+ ]
+} \ No newline at end of file
diff --git a/tests/topotests/static_srv6_sids/r1/frr.conf b/tests/topotests/static_srv6_sids/r1/frr.conf
new file mode 100644
index 000000000..999e35c35
--- /dev/null
+++ b/tests/topotests/static_srv6_sids/r1/frr.conf
@@ -0,0 +1,16 @@
+hostname r1
+!
+segment-routing
+ srv6
+ locators
+ locator MAIN
+ prefix fcbb:bbbb:1::/48 block-len 32 node-len 16 func-bits 16
+ !
+ !
+ static-sids
+ sid fcbb:bbbb:1:fe10::/64 locator MAIN behavior uDT4 vrf Vrf10
+ sid fcbb:bbbb:1:fe20::/64 locator MAIN behavior uDT6 vrf Vrf20
+ sid fcbb:bbbb:1:fe30::/64 locator MAIN behavior uDT46 vrf Vrf30
+ !
+ !
+! \ No newline at end of file
diff --git a/tests/topotests/static_srv6_sids/r1/setup.sh b/tests/topotests/static_srv6_sids/r1/setup.sh
new file mode 100644
index 000000000..040be7391
--- /dev/null
+++ b/tests/topotests/static_srv6_sids/r1/setup.sh
@@ -0,0 +1,13 @@
+ip link add sr0 type dummy
+ip link set sr0 up
+
+ip link add Vrf10 type vrf table 10
+ip link set Vrf10 up
+
+ip link add Vrf20 type vrf table 20
+ip link set Vrf20 up
+
+ip link add Vrf30 type vrf table 30
+ip link set Vrf30 up
+
+sysctl -w net.vrf.strict_mode=1
diff --git a/tests/topotests/static_srv6_sids/test_static_srv6_sids.py b/tests/topotests/static_srv6_sids/test_static_srv6_sids.py
new file mode 100755
index 000000000..453a30af4
--- /dev/null
+++ b/tests/topotests/static_srv6_sids/test_static_srv6_sids.py
@@ -0,0 +1,83 @@
+#!/usr/bin/env python
+# SPDX-License-Identifier: ISC
+
+#
+# test_static_srv6_sids.py
+#
+# Copyright (c) 2025 by
+# Alibaba Inc, Yuqing Zhao <galadriel.zyq@alibaba-inc.com>
+# Lingyu Zhang <hanyu.zly@alibaba-inc.com>
+#
+
+"""
+test_static_srv6_sids.py:
+Test for SRv6 static route on zebra
+"""
+
+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.staticd]
+
+
+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 setup_module(mod):
+ tgen = Topogen({None: "r1"}, mod.__name__)
+ tgen.start_topology()
+ for rname, router in tgen.routers().items():
+ router.run("/bin/bash {}/{}/setup.sh".format(CWD, rname))
+ router.load_frr_config("frr.conf")
+ tgen.start_router()
+
+
+def teardown_module():
+ tgen = get_topogen()
+ tgen.stop_topology()
+
+
+def test_srv6_static_sids():
+ tgen = get_topogen()
+ if tgen.routers_have_failure():
+ pytest.skip(tgen.errors)
+ router = tgen.gears["r1"]
+
+ def _check_srv6_static_sids(router, expected_route_file):
+ logger.info("checking zebra srv6 static sids")
+ output = json.loads(router.vtysh_cmd("show ipv6 route static json"))
+ expected = open_json_file("{}/{}".format(CWD, expected_route_file))
+ return topotest.json_cmp(output, expected)
+
+ def check_srv6_static_sids(router, expected_file):
+ func = functools.partial(_check_srv6_static_sids, router, expected_file)
+ _, result = topotest.run_and_expect(func, None, count=15, wait=1)
+ assert result is None, "Failed"
+
+ # FOR DEVELOPER:
+ # If you want to stop some specific line and start interactive shell,
+ # please use tgen.mininet_cli() to start it.
+
+ logger.info("Test for srv6 sids configuration")
+ check_srv6_static_sids(router, "expected_srv6_sids.json")
+
+
+if __name__ == "__main__":
+ args = ["-s"] + sys.argv[1:]
+ sys.exit(pytest.main(args))
diff --git a/tools/frr-reload.py b/tools/frr-reload.py
index dba50b3c5..a138e4e23 100755
--- a/tools/frr-reload.py
+++ b/tools/frr-reload.py
@@ -948,10 +948,14 @@ def bgp_remove_neighbor_cfg(lines_to_del, del_nbr_dict):
lines_to_del_to_del = []
for ctx_keys, line in lines_to_del:
+ # lines_to_del has following
+ # (('router bgp 100',), 'neighbor swp1.10 interface peer-group dpeergrp_2'),
+ # (('router bgp 100',), 'neighbor swp1.10 advertisement-interval 1'),
+ # (('router bgp 100',), 'no neighbor swp1.10 capability dynamic'),
if (
ctx_keys[0].startswith("router bgp")
and line
- and line.startswith("neighbor ")
+ and ((line.startswith("neighbor ") or line.startswith("no neighbor ")))
):
if ctx_keys[0] in del_nbr_dict:
for nbr in del_nbr_dict[ctx_keys[0]]:
@@ -1753,12 +1757,13 @@ def compare_context_objects(newconf, running):
delete_bgpd = True
lines_to_del.append((running_ctx_keys, None))
- # We cannot do 'no interface' or 'no vrf' in FRR, and so deal with it
- elif (
- running_ctx_keys[0].startswith("interface")
- or running_ctx_keys[0].startswith("vrf")
- or running_ctx_keys[0].startswith("router pim")
- ):
+ elif running_ctx_keys[0].startswith("interface"):
+ lines_to_del.append((running_ctx_keys, None))
+
+ # We cannot do 'no vrf' in FRR, and so deal with it
+ elif running_ctx_keys[0].startswith("vrf") or running_ctx_keys[
+ 0
+ ].startswith("router pim"):
for line in running_ctx.lines:
lines_to_del.append((running_ctx_keys, line))
diff --git a/tools/gen_northbound_callbacks.c b/tools/gen_northbound_callbacks.c
index 019404d7c..87ba43eaa 100644
--- a/tools/gen_northbound_callbacks.c
+++ b/tools/gen_northbound_callbacks.c
@@ -238,12 +238,10 @@ static int generate_prototypes(const struct lysc_node *snode, void *arg)
generate_callback_name(snode, cb->operation, cb_name,
sizeof(cb_name));
- if (cb->operation == NB_CB_GET_ELEM) {
- if (f_new_cbs)
- generate_prototype(&nb_oper_get, cb_name);
- else
- generate_prototype(cb, cb_name);
- }
+ if (cb->operation == NB_CB_GET_ELEM && f_new_cbs)
+ generate_prototype(&nb_oper_get, cb_name);
+ else
+ generate_prototype(cb, cb_name);
if (cb->need_config_write && need_config_write) {
generate_config_write_cb_name(snode, cb_name,
@@ -344,12 +342,10 @@ static int generate_callbacks(const struct lysc_node *snode, void *arg)
generate_callback_name(snode, cb->operation, cb_name,
sizeof(cb_name));
- if (cb->operation == NB_CB_GET_ELEM) {
- if (f_new_cbs)
- generate_callback(&nb_oper_get, cb_name);
- else
- generate_callback(cb, cb_name);
- }
+ if (cb->operation == NB_CB_GET_ELEM && f_new_cbs)
+ generate_callback(&nb_oper_get, cb_name);
+ else
+ generate_callback(cb, cb_name);
if (cb->need_config_write && need_config_write) {
generate_config_write_cb_name(snode, cb_name,
diff --git a/vtysh/vtysh.c b/vtysh/vtysh.c
index c460dea70..a1b16c2b6 100644
--- a/vtysh/vtysh.c
+++ b/vtysh/vtysh.c
@@ -1312,6 +1312,13 @@ static struct cmd_node srv6_node = {
.prompt = "%s(config-srv6)# ",
};
+static struct cmd_node srv6_sids_node = {
+ .name = "srv6-sids",
+ .node = SRV6_SIDS_NODE,
+ .parent_node = SRV6_NODE,
+ .prompt = "%s(config-srv6-sids)# ",
+};
+
static struct cmd_node srv6_locs_node = {
.name = "srv6-locators",
.node = SRV6_LOCS_NODE,
@@ -1685,7 +1692,7 @@ DEFUNSH(VTYSH_REALLYALL, vtysh_end_all, vtysh_end_all_cmd, "end",
return vtysh_end();
}
-DEFUNSH(VTYSH_ZEBRA, srv6, srv6_cmd,
+DEFUNSH(VTYSH_ZEBRA | VTYSH_MGMTD, srv6, srv6_cmd,
"srv6",
"Segment-Routing SRv6 configuration\n")
{
@@ -1693,6 +1700,14 @@ DEFUNSH(VTYSH_ZEBRA, srv6, srv6_cmd,
return CMD_SUCCESS;
}
+DEFUNSH(VTYSH_MGMTD, srv6_sids, srv6_sids_cmd,
+ "static-sids",
+ "Segment-Routing SRv6 SIDs configuration\n")
+{
+ vty->node = SRV6_SIDS_NODE;
+ return CMD_SUCCESS;
+}
+
DEFUNSH(VTYSH_ZEBRA, srv6_locators, srv6_locators_cmd,
"locators",
"Segment-Routing SRv6 locators configuration\n")
@@ -2216,7 +2231,7 @@ DEFUNSH(VTYSH_FABRICD, router_openfabric, router_openfabric_cmd, "router openfab
}
#endif /* HAVE_FABRICD */
-DEFUNSH(VTYSH_SR, segment_routing, segment_routing_cmd,
+DEFUNSH(VTYSH_SR | VTYSH_MGMTD, segment_routing, segment_routing_cmd,
"segment-routing",
"Configure segment routing\n")
{
@@ -2535,7 +2550,7 @@ DEFUNSH(VTYSH_VRF, exit_vrf_config, exit_vrf_config_cmd, "exit-vrf",
return CMD_SUCCESS;
}
-DEFUNSH(VTYSH_ZEBRA, exit_srv6_config, exit_srv6_config_cmd, "exit",
+DEFUNSH(VTYSH_ZEBRA | VTYSH_MGMTD, exit_srv6_config, exit_srv6_config_cmd, "exit",
"Exit from SRv6 configuration mode\n")
{
if (vty->node == SRV6_NODE)
@@ -2551,6 +2566,14 @@ DEFUNSH(VTYSH_ZEBRA, exit_srv6_locs_config, exit_srv6_locs_config_cmd, "exit",
return CMD_SUCCESS;
}
+DEFUNSH(VTYSH_MGMTD, exit_srv6_sids_config, exit_srv6_sids_config_cmd, "exit",
+ "Exit from SRv6-SIDs configuration mode\n")
+{
+ if (vty->node == SRV6_SIDS_NODE)
+ vty->node = SRV6_NODE;
+ return CMD_SUCCESS;
+}
+
DEFUNSH(VTYSH_ZEBRA, exit_srv6_loc_config, exit_srv6_loc_config_cmd, "exit",
"Exit from SRv6-locators configuration mode\n")
{
@@ -2806,13 +2829,13 @@ DEFUNSH(VTYSH_KEYS, vtysh_quit_keys, vtysh_quit_keys_cmd, "quit",
return vtysh_exit_keys(self, vty, argc, argv);
}
-DEFUNSH(VTYSH_SR, vtysh_exit_sr, vtysh_exit_sr_cmd, "exit",
+DEFUNSH(VTYSH_SR | VTYSH_MGMTD, vtysh_exit_sr, vtysh_exit_sr_cmd, "exit",
"Exit current mode and down to previous mode\n")
{
return vtysh_exit(vty);
}
-DEFUNSH(VTYSH_SR, vtysh_quit_sr, vtysh_quit_sr_cmd, "quit",
+DEFUNSH(VTYSH_SR | VTYSH_MGMTD, vtysh_quit_sr, vtysh_quit_sr_cmd, "quit",
"Exit current mode and down to previous mode\n")
{
return vtysh_exit(vty);
@@ -4999,6 +5022,7 @@ void vtysh_init_vty(void)
install_node(&rmap_node);
install_node(&vty_node);
install_node(&srv6_node);
+ install_node(&srv6_sids_node);
install_node(&srv6_locs_node);
install_node(&srv6_loc_node);
install_node(&srv6_encap_node);
@@ -5442,6 +5466,10 @@ void vtysh_init_vty(void)
install_element(SRV6_NODE, &exit_srv6_config_cmd);
install_element(SRV6_NODE, &vtysh_end_all_cmd);
install_element(SRV6_NODE, &srv6_encap_cmd);
+ install_element(SRV6_NODE, &srv6_sids_cmd);
+
+ install_element(SRV6_SIDS_NODE, &exit_srv6_sids_config_cmd);
+ install_element(SRV6_SIDS_NODE, &vtysh_end_all_cmd);
install_element(SRV6_LOCS_NODE, &srv6_locator_cmd);
install_element(SRV6_LOCS_NODE, &exit_srv6_locs_config_cmd);
diff --git a/yang/frr-backend.yang b/yang/frr-backend.yang
new file mode 100644
index 000000000..7149cbb99
--- /dev/null
+++ b/yang/frr-backend.yang
@@ -0,0 +1,102 @@
+// SPDX-License-Identifier: BSD-2-Clause
+module frr-backend {
+ yang-version 1.1;
+ namespace "http://frrouting.org/yang/oper";
+ prefix frr-backend;
+
+ organization
+ "FRRouting";
+ contact
+ "FRR Users List: <mailto:frog@lists.frrouting.org>
+ FRR Development List: <mailto:dev@lists.frrouting.org>";
+ description
+ "This module defines a model for FRR backend management.
+
+ Copyright (c) 2024, LabN Consulting, L.L.C.
+
+ 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.";
+
+ revision 2024-12-29 {
+ description "Initial revision";
+ reference "FRR source code";
+ }
+
+ container clients {
+ config false;
+ description "The backend clients";
+
+ list client {
+ key name;
+ description "A backend client";
+
+ leaf name {
+ type string;
+ description "Name of the backend client";
+ }
+
+ container state {
+ description "FRR backend operational state";
+
+ leaf candidate-config-version {
+ type uint64;
+ description "Local candidate config version.";
+ }
+ leaf running-config-version {
+ type uint64;
+ description "Local running config version.";
+ }
+ leaf edit-count {
+ type uint64;
+ description "Number of config edits handled.";
+ }
+ leaf avg-edit-time {
+ type uint64;
+ description "Average edit time in microseconds.";
+ }
+ leaf prep-count {
+ type uint64;
+ description "Number of config preps handled.";
+ }
+ leaf avg-prep-time {
+ type uint64;
+ description "Average prep time in microseconds.";
+ }
+ leaf apply-count {
+ type uint64;
+ description "Number of config applies handled.";
+ }
+ leaf avg-apply-time {
+ type uint64;
+ description "Average apply time in microseconds.";
+ }
+ leaf-list notify-selectors {
+ type string;
+ description
+ "List of paths identifying which state to send change
+ notifications for.";
+ }
+ }
+ }
+ }
+}
diff --git a/yang/frr-bgp-route-map.yang b/yang/frr-bgp-route-map.yang
index 5f701d514..efb0b2fa0 100644
--- a/yang/frr-bgp-route-map.yang
+++ b/yang/frr-bgp-route-map.yang
@@ -148,6 +148,12 @@ module frr-bgp-route-map {
"Match BGP community list";
}
+ identity match-community-limit {
+ base frr-route-map:rmap-match-type;
+ description
+ "Match BGP community limit count";
+ }
+
identity match-large-community {
base frr-route-map:rmap-match-type;
description
@@ -802,6 +808,17 @@ identity set-extcommunity-color {
}
}
+ case community-limit {
+ when "derived-from-or-self(../frr-route-map:condition, 'frr-bgp-route-map:match-community-limit')";
+ description
+ "Match BGP updates when the list of communities count is less than the configured limit.";
+ leaf community-limit {
+ type uint16 {
+ range "1..1024";
+ }
+ }
+ }
+
case comm-list-name {
when "derived-from-or-self(../frr-route-map:condition, 'frr-bgp-route-map:match-community') or "
+ "derived-from-or-self(../frr-route-map:condition, 'frr-bgp-route-map:match-large-community') or "
diff --git a/yang/frr-pim.yang b/yang/frr-pim.yang
index 8dadf4fd7..6b6870f66 100644
--- a/yang/frr-pim.yang
+++ b/yang/frr-pim.yang
@@ -202,11 +202,29 @@ module frr-pim {
description
"A grouping defining per address family pim global attributes";
- leaf mcast-rpf-lookup {
- type mcast-rpf-lookup-mode;
- default "none";
+ list mcast-rpf-lookup {
+ key "group-list source-list";
description
- "Multicast RPF lookup behavior.";
+ "RPF lookup modes.";
+
+ leaf group-list {
+ type plist-ref;
+ description
+ "Multicast group prefix list.";
+ }
+
+ leaf source-list {
+ type plist-ref;
+ description
+ "Unicast source address prefix list.";
+ }
+
+ leaf mode {
+ type mcast-rpf-lookup-mode;
+ default "none";
+ description
+ "Multicast RPF lookup behavior.";
+ }
}
leaf ecmp {
diff --git a/yang/frr-staticd.yang b/yang/frr-staticd.yang
index 1e6c54c00..904e2058e 100644
--- a/yang/frr-staticd.yang
+++ b/yang/frr-staticd.yang
@@ -20,6 +20,10 @@ module frr-staticd {
prefix frr-bfdd;
}
+ import frr-vrf {
+ prefix frr-vrf;
+ }
+
organization
"FRRouting";
contact
@@ -92,6 +96,64 @@ module frr-staticd {
}
}
+ typedef srv6-behavior-codepoint {
+ description
+ "SRv6 Endpoint Behaviors Codepoints as per
+ https://www.iana.org/assignments/segment-routing/segment-routing.xhtml.";
+ type enumeration {
+ enum End {
+ value 1;
+ description
+ "This enum indicates End endpoint behavior.";
+ }
+ enum End.X {
+ value 5;
+ description
+ "This enum indicates End.X endpoint behavior.";
+ }
+ enum End.DT6 {
+ value 18;
+ description
+ "This enum indicates End.DT6 endpoint behavior.";
+ }
+ enum End.DT4 {
+ value 19;
+ description
+ "This enum indicates End.DT4 endpoint behavior.";
+ }
+ enum End.DT46 {
+ value 20;
+ description
+ "This enum indicates End.DT46 endpoint behavior.";
+ }
+ enum uN {
+ value 43;
+ description
+ "This enum indicates End with NEXT-CSID endpoint behavior.";
+ }
+ enum uA {
+ value 52;
+ description
+ "This enum indicates End.X with NEXT-CSID endpoint behavior.";
+ }
+ enum uDT6 {
+ value 62;
+ description
+ "This enum indicates End.DT6 with NEXT-CSID endpoint behavior.";
+ }
+ enum uDT4 {
+ value 63;
+ description
+ "This enum indicates End.DT4 with NEXT-CSID endpoint behavior.";
+ }
+ enum uDT46 {
+ value 64;
+ description
+ "This enum indicates End.DT46 with NEXT-CSID endpoint behavior.";
+ }
+ }
+ }
+
augment "/frr-rt:routing/frr-rt:control-plane-protocols/frr-rt:control-plane-protocol" {
container staticd {
when "../frr-rt:type = 'frr-staticd:staticd'" {
@@ -144,6 +206,44 @@ module frr-staticd {
uses staticd-prefix-attributes;
}
}
+
+ container segment-routing {
+ description
+ "Segment Routing configuration.";
+ container srv6 {
+ description
+ "Segment Routing over IPv6 (SRv6) configuration.";
+ container static-sids {
+ description
+ "This container lists the SRv6 Static SIDs instantiated on the local node.";
+ list sid {
+ description
+ "List of SRv6 Static SIDs.";
+ key "sid";
+ leaf sid {
+ type inet:ipv6-prefix;
+ description
+ "Value of the SRv6 SID.";
+ }
+ leaf behavior {
+ type srv6-behavior-codepoint;
+ description
+ "Behavior bound to the SRv6 SID.";
+ }
+ leaf locator-name {
+ type string;
+ description
+ "SRv6 locator name.";
+ }
+ leaf vrf-name {
+ type frr-vrf:vrf-ref;
+ description
+ "The VRF name.";
+ }
+ }
+ }
+ }
+ }
}
}
-}
+} \ No newline at end of file
diff --git a/yang/frr-test-module.yang b/yang/frr-test-module.yang
index 90086d05a..773a95955 100644
--- a/yang/frr-test-module.yang
+++ b/yang/frr-test-module.yang
@@ -7,13 +7,14 @@ module frr-test-module {
import ietf-inet-types {
prefix inet;
}
- import ietf-yang-types {
- prefix yang;
- }
import frr-interface {
prefix frr-interface;
}
+ organization "placeholder for lint";
+
+ contact "placeholder for lint";
+
description
"FRRouting internal testing module.
@@ -45,41 +46,56 @@ module frr-test-module {
revision 2018-11-26 {
description
"Initial revision.";
+ reference "placeholder for lint";
}
container frr-test-module {
config false;
+ description "a container for test module data";
container vrfs {
+ description "a container of vrfs";
list vrf {
key "name";
+ description "a keyed vrf list object";
leaf name {
type string;
+ description "name of vrf";
}
container interfaces {
+ description "container of leaf-list interfaces";
leaf-list interface {
type frr-interface:interface-ref;
+ description "leaf list interface object";
}
leaf-list interface-new {
type frr-interface:interface-ref;
+ description "second leaf list interface object";
}
}
container routes {
+ description "container of key-less route objects";
list route {
+ description "a key-less route object";
leaf prefix {
type inet:ipv4-prefix;
+ description "prefix of the route object";
}
leaf next-hop {
type inet:ipv4-address;
+ description "nexthop of the route object";
}
leaf interface {
type frr-interface:interface-ref;
+ description "interface of the route object";
}
leaf metric {
type uint8;
+ description "metric of the route object";
}
leaf active {
type empty;
+ description "active status of the route object";
}
}
}
@@ -87,16 +103,19 @@ module frr-test-module {
input {
leaf data {
type string;
+ description "data input to ping action.";
}
}
output {
leaf vrf {
type string;
+ description "vrf returned from ping action.";
}
// can't use the same name in input and output
// because of a bug in libyang < 2.1.148
leaf data-out {
type string;
+ description "data return from ping action.";
}
}
}
diff --git a/yang/subdir.am b/yang/subdir.am
index 786bd0bca..9d4bc8e78 100644
--- a/yang/subdir.am
+++ b/yang/subdir.am
@@ -20,6 +20,7 @@ EXTRA_DIST += yang/embedmodel.py
# without problems, as seen in libfrr.
dist_yangmodels_DATA += yang/frr-affinity-map.yang
+dist_yangmodels_DATA += yang/frr-backend.yang
dist_yangmodels_DATA += yang/frr-filter.yang
dist_yangmodels_DATA += yang/frr-module-translator.yang
dist_yangmodels_DATA += yang/frr-nexthop.yang
diff --git a/zebra/dplane_fpm_nl.c b/zebra/dplane_fpm_nl.c
index 3ec1c9d65..b8dbabb60 100644
--- a/zebra/dplane_fpm_nl.c
+++ b/zebra/dplane_fpm_nl.c
@@ -587,6 +587,7 @@ static void fpm_read(struct event *t)
struct zebra_dplane_ctx *ctx;
size_t available_bytes;
size_t hdr_available_bytes;
+ int ival;
/* Let's ignore the input at the moment. */
rv = stream_read_try(fnc->ibuf, fnc->socket,
@@ -715,17 +716,28 @@ static void fpm_read(struct event *t)
break;
}
+ /* Parse the route data into a dplane ctx, then
+ * enqueue it to zebra for processing.
+ */
ctx = dplane_ctx_alloc();
dplane_ctx_route_init(ctx, DPLANE_OP_ROUTE_NOTIFY, NULL,
NULL);
- if (netlink_route_change_read_unicast_internal(
- hdr, 0, false, ctx) != 1) {
- dplane_ctx_fini(&ctx);
- stream_pulldown(fnc->ibuf);
+
+ if (netlink_route_notify_read_ctx(hdr, 0, ctx) >= 0) {
+ /* In the FPM encoding, the vrfid is present */
+ ival = dplane_ctx_get_table(ctx);
+ dplane_ctx_set_vrf(ctx, ival);
+ dplane_ctx_set_table(ctx,
+ ZEBRA_ROUTE_TABLE_UNKNOWN);
+
+ dplane_provider_enqueue_to_zebra(ctx);
+ } else {
/*
* Let's continue to read other messages
* Even if we ignore this one.
*/
+ dplane_ctx_fini(&ctx);
+ stream_pulldown(fnc->ibuf);
}
break;
default:
diff --git a/zebra/interface.c b/zebra/interface.c
index 1c86a6a5c..e49e8eac5 100644
--- a/zebra/interface.c
+++ b/zebra/interface.c
@@ -81,7 +81,7 @@ static void if_zebra_speed_update(struct event *thread)
if (new_speed != ifp->speed) {
zlog_info("%s: %s old speed: %u new speed: %u", __func__,
ifp->name, ifp->speed, new_speed);
- ifp->speed = new_speed;
+ if_update_state_speed(ifp, new_speed);
if_add_update(ifp);
changed = true;
}
@@ -1563,17 +1563,20 @@ static inline void zebra_if_set_ziftype(struct interface *ifp,
static void interface_update_hw_addr(struct zebra_dplane_ctx *ctx,
struct interface *ifp)
{
- int i;
+ uint8_t hw_addr[INTERFACE_HWADDR_MAX];
+ uint i, hw_addr_len;
- ifp->hw_addr_len = dplane_ctx_get_ifp_hw_addr_len(ctx);
- memcpy(ifp->hw_addr, dplane_ctx_get_ifp_hw_addr(ctx), ifp->hw_addr_len);
+ hw_addr_len = dplane_ctx_get_ifp_hw_addr_len(ctx);
+ memcpy(hw_addr, dplane_ctx_get_ifp_hw_addr(ctx), hw_addr_len);
- for (i = 0; i < ifp->hw_addr_len; i++)
- if (ifp->hw_addr[i] != 0)
+ for (i = 0; i < hw_addr_len; i++)
+ if (hw_addr[i] != 0)
break;
- if (i == ifp->hw_addr_len)
- ifp->hw_addr_len = 0;
+ if (i == hw_addr_len)
+ hw_addr_len = 0;
+
+ if_update_state_hw_addr(ifp, hw_addr, hw_addr_len);
}
static void interface_update_l2info(struct zebra_dplane_ctx *ctx,
@@ -1984,9 +1987,10 @@ static void zebra_if_dplane_ifp_handling(struct zebra_dplane_ctx *ctx)
/* Update interface information. */
set_ifindex(ifp, ifindex, zns);
ifp->flags = flags;
- ifp->mtu6 = ifp->mtu = mtu;
- ifp->metric = 0;
- ifp->speed = kernel_get_speed(ifp, NULL);
+ if_update_state_mtu(ifp, mtu);
+ if_update_state_mtu6(ifp, mtu);
+ if_update_state_metric(ifp, 0);
+ if_update_state_speed(ifp, kernel_get_speed(ifp, NULL));
ifp->ptm_status = ZEBRA_PTM_STATUS_UNKNOWN;
ifp->txqlen = dplane_ctx_get_intf_txqlen(ctx);
@@ -2036,6 +2040,7 @@ static void zebra_if_dplane_ifp_handling(struct zebra_dplane_ctx *ctx)
IS_ZEBRA_IF_BRIDGE_VLAN_AWARE(
zif));
}
+ // if_update_state(ifp);
} else if (ifp->vrf->vrf_id != vrf_id) {
/* VRF change for an interface. */
if (IS_ZEBRA_DEBUG_KERNEL)
@@ -2058,8 +2063,9 @@ static void zebra_if_dplane_ifp_handling(struct zebra_dplane_ctx *ctx)
(unsigned long long)flags);
set_ifindex(ifp, ifindex, zns);
- ifp->mtu6 = ifp->mtu = mtu;
- ifp->metric = 0;
+ if_update_state_mtu(ifp, mtu);
+ if_update_state_mtu6(ifp, mtu);
+ if_update_state_metric(ifp, 0);
ifp->txqlen = dplane_ctx_get_intf_txqlen(ctx);
/*
diff --git a/zebra/main.c b/zebra/main.c
index 4546d1477..fd242e762 100644
--- a/zebra/main.c
+++ b/zebra/main.c
@@ -287,6 +287,7 @@ struct frr_signal_t zebra_signals[] = {
/* clang-format off */
static const struct frr_yang_module_info *const zebra_yang_modules[] = {
+ &frr_backend_info,
&frr_filter_info,
&frr_interface_info,
&frr_route_map_info,
@@ -356,7 +357,8 @@ int main(int argc, char **argv)
zserv_path = NULL;
- vrf_configure_backend(VRF_BACKEND_VRF_LITE);
+ if_notify_oper_changes = true;
+ vrf_notify_oper_changes = true;
frr_preinit(&zebra_di, argc, argv);
@@ -375,7 +377,7 @@ int main(int argc, char **argv)
" --v6-with-v4-nexthops Underlying dataplane supports v6 routes with v4 nexthops\n"
#ifdef HAVE_NETLINK
" -s, --nl-bufsize Set netlink receive buffer size\n"
- " -n, --vrfwnetns Use NetNS as VRF backend\n"
+ " -n, --vrfwnetns Use NetNS as VRF backend (deprecated, use -w)\n"
" --v6-rr-semantics Use v6 RR semantics\n"
#else
" -s, Set kernel socket receive buffer size\n"
@@ -436,6 +438,8 @@ int main(int argc, char **argv)
break;
#ifdef HAVE_NETLINK
case 'n':
+ fprintf(stderr,
+ "The -n option is deprecated, please use global -w option instead.\n");
vrf_configure_backend(VRF_BACKEND_NETNS);
break;
case OPTION_V6_RR_SEMANTICS:
diff --git a/zebra/rt_netlink.c b/zebra/rt_netlink.c
index 492fe5288..b32882e85 100644
--- a/zebra/rt_netlink.c
+++ b/zebra/rt_netlink.c
@@ -723,44 +723,52 @@ static uint16_t parse_multipath_nexthops_unicast(ns_id_t ns_id, struct nexthop_g
return nhop_num;
}
-/* Looking up routing table by netlink interface. */
-int netlink_route_change_read_unicast_internal(struct nlmsghdr *h,
- ns_id_t ns_id, int startup,
- struct zebra_dplane_ctx *ctx)
+/*
+ * Parse netlink route message and capture info into a dplane ctx.
+ * Returns <0 if the message is to be skipped (might be an error)
+ */
+static int netlink_route_read_unicast_ctx(struct nlmsghdr *h, ns_id_t ns_id,
+ struct rtattr **tb_in,
+ struct zebra_dplane_ctx *ctx)
{
+ int ret = 0;
int len;
struct rtmsg *rtm;
- struct rtattr *tb[RTA_MAX + 1];
+ struct rtattr **tb, *tb_array[RTA_MAX + 1];
uint32_t flags = 0;
struct prefix p;
- struct prefix_ipv6 src_p = {};
- vrf_id_t vrf_id;
+ struct prefix src_p = {};
bool selfroute;
-
- char anyaddr[16] = {0};
-
+ char anyaddr[16] = {};
int proto = ZEBRA_ROUTE_KERNEL;
int index = 0;
- int table;
+ int tableid;
int metric = 0;
uint32_t mtu = 0;
uint8_t distance = 0;
route_tag_t tag = 0;
- uint32_t nhe_id = 0;
-
+ uint32_t nhg_id = 0;
void *dest = NULL;
void *gate = NULL;
+ int gate_len;
void *prefsrc = NULL; /* IPv4 preferred source host address */
+ int prefsrc_len;
void *src = NULL; /* IPv6 srcdest source prefix */
enum blackhole_type bh_type = BLACKHOLE_UNSPEC;
+ afi_t afi = AFI_IP;
+ struct ipaddr addr = {};
- frrtrace(3, frr_zebra, netlink_route_change_read_unicast, h, ns_id,
- startup);
+ len = h->nlmsg_len - NLMSG_LENGTH(sizeof(struct rtmsg));
+ if (len < 0) {
+ zlog_err(
+ "%s: Netlink route message received with invalid size %d %zu",
+ __func__, h->nlmsg_len,
+ (size_t)NLMSG_LENGTH(sizeof(struct rtmsg)));
+ return -1;
+ }
rtm = NLMSG_DATA(h);
- if (startup && h->nlmsg_type != RTM_NEWROUTE)
- return 0;
switch (rtm->rtm_type) {
case RTN_UNICAST:
break;
@@ -778,54 +786,42 @@ int netlink_route_change_read_unicast_internal(struct nlmsghdr *h,
zlog_debug("Route rtm_type: %s(%d) intentionally ignoring",
nl_rttype_to_str(rtm->rtm_type),
rtm->rtm_type);
- return 0;
+ ret = -1;
+ goto done;
}
- len = h->nlmsg_len - NLMSG_LENGTH(sizeof(struct rtmsg));
- if (len < 0) {
- zlog_err(
- "%s: Message received from netlink is of a broken size %d %zu",
- __func__, h->nlmsg_len,
- (size_t)NLMSG_LENGTH(sizeof(struct rtmsg)));
- return -1;
+ if ((rtm->rtm_flags & RTM_F_CLONED) ||
+ (rtm->rtm_protocol == RTPROT_REDIRECT)) {
+ ret = -1;
+ goto done;
}
- netlink_parse_rtattr(tb, RTA_MAX, RTM_RTA(rtm), len);
-
- if (rtm->rtm_flags & RTM_F_CLONED)
- return 0;
- if (rtm->rtm_protocol == RTPROT_REDIRECT)
- return 0;
+ /* We don't care about change notifications for the MPLS table. */
+ /* TODO: Revisit this. */
+ if (rtm->rtm_family == AF_MPLS) {
+ ret = -1;
+ goto done;
+ }
- selfroute = is_selfroute(rtm->rtm_protocol);
+ dplane_ctx_set_ns_id(ctx, ns_id);
- if (!startup && selfroute && h->nlmsg_type == RTM_NEWROUTE &&
- !zrouter.asic_offloaded && !ctx) {
- if (IS_ZEBRA_DEBUG_KERNEL)
- zlog_debug("Route type: %d Received that we think we have originated, ignoring",
- rtm->rtm_protocol);
- return 0;
+ /* Parse attrs if necessary */
+ if (tb_in != NULL) {
+ tb = tb_in;
+ } else {
+ netlink_parse_rtattr(tb_array, RTA_MAX, RTM_RTA(rtm), len);
+ tb = tb_array;
}
- /* We don't care about change notifications for the MPLS table. */
- /* TODO: Revisit this. */
- if (rtm->rtm_family == AF_MPLS)
- return 0;
+ selfroute = is_selfroute(rtm->rtm_protocol);
/* Table corresponding to route. */
if (tb[RTA_TABLE])
- table = *(int *)RTA_DATA(tb[RTA_TABLE]);
+ tableid = *(int *)RTA_DATA(tb[RTA_TABLE]);
else
- table = rtm->rtm_table;
-
- /* Map to VRF */
- vrf_id = zebra_vrf_lookup_by_table(table, ns_id);
- if (vrf_id == VRF_DEFAULT) {
- if (!is_zebra_valid_kernel_table(table)
- && !is_zebra_main_routing_table(table))
- return 0;
- }
+ tableid = rtm->rtm_table;
+ /* Map flags values */
if (rtm->rtm_flags & RTM_F_TRAP)
flags |= ZEBRA_FLAG_TRAPPED;
if (rtm->rtm_flags & RTM_F_OFFLOAD)
@@ -836,7 +832,7 @@ int netlink_route_change_read_unicast_internal(struct nlmsghdr *h,
if (h->nlmsg_flags & NLM_F_APPEND)
flags |= ZEBRA_FLAG_OUTOFSYNC;
- /* Route which inserted by Zebra. */
+ /* Route which was inserted by Zebra. */
if (selfroute) {
flags |= ZEBRA_FLAG_SELFROUTE;
proto = proto2zebra(rtm->rtm_protocol, rtm->rtm_family, false);
@@ -854,14 +850,18 @@ int netlink_route_change_read_unicast_internal(struct nlmsghdr *h,
else
src = anyaddr;
- if (tb[RTA_PREFSRC])
+ if (tb[RTA_PREFSRC]) {
prefsrc = RTA_DATA(tb[RTA_PREFSRC]);
+ prefsrc_len = RTA_PAYLOAD(tb[RTA_PREFSRC]);
+ }
- if (tb[RTA_GATEWAY])
+ if (tb[RTA_GATEWAY]) {
gate = RTA_DATA(tb[RTA_GATEWAY]);
+ gate_len = RTA_PAYLOAD(tb[RTA_GATEWAY]);
+ }
if (tb[RTA_NH_ID])
- nhe_id = *(uint32_t *)RTA_DATA(tb[RTA_NH_ID]);
+ nhg_id = *(uint32_t *)RTA_DATA(tb[RTA_NH_ID]);
if (tb[RTA_PRIORITY])
metric = *(int *)RTA_DATA(tb[RTA_PRIORITY]);
@@ -887,7 +887,8 @@ int netlink_route_change_read_unicast_internal(struct nlmsghdr *h,
zlog_err(
"Invalid destination prefix length: %u received from kernel route change",
rtm->rtm_dst_len);
- return -1;
+ ret = -1;
+ goto done;
}
memcpy(&p.u.prefix4, dest, 4);
p.prefixlen = rtm->rtm_dst_len;
@@ -895,14 +896,16 @@ int netlink_route_change_read_unicast_internal(struct nlmsghdr *h,
if (rtm->rtm_src_len != 0) {
flog_warn(
EC_ZEBRA_UNSUPPORTED_V4_SRCDEST,
- "unsupported IPv4 sourcedest route (dest %pFX vrf %u)",
- &p, vrf_id);
- return 0;
+ "unsupported IPv4 sourcedest route (dest %pFX table %u)",
+ &p, tableid);
+ ret = -1;
+ goto done;
}
/* Force debug below to not display anything for source */
src_p.prefixlen = 0;
} else if (rtm->rtm_family == AF_INET6) {
+ afi = AFI_IP6;
p.family = AF_INET6;
if (rtm->rtm_dst_len > IPV6_MAX_BITLEN) {
zlog_err(
@@ -920,14 +923,15 @@ int netlink_route_change_read_unicast_internal(struct nlmsghdr *h,
rtm->rtm_src_len);
return -1;
}
- memcpy(&src_p.prefix, src, 16);
+ memcpy(&src_p.u.prefix6, src, 16);
src_p.prefixlen = rtm->rtm_src_len;
} else {
/* We only handle the AFs we handle... */
if (IS_ZEBRA_DEBUG_KERNEL)
zlog_debug("%s: unknown address-family %u", __func__,
rtm->rtm_family);
- return 0;
+ ret = -1;
+ goto done;
}
/*
@@ -956,6 +960,249 @@ int netlink_route_change_read_unicast_internal(struct nlmsghdr *h,
char buf2[PREFIX_STRLEN];
zlog_debug(
+ "%s %pFX%s%s nsid: %u table_id: %u metric: %d Admin Distance: %d",
+ nl_msg_type_to_str(h->nlmsg_type), &p,
+ src_p.prefixlen ? " from " : "",
+ src_p.prefixlen ? prefix2str(&src_p, buf2, sizeof(buf2))
+ : "",
+ ns_id, tableid, metric, distance);
+ }
+
+ /* Set values in ctx. Note that vrf is not set, because we can only
+ * resolve the FRR vrf info in the main pthread.
+ */
+ dplane_ctx_set_afi(ctx, afi);
+ dplane_ctx_set_safi(ctx, SAFI_UNICAST);
+ dplane_ctx_set_table(ctx, tableid);
+ dplane_ctx_set_vrf(ctx, VRF_UNKNOWN);
+ dplane_ctx_set_ns_id(ctx, ns_id);
+ dplane_ctx_set_dest(ctx, &p);
+ if (src_p.prefixlen > 0)
+ dplane_ctx_set_src(ctx, &src_p);
+ else
+ dplane_ctx_set_src(ctx, NULL);
+ dplane_ctx_set_type(ctx, proto);
+ dplane_ctx_set_flags(ctx, flags);
+ dplane_ctx_set_route_metric(ctx, metric);
+ dplane_ctx_set_route_mtu(ctx, mtu);
+ dplane_ctx_set_distance(ctx, distance);
+ dplane_ctx_set_tag(ctx, tag);
+
+ dplane_ctx_set_ifindex(ctx, index);
+ dplane_ctx_set_route_bhtype(ctx, bh_type);
+ if (prefsrc) {
+ /* Convert to ipaddr */
+ memset(&addr, 0, sizeof(addr));
+
+ if (afi == AFI_IP) {
+ SET_IPADDR_V4(&addr);
+ memcpy(&addr.ipaddr_v4, prefsrc, prefsrc_len);
+ } else {
+ SET_IPADDR_V6(&addr);
+ memcpy(&addr.ipaddr_v6, prefsrc, prefsrc_len);
+ }
+
+ dplane_ctx_set_route_prefsrc(ctx, &addr);
+ } else {
+ dplane_ctx_set_route_prefsrc(ctx, NULL);
+ }
+
+ if (gate) {
+ /* Convert to ipaddr */
+ memset(&addr, 0, sizeof(addr));
+
+ if (afi == AFI_IP) {
+ SET_IPADDR_V4(&addr);
+ memcpy(&addr.ipaddr_v4, gate, gate_len);
+ } else {
+ SET_IPADDR_V6(&addr);
+ memcpy(&addr.ipaddr_v6, gate, gate_len);
+ }
+
+ dplane_ctx_set_route_gw(ctx, &addr);
+ }
+
+ if (nhg_id > 0)
+ dplane_ctx_set_nhg_id(ctx, nhg_id);
+
+done:
+
+ return ret;
+}
+
+/*
+ * Public api for use parsing a route notification message: this notification
+ * only parses the top-level route attributes, and doesn't include nexthops.
+ */
+int netlink_route_notify_read_ctx(struct nlmsghdr *h, ns_id_t ns_id,
+ struct zebra_dplane_ctx *ctx)
+{
+ /* Use the common parser for route-level netlink message info;
+ * we expect the caller to have set the context up with the correct
+ * dplane opcode, and we expect the caller to submit the resulting ctx
+ * for processing in zebra.
+ */
+ return netlink_route_read_unicast_ctx(h, ns_id, NULL, ctx);
+}
+
+/*
+ * Parse a route update netlink message, extract and validate its data,
+ * call into zebra with an update.
+ */
+static int netlink_route_change_read_unicast_internal(struct nlmsghdr *h,
+ ns_id_t ns_id, int startup)
+{
+ int len;
+ struct rtmsg *rtm;
+ struct rtattr *tb[RTA_MAX + 1];
+ uint32_t flags = 0;
+ struct prefix p;
+ struct prefix src_p = {};
+ vrf_id_t vrf_id;
+ bool selfroute;
+
+ int proto = ZEBRA_ROUTE_KERNEL;
+ int index = 0;
+ int table;
+ int metric = 0;
+ uint32_t mtu = 0;
+ uint8_t distance = 0;
+ route_tag_t tag = 0;
+ uint32_t nhe_id = 0;
+ void *gate = NULL;
+ const struct ipaddr *gate_addr;
+ void *prefsrc = NULL; /* IPv4 preferred source host address */
+ const struct ipaddr *prefsrc_addr;
+ enum blackhole_type bh_type = BLACKHOLE_UNSPEC;
+ afi_t afi;
+ struct zebra_dplane_ctx *ctx = NULL;
+ int ret;
+
+ frrtrace(3, frr_zebra, netlink_route_change_read_unicast, h, ns_id,
+ startup);
+
+ rtm = NLMSG_DATA(h);
+
+ if (startup && h->nlmsg_type != RTM_NEWROUTE)
+ return 0;
+
+ switch (rtm->rtm_type) {
+ case RTN_UNICAST:
+ case RTN_BLACKHOLE:
+ case RTN_UNREACHABLE:
+ case RTN_PROHIBIT:
+ break;
+ default:
+ if (IS_ZEBRA_DEBUG_KERNEL)
+ zlog_debug("Route rtm_type: %s(%d) intentionally ignoring",
+ nl_rttype_to_str(rtm->rtm_type),
+ rtm->rtm_type);
+ return 0;
+ }
+
+ len = h->nlmsg_len - NLMSG_LENGTH(sizeof(struct rtmsg));
+ if (len < 0) {
+ zlog_err(
+ "%s: Message received from netlink is of a broken size %d %zu",
+ __func__, h->nlmsg_len,
+ (size_t)NLMSG_LENGTH(sizeof(struct rtmsg)));
+ return -1;
+ }
+
+ if (rtm->rtm_flags & RTM_F_CLONED)
+ return 0;
+ if (rtm->rtm_protocol == RTPROT_REDIRECT)
+ return 0;
+
+ /* We don't care about change notifications for the MPLS table. */
+ /* TODO: Revisit this. */
+ if (rtm->rtm_family == AF_MPLS)
+ return 0;
+
+ netlink_parse_rtattr(tb, RTA_MAX, RTM_RTA(rtm), len);
+
+ /*
+ * Allocate a context object and parse the core parts of the route
+ * message.
+ * After this point, note that we need to 'goto done' to exit,
+ * so that the ctx gets cleaned-up.
+ */
+ ctx = dplane_ctx_alloc();
+
+ dplane_ctx_route_init(ctx,
+ h->nlmsg_type == RTM_NEWROUTE ?
+ DPLANE_OP_ROUTE_INSTALL :
+ DPLANE_OP_ROUTE_DELETE, NULL, NULL);
+
+ /* Finish parsing the core route info */
+ ret = netlink_route_read_unicast_ctx(h, ns_id, tb, ctx);
+ if (ret < 0) {
+ ret = 0;
+ goto done;
+ }
+
+ flags = dplane_ctx_get_flags(ctx);
+
+ selfroute = CHECK_FLAG(flags, ZEBRA_FLAG_SELFROUTE);
+
+ if (!startup && selfroute && h->nlmsg_type == RTM_NEWROUTE &&
+ !zrouter.asic_offloaded) {
+ if (IS_ZEBRA_DEBUG_KERNEL)
+ zlog_debug("Route type: %d Received that we think we have originated, ignoring",
+ rtm->rtm_protocol);
+ ret = 0;
+ goto done;
+ }
+
+ /* Table corresponding to route. */
+ table = dplane_ctx_get_table(ctx);
+
+ /* Map to VRF: note that this can _only_ be done in the main pthread */
+ vrf_id = zebra_vrf_lookup_by_table(table, ns_id);
+ if (vrf_id == VRF_DEFAULT) {
+ if (!is_zebra_valid_kernel_table(table)
+ && !is_zebra_main_routing_table(table)) {
+ ret = 0;
+ goto done;
+ }
+ }
+
+ /* Route which inserted by Zebra. */
+ if (selfroute)
+ proto = dplane_ctx_get_type(ctx);
+
+ index = dplane_ctx_get_ifindex(ctx);
+
+ p = *(dplane_ctx_get_dest(ctx));
+
+ if (dplane_ctx_get_src(ctx) == NULL)
+ src_p.prefixlen = 0;
+ else
+ src_p = *(dplane_ctx_get_src(ctx));
+
+ prefsrc_addr = dplane_ctx_get_route_prefsrc(ctx);
+ if (prefsrc_addr)
+ prefsrc = (void *)&(prefsrc_addr->ip.addr);
+
+ gate_addr = dplane_ctx_get_route_gw(ctx);
+ if (!IS_IPADDR_NONE(gate_addr))
+ gate = (void *)&(gate_addr->ip.addr);
+
+ nhe_id = dplane_ctx_get_nhe_id(ctx);
+
+ metric = dplane_ctx_get_metric(ctx);
+ distance = dplane_ctx_get_distance(ctx);
+ tag = dplane_ctx_get_tag(ctx);
+ mtu = dplane_ctx_get_mtu(ctx);
+
+ afi = dplane_ctx_get_afi(ctx);
+
+ bh_type = dplane_ctx_get_route_bhtype(ctx);
+
+ if (IS_ZEBRA_DEBUG_KERNEL) {
+ char buf2[PREFIX_STRLEN];
+
+ zlog_debug(
"%s %pFX%s%s vrf %s(%u) table_id: %u metric: %d Admin Distance: %d",
nl_msg_type_to_str(h->nlmsg_type), &p,
src_p.prefixlen ? " from " : "",
@@ -965,10 +1212,6 @@ int netlink_route_change_read_unicast_internal(struct nlmsghdr *h,
distance);
}
- afi_t afi = AFI_IP;
- if (rtm->rtm_family == AF_INET6)
- afi = AFI_IP6;
-
if (h->nlmsg_type == RTM_NEWROUTE) {
struct route_entry *re;
struct nexthop_group *ng = NULL;
@@ -1018,12 +1261,11 @@ int netlink_route_change_read_unicast_internal(struct nlmsghdr *h,
}
}
if (nhe_id || ng) {
- dplane_rib_add_multipath(afi, SAFI_UNICAST, &p, &src_p,
- re, ng, startup, ctx);
+ rib_add_multipath(afi, SAFI_UNICAST, &p,
+ (struct prefix_ipv6 *)&src_p,
+ re, ng, startup);
if (ng)
nexthop_group_delete(&ng);
- if (ctx)
- zebra_rib_route_entry_free(re);
} else {
/*
* I really don't see how this is possible
@@ -1038,17 +1280,10 @@ int netlink_route_change_read_unicast_internal(struct nlmsghdr *h,
zebra_rib_route_entry_free(re);
}
} else {
- if (ctx) {
- zlog_err(
- "%s: %pFX RTM_DELROUTE received but received a context as well",
- __func__, &p);
- return 0;
- }
-
if (nhe_id) {
rib_delete(afi, SAFI_UNICAST, vrf_id, proto, 0, flags,
- &p, &src_p, NULL, nhe_id, table, metric,
- distance, true);
+ &p, (struct prefix_ipv6 *)&src_p, NULL,
+ nhe_id, table, metric, distance, true);
} else {
if (!tb[RTA_MULTIPATH]) {
struct nexthop nh;
@@ -1057,26 +1292,33 @@ int netlink_route_change_read_unicast_internal(struct nlmsghdr *h,
ns_id, rtm, tb, bh_type, index, prefsrc,
gate, afi, vrf_id);
rib_delete(afi, SAFI_UNICAST, vrf_id, proto, 0,
- flags, &p, &src_p, &nh, 0, table,
- metric, distance, true);
+ flags, &p,
+ (struct prefix_ipv6 *)&src_p, &nh, 0,
+ table, metric, distance, true);
} else {
/* XXX: need to compare the entire list of
* nexthops here for NLM_F_APPEND stupidity */
rib_delete(afi, SAFI_UNICAST, vrf_id, proto, 0,
- flags, &p, &src_p, NULL, 0, table,
- metric, distance, true);
+ flags, &p,
+ (struct prefix_ipv6 *)&src_p, NULL, 0,
+ table, metric, distance, true);
}
}
}
- return 1;
+ ret = 1;
+
+done:
+ if (ctx)
+ dplane_ctx_fini(&ctx);
+
+ return ret;
}
static int netlink_route_change_read_unicast(struct nlmsghdr *h, ns_id_t ns_id,
int startup)
{
- return netlink_route_change_read_unicast_internal(h, ns_id, startup,
- NULL);
+ return netlink_route_change_read_unicast_internal(h, ns_id, startup);
}
static struct mcast_route_data *mroute = NULL;
@@ -1615,13 +1857,10 @@ static bool _netlink_route_build_singlepath(const struct prefix *p,
{
char label_buf[256];
- struct vrf *vrf;
char addrstr[INET6_ADDRSTRLEN];
assert(nexthop);
- vrf = vrf_lookup_by_id(nexthop->vrf_id);
-
if (!_netlink_route_encode_label_info(nexthop, nlmsg, req_size, rtmsg,
label_buf, sizeof(label_buf)))
return false;
@@ -1782,10 +2021,10 @@ static bool _netlink_route_build_singlepath(const struct prefix *p,
}
if (IS_ZEBRA_DEBUG_KERNEL)
- zlog_debug("%s: 5549 (%s): %pFX nexthop via %s %s if %u vrf %s(%u)",
+ zlog_debug("%s: 5549 (%s): %pFX nexthop via %s %s if %u vrf %u",
__func__, routedesc, p, ipv4_ll_buf,
label_buf, nexthop->ifindex,
- VRF_LOGNAME(vrf), nexthop->vrf_id);
+ nexthop->vrf_id);
return true;
}
@@ -1808,10 +2047,9 @@ static bool _netlink_route_build_singlepath(const struct prefix *p,
if (IS_ZEBRA_DEBUG_KERNEL) {
inet_ntop(AF_INET, &nexthop->gate.ipv4, addrstr,
sizeof(addrstr));
- zlog_debug("%s: (%s): %pFX nexthop via %s %s if %u vrf %s(%u)",
+ zlog_debug("%s: (%s): %pFX nexthop via %s %s if %u vrf %u",
__func__, routedesc, p, addrstr, label_buf,
- nexthop->ifindex, VRF_LOGNAME(vrf),
- nexthop->vrf_id);
+ nexthop->ifindex, nexthop->vrf_id);
}
}
@@ -1832,10 +2070,9 @@ static bool _netlink_route_build_singlepath(const struct prefix *p,
if (IS_ZEBRA_DEBUG_KERNEL) {
inet_ntop(AF_INET6, &nexthop->gate.ipv6, addrstr,
sizeof(addrstr));
- zlog_debug("%s: (%s): %pFX nexthop via %s %s if %u vrf %s(%u)",
+ zlog_debug("%s: (%s): %pFX nexthop via %s %s if %u vrf %u",
__func__, routedesc, p, addrstr, label_buf,
- nexthop->ifindex, VRF_LOGNAME(vrf),
- nexthop->vrf_id);
+ nexthop->ifindex, nexthop->vrf_id);
}
}
@@ -1857,9 +2094,9 @@ static bool _netlink_route_build_singlepath(const struct prefix *p,
}
if (IS_ZEBRA_DEBUG_KERNEL)
- zlog_debug("%s: (%s): %pFX nexthop via if %u vrf %s(%u)",
+ zlog_debug("%s: (%s): %pFX nexthop via if %u vrf %u",
__func__, routedesc, p, nexthop->ifindex,
- VRF_LOGNAME(vrf), nexthop->vrf_id);
+ nexthop->vrf_id);
}
return true;
@@ -1943,7 +2180,6 @@ static bool _netlink_route_build_multipath(const struct prefix *p,
route_tag_t tag, bool fpm)
{
char label_buf[256];
- struct vrf *vrf;
struct rtnexthop *rtnh;
rtnh = nl_attr_rtnh(nlmsg, req_size);
@@ -1952,8 +2188,6 @@ static bool _netlink_route_build_multipath(const struct prefix *p,
assert(nexthop);
- vrf = vrf_lookup_by_id(nexthop->vrf_id);
-
if (!_netlink_route_encode_label_info(nexthop, nlmsg, req_size, rtmsg,
label_buf, sizeof(label_buf)))
return false;
@@ -1976,10 +2210,9 @@ static bool _netlink_route_build_multipath(const struct prefix *p,
if (IS_ZEBRA_DEBUG_KERNEL)
zlog_debug(
- "%s: 5549 (%s): %pFX nexthop via %s %s if %u vrf %s(%u)",
+ "%s: 5549 (%s): %pFX nexthop via %s %s if %u vrf %u",
__func__, routedesc, p, ipv4_ll_buf, label_buf,
- nexthop->ifindex, VRF_LOGNAME(vrf),
- nexthop->vrf_id);
+ nexthop->ifindex, nexthop->vrf_id);
nl_attr_rtnh_end(nlmsg, rtnh);
return true;
}
@@ -1997,10 +2230,9 @@ static bool _netlink_route_build_multipath(const struct prefix *p,
*src = &nexthop->src;
if (IS_ZEBRA_DEBUG_KERNEL)
- zlog_debug("%s: (%s): %pFX nexthop via %pI4 %s if %u vrf %s(%u)",
+ zlog_debug("%s: (%s): %pFX nexthop via %pI4 %s if %u vrf %u",
__func__, routedesc, p, &nexthop->gate.ipv4,
- label_buf, nexthop->ifindex,
- VRF_LOGNAME(vrf), nexthop->vrf_id);
+ label_buf, nexthop->ifindex, nexthop->vrf_id);
}
if (nexthop->type == NEXTHOP_TYPE_IPV6
|| nexthop->type == NEXTHOP_TYPE_IPV6_IFINDEX) {
@@ -2015,10 +2247,9 @@ static bool _netlink_route_build_multipath(const struct prefix *p,
*src = &nexthop->src;
if (IS_ZEBRA_DEBUG_KERNEL)
- zlog_debug("%s: (%s): %pFX nexthop via %pI6 %s if %u vrf %s(%u)",
+ zlog_debug("%s: (%s): %pFX nexthop via %pI6 %s if %u vrf %u",
__func__, routedesc, p, &nexthop->gate.ipv6,
- label_buf, nexthop->ifindex,
- VRF_LOGNAME(vrf), nexthop->vrf_id);
+ label_buf, nexthop->ifindex, nexthop->vrf_id);
}
/*
@@ -2037,9 +2268,9 @@ static bool _netlink_route_build_multipath(const struct prefix *p,
*src = &nexthop->src;
if (IS_ZEBRA_DEBUG_KERNEL)
- zlog_debug("%s: (%s): %pFX nexthop via if %u vrf %s(%u)",
+ zlog_debug("%s: (%s): %pFX nexthop via if %u vrf %u",
__func__, routedesc, p, nexthop->ifindex,
- VRF_LOGNAME(vrf), nexthop->vrf_id);
+ nexthop->vrf_id);
}
if (nexthop->weight)
@@ -3057,9 +3288,8 @@ ssize_t netlink_nexthop_msg_encode(uint16_t cmd,
nexthop_done:
if (IS_ZEBRA_DEBUG_KERNEL)
- zlog_debug("%s: ID (%u): %pNHv(%d) vrf %s(%u) %s ",
+ zlog_debug("%s: ID (%u): %pNHv(%d) vrf %u %s ",
__func__, id, nh, nh->ifindex,
- vrf_id_to_name(nh->vrf_id),
nh->vrf_id, label_buf);
}
diff --git a/zebra/rt_netlink.h b/zebra/rt_netlink.h
index d51944f1a..1c113baee 100644
--- a/zebra/rt_netlink.h
+++ b/zebra/rt_netlink.h
@@ -64,6 +64,15 @@ extern ssize_t netlink_macfdb_update_ctx(struct zebra_dplane_ctx *ctx,
extern int netlink_route_change(struct nlmsghdr *h, ns_id_t ns_id, int startup);
extern int netlink_route_read(struct zebra_ns *zns);
+/*
+ * Public api for parsing a route notification message: this notification
+ * only parses the top-level route attributes, and doesn't include nexthops.
+ * FPM, for example, is a user.
+ * Returns <0 if the message should be ignored/skipped.
+ */
+int netlink_route_notify_read_ctx(struct nlmsghdr *h, ns_id_t ns_id,
+ struct zebra_dplane_ctx *ctx);
+
extern int netlink_nexthop_change(struct nlmsghdr *h, ns_id_t ns_id,
int startup);
extern int netlink_nexthop_read(struct zebra_ns *zns);
@@ -109,10 +118,6 @@ netlink_put_lsp_update_msg(struct nl_batch *bth, struct zebra_dplane_ctx *ctx);
extern enum netlink_msg_status
netlink_put_pw_update_msg(struct nl_batch *bth, struct zebra_dplane_ctx *ctx);
-int netlink_route_change_read_unicast_internal(struct nlmsghdr *h,
- ns_id_t ns_id, int startup,
- struct zebra_dplane_ctx *ctx);
-
#ifdef NETLINK_DEBUG
const char *nlmsg_type2str(uint16_t type);
const char *af_type2str(int type);
diff --git a/zebra/zebra_dplane.c b/zebra/zebra_dplane.c
index 88c1a0493..b57c93015 100644
--- a/zebra/zebra_dplane.c
+++ b/zebra/zebra_dplane.c
@@ -150,6 +150,11 @@ struct dplane_route_info {
/* Optional list of extra interface info */
struct dplane_intf_extra_list_head intf_extra_list;
+
+ /* Route-level info that aligns with some netlink route data */
+ enum blackhole_type zd_bh_type;
+ struct ipaddr zd_prefsrc;
+ struct ipaddr zd_gateway;
};
/*
@@ -1906,6 +1911,12 @@ void dplane_ctx_set_flags(struct zebra_dplane_ctx *ctx, uint32_t flags)
ctx->u.rinfo.zd_flags = flags;
}
+void dplane_ctx_set_route_metric(struct zebra_dplane_ctx *ctx, uint32_t metric)
+{
+ DPLANE_CTX_VALID(ctx);
+ ctx->u.rinfo.zd_metric = metric;
+}
+
uint32_t dplane_ctx_get_metric(const struct zebra_dplane_ctx *ctx)
{
DPLANE_CTX_VALID(ctx);
@@ -1927,6 +1938,12 @@ uint32_t dplane_ctx_get_mtu(const struct zebra_dplane_ctx *ctx)
return ctx->u.rinfo.zd_mtu;
}
+void dplane_ctx_set_route_mtu(struct zebra_dplane_ctx *ctx, uint32_t mtu)
+{
+ DPLANE_CTX_VALID(ctx);
+ ctx->u.rinfo.zd_mtu = mtu;
+}
+
uint32_t dplane_ctx_get_nh_mtu(const struct zebra_dplane_ctx *ctx)
{
DPLANE_CTX_VALID(ctx);
@@ -1955,6 +1972,58 @@ uint8_t dplane_ctx_get_old_distance(const struct zebra_dplane_ctx *ctx)
return ctx->u.rinfo.zd_old_distance;
}
+/* Route blackhole type */
+enum blackhole_type dplane_ctx_get_route_bhtype(const struct zebra_dplane_ctx *ctx)
+{
+ DPLANE_CTX_VALID(ctx);
+ return ctx->u.rinfo.zd_bh_type;
+}
+
+void dplane_ctx_set_route_bhtype(struct zebra_dplane_ctx *ctx,
+ enum blackhole_type bhtype)
+{
+ DPLANE_CTX_VALID(ctx);
+ ctx->u.rinfo.zd_bh_type = bhtype;
+}
+
+/* IP 'preferred source', at route-level */
+const struct ipaddr *dplane_ctx_get_route_prefsrc(const struct zebra_dplane_ctx *ctx)
+{
+ DPLANE_CTX_VALID(ctx);
+
+ if (ctx->u.rinfo.zd_prefsrc.ipa_type != 0)
+ return &(ctx->u.rinfo.zd_prefsrc);
+ else
+ return NULL;
+}
+
+void dplane_ctx_set_route_prefsrc(struct zebra_dplane_ctx *ctx,
+ const struct ipaddr *addr)
+{
+ DPLANE_CTX_VALID(ctx);
+ if (addr)
+ ctx->u.rinfo.zd_prefsrc = *addr;
+ else
+ memset(&ctx->u.rinfo.zd_prefsrc, 0,
+ sizeof(ctx->u.rinfo.zd_prefsrc));
+}
+
+/* Route-level 'gateway' */
+const struct ipaddr *dplane_ctx_get_route_gw(const struct zebra_dplane_ctx *ctx)
+{
+ DPLANE_CTX_VALID(ctx);
+ return &(ctx->u.rinfo.zd_gateway);
+}
+
+void dplane_ctx_set_route_gw(struct zebra_dplane_ctx *ctx, const struct ipaddr *gw)
+{
+ DPLANE_CTX_VALID(ctx);
+ if (gw)
+ ctx->u.rinfo.zd_gateway = *gw;
+ else
+ memset(&ctx->u.rinfo.zd_gateway, 0, sizeof(ctx->u.rinfo.zd_gateway));
+}
+
int dplane_ctx_tc_qdisc_get_kind(const struct zebra_dplane_ctx *ctx)
{
DPLANE_CTX_VALID(ctx);
@@ -2179,6 +2248,12 @@ uint32_t dplane_ctx_get_nhg_id(const struct zebra_dplane_ctx *ctx)
return ctx->u.rinfo.zd_nhg_id;
}
+void dplane_ctx_set_nhg_id(struct zebra_dplane_ctx *ctx, uint32_t nhgid)
+{
+ DPLANE_CTX_VALID(ctx);
+ ctx->u.rinfo.zd_nhg_id = nhgid;
+}
+
const struct nexthop_group *dplane_ctx_get_ng(
const struct zebra_dplane_ctx *ctx)
{
@@ -6923,20 +6998,6 @@ kernel_dplane_process_ipset_entry(struct zebra_dplane_provider *prov,
dplane_provider_enqueue_out_ctx(prov, ctx);
}
-void dplane_rib_add_multipath(afi_t afi, safi_t safi, struct prefix *p,
- struct prefix_ipv6 *src_p, struct route_entry *re,
- struct nexthop_group *ng, int startup,
- struct zebra_dplane_ctx *ctx)
-{
- if (!ctx)
- rib_add_multipath(afi, safi, p, src_p, re, ng, startup);
- else {
- dplane_ctx_route_init_basic(ctx, dplane_ctx_get_op(ctx), re, p,
- src_p, afi, safi);
- dplane_provider_enqueue_to_zebra(ctx);
- }
-}
-
/*
* Kernel provider callback
*/
diff --git a/zebra/zebra_dplane.h b/zebra/zebra_dplane.h
index 285b00c9b..cabc70c23 100644
--- a/zebra/zebra_dplane.h
+++ b/zebra/zebra_dplane.h
@@ -524,11 +524,26 @@ uint32_t dplane_ctx_get_flags(const struct zebra_dplane_ctx *ctx);
void dplane_ctx_set_flags(struct zebra_dplane_ctx *ctx, uint32_t flags);
uint32_t dplane_ctx_get_metric(const struct zebra_dplane_ctx *ctx);
uint32_t dplane_ctx_get_old_metric(const struct zebra_dplane_ctx *ctx);
+void dplane_ctx_set_route_metric(struct zebra_dplane_ctx *ctx, uint32_t metric);
+void dplane_ctx_set_route_mtu(struct zebra_dplane_ctx *ctx, uint32_t mtu);
uint32_t dplane_ctx_get_mtu(const struct zebra_dplane_ctx *ctx);
uint32_t dplane_ctx_get_nh_mtu(const struct zebra_dplane_ctx *ctx);
uint8_t dplane_ctx_get_distance(const struct zebra_dplane_ctx *ctx);
void dplane_ctx_set_distance(struct zebra_dplane_ctx *ctx, uint8_t distance);
uint8_t dplane_ctx_get_old_distance(const struct zebra_dplane_ctx *ctx);
+/* Route blackhole type */
+enum blackhole_type dplane_ctx_get_route_bhtype(
+ const struct zebra_dplane_ctx *ctx);
+void dplane_ctx_set_route_bhtype(struct zebra_dplane_ctx *ctx,
+ enum blackhole_type bhtype);
+/* IPv4 'preferred source', at route-level */
+const struct ipaddr *dplane_ctx_get_route_prefsrc(
+ const struct zebra_dplane_ctx *ctx);
+void dplane_ctx_set_route_prefsrc(struct zebra_dplane_ctx *ctx,
+ const struct ipaddr *addr);
+/* Route 'gateway', at route-level */
+const struct ipaddr *dplane_ctx_get_route_gw(const struct zebra_dplane_ctx *ctx);
+void dplane_ctx_set_route_gw(struct zebra_dplane_ctx *ctx, const struct ipaddr *gw);
/* Accessors for traffic control context */
int dplane_ctx_tc_qdisc_get_kind(const struct zebra_dplane_ctx *ctx);
@@ -572,6 +587,7 @@ void dplane_ctx_set_nexthops(struct zebra_dplane_ctx *ctx, struct nexthop *nh);
void dplane_ctx_set_backup_nhg(struct zebra_dplane_ctx *ctx,
const struct nexthop_group *nhg);
+void dplane_ctx_set_nhg_id(struct zebra_dplane_ctx *ctx, uint32_t nhgid);
uint32_t dplane_ctx_get_nhg_id(const struct zebra_dplane_ctx *ctx);
const struct nexthop_group *dplane_ctx_get_ng(
const struct zebra_dplane_ctx *ctx);
@@ -1256,16 +1272,6 @@ void zebra_dplane_shutdown(void);
void zebra_dplane_startup_stage(struct zebra_ns *zns,
enum zebra_dplane_startup_notifications spot);
-/*
- * decision point for sending a routing update through the old
- * straight to zebra master pthread or through the dplane to
- * the master pthread for handling
- */
-void dplane_rib_add_multipath(afi_t afi, safi_t safi, struct prefix *p,
- struct prefix_ipv6 *src_p, struct route_entry *re,
- struct nexthop_group *ng, int startup,
- struct zebra_dplane_ctx *ctx);
-
enum zebra_dplane_startup_notifications
dplane_ctx_get_startup_spot(struct zebra_dplane_ctx *ctx);
diff --git a/zebra/zebra_nhg.c b/zebra/zebra_nhg.c
index a32fc2bb1..4f6bc02c6 100644
--- a/zebra/zebra_nhg.c
+++ b/zebra/zebra_nhg.c
@@ -1762,7 +1762,8 @@ void zebra_nhg_decrement_ref(struct nhg_hash_entry *nhe)
nhe->refcnt--;
if (!zebra_router_in_shutdown() && nhe->refcnt <= 0 &&
- CHECK_FLAG(nhe->flags, NEXTHOP_GROUP_INSTALLED) &&
+ (CHECK_FLAG(nhe->flags, NEXTHOP_GROUP_INSTALLED) ||
+ CHECK_FLAG(nhe->flags, NEXTHOP_GROUP_QUEUED)) &&
!CHECK_FLAG(nhe->flags, NEXTHOP_GROUP_KEEP_AROUND)) {
nhe->refcnt = 1;
SET_FLAG(nhe->flags, NEXTHOP_GROUP_KEEP_AROUND);
@@ -3089,7 +3090,7 @@ static struct nhg_hash_entry *zebra_nhg_rib_compare_old_nhe(
int nexthop_active_update(struct route_node *rn, struct route_entry *re,
struct route_entry *old_re)
{
- struct nhg_hash_entry *curr_nhe;
+ struct nhg_hash_entry *curr_nhe, *remove;
uint32_t curr_active = 0, backup_active = 0;
if (PROTO_OWNED(re->nhe))
@@ -3143,16 +3144,25 @@ backups_done:
new_nhe = zebra_nhg_rib_find_nhe(curr_nhe, rt_afi);
- if (old_re && old_re->type == re->type &&
- old_re->instance == re->instance)
+ remove = new_nhe;
+
+ if (old_re && old_re->type == re->type && old_re->instance == re->instance &&
+ new_nhe != old_re->nhe)
new_nhe = zebra_nhg_rib_compare_old_nhe(rn, re, new_nhe,
old_re->nhe);
if (IS_ZEBRA_DEBUG_NHG_DETAIL)
- zlog_debug(
- "%s: re %p CHANGED: nhe %p (%pNG) => new_nhe %p (%pNG)",
- __func__, re, re->nhe, re->nhe, new_nhe,
- new_nhe);
+ zlog_debug("%s: re %p CHANGED: nhe %p (%pNG) => new_nhe %p (%pNG) rib_find_nhe returned %p (%pNG) refcnt: %d",
+ __func__, re, re->nhe, re->nhe, new_nhe, new_nhe, remove, remove,
+ remove ? remove->refcnt : 0);
+
+ /*
+ * if the results from zebra_nhg_rib_find_nhe is being
+ * dropped and it was generated in that function
+ * (refcnt of 0) then we know we can clean it up
+ */
+ if (remove && remove != new_nhe && remove != re->nhe && remove->refcnt == 0)
+ zebra_nhg_handle_uninstall(remove);
route_entry_update_nhe(re, new_nhe);
}
@@ -3373,7 +3383,17 @@ void zebra_nhg_install_kernel(struct nhg_hash_entry *nhe, uint8_t type)
void zebra_nhg_uninstall_kernel(struct nhg_hash_entry *nhe)
{
- if (CHECK_FLAG(nhe->flags, NEXTHOP_GROUP_INSTALLED)) {
+ /*
+ * Clearly if the nexthop group is installed we should
+ * remove it. Additionally If the nexthop is already
+ * QUEUED for installation, we should also just send
+ * a deletion down as well. We cannot necessarily pluck
+ * the installation out of the queue ( since it may have
+ * already been acted on, but not processed yet in the
+ * main pthread ).
+ */
+ if (CHECK_FLAG(nhe->flags, NEXTHOP_GROUP_INSTALLED) ||
+ CHECK_FLAG(nhe->flags, NEXTHOP_GROUP_QUEUED)) {
int ret = dplane_nexthop_delete(nhe);
switch (ret) {
@@ -3445,7 +3465,13 @@ void zebra_nhg_dplane_result(struct zebra_dplane_ctx *ctx)
ZAPI_NHG_INSTALLED);
break;
case ZEBRA_DPLANE_REQUEST_FAILURE:
- UNSET_FLAG(nhe->flags, NEXTHOP_GROUP_INSTALLED);
+ /*
+ * With a request failure it is unknown what we now know
+ * this is because Zebra has lost track of whether or not
+ * any previous versions of this NHG are in the kernel
+ * or even what those versions were. So at this point
+ * we cannot unset the INSTALLED flag.
+ */
/* If daemon nhg, send it an update */
if (PROTO_OWNED(nhe))
zsend_nhg_notify(nhe->type, nhe->zapi_instance,
@@ -3909,7 +3935,14 @@ void zebra_interface_nhg_reinstall(struct interface *ifp)
__func__, ifp->name);
frr_each (nhg_connected_tree, &zif->nhg_dependents, rb_node_dep) {
+ /*
+ * The nexthop associated with this was set as !ACTIVE
+ * so we need to turn it back to active when we get to
+ * this point again
+ */
+ SET_FLAG(rb_node_dep->nhe->nhg.nexthop->flags, NEXTHOP_FLAG_ACTIVE);
nh = rb_node_dep->nhe->nhg.nexthop;
+
if (zebra_nhg_set_valid_if_active(rb_node_dep->nhe)) {
if (IS_ZEBRA_DEBUG_NHG_DETAIL)
zlog_debug(
diff --git a/zebra/zebra_rib.c b/zebra/zebra_rib.c
index 0226c355c..2881192eb 100644
--- a/zebra/zebra_rib.c
+++ b/zebra/zebra_rib.c
@@ -2220,8 +2220,20 @@ static void rib_process_dplane_notify(struct zebra_dplane_ctx *ctx)
bool fib_changed = false;
bool debug_p = IS_ZEBRA_DEBUG_DPLANE | IS_ZEBRA_DEBUG_RIB;
int start_count, end_count;
+ vrf_id_t vrf_id;
+ int tableid;
+
+ /* Locate vrf and route table - we must have one or the other */
+ tableid = dplane_ctx_get_table(ctx);
+ vrf_id = dplane_ctx_get_vrf(ctx);
+ if (vrf_id == VRF_UNKNOWN)
+ vrf_id = zebra_vrf_lookup_by_table(tableid,
+ dplane_ctx_get_ns_id(ctx));
+ else if (tableid == ZEBRA_ROUTE_TABLE_UNKNOWN)
+ tableid = zebra_vrf_lookup_tableid(vrf_id,
+ dplane_ctx_get_ns_id(ctx));
- vrf = vrf_lookup_by_id(dplane_ctx_get_vrf(ctx));
+ vrf = vrf_lookup_by_id(vrf_id);
/* Locate rn and re(s) from ctx */
rn = rib_find_rn_from_ctx(ctx);
@@ -2230,7 +2242,7 @@ static void rib_process_dplane_notify(struct zebra_dplane_ctx *ctx)
zlog_debug(
"Failed to process dplane notification: no routes for %s(%u:%u):%pRN",
VRF_LOGNAME(vrf), dplane_ctx_get_vrf(ctx),
- dplane_ctx_get_table(ctx), rn);
+ tableid, rn);
}
goto done;
}
@@ -2240,7 +2252,7 @@ static void rib_process_dplane_notify(struct zebra_dplane_ctx *ctx)
if (debug_p)
zlog_debug("%s(%u:%u):%pRN Processing dplane notif ctx %p",
VRF_LOGNAME(vrf), dplane_ctx_get_vrf(ctx),
- dplane_ctx_get_table(ctx), rn, ctx);
+ tableid, rn, ctx);
/*
* Take a pass through the routes, look for matches with the context
@@ -2257,7 +2269,7 @@ static void rib_process_dplane_notify(struct zebra_dplane_ctx *ctx)
zlog_debug(
"%s(%u:%u):%pRN Unable to process dplane notification: no entry for type %s",
VRF_LOGNAME(vrf), dplane_ctx_get_vrf(ctx),
- dplane_ctx_get_table(ctx), rn,
+ tableid, rn,
zebra_route_string(dplane_ctx_get_type(ctx)));
goto done;
@@ -2293,7 +2305,7 @@ static void rib_process_dplane_notify(struct zebra_dplane_ctx *ctx)
"%s(%u:%u):%pRN dplane notif, uninstalled type %s route",
VRF_LOGNAME(vrf),
dplane_ctx_get_vrf(ctx),
- dplane_ctx_get_table(ctx), rn,
+ tableid, rn,
zebra_route_string(
dplane_ctx_get_type(ctx)));
} else {
@@ -2303,7 +2315,7 @@ static void rib_process_dplane_notify(struct zebra_dplane_ctx *ctx)
"%s(%u:%u):%pRN dplane notif, but type %s not selected_fib",
VRF_LOGNAME(vrf),
dplane_ctx_get_vrf(ctx),
- dplane_ctx_get_table(ctx), rn,
+ tableid, rn,
zebra_route_string(
dplane_ctx_get_type(ctx)));
}
@@ -2342,7 +2354,7 @@ static void rib_process_dplane_notify(struct zebra_dplane_ctx *ctx)
zlog_debug(
"%s(%u:%u):%pRN dplane notification: rib_update returns FALSE",
VRF_LOGNAME(vrf), dplane_ctx_get_vrf(ctx),
- dplane_ctx_get_table(ctx), rn);
+ tableid, rn);
}
/*
@@ -2361,7 +2373,7 @@ static void rib_process_dplane_notify(struct zebra_dplane_ctx *ctx)
"%s(%u:%u):%pRN applied nexthop changes from dplane notification",
VRF_LOGNAME(vrf),
dplane_ctx_get_vrf(ctx),
- dplane_ctx_get_table(ctx), rn);
+ tableid, rn);
/* Changed nexthops - update kernel/others */
dplane_route_notif_update(rn, re,
@@ -2373,7 +2385,7 @@ static void rib_process_dplane_notify(struct zebra_dplane_ctx *ctx)
"%s(%u:%u):%pRN installed transition from dplane notification",
VRF_LOGNAME(vrf),
dplane_ctx_get_vrf(ctx),
- dplane_ctx_get_table(ctx), rn);
+ tableid, rn);
/* We expect this to be the selected route, so we want
* to tell others about this transition.
@@ -2393,7 +2405,7 @@ static void rib_process_dplane_notify(struct zebra_dplane_ctx *ctx)
"%s(%u:%u):%pRN un-installed transition from dplane notification",
VRF_LOGNAME(vrf),
dplane_ctx_get_vrf(ctx),
- dplane_ctx_get_table(ctx), rn);
+ tableid, rn);
/* Transition from _something_ installed to _nothing_
* installed.
@@ -3973,10 +3985,10 @@ static void rib_link(struct route_node *rn, struct route_entry *re, int process)
dest = rib_dest_from_rnode(rn);
if (!dest) {
+ dest = zebra_rib_create_dest(rn);
+
if (IS_ZEBRA_DEBUG_RIB_DETAILED)
rnode_debug(rn, re->vrf_id, "rn %p adding dest", rn);
-
- dest = zebra_rib_create_dest(rn);
}
re_list_add_head(&dest->routes, re);
diff --git a/zebra/zebra_vrf.c b/zebra/zebra_vrf.c
index 2b3cfc876..7bfe07b4c 100644
--- a/zebra/zebra_vrf.c
+++ b/zebra/zebra_vrf.c
@@ -98,6 +98,14 @@ static int zebra_vrf_new(struct vrf *vrf)
zvrf = zebra_vrf_alloc(vrf);
if (!vrf_is_backend_netns())
zvrf->zns = zebra_ns_lookup(NS_DEFAULT);
+ else if (vrf->vrf_id == VRF_DEFAULT) {
+ struct ns *ns;
+
+ strlcpy(vrf->data.l.netns_name, VRF_DEFAULT_NAME, NS_NAMSIZ);
+ ns = ns_lookup(NS_DEFAULT);
+ ns->vrf_ctxt = vrf;
+ vrf->ns_ctxt = ns;
+ }
otable_init(&zvrf->other_tables);
@@ -417,6 +425,25 @@ vrf_id_t zebra_vrf_lookup_by_table(uint32_t table_id, ns_id_t ns_id)
return VRF_DEFAULT;
}
+/*
+ * Lookup tableid by vrfid; handle vrf-lite and vrf-netns cases
+ */
+int zebra_vrf_lookup_tableid(vrf_id_t vrf_id, ns_id_t ns_id)
+{
+ struct zebra_vrf *zvrf;
+
+ /* Handle vrf-lite and vrf-netns */
+ if (vrf_is_backend_netns())
+ zvrf = vrf_info_lookup(ns_id);
+ else
+ zvrf = vrf_info_lookup(vrf_id);
+
+ if (zvrf)
+ return zvrf->table_id;
+ else
+ return ZEBRA_ROUTE_TABLE_UNKNOWN;
+}
+
/* Lookup VRF by identifier. */
struct zebra_vrf *zebra_vrf_lookup_by_id(vrf_id_t vrf_id)
{
diff --git a/zebra/zebra_vrf.h b/zebra/zebra_vrf.h
index f97138c81..334bb9368 100644
--- a/zebra/zebra_vrf.h
+++ b/zebra/zebra_vrf.h
@@ -24,6 +24,8 @@ FRR_CFG_DEFAULT_BOOL(ZEBRA_IP_NHT_RESOLVE_VIA_DEFAULT,
{ .val_bool = false },
);
+#define ZEBRA_ROUTE_TABLE_UNKNOWN 0
+
/* MPLS (Segment Routing) global block */
struct mpls_srgb {
uint32_t start_label;
@@ -247,6 +249,7 @@ extern struct zebra_vrf *zebra_vrf_lookup_by_name(const char *);
extern vrf_id_t zebra_vrf_lookup_by_table(uint32_t table_id, ns_id_t ns_id);
extern struct zebra_vrf *zebra_vrf_alloc(struct vrf *vrf);
extern struct route_table *zebra_vrf_table(afi_t, safi_t, vrf_id_t);
+int zebra_vrf_lookup_tableid(vrf_id_t vrf_id, ns_id_t ns_id);
/*
* API to associate a VRF with a NETNS.