diff options
144 files changed, 7419 insertions, 1301 deletions
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_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 f520c2e2b..f27f0b97c 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) @@ -8874,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, @@ -8913,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); @@ -8948,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. @@ -9311,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); @@ -12003,9 +12026,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 43033c8c3..7f4a3b918 100644 --- a/bgpd/bgp_route.h +++ b/bgpd/bgp_route.h @@ -492,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 f0bc84ce3..d8d1fa5dd 100644 --- a/bgpd/bgp_routemap.c +++ b/bgpd/bgp_routemap.c @@ -2066,10 +2066,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 +4142,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; 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..146630ec6 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; 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/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/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/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/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/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/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; } @@ -630,6 +743,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 +764,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 ? */ @@ -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/mgmt_be_client.c b/lib/mgmt_be_client.c index 155d56aa6..3a07a1d2d 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; } @@ -1155,6 +1153,22 @@ static void be_client_handle_notify(struct mgmt_be_client *client, void *msgbuf, } /* + * 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 +1189,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 +1332,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..418cb246f 100644 --- a/lib/northbound.c +++ b/lib/northbound.c @@ -2754,10 +2754,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..ce59bfd01 100644 --- a/lib/northbound.h +++ b/lib/northbound.h @@ -836,6 +836,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); @@ -1512,6 +1515,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 +1763,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..b75c86561 --- /dev/null +++ b/lib/northbound_notif.c @@ -0,0 +1,680 @@ +// 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_continue(struct event *event); + +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; + const char *op = group->cur_changes == &group->adds ? "add" : "delete"; + + /* we don't send batches when yielding as we need completed edit in any patch */ + assert(ret != NB_YIELD); + + nb_notif_walk = NULL; + + if (ret == NB_ERR_NOT_FOUND) { + __dbg("Path not found while walking oper tree: %s", path); + XFREE(MTYPE_NB_NOTIF_WALK_ARGS, args); + return ret; + } + /* Something else went wrong with the walk */ + if (ret != NB_OK) { +error: + __log_err("Error notifying for datastore change on path: %s: %s", path, + nb_err_name(ret)); + XFREE(MTYPE_NB_NOTIF_WALK_ARGS, args); + /* XXX Need to inform mgmtd/front-ends things are out-of-sync */ + return ret; + } + + __dbg("done with oper-path collection for %s path: %s", op, 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)) { + ret = NB_ERR; + goto error; + } + + /* + * Advance to next change (either dels or adds or both). + */ + + group->cur_change = RB_NEXT(op_changes, group->cur_change); + if (!group->cur_change) { + __dbg("done with oper-path collection for group"); + op_changes_group_free(group); + + group = op_changes_group_next(); + args->group = group; + if (!group) { + __dbg("done with ALL oper-path collection for notification"); + XFREE(MTYPE_NB_NOTIF_WALK_ARGS, args); + goto done; + } + } + + 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 NB_OK; +} + +static LY_ERR nb_notify_delete_changes(struct nb_notif_walk_args *args) +{ + struct op_changes_group *group = args->group; + LY_ERR err; + + group->cur_change = RB_MIN(op_changes, group->cur_changes); + while (group->cur_change) { + err = mgmt_be_send_ds_delete_notification(group->cur_change->path); + assert(err == LY_SUCCESS); /* XXX */ + + group->cur_change = RB_NEXT(op_changes, group->cur_change); + } + + return LY_SUCCESS; +} + +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; + LY_ERR err; + + /* + * Notify about deletes until we have add changes to collect. + */ + while (group->cur_changes == &group->dels) { + err = nb_notify_delete_changes(args); + assert(err == LY_SUCCESS); /* XXX */ + assert(!group->cur_change); /* we send all the deletes in one message */ + + /* 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; + + __dbg("done with oper-path change group"); + op_changes_group_free(group); + + group = op_changes_group_next(); + args->group = group; + if (!group) { + __dbg("done with ALL oper-path changes"); + XFREE(MTYPE_NB_NOTIF_WALK_ARGS, 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 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; + struct op_changes_group *group; + + EVENT_OFF(nb_notif_timer); + + if (nb_notif_walk) { + nb_oper_cancel_walk(nb_notif_walk); + /* need to free the group that's in the walk */ + args = nb_oper_walk_finish_arg(nb_notif_walk); + if (args) + op_changes_group_free(args->group); + nb_notif_walk = NULL; + } + + 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/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" @@ -105,6 +108,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 +171,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 +240,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 +287,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 +320,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 +347,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); @@ -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); @@ -299,6 +301,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..2aa353925 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; diff --git a/lib/yang.h b/lib/yang.h index 52857ecf0..eed2fa8db 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. * 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..1c32f936b 100644 --- a/mgmtd/mgmt_be_adapter.c +++ b/mgmtd/mgmt_be_adapter.c @@ -77,6 +77,7 @@ 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", @@ -94,6 +95,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 +116,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 +133,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] = { @@ -152,6 +160,9 @@ static const char *const *be_client_oper_xpaths[MGMTD_BE_CLIENT_ID_MAX] = { #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 +331,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 +662,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 +684,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 +756,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..96b7cbd59 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,72 @@ 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 void mgmt_fe_ns_string_remove_session(struct ns_string_head *head, + struct mgmt_fe_session_ctx *session) +{ + struct ns_string *ns; + + frr_each_safe (ns_string, head, ns) { + listnode_delete(ns->sessions, session); + if (list_isempty(ns->sessions)) { + ns_string_del(head, ns); + mgmt_fe_free_ns_string(ns); + } + } +} + +static void 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; + + 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(); + if (!listnode_lookup(ns->sessions, session)) + listnode_add(ns->sessions, session); +} + +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 +274,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 +1627,90 @@ 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) { + 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) + mgmt_fe_add_ns_string(&mgmt_fe_ns_strings, *sp, darr_strlen(*sp), session); + + /* Check if any backends are interested in the new selectors. */ + if (msg->replace) { + /* If we are replacing we'll send all the selectors again with replace flag */ + clients = mgmt_be_interested_clients("/", MGMT_BE_XPATH_SUBSCR_TYPE_OPER); + } else { + darr_foreach_p (selectors, sp) + clients |= mgmt_be_interested_clients(*sp, MGMT_BE_XPATH_SUBSCR_TYPE_OPER); + } + if (!clients) { + __dbg("No backends provide oper for notify 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 +1901,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 +1916,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 +1966,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 +1997,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 +2028,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 +2047,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..7d909446c 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, 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/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_main.c b/staticd/static_main.c index 9468a98b8..5e74326e3 100644 --- a/staticd/static_main.c +++ b/staticd/static_main.c @@ -107,6 +107,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, 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_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..aa0508e88 --- /dev/null +++ b/tests/topotests/bgp_dynamic_capability/test_bgp_dynamic_capability_enhe.py @@ -0,0 +1,215 @@ +#!/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. + r1.vtysh_cmd("clear bgp 2001:db8::2 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_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_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/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/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_notif.py b/tests/topotests/mgmt_notif/test_notif.py index e5286faae..526f051e6 100644 --- a/tests/topotests/mgmt_notif/test_notif.py +++ b/tests/topotests/mgmt_notif/test_notif.py @@ -10,9 +10,12 @@ Test YANG Notifications """ import json +import logging import os +import re import pytest +from lib.micronet import Timeout, comm_error from lib.topogen import Topogen from lib.topotest import json_cmp from oper import check_kernel_32 @@ -42,7 +45,57 @@ def tgen(request): tgen.stop_topology() -def test_frontend_notification(tgen): +def myreadline(f): + buf = "" + while True: + # logging.debug("READING 1 CHAR") + c = f.read(1) + if not c: + return buf if buf else None + buf += c + # logging.debug("READ CHAR: '%s'", c) + if c == "\n": + return buf + + +def _wait_output(f, regex, maxwait=120): + timeout = Timeout(maxwait) + while not timeout.is_expired(): + # line = p.stdout.readline() + line = myreadline(f) + if not line: + assert None, "EOF waiting for '{}'".format(regex) + line = line.rstrip() + if line: + logging.debug("GOT LINE: '%s'", line) + m = re.search(regex, line) + if m: + return m + assert None, "Failed to get output matching '{}' withint {} actual {}s".format( + regex, maxwait, timeout.elapsed() + ) + + +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) @@ -50,30 +103,141 @@ 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" + # Start our FE client in the background + p = r1.popen( + [fe_client_path, "--datastore", "--listen=/frr-interface:lib/interface"] ) - jsout = json.loads(output) + _wait_output(p.stderr, "Connected", maxwait=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_frontend_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, "") + + 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") + + # 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" + ) - expected = {"frr-ripd:authentication-failure": {"interface-name": "r1-eth0"}} - result = json_cmp(jsout, expected) - assert result is None + 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") - jsout = json.loads(output) + 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) - expected = {"frr-ripd:authentication-failure": {"interface-name": "r1-eth0"}} - result = json_cmp(jsout, expected) - assert result is None + +def test_frontend_all_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, "") + + 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") + + # 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_notification(tgen): @@ -90,12 +254,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) + # Update config to non-matching authentication. + conf = """ + conf t + interface r1-eth0 + ip rip authentication string bar + """ + r1.cmd_raises("vtysh", stdin=conf) - expected = {"frr-ripd:authentication-failure": {"interface-name": "r1-eth0"}} - result = json_cmp(jsout, expected) - assert result is None + 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/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/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/tools/frr-reload.py b/tools/frr-reload.py index dba50b3c5..3ea63ce2a 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]]: 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-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/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..d189d1e0a 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,6 +357,9 @@ int main(int argc, char **argv) zserv_path = NULL; + if_notify_oper_changes = true; + vrf_notify_oper_changes = true; + vrf_configure_backend(VRF_BACKEND_VRF_LITE); frr_preinit(&zebra_di, argc, argv); 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 6a43fe0c8..712b2534c 100644 --- a/zebra/zebra_nhg.c +++ b/zebra/zebra_nhg.c @@ -3145,8 +3145,8 @@ backups_done: remove = new_nhe; - if (old_re && old_re->type == re->type && - old_re->instance == re->instance) + 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); @@ -3454,7 +3454,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, @@ -3918,7 +3924,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..c7781e86d 100644 --- a/zebra/zebra_vrf.c +++ b/zebra/zebra_vrf.c @@ -417,6 +417,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. |