diff options
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)); @@ -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) { @@ -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 \ @@ -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; } @@ -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, ¬e, + &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. |