diff --git a/bgpd/bgp_advertise.c b/bgpd/bgp_advertise.c index 2ca3ffaa1ae8..b36327432f0f 100644 --- a/bgpd/bgp_advertise.c +++ b/bgpd/bgp_advertise.c @@ -162,8 +162,8 @@ bool bgp_adj_out_lookup(struct peer *peer, struct bgp_dest *dest, } -void bgp_adj_in_set(struct bgp_dest *dest, struct peer *peer, struct attr *attr, - uint32_t addpath_id) +void bgp_adj_in_set(struct bgp_dest *dest, afi_t afi, safi_t safi, + struct peer *peer, struct attr *attr, uint32_t addpath_id) { struct bgp_adj_in *adj; @@ -178,6 +178,7 @@ void bgp_adj_in_set(struct bgp_dest *dest, struct peer *peer, struct attr *attr, } adj = XCALLOC(MTYPE_BGP_ADJ_IN, sizeof(struct bgp_adj_in)); adj->peer = peer_lock(peer); /* adj_in peer reference */ + adj->peer->stat_adj_in_count[afi][safi]++; adj->attr = bgp_attr_intern(attr); adj->uptime = monotime(NULL); adj->addpath_rx_id = addpath_id; @@ -185,17 +186,19 @@ void bgp_adj_in_set(struct bgp_dest *dest, struct peer *peer, struct attr *attr, bgp_dest_lock_node(dest); } -void bgp_adj_in_remove(struct bgp_dest **dest, struct bgp_adj_in *bai) +void bgp_adj_in_remove(struct bgp_dest **dest, afi_t afi, safi_t safi, + struct bgp_adj_in *bai) { bgp_attr_unintern(&bai->attr); BGP_ADJ_IN_DEL(*dest, bai); + bai->peer->stat_adj_in_count[afi][safi]--; *dest = bgp_dest_unlock_node(*dest); peer_unlock(bai->peer); /* adj_in peer reference */ XFREE(MTYPE_BGP_ADJ_IN, bai); } -bool bgp_adj_in_unset(struct bgp_dest **dest, struct peer *peer, - uint32_t addpath_id) +bool bgp_adj_in_unset(struct bgp_dest **dest, afi_t afi, safi_t safi, + struct peer *peer, uint32_t addpath_id) { struct bgp_adj_in *adj; struct bgp_adj_in *adj_next; @@ -209,7 +212,7 @@ bool bgp_adj_in_unset(struct bgp_dest **dest, struct peer *peer, adj_next = adj->next; if (adj->peer == peer && adj->addpath_rx_id == addpath_id) - bgp_adj_in_remove(dest, adj); + bgp_adj_in_remove(dest, afi, safi, adj); adj = adj_next; diff --git a/bgpd/bgp_advertise.h b/bgpd/bgp_advertise.h index 7c3b23ab54e9..33bbc43b573a 100644 --- a/bgpd/bgp_advertise.h +++ b/bgpd/bgp_advertise.h @@ -134,11 +134,13 @@ struct bgp_synchronize { /* Prototypes. */ extern bool bgp_adj_out_lookup(struct peer *peer, struct bgp_dest *dest, uint32_t addpath_tx_id); -extern void bgp_adj_in_set(struct bgp_dest *dest, struct peer *peer, - struct attr *attr, uint32_t addpath_id); -extern bool bgp_adj_in_unset(struct bgp_dest **dest, struct peer *peer, - uint32_t addpath_id); -extern void bgp_adj_in_remove(struct bgp_dest **dest, struct bgp_adj_in *bai); +extern void bgp_adj_in_set(struct bgp_dest *dest, afi_t afi, safi_t safi, + struct peer *peer, struct attr *attr, + uint32_t addpath_id); +extern bool bgp_adj_in_unset(struct bgp_dest **dest, afi_t afi, safi_t safi, + struct peer *peer, uint32_t addpath_id); +extern void bgp_adj_in_remove(struct bgp_dest **dest, afi_t afi, safi_t safi, + struct bgp_adj_in *bai); extern unsigned int bgp_advertise_attr_hash_key(const void *p); extern bool bgp_advertise_attr_hash_cmp(const void *p1, const void *p2); diff --git a/bgpd/bgp_bmp.c b/bgpd/bgp_bmp.c index 2c91e5c51779..5e4e8a6c59ea 100644 --- a/bgpd/bgp_bmp.c +++ b/bgpd/bgp_bmp.c @@ -24,6 +24,7 @@ #include "lib/version.h" #include "jhash.h" #include "termtable.h" +#include "time.h" #include "bgpd/bgp_table.h" #include "bgpd/bgpd.h" @@ -60,9 +61,40 @@ 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_LBPI, "BMP locked BPI"); DEFINE_QOBJ_TYPE(bmp_targets); +static struct timeval bmp_startup_time = {0}; + +static uint32_t bmp_time_since_startup(struct timeval *delay) +{ + + if (bmp_startup_time.tv_sec == 0 && bmp_startup_time.tv_usec == 0) { + zlog_info("bmp [%s]: Startup time not recorded", __func__); + return 0; + } + + uint32_t micros = (uint32_t)(monotime_since(&bmp_startup_time, delay)); + + return micros / 1000; +} + +static const char *bmp_state_str(enum BMP_State state) +{ + switch (state) { + + case BMP_StartupIdle: + return "Startup-Wait"; + case BMP_PeerUp: + return "Peer-Up"; + case BMP_Run: + return "Running"; + default: + return "Unknown"; + } +} + static int bmp_bgp_cmp(const struct bmp_bgp *a, const struct bmp_bgp *b) { if (a->bgp < b->bgp) @@ -101,6 +133,110 @@ DECLARE_HASH(bmp_peerh, struct bmp_bgp_peer, bpi, struct bmp_peerh_head bmp_peerh; +static int bmp_bpi_lock_cmp(const struct bmp_bpi_lock *a, + const struct bmp_bpi_lock *b) +{ + if (a->locked < b->locked) + return -1; + if (a->locked > b->locked) + return 1; + return 0; +} + +static uint32_t bmp_bpi_lock_hash(const struct bmp_bpi_lock *e) +{ + return jhash(&e->locked, sizeof(e->locked), 0x55aa5a5a); +} + +DECLARE_HASH(bmp_lbpi, struct bmp_bpi_lock, lbpi, bmp_bpi_lock_cmp, + bmp_bpi_lock_hash); + +struct bmp_lbpi_head bmp_lbpi; + +static struct bmp_bpi_lock *bmp_lock_bpi(struct bgp_path_info *bpi) +{ + + if (!bpi) + return NULL; + + struct bmp_bpi_lock dummy = {.locked = bpi}, *lbpi_ptr; + + lbpi_ptr = bmp_lbpi_find(&bmp_lbpi, &dummy); + if (!lbpi_ptr) { + lbpi_ptr = XCALLOC(MTYPE_BMP_LBPI, sizeof(struct bmp_bpi_lock)); + SET_FLAG(bpi->flags, BGP_BMP_HELD); + lbpi_ptr->locked = bpi; + lbpi_ptr->lock = 0; + bgp_path_info_lock(lbpi_ptr->locked); + bmp_lbpi_add(&bmp_lbpi, lbpi_ptr); + } + + lbpi_ptr->lock++; + + return lbpi_ptr; +} + +static int bmp_mainlock_bpi(struct bgp_path_info *bpi) +{ + + if (!bpi) + return -1; + struct bmp_bpi_lock *lbpi = bmp_lock_bpi(bpi); + lbpi->main = 1; + lbpi->lock--; + + + return 1; +} + +static struct bmp_bpi_lock *bmp_unlock_bpi(struct bgp_path_info *bpi) +{ + + if (!bpi) + return NULL; + + struct bmp_bpi_lock dummy = {.locked = bpi}, *lbpi_ptr; + + lbpi_ptr = bmp_lbpi_find(&bmp_lbpi, &dummy); + + if (!lbpi_ptr) + return NULL; + + lbpi_ptr->lock--; + + if (lbpi_ptr->lock-- <= 0 && lbpi_ptr->main == 0) { + bgp_path_info_unlock(lbpi_ptr->locked); + bmp_lbpi_del(&bmp_lbpi, lbpi_ptr); + UNSET_FLAG(bpi->flags, BGP_BMP_HELD); + XFREE(MTYPE_BMP_LBPI, lbpi_ptr); + return NULL; + } + + return lbpi_ptr; +} + + +static inline void bmp_bqe_free(struct bmp_queue_entry *bqe) +{ + + if (!bqe) + return; + if (bqe->locked_bpi) + bmp_unlock_bpi(bqe->locked_bpi); + + XFREE(MTYPE_BMP_QUEUE, bqe); +} + +static int bmp_mainunlock_bpi(struct bgp_path_info *bpi) +{ + + struct bmp_bpi_lock dummy = {.locked = bpi}, *lbpi_ptr; + + lbpi_ptr = bmp_lbpi_find(&bmp_lbpi, &dummy); + + return lbpi_ptr ? lbpi_ptr->main = 0 : -1; +} + DECLARE_LIST(bmp_mirrorq, struct bmp_mirrorq, bmi); /* listener management */ @@ -298,6 +434,7 @@ static void bmp_per_peer_hdr(struct stream *s, struct bgp *bgp, #define BMP_PEER_FLAG_V (1 << 7) #define BMP_PEER_FLAG_L (1 << 6) #define BMP_PEER_FLAG_A (1 << 5) +#define BMP_PEER_FLAG_O (1 << 4) bool is_locrib = peer_type_flag == BMP_PEER_TYPE_LOC_RIB_INSTANCE; @@ -705,6 +842,9 @@ static void bmp_wrmirror_lost(struct bmp *bmp, struct pullwr *pullwr) stream_free(s); } + +/* pulls a bmq and sends a bmp mirror message on the session + */ static bool bmp_wrmirror(struct bmp *bmp, struct pullwr *pullwr) { struct bmp_mirrorq *bmq; @@ -752,6 +892,10 @@ static bool bmp_wrmirror(struct bmp *bmp, struct pullwr *pullwr) return written; } + +/* triggered when a bgp packet is sent + * saves the packet if the packet was a bgp open + */ static int bmp_outgoing_packet(struct peer *peer, uint8_t type, bgp_size_t size, struct stream *packet) { @@ -769,6 +913,10 @@ static int bmp_outgoing_packet(struct peer *peer, uint8_t type, bgp_size_t size, return 0; } + +/* triggered when a bgp peer goes up + * sends a bmp peer up to all bmp peers and saves the bgp open packet + */ static int bmp_peer_status_changed(struct peer *peer) { struct bmp_bgp *bmpbgp = bmp_bgp_find(peer->bgp); @@ -817,6 +965,9 @@ static int bmp_peer_status_changed(struct peer *peer) return 0; } +/* triggered when a bgp peer goes down + * sends a bmp peer down to all bmp peers and free the saved bgp open packet + */ static int bmp_peer_backward(struct peer *peer) { struct bmp_bgp *bmpbgp = bmp_bgp_find(peer->bgp); @@ -839,6 +990,10 @@ static int bmp_peer_backward(struct peer *peer) return 0; } + +/* sends a bmp end-of-rib on the bmp session for the given afi/safi + * for each peer + */ static void bmp_eor(struct bmp *bmp, afi_t afi, safi_t safi, uint8_t flags, uint8_t peer_type_flag) { @@ -908,6 +1063,8 @@ static void bmp_eor(struct bmp *bmp, afi_t afi, safi_t safi, uint8_t flags, stream_free(s); } +/* makes a bgp update to be embedded in a bmp monitoring message + */ static struct stream *bmp_update(const struct prefix *p, struct prefix_rd *prd, struct peer *peer, struct attr *attr, afi_t afi, safi_t safi) @@ -958,6 +1115,8 @@ static struct stream *bmp_update(const struct prefix *p, struct prefix_rd *prd, return s; } +/* makes a bgp withdraw to be embedded in a bmp monitoring message + */ static struct stream *bmp_withdraw(const struct prefix *p, struct prefix_rd *prd, afi_t afi, safi_t safi) @@ -999,6 +1158,8 @@ static struct stream *bmp_withdraw(const struct prefix *p, return s; } +/* sends a bmp monitoring message using the given information on the bmp session + */ static void bmp_monitor(struct bmp *bmp, struct peer *peer, uint8_t flags, uint8_t peer_type_flag, const struct prefix *p, struct prefix_rd *prd, struct attr *attr, afi_t afi, @@ -1039,6 +1200,145 @@ static void bmp_monitor(struct bmp *bmp, struct peer *peer, uint8_t flags, stream_free(msg); } + +struct rib_out_pre_updgrp_walkctx { + struct bmp *bmp; + const struct prefix *pfx; + struct bgp_dest *dest; + struct bgp_path_info *bpi; + struct prefix_rd *prd; + struct attr *attr; + bool *written_ref; +}; + +static int bmp_monitor_rib_out_pre_updgrp_walkcb(struct update_group *updgrp, + void *hidden_ctx) +{ + + + struct rib_out_pre_updgrp_walkctx *ctx = + (struct rib_out_pre_updgrp_walkctx *)hidden_ctx; + + struct update_subgroup *subgrp; + struct peer_af *paf; + UPDGRP_FOREACH_SUBGRP (updgrp, subgrp) { + + struct attr dummy_attr = {0}; + if (!subgroup_announce_check(ctx->dest, ctx->bpi, subgrp, + ctx->pfx, &dummy_attr, NULL, + BGP_ANNCHK_SPECIAL_PREPOLICY)) + continue; + + SUBGRP_FOREACH_PEER (subgrp, paf) { + bmp_monitor(ctx->bmp, PAF_PEER(paf), BMP_PEER_FLAG_O, + BMP_PEER_TYPE_GLOBAL_INSTANCE, + bgp_dest_get_prefix(ctx->dest), ctx->prd, ctx->attr, + SUBGRP_AFI(subgrp), SUBGRP_SAFI(subgrp), + monotime(NULL)); + + *ctx->written_ref = true; + } + } + + return HASHWALK_CONTINUE; +}; + +/* only for bmp sync */ +static inline bool bmp_monitor_rib_out_pre_walk(struct bmp *bmp, afi_t afi, + safi_t safi, const struct prefix *pfx, + struct bgp_dest *dest, + struct bgp_path_info *bpi, + struct attr *attr, + struct prefix_rd *prd) +{ + bool written = false; + struct rib_out_pre_updgrp_walkctx walkctx = {.bmp = bmp, + .pfx = pfx, + .dest = dest ? dest + : bpi->net, + .bpi = bpi, + .attr = attr, + .prd = prd, + .written_ref = &written}; + + update_group_af_walk(bmp->targets->bgp, afi, safi, + bmp_monitor_rib_out_pre_updgrp_walkcb, + (void *)&walkctx); + + return written; +} + +struct rib_out_post_updgrp_walkctx { + struct bmp *bmp; + const struct prefix *pfx; + struct bgp_dest *dest; + struct prefix_rd *prd; + bool *written_ref; +}; + +/* only for bmp sync */ +static int bmp_monitor_rib_out_post_updgrp_walkcb(struct update_group *updgrp, + void *hidden_ctx) +{ + + + struct rib_out_post_updgrp_walkctx *ctx = + (struct rib_out_post_updgrp_walkctx *)hidden_ctx; + + struct update_subgroup *subgrp; + struct peer_af *paf; + struct bgp_adj_out *adj; + struct attr *advertised_attr; + + UPDGRP_FOREACH_SUBGRP (updgrp, subgrp) { + + adj = adj_lookup(ctx->dest, subgrp, 0); + + if (!adj) + continue; + + advertised_attr = !adj->adv ? adj->attr + : adj->adv->baa ? adj->adv->baa->attr + : NULL; + + SUBGRP_FOREACH_PEER (subgrp, paf) { + bmp_monitor(ctx->bmp, PAF_PEER(paf), + BMP_PEER_FLAG_O | BMP_PEER_FLAG_L, + BMP_PEER_TYPE_GLOBAL_INSTANCE, ctx->pfx, + ctx->prd, advertised_attr, + SUBGRP_AFI(subgrp), SUBGRP_SAFI(subgrp), + monotime(NULL)); + + *ctx->written_ref = true; + } + } + + return HASHWALK_CONTINUE; +}; + +static inline bool bmp_monitor_rib_out_post_walk(struct bmp *bmp, afi_t afi, + safi_t safi, + const struct prefix *pfx, + struct bgp_dest *dest, + struct prefix_rd *prd) +{ + bool written = false; + struct rib_out_post_updgrp_walkctx walkctx = {.bmp = bmp, + .pfx = pfx, + .dest = dest, + .prd = prd, + .written_ref = &written}; + + update_group_af_walk(bmp->targets->bgp, afi, safi, + bmp_monitor_rib_out_post_updgrp_walkcb, + (void *)&walkctx); + + + return written; +} + +/* does the bmp initial rib synchronization + */ static bool bmp_wrsync(struct bmp *bmp, struct pullwr *pullwr) { afi_t afi; @@ -1139,12 +1439,28 @@ static bool bmp_wrsync(struct bmp *bmp, struct pullwr *pullwr) bmp->remote, afi2str(afi), safi2str(safi)); - bmp_eor(bmp, afi, safi, BMP_PEER_FLAG_L, - BMP_PEER_TYPE_GLOBAL_INSTANCE); - bmp_eor(bmp, afi, safi, 0, - BMP_PEER_TYPE_GLOBAL_INSTANCE); - bmp_eor(bmp, afi, safi, 0, - BMP_PEER_TYPE_LOC_RIB_INSTANCE); + if (CHECK_FLAG(bmp->targets->afimon[afi][safi], + BMP_MON_IN_PREPOLICY)) + bmp_eor(bmp, afi, safi, 0, + BMP_PEER_TYPE_GLOBAL_INSTANCE); + if (CHECK_FLAG(bmp->targets->afimon[afi][safi], + BMP_MON_IN_POSTPOLICY)) + bmp_eor(bmp, afi, safi, BMP_PEER_FLAG_L, + BMP_PEER_TYPE_GLOBAL_INSTANCE); + if (CHECK_FLAG(bmp->targets->afimon[afi][safi], + BMP_MON_LOC_RIB)) + bmp_eor(bmp, afi, safi, 0, + BMP_PEER_TYPE_LOC_RIB_INSTANCE); + if (CHECK_FLAG(bmp->targets->afimon[afi][safi], + BMP_MON_OUT_PREPOLICY)) + bmp_eor(bmp, afi, safi, BMP_PEER_FLAG_O, + BMP_PEER_TYPE_GLOBAL_INSTANCE); + if (CHECK_FLAG(bmp->targets->afimon[afi][safi], + BMP_MON_OUT_POSTPOLICY)) + bmp_eor(bmp, afi, safi, + BMP_PEER_FLAG_O | + BMP_PEER_FLAG_L, + BMP_PEER_TYPE_GLOBAL_INSTANCE); bmp->afistate[afi][safi] = BMP_AFI_LIVE; bmp->syncafi = AFI_MAX; @@ -1156,9 +1472,9 @@ static bool bmp_wrsync(struct bmp *bmp, struct pullwr *pullwr) } if (CHECK_FLAG(bmp->targets->afimon[afi][safi], - BMP_MON_POSTPOLICY) || - CHECK_FLAG(bmp->targets->afimon[afi][safi], - BMP_MON_LOC_RIB)) { + BMP_MON_IN_POSTPOLICY | BMP_MON_LOC_RIB | + BMP_MON_OUT_PREPOLICY | + BMP_MON_OUT_POSTPOLICY)) { for (bpiter = bgp_dest_get_bgp_path_info(bn); bpiter; bpiter = bpiter->next) { if (!CHECK_FLAG(bpiter->flags, @@ -1176,7 +1492,7 @@ static bool bmp_wrsync(struct bmp *bmp, struct pullwr *pullwr) } } if (CHECK_FLAG(bmp->targets->afimon[afi][safi], - BMP_MON_PREPOLICY)) { + BMP_MON_IN_PREPOLICY)) { for (adjiter = bn->adj_in; adjiter; adjiter = adjiter->next) { if (adjiter->peer->qobj_node.nid @@ -1214,30 +1530,62 @@ static bool bmp_wrsync(struct bmp *bmp, struct pullwr *pullwr) (safi == SAFI_MPLS_VPN)) prd = (struct prefix_rd *)bgp_dest_get_prefix(bmp->syncrdpos); + bool written = false; + + if (adjin) { + bmp_monitor(bmp, adjin->peer, 0, BMP_PEER_TYPE_GLOBAL_INSTANCE, + bn_p, prd, adjin->attr, afi, safi, adjin->uptime); + written = true; + } + + if (bpi && CHECK_FLAG(bpi->flags, BGP_PATH_VALID) && + CHECK_FLAG(bmp->targets->afimon[afi][safi], + BMP_MON_IN_POSTPOLICY)) { + bmp_monitor(bmp, bpi->peer, BMP_PEER_FLAG_L, + BMP_PEER_TYPE_GLOBAL_INSTANCE, bn_p, prd, bpi->attr, + afi, safi, bpi->uptime); + written = true; + } + if (bpi && CHECK_FLAG(bpi->flags, BGP_PATH_SELECTED) && CHECK_FLAG(bmp->targets->afimon[afi][safi], BMP_MON_LOC_RIB)) { bmp_monitor(bmp, bpi->peer, 0, BMP_PEER_TYPE_LOC_RIB_INSTANCE, bn_p, prd, bpi->attr, afi, safi, bpi && bpi->extra ? bpi->extra->bgp_rib_uptime : (time_t)(-1L)); + written = true; } - if (bpi && CHECK_FLAG(bpi->flags, BGP_PATH_VALID) && - CHECK_FLAG(bmp->targets->afimon[afi][safi], BMP_MON_POSTPOLICY)) - bmp_monitor(bmp, bpi->peer, BMP_PEER_FLAG_L, - BMP_PEER_TYPE_GLOBAL_INSTANCE, bn_p, prd, bpi->attr, - afi, safi, bpi->uptime); + if (bpi && CHECK_FLAG(bpi->flags, BGP_PATH_SELECTED) && + CHECK_FLAG(bmp->targets->afimon[afi][safi], + BMP_MON_OUT_PREPOLICY)) { + written |= bmp_monitor_rib_out_pre_walk( + bmp, afi, safi, bgp_dest_get_prefix(bn), bn, bpi, bpi->attr, prd); + } - if (adjin) - bmp_monitor(bmp, adjin->peer, 0, BMP_PEER_TYPE_GLOBAL_INSTANCE, - bn_p, prd, adjin->attr, afi, safi, adjin->uptime); + if (bpi && CHECK_FLAG(bpi->flags, BGP_PATH_SELECTED) && + CHECK_FLAG(bmp->targets->afimon[afi][safi], + BMP_MON_OUT_POSTPOLICY)) { + written |= bmp_monitor_rib_out_post_walk(bmp, afi, safi, bgp_dest_get_prefix(bn), + bn, prd); + } if (bn) bgp_dest_unlock_node(bn); - return true; + /* if we got here and nothing is written it means this specific + * destination had no monitoring configured or configured monitoring + * had nothing to send. we need to bump for wrsync to be called for the + * following destinations in the table + */ + if (!written) + pullwr_bump(bmp->pullwr); + + return written; } +/* pulls a bqe from a given list + */ static struct bmp_queue_entry * bmp_pull_from_queue(struct bmp_qlist_head *list, struct bmp_qhash_head *hash, struct bmp_queue_entry **queuepos_ptr) @@ -1258,25 +1606,75 @@ bmp_pull_from_queue(struct bmp_qlist_head *list, struct bmp_qhash_head *hash, return bqe; } -static inline struct bmp_queue_entry *bmp_pull(struct bmp *bmp) +/* shortcut to pull a bqe from the rib-in pre/post queue + */ +static inline struct bmp_queue_entry *bmp_pull_ribin(struct bmp *bmp) { - return bmp_pull_from_queue(&bmp->targets->updlist, - &bmp->targets->updhash, &bmp->queuepos); + return bmp_pull_from_queue(&bmp->targets->mon_in_updlist, + &bmp->targets->mon_in_updhash, + &bmp->mon_in_queuepos); } +/* shortcut to pull a bqe from the loc-rib + */ static inline struct bmp_queue_entry *bmp_pull_locrib(struct bmp *bmp) { - return bmp_pull_from_queue(&bmp->targets->locupdlist, - &bmp->targets->locupdhash, - &bmp->locrib_queuepos); + return bmp_pull_from_queue(&bmp->targets->mon_loc_updlist, + &bmp->targets->mon_loc_updhash, + &bmp->mon_loc_queuepos); } -/* TODO BMP_MON_LOCRIB find a way to merge properly this function with - * bmp_wrqueue or abstract it if possible +/* shortcut to pull a bqe from the rib-out pre/post queue */ -static bool bmp_wrqueue_locrib(struct bmp *bmp, struct pullwr *pullwr) +static inline struct bmp_queue_entry *bmp_pull_ribout(struct bmp *bmp) +{ + return bmp_pull_from_queue(&bmp->targets->mon_out_updlist, + &bmp->targets->mon_out_updhash, + &bmp->mon_out_queuepos); +} + +/* returns 1 if the prefix will be synced later + * it means that we do not need to send an update about this prefix + */ +static inline int bmp_prefix_will_sync(struct bmp *bmp, afi_t afi, safi_t safi, + struct prefix *prefix) { + switch (bmp->afistate[afi][safi]) { + case BMP_AFI_INACTIVE: + case BMP_AFI_NEEDSYNC: + /* this afi will be synced later, wait for sync + */ + return 1; + case BMP_AFI_SYNC: + if (prefix_cmp(prefix, &bmp->syncpos) <= 0) + /* currently syncing but have already passed this + * prefix => send it. */ + return 0; + + /* currently syncing & haven't reached this prefix yet + * => it'll be sent as part of the table sync, no update here + */ + + return 1; + case BMP_AFI_LIVE: + /* this afi has already been synced, send an update + */ + return 0; + } + + return 0; +} + +/* gets a bqe from the loc-rib queue and sends a bmp monitoring message for + * loc-rib (if configured) + * the messages use the first selected path found in rib matching the prefix + * + * TODO BMP_MON_LOCRIB find a way to merge properly this function with + * bmp_wrqueue_in or abstract it if possible + */ +static bool bmp_wrqueue_locrib(struct bmp *bmp, struct pullwr *pullwr) +{ struct bmp_queue_entry *bqe; struct peer *peer; struct bgp_dest *bn = NULL; @@ -1292,24 +1690,9 @@ static bool bmp_wrqueue_locrib(struct bmp *bmp, struct pullwr *pullwr) if (!CHECK_FLAG(bmp->targets->afimon[afi][safi], BMP_MON_LOC_RIB)) goto out; - switch (bmp->afistate[afi][safi]) { - case BMP_AFI_INACTIVE: - case BMP_AFI_NEEDSYNC: + if (bmp_prefix_will_sync(bmp, afi, safi, &bqe->p)) goto out; - case BMP_AFI_SYNC: - if (prefix_cmp(&bqe->p, &bmp->syncpos) <= 0) - /* currently syncing but have already passed this - * prefix => send it. - */ - break; - /* currently syncing & haven't reached this prefix yet - * => it'll be sent as part of the table sync, no need here - */ - goto out; - case BMP_AFI_LIVE: - break; - } peer = QOBJ_GET_TYPESAFE(bqe->peerid, peer); if (!peer) { @@ -1323,6 +1706,8 @@ static bool bmp_wrqueue_locrib(struct bmp *bmp, struct pullwr *pullwr) goto out; } + /* retrieve info about the selected path + */ bool is_vpn = (bqe->afi == AFI_L2VPN && bqe->safi == SAFI_EVPN) || (bqe->safi == SAFI_MPLS_VPN); @@ -1345,10 +1730,9 @@ static bool bmp_wrqueue_locrib(struct bmp *bmp, struct pullwr *pullwr) bpi && bpi->extra ? bpi->extra->bgp_rib_uptime : (time_t)(-1L)); written = true; - out: if (!bqe->refcount) - XFREE(MTYPE_BMP_QUEUE, bqe); + bmp_bqe_free(bqe); if (bn) bgp_dest_unlock_node(bn); @@ -1356,35 +1740,26 @@ static bool bmp_wrqueue_locrib(struct bmp *bmp, struct pullwr *pullwr) return written; } -static bool bmp_wrqueue(struct bmp *bmp, struct pullwr *pullwr) +/* gets a bqe from the rib-in pre/post queue and sends a bmp monitoring + * message to the peer for each configured monitoring feature about the + * first valid path found in rib for this prefix + */ +static bool bmp_wrqueue_in(struct bmp *bmp, struct pullwr *pullwr) { struct bmp_queue_entry *bqe; struct peer *peer; struct bgp_dest *bn = NULL; bool written = false; - bqe = bmp_pull(bmp); + bqe = bmp_pull_ribin(bmp); if (!bqe) return false; afi_t afi = bqe->afi; safi_t safi = bqe->safi; - switch (bmp->afistate[afi][safi]) { - case BMP_AFI_INACTIVE: - case BMP_AFI_NEEDSYNC: - goto out; - case BMP_AFI_SYNC: - if (prefix_cmp(&bqe->p, &bmp->syncpos) <= 0) - /* currently syncing but have already passed this - * prefix => send it. */ - break; - - /* currently syncing & haven't reached this prefix yet - * => it'll be sent as part of the table sync, no need here */ + if (bmp_prefix_will_sync(bmp, afi, safi, &bqe->p)) { goto out; - case BMP_AFI_LIVE: - break; } peer = QOBJ_GET_TYPESAFE(bqe->peerid, peer); @@ -1402,7 +1777,22 @@ static bool bmp_wrqueue(struct bmp *bmp, struct pullwr *pullwr) bn = bgp_safi_node_lookup(bmp->targets->bgp->rib[afi][safi], safi, &bqe->p, prd); - if (CHECK_FLAG(bmp->targets->afimon[afi][safi], BMP_MON_POSTPOLICY)) { + if (CHECK_FLAG(bmp->targets->afimon[afi][safi], BMP_MON_IN_PREPOLICY)) { + struct bgp_adj_in *adjin; + + for (adjin = bn ? bn->adj_in : NULL; adjin; + adjin = adjin->next) { + if (adjin->peer == peer) + break; + } + bmp_monitor(bmp, peer, 0, BMP_PEER_TYPE_GLOBAL_INSTANCE, + &bqe->p, prd, adjin ? adjin->attr : NULL, afi, safi, + adjin ? adjin->uptime : monotime(NULL)); + written = true; + } + + if (CHECK_FLAG(bmp->targets->afimon[afi][safi], + BMP_MON_IN_POSTPOLICY)) { struct bgp_path_info *bpi; for (bpi = bgp_dest_get_bgp_path_info(bn); bpi; @@ -1420,23 +1810,99 @@ static bool bmp_wrqueue(struct bmp *bmp, struct pullwr *pullwr) written = true; } - if (CHECK_FLAG(bmp->targets->afimon[afi][safi], BMP_MON_PREPOLICY)) { - struct bgp_adj_in *adjin; +out: + if (!bqe->refcount) + bmp_bqe_free(bqe); - for (adjin = bn ? bn->adj_in : NULL; adjin; - adjin = adjin->next) { - if (adjin->peer == peer) - break; + if (bn) + bgp_dest_unlock_node(bn); + + return written; +} + +/* gets a bqe from the rib-out post queue and sends a bmp rib-out + * pre/post-policy monitoring message to the peer + */ +static bool bmp_wrqueue_ribout(struct bmp *bmp, struct pullwr *pullwr) +{ + struct bmp_queue_entry *bqe; + struct peer *peer; + struct bgp_dest *bn = NULL; + bool written = false; + + bqe = bmp_pull_ribout(bmp); + if (!bqe) + return false; + + afi_t afi = bqe->afi; + safi_t safi = bqe->safi; + + if (!CHECK_FLAG(bmp->targets->afimon[afi][safi], + BMP_MON_OUT_POSTPOLICY | BMP_MON_OUT_PREPOLICY)) { + goto out; + } + + if (bmp_prefix_will_sync(bmp, afi, safi, &bqe->p)) { + goto out; + } + + peer = QOBJ_GET_TYPESAFE(bqe->peerid, peer); + if (!peer) { + zlog_info("bmp: skipping queued item for deleted peer"); + goto out; + } + + bool is_vpn = (bqe->afi == AFI_L2VPN && bqe->safi == SAFI_EVPN) || + (bqe->safi == SAFI_MPLS_VPN); + + struct prefix_rd *prd = is_vpn ? &bqe->rd : NULL; + + bn = bgp_safi_node_lookup(bmp->targets->bgp->rib[afi][safi], safi, + &bqe->p, prd); + + if (CHECK_FLAG(bmp->targets->afimon[afi][safi], + BMP_MON_OUT_PREPOLICY) && + CHECK_FLAG(bqe->flags, BMP_MON_OUT_PREPOLICY)) { + + struct bgp_path_info *bpi = bqe->locked_bpi; + + if (!bpi) { + for (bpi = bgp_dest_get_bgp_path_info(bn); + bpi; bpi = bpi->next) + if (CHECK_FLAG(bpi->flags, BGP_PATH_SELECTED)) + break; } - bmp_monitor(bmp, peer, 0, BMP_PEER_TYPE_GLOBAL_INSTANCE, - &bqe->p, prd, adjin ? adjin->attr : NULL, afi, safi, - adjin ? adjin->uptime : monotime(NULL)); + + bmp_monitor(bmp, peer, BMP_PEER_FLAG_O, + BMP_PEER_TYPE_GLOBAL_INSTANCE, &bqe->p, prd, + !bqe->locked_bpi && bpi ? bpi->attr : NULL, afi, + safi, monotime(NULL)); + + written = true; + } + + if (CHECK_FLAG(bmp->targets->afimon[afi][safi], + BMP_MON_OUT_POSTPOLICY) && + CHECK_FLAG(bqe->flags, BMP_MON_OUT_POSTPOLICY)) { + struct bgp_adj_out *adj; + struct attr *advertised_attr; + + adj = adj_lookup(bn, peer_subgroup(peer, afi, safi), 0); + advertised_attr = adj ? !adj->adv ? adj->attr + : adj->adv->baa ? adj->adv->baa->attr + : NULL + : NULL; + + bmp_monitor(bmp, peer, BMP_PEER_FLAG_L | BMP_PEER_FLAG_O, + BMP_PEER_TYPE_GLOBAL_INSTANCE, &bqe->p, prd, + advertised_attr, afi, safi, monotime(NULL)); + written = true; } out: if (!bqe->refcount) - XFREE(MTYPE_BMP_QUEUE, bqe); + bmp_bqe_free(bqe); if (bn) bgp_dest_unlock_node(bn); @@ -1444,9 +1910,27 @@ static bool bmp_wrqueue(struct bmp *bmp, struct pullwr *pullwr) return written; } +/* called, when the socket is available, to retrieve data to send + */ static void bmp_wrfill(struct bmp *bmp, struct pullwr *pullwr) { + + uint32_t timeout_ms; + uint32_t startup_delay = bmp->targets->bmpbgp->startup_delay_ms; switch(bmp->state) { + case BMP_StartupIdle: + if ((timeout_ms = bmp_time_since_startup(NULL)) < + startup_delay) { + pullwr_timeout(pullwr, startup_delay - timeout_ms); + return; + } + + zlog_info( + "bmp: Startup timeout expired, time since startup is %" PRIu32 + "ms", + timeout_ms); + bmp->state = BMP_PeerUp; + // fall through case BMP_PeerUp: bmp_send_peerup(bmp); bmp->state = BMP_Run; @@ -1455,10 +1939,12 @@ static void bmp_wrfill(struct bmp *bmp, struct pullwr *pullwr) case BMP_Run: if (bmp_wrmirror(bmp, pullwr)) break; - if (bmp_wrqueue(bmp, pullwr)) + if (bmp_wrqueue_in(bmp, pullwr)) break; if (bmp_wrqueue_locrib(bmp, pullwr)) break; + if (bmp_wrqueue_ribout(bmp, pullwr)) + break; if (bmp_wrsync(bmp, pullwr)) break; break; @@ -1477,10 +1963,19 @@ static void bmp_wrerr(struct bmp *bmp, struct pullwr *pullwr, bool eof) bmp_free(bmp); } +/* inserts a bmp_queue_entry in the updlist. overwrites any similar + * bmp_queue_entry in the list. + * + * returns the bqe inserted or NULL if ignored + * + * need to update correct queue pos for all sessions of the target after + * a call to this function + */ static struct bmp_queue_entry * bmp_process_one(struct bmp_targets *bt, struct bmp_qhash_head *updhash, struct bmp_qlist_head *updlist, struct bgp *bgp, afi_t afi, - safi_t safi, struct bgp_dest *bn, struct peer *peer) + safi_t safi, struct bgp_dest *bn, struct peer *peer, + uint8_t mon_flag, struct bgp_path_info *lock_bpi) { struct bmp_queue_entry *bqe, bqeref; size_t refcount; @@ -1494,6 +1989,8 @@ bmp_process_one(struct bmp_targets *bt, struct bmp_qhash_head *updhash, bqeref.peerid = peer->qobj_node.nid; bqeref.afi = afi; bqeref.safi = safi; + bqeref.flags = mon_flag; + bqeref.locked_bpi = lock_bpi; if ((afi == AFI_L2VPN && safi == SAFI_EVPN && bn->pdest) || (safi == SAFI_MPLS_VPN)) @@ -1502,8 +1999,17 @@ bmp_process_one(struct bmp_targets *bt, struct bmp_qhash_head *updhash, bqe = bmp_qhash_find(updhash, &bqeref); if (bqe) { + SET_FLAG(bqe->flags, mon_flag); + /* swap locked bpis and un/lock pre/new bpis */ + if (lock_bpi && lock_bpi == bqe->locked_bpi) { + bmp_lock_bpi(lock_bpi); + bmp_unlock_bpi(bqe->locked_bpi); + bqe->locked_bpi = lock_bpi; + } + if (bqe->refcount >= refcount) - /* nothing to do here */ + /* same update, not sent to anyone yet, + * nothing to do here */ return NULL; bmp_qlist_del(updlist, bqe); @@ -1512,46 +2018,50 @@ bmp_process_one(struct bmp_targets *bt, struct bmp_qhash_head *updhash, memcpy(bqe, &bqeref, sizeof(*bqe)); bmp_qhash_add(updhash, bqe); + + if (lock_bpi) + bmp_lock_bpi(lock_bpi); } bqe->refcount = refcount; bmp_qlist_add_tail(updlist, bqe); return bqe; - - /* need to update correct queue pos for all sessions of the target after - * a call to this function - */ } -static int bmp_process(struct bgp *bgp, afi_t afi, safi_t safi, - struct bgp_dest *bn, struct peer *peer, bool withdraw) + +/* triggered when a change in adj-rib-in is detected. + * inserts a bqe to the adj-rib-in monitoring queue which will trigger a + * bmp monitoring message to be sent for adj-rib-in pre and/or post policy + * if enabled in config + */ +static int bmp_process_ribin(struct bgp *bgp, afi_t afi, safi_t safi, + struct bgp_dest *bn, struct peer *peer, + bool withdraw) { struct bmp_bgp *bmpbgp = bmp_bgp_find(peer->bgp); struct bmp_targets *bt; struct bmp *bmp; - if (frrtrace_enabled(frr_bgp, bmp_process)) { + if (frrtrace_enabled(frr_bgp, bmp_process_ribin)) { char pfxprint[PREFIX2STR_BUFFER]; prefix2str(&bn->rn->p, pfxprint, sizeof(pfxprint)); - frrtrace(5, frr_bgp, bmp_process, peer, pfxprint, afi, safi, - withdraw); + frrtrace(5, frr_bgp, bmp_process_ribin, peer, pfxprint, afi, + safi, withdraw); } if (!bmpbgp) return 0; frr_each(bmp_targets, &bmpbgp->targets, bt) { - /* check if any monitoring is enabled (ignoring loc-rib since it - * uses another hook & queue - */ - if (!CHECK_FLAG(bt->afimon[afi][safi], ~BMP_MON_LOC_RIB)) + if (!CHECK_FLAG(bt->afimon[afi][safi], + BMP_MON_IN_PREPOLICY | BMP_MON_IN_POSTPOLICY)) continue; - struct bmp_queue_entry *last_item = - bmp_process_one(bt, &bt->updhash, &bt->updlist, bgp, - afi, safi, bn, peer); + struct bmp_queue_entry *last_item = bmp_process_one( + bt, &bt->mon_in_updhash, &bt->mon_in_updlist, bgp, afi, + safi, bn, peer, BMP_QUEUE_FLAGS_NONE, NULL); /* if bmp_process_one returns NULL * we don't have anything to do next @@ -1560,8 +2070,8 @@ static int bmp_process(struct bgp *bgp, afi_t afi, safi_t safi, continue; frr_each(bmp_session, &bt->sessions, bmp) { - if (!bmp->queuepos) - bmp->queuepos = last_item; + if (!bmp->mon_in_queuepos) + bmp->mon_in_queuepos = last_item; pullwr_bump(bmp->pullwr); } @@ -1578,6 +2088,25 @@ static void bmp_stat_put_u32(struct stream *s, size_t *cnt, uint16_t type, (*cnt)++; } +static void bmp_stat_put_u64(struct stream *s, size_t *cnt, uint16_t type, + uint64_t value) +{ + stream_putw(s, type); + stream_putw(s, 8); + stream_putq(s, value); + (*cnt)++; +} + +static void bmp_stat_put_af_u64(struct stream *s, size_t *cnt, uint16_t type, + afi_t afi, safi_t safi, uint64_t value) +{ + stream_putw(s, type); + stream_putw(s, 2 + 1 + 8); + stream_put3(s, (afi_int2iana(afi) << 8) + safi_int2iana(safi)); + stream_putq(s, value); + (*cnt)++; +} + static void bmp_stats(struct event *thread) { struct bmp_targets *bt = EVENT_ARG(thread); @@ -1585,6 +2114,12 @@ static void bmp_stats(struct event *thread) struct peer *peer; struct listnode *node; struct timeval tv; + afi_t afi; + safi_t safi; + + uint64_t af_stat[AFI_MAX][SAFI_MAX]; + struct update_subgroup *subgrp; + if (bt->stat_msec) event_add_timer_msec(bm->master, bmp_stats, bt, bt->stat_msec, @@ -1595,6 +2130,7 @@ static void bmp_stats(struct event *thread) /* Walk down all peers */ for (ALL_LIST_ELEMENTS_RO(bt->bgp->peer, node, peer)) { size_t count = 0, count_pos, len; + uint64_t per_af_sum = 0; if (!peer_established(peer->connection)) continue; @@ -1609,16 +2145,61 @@ static void bmp_stats(struct event *thread) bmp_stat_put_u32(s, &count, BMP_STATS_PFX_REJECTED, peer->stat_pfx_filter); + bmp_stat_put_u32(s, &count, BMP_STATS_PFX_DUP_WITHDRAW, + peer->stat_pfx_dup_withdraw); + bmp_stat_put_u32(s, &count, BMP_STATS_UPD_LOOP_CLUSTER, + peer->stat_pfx_cluster_loop); bmp_stat_put_u32(s, &count, BMP_STATS_UPD_LOOP_ASPATH, peer->stat_pfx_aspath_loop); bmp_stat_put_u32(s, &count, BMP_STATS_UPD_LOOP_ORIGINATOR, - peer->stat_pfx_originator_loop); - bmp_stat_put_u32(s, &count, BMP_STATS_UPD_LOOP_CLUSTER, - peer->stat_pfx_cluster_loop); - bmp_stat_put_u32(s, &count, BMP_STATS_PFX_DUP_WITHDRAW, - peer->stat_pfx_dup_withdraw); + peer->stat_pfx_originator_loop); bmp_stat_put_u32(s, &count, BMP_STATS_UPD_7606_WITHDRAW, peer->stat_upd_7606); + +#define BMP_PER_AF_STAT(afi_var, safi_var, af_stat_arr, sum_var, safi_stat, \ + safi_stat_call, sum_stat_call) \ + do { \ + (sum_var) = 0; \ + FOREACH_AFI_SAFI ((afi_var), (safi_var)) { \ + (sum_var) += ((af_stat_arr)[(afi_var)][(safi_var)] = \ + (safi_stat)); \ + if ((af_stat_arr)[(afi_var)][(safi_var)]) \ + (safi_stat_call); \ + }; \ + (sum_stat_call); \ + } while (0); + + BMP_PER_AF_STAT( + afi, safi, af_stat, per_af_sum, + peer->stat_adj_in_count[afi][safi], + bmp_stat_put_af_u64(s, &count, + BMP_STATS_SIZE_ADJ_RIB_IN_SAFI, afi, + safi, af_stat[afi][safi]), + bmp_stat_put_u64(s, &count, BMP_STATS_SIZE_ADJ_RIB_IN, + per_af_sum)); + + BMP_PER_AF_STAT( + afi, safi, af_stat, per_af_sum, + peer->stat_loc_rib_count[afi][safi], + bmp_stat_put_af_u64(s, &count, + BMP_STATS_SIZE_LOC_RIB_SAFI, afi, + safi, af_stat[afi][safi]), + bmp_stat_put_u64(s, &count, BMP_STATS_SIZE_LOC_RIB, + per_af_sum)); + + BMP_PER_AF_STAT( + afi, safi, af_stat, per_af_sum, + ((subgrp = peer_subgroup(peer, afi, safi)) + ? subgrp->pscount + : 0), + bmp_stat_put_af_u64( + s, &count, BMP_STATS_SIZE_ADJ_RIB_OUT_POST_SAFI, + afi, safi, af_stat[afi][safi]), + bmp_stat_put_u64(s, &count, + BMP_STATS_SIZE_ADJ_RIB_OUT_POST, + per_af_sum)); + + bmp_stat_put_u32(s, &count, BMP_STATS_FRR_NH_INVALID, peer->stat_pfx_nh_invalid); @@ -1726,7 +2307,7 @@ static struct bmp *bmp_open(struct bmp_targets *bt, int bmp_sock) bmp = bmp_new(bt, bmp_sock); strlcpy(bmp->remote, buf, sizeof(bmp->remote)); - bmp->state = BMP_PeerUp; + bmp->state = BMP_StartupIdle; bmp->pullwr = pullwr_new(bm->master, bmp_sock, bmp, bmp_wrfill, bmp_wrerr); event_add_read(bm->master, bmp_read, bmp, bmp_sock, &bmp->t_read); @@ -1769,12 +2350,15 @@ static void bmp_close(struct bmp *bmp) while ((bmq = bmp_pull_mirror(bmp))) if (!bmq->refcount) XFREE(MTYPE_BMP_MIRRORQ, bmq); - while ((bqe = bmp_pull(bmp))) + while ((bqe = bmp_pull_ribin(bmp))) if (!bqe->refcount) - XFREE(MTYPE_BMP_QUEUE, bqe); + bmp_bqe_free(bqe); while ((bqe = bmp_pull_locrib(bmp))) if (!bqe->refcount) - XFREE(MTYPE_BMP_QUEUE, bqe); + bmp_bqe_free(bqe); + while ((bqe = bmp_pull_ribout(bmp))) + if (!bqe->refcount) + bmp_bqe_free(bqe); EVENT_OFF(bmp->t_read); pullwr_del(bmp->pullwr); @@ -1798,6 +2382,7 @@ static struct bmp_bgp *bmp_bgp_get(struct bgp *bgp) bmpbgp = XCALLOC(MTYPE_BMP, sizeof(*bmpbgp)); bmpbgp->bgp = bgp; bmpbgp->mirror_qsizelimit = ~0UL; + bmpbgp->startup_delay_ms = 0; bmp_mirrorq_init(&bmpbgp->mirrorq); bmp_bgph_add(&bmp_bgph, bmpbgp); @@ -1876,10 +2461,12 @@ static struct bmp_targets *bmp_targets_get(struct bgp *bgp, const char *name) bt->bgp = bgp; bt->bmpbgp = bmp_bgp_get(bgp); bmp_session_init(&bt->sessions); - bmp_qhash_init(&bt->updhash); - bmp_qlist_init(&bt->updlist); - bmp_qhash_init(&bt->locupdhash); - bmp_qlist_init(&bt->locupdlist); + bmp_qhash_init(&bt->mon_in_updhash); + bmp_qlist_init(&bt->mon_in_updlist); + bmp_qhash_init(&bt->mon_loc_updhash); + bmp_qlist_init(&bt->mon_loc_updlist); + bmp_qhash_init(&bt->mon_out_updhash); + bmp_qlist_init(&bt->mon_out_updlist); bmp_actives_init(&bt->actives); bmp_listeners_init(&bt->listeners); @@ -1908,10 +2495,12 @@ static void bmp_targets_put(struct bmp_targets *bt) bmp_listeners_fini(&bt->listeners); bmp_actives_fini(&bt->actives); - bmp_qhash_fini(&bt->updhash); - bmp_qlist_fini(&bt->updlist); - bmp_qhash_fini(&bt->locupdhash); - bmp_qlist_fini(&bt->locupdlist); + bmp_qhash_fini(&bt->mon_in_updhash); + bmp_qlist_fini(&bt->mon_in_updlist); + bmp_qhash_fini(&bt->mon_loc_updhash); + bmp_qlist_fini(&bt->mon_loc_updlist); + bmp_qhash_fini(&bt->mon_out_updhash); + bmp_qlist_fini(&bt->mon_out_updlist); XFREE(MTYPE_BMP_ACLNAME, bt->acl_name); XFREE(MTYPE_BMP_ACLNAME, bt->acl6_name); @@ -2455,17 +3044,57 @@ DEFPY(bmp_stats_cfg, return CMD_SUCCESS; } -#define BMP_POLICY_IS_LOCRIB(str) ((str)[0] == 'l') /* __l__oc-rib */ -#define BMP_POLICY_IS_PRE(str) ((str)[1] == 'r') /* p__r__e-policy */ +/* __l__oc-rib */ +#define BMP_POLICY_IS_LOCRIB(rib) ((rib)[0] == 'l') +/* rib-__i__n p__r__e-policy */ +#define BMP_POLICY_IS_IN_PRE(rib, policy) ((rib)[4] == 'i' \ + && (policy) \ + && (policy)[1] == 'r') \ + /* rib-__i__n p__o__st-policy */ +#define BMP_POLICY_IS_IN_POST(rib, policy) ((rib)[4] == 'i' \ + && (policy) \ + && (policy)[1] == 'o') \ + /* rib-__o__ut p__r__e-policy */ +#define BMP_POLICY_IS_OUT_PRE(rib, policy) ((rib)[4] == 'o' \ + && (policy) \ + && (policy)[1] == 'r') \ + /* rib-__o__ut p__o__st-policy */ +#define BMP_POLICY_IS_OUT_POST(rib, policy) ((rib)[4] == 'o' \ + && (policy) \ + && (policy)[1] == 'o') + + +DEFPY(bmp_show_locked_cfg, bmp_show_locked_cmd, "bmp locked", + BMP_STR "show cmd to debug locked bpi still in hash table\n") +{ + struct bmp_bpi_lock *lbpi; + frr_each (bmp_lbpi, &bmp_lbpi, lbpi) { + if (!lbpi) { + zlog_info("no bucket"); + continue; + } + if (!lbpi->locked) { + zlog_info("empty bucket"); + continue; + } + zlog_info("bucket: contains %pRN", lbpi->locked->net); + vty_out(vty, "bucket: contains %pRN", lbpi->locked->net); + } + + return CMD_SUCCESS; +} DEFPY(bmp_monitor_cfg, bmp_monitor_cmd, - "[no] bmp monitor $policy", + "[no] bmp monitor $rib [pre-policy|post-policy]$policy", NO_STR BMP_STR "Send BMP route monitoring messages\n" BGP_AF_STR BGP_AF_STR BGP_AF_STR - BGP_AF_STR BGP_AF_STR BGP_AF_STR BGP_AF_STR - "Send state before policy and filter processing\n" - "Send state with policy and filters applied\n" - "Send state after decision process is applied\n") + BGP_AF_MODIFIER_STR BGP_AF_MODIFIER_STR BGP_AF_MODIFIER_STR + BGP_AF_MODIFIER_STR + "Monitor BGP Adj-RIB-In\n" + "Monitor BGP Local-RIB\n" + "Monitor BGP Adj-RIB-Out\n" + "Send state of Adj-RIB-In/out before in/outbound policy is applied\n" + "Send state of Adj-RIB-In/out after in/outbound policy is applied\n") { int index = 0; uint8_t flag, prev; @@ -2478,18 +3107,26 @@ DEFPY(bmp_monitor_cfg, bmp_monitor_cmd, argv_find_and_parse_afi(argv, argc, &index, &afi); argv_find_and_parse_safi(argv, argc, &index, &safi); - if (BMP_POLICY_IS_LOCRIB(policy)) + if (BMP_POLICY_IS_LOCRIB(rib)) { flag = BMP_MON_LOC_RIB; - else if (BMP_POLICY_IS_PRE(policy)) - flag = BMP_MON_PREPOLICY; - else - flag = BMP_MON_POSTPOLICY; + } else if (BMP_POLICY_IS_IN_PRE(rib, policy)) { + flag = BMP_MON_IN_PREPOLICY; + } else if (BMP_POLICY_IS_IN_POST(rib, policy)) { + flag = BMP_MON_IN_POSTPOLICY; + } else if (BMP_POLICY_IS_OUT_PRE(rib, policy)) { + flag = BMP_MON_OUT_PREPOLICY; + } else if (BMP_POLICY_IS_OUT_POST(rib, policy)) { + flag = BMP_MON_OUT_POSTPOLICY; + } else { + vty_out(vty, "%% Target RIB doesn't exist\n"); + return CMD_WARNING; + } prev = bt->afimon[afi][safi]; if (no) - bt->afimon[afi][safi] &= ~flag; + UNSET_FLAG(bt->afimon[afi][safi], flag); else - bt->afimon[afi][safi] |= flag; + SET_FLAG(bt->afimon[afi][safi], flag); if (prev == bt->afimon[afi][safi]) return CMD_SUCCESS; @@ -2506,6 +3143,8 @@ DEFPY(bmp_monitor_cfg, bmp_monitor_cmd, } bmp->afistate[afi][safi] = BMP_AFI_NEEDSYNC; + + pullwr_bump(bmp->pullwr); } return CMD_SUCCESS; @@ -2573,6 +3212,26 @@ DEFPY(no_bmp_mirror_limit_cfg, return CMD_SUCCESS; } +DEFPY(bmp_startup_delay_cfg, bmp_startup_delay_cmd, + "[no] bmp startup-delay [(0-4294967294)]$startup_delay", + NO_STR BMP_STR + "Configure delay before BMP starts sending monitoring and mirroring messages\n" + "Time in milliseconds\n") +{ + VTY_DECLVAR_CONTEXT(bgp, bgp); + struct bmp_bgp *bmpbgp; + + if (!no && startup_delay == 0) { + vty_out(vty, "Missing startup delay parameter\n"); + return CMD_ERR_INCOMPLETE; + } + + bmpbgp = bmp_bgp_get(bgp); + bmpbgp->startup_delay_ms = !no ? startup_delay : 0; + + return CMD_SUCCESS; +} + DEFPY(show_bmp, show_bmp_cmd, @@ -2589,6 +3248,7 @@ DEFPY(show_bmp, char uptime[BGP_UPTIME_LEN]; char *out; + vty_out(vty, "BMP Module started at %pTVM\n\n", &bmp_startup_time); frr_each(bmp_bgph, &bmp_bgph, bmpbgp) { vty_out(vty, "BMP state for BGP %s:\n\n", bmpbgp->bgp->name_pretty); @@ -2602,6 +3262,12 @@ DEFPY(show_bmp, bmpbgp->mirror_qsizelimit); vty_out(vty, "\n"); + vty_out(vty, " Startup delay : %s", + bmpbgp->startup_delay_ms == 0 ? "Immediate\n\n" : ""); + if (bmpbgp->startup_delay_ms != 0) + vty_out(vty, "%" PRIu32 "ms\n\n", + bmpbgp->startup_delay_ms); + frr_each(bmp_targets, &bmpbgp->targets, bt) { vty_out(vty, " Targets \"%s\":\n", bt->name); vty_out(vty, " Route Mirroring %sabled\n", @@ -2617,25 +3283,36 @@ DEFPY(show_bmp, if (!afimon_flag) continue; - const char *pre_str = + const char *in_pre_str = CHECK_FLAG(afimon_flag, - BMP_MON_PREPOLICY) - ? "pre-policy " + BMP_MON_IN_PREPOLICY) + ? "rib-in pre-policy " : ""; - const char *post_str = + const char *in_post_str = CHECK_FLAG(afimon_flag, - BMP_MON_POSTPOLICY) - ? "post-policy " + BMP_MON_IN_POSTPOLICY) + ? "rib-in post-policy " : ""; const char *locrib_str = CHECK_FLAG(afimon_flag, BMP_MON_LOC_RIB) - ? "loc-rib" + ? "loc-rib " + : ""; + const char *out_pre_str = + CHECK_FLAG(afimon_flag, + BMP_MON_OUT_PREPOLICY) + ? "rib-out pre-policy " + : ""; + const char *out_post_str = + CHECK_FLAG(afimon_flag, + BMP_MON_OUT_POSTPOLICY) + ? "rib-out post-policy " : ""; vty_out(vty, - " Route Monitoring %s %s %s%s%s\n", - afi2str(afi), safi2str(safi), pre_str, - post_str, locrib_str); + " Route Monitoring %s %s %s%s%s%s%s\n", + afi2str(afi), safi2str(safi), + in_pre_str, in_post_str, locrib_str, + out_pre_str, out_post_str); } vty_out(vty, " Listeners:\n"); @@ -2693,7 +3370,9 @@ DEFPY(show_bmp, vty_out(vty, "\n %zu connected clients:\n", bmp_session_count(&bt->sessions)); tt = ttable_new(&ttable_styles[TTSTYLE_BLANK]); - ttable_add_row(tt, "remote|uptime|MonSent|MirrSent|MirrLost|ByteSent|ByteQ|ByteQKernel"); + ttable_add_row( + tt, + "remote|uptime|state|MonSent|MirrSent|MirrLost|ByteSent|ByteQ|ByteQKernel"); ttable_rowseps(tt, 0, BOTTOM, true, '-'); frr_each (bmp_session, &bt->sessions, bmp) { @@ -2705,12 +3384,12 @@ DEFPY(show_bmp, peer_uptime(bmp->t_up.tv_sec, uptime, sizeof(uptime), false, NULL); - ttable_add_row(tt, "%s|%s|%Lu|%Lu|%Lu|%Lu|%zu|%zu", - bmp->remote, uptime, - bmp->cnt_update, - bmp->cnt_mirror, - bmp->cnt_mirror_overruns, - total, q, kq); + ttable_add_row( + tt, "%s|%s|%s|%Lu|%Lu|%Lu|%Lu|%zu|%zu", + bmp->remote, uptime, + bmp_state_str(bmp->state), + bmp->cnt_update, bmp->cnt_mirror, + bmp->cnt_mirror_overruns, total, q, kq); } out = ttable_dump(tt, "\n"); vty_out(vty, "%s", out); @@ -2739,6 +3418,10 @@ static int bmp_config_write(struct bgp *bgp, struct vty *vty) vty_out(vty, " !\n bmp mirror buffer-limit %zu\n", bmpbgp->mirror_qsizelimit); + if (bmpbgp->startup_delay_ms != 0) + vty_out(vty, " !\n bmp startup-delay %" PRIu32 "\n", + bmpbgp->startup_delay_ms); + frr_each(bmp_targets, &bmpbgp->targets, bt) { vty_out(vty, " !\n bmp targets %s\n", bt->name); @@ -2756,17 +3439,28 @@ static int bmp_config_write(struct bgp *bgp, struct vty *vty) FOREACH_AFI_SAFI (afi, safi) { if (CHECK_FLAG(bt->afimon[afi][safi], - BMP_MON_PREPOLICY)) - vty_out(vty, " bmp monitor %s %s pre-policy\n", + BMP_MON_IN_PREPOLICY)) + vty_out(vty, + " bmp monitor %s %s rib-in pre-policy\n", afi2str_lower(afi), safi2str(safi)); if (CHECK_FLAG(bt->afimon[afi][safi], - BMP_MON_POSTPOLICY)) + BMP_MON_IN_POSTPOLICY)) vty_out(vty, - " bmp monitor %s %s post-policy\n", + " bmp monitor %s %s rib-in post-policy\n", afi2str_lower(afi), safi2str(safi)); if (CHECK_FLAG(bt->afimon[afi][safi], BMP_MON_LOC_RIB)) vty_out(vty, " bmp monitor %s %s loc-rib\n", - afi2str(afi), safi2str(safi)); + afi2str_lower(afi), safi2str(safi)); + if (CHECK_FLAG(bt->afimon[afi][safi], + BMP_MON_OUT_PREPOLICY)) + vty_out(vty, + " bmp monitor %s %s rib-out pre-policy\n", + afi2str_lower(afi), safi2str(safi)); + if (CHECK_FLAG(bt->afimon[afi][safi], + BMP_MON_OUT_POSTPOLICY)) + vty_out(vty, + " bmp monitor %s %s rib-out post-policy\n", + afi2str_lower(afi), safi2str(safi)); } frr_each (bmp_listeners, &bt->listeners, bl) vty_out(vty, " \n bmp listener %pSU port %d\n", @@ -2804,17 +3498,26 @@ static int bgp_bmp_init(struct event_loop *tm) install_element(BMP_NODE, &bmp_acl_cmd); install_element(BMP_NODE, &bmp_stats_cmd); install_element(BMP_NODE, &bmp_monitor_cmd); + install_element(BMP_NODE, &bmp_show_locked_cmd); install_element(BMP_NODE, &bmp_mirror_cmd); install_element(BGP_NODE, &bmp_mirror_limit_cmd); install_element(BGP_NODE, &no_bmp_mirror_limit_cmd); + install_element(BGP_NODE, &bmp_startup_delay_cmd); install_element(VIEW_NODE, &show_bmp_cmd); resolver_init(tm); + + monotime(&bmp_startup_time); + return 0; } +/* this function is triggered when a route is updated is the BGP RIB + * it puts a bmp_queue_entry in the loc-rib queue which will trigger a bmp + * monitoring message for loc-rib based on config + */ static int bmp_route_update(struct bgp *bgp, afi_t afi, safi_t safi, struct bgp_dest *bn, struct bgp_path_info *old_route, @@ -2837,6 +3540,15 @@ static int bmp_route_update(struct bgp *bgp, afi_t afi, safi_t safi, struct bmp_targets *bt; struct bmp *bmp; + /* lock the bpi in case of withdraw for rib-out pre-policy + * do this unconditionally because adj_out_changed hook will always be + * called whether rib-out mon is configured or not and this avoids + * problems in case of configuration changes between lock and unlock + * calls + */ + if (is_withdraw) + bmp_mainlock_bpi(updated_route); + frr_each (bmp_targets, &bmpbgp->targets, bt) { if (CHECK_FLAG(bt->afimon[afi][safi], BMP_MON_LOC_RIB)) { is_locribmon_enabled = true; @@ -2844,15 +3556,18 @@ static int bmp_route_update(struct bgp *bgp, afi_t afi, safi_t safi, } } + /* dont waste time recording the loc-rib install time if not configured + */ if (!is_locribmon_enabled) return 0; - /* route is not installed in locrib anymore and rib uptime was saved */ + /* route is not installed in loc-rib anymore and rib uptime was + * saved */ if (old_route && old_route->extra) bgp_path_info_extra_get(old_route)->bgp_rib_uptime = (time_t)(-1L); - /* route is installed in locrib from now on so + /* route is installed in loc-rib from now on so * save rib uptime in bgp_path_info_extra */ if (new_route) @@ -2860,11 +3575,85 @@ static int bmp_route_update(struct bgp *bgp, afi_t afi, safi_t safi, monotime(NULL); frr_each (bmp_targets, &bmpbgp->targets, bt) { - if (CHECK_FLAG(bt->afimon[afi][safi], BMP_MON_LOC_RIB)) { + if (!CHECK_FLAG(bt->afimon[afi][safi], BMP_MON_LOC_RIB)) + continue; + + struct bmp_queue_entry *last_item = bmp_process_one( + bt, &bt->mon_loc_updhash, &bt->mon_loc_updlist, bgp, + afi, safi, bn, peer, BMP_QUEUE_FLAGS_NONE, NULL); + + /* 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->mon_loc_queuepos) + bmp->mon_loc_queuepos = last_item; + + pullwr_bump(bmp->pullwr); + }; + }; + + return 0; +} + +/* this function is triggered when a change has been registered in the + * adj-rib-out. it puts a bmp_queue_entry in the rib-out queue + * and which will trigger a bmp monitoring message for rib-out pre/post-policy + * if either is configured. + */ +static int bmp_adj_out_changed(struct bgp_dest *dest, + struct update_subgroup *subgrp, + struct attr *attr, + struct bgp_path_info *locked_path, + bool post_policy, bool withdraw) +{ + + if (!subgrp) { + zlog_debug("%s: no subgrp, bmp rib-out post will not proceed", + __func__); + goto out; + } + + struct bmp_targets *bt; + struct bgp *bgp = SUBGRP_INST(subgrp); + struct bmp_bgp *bmpbgp = bmp_bgp_get(bgp); + afi_t afi = SUBGRP_AFI(subgrp); + safi_t safi = SUBGRP_SAFI(subgrp); + + uint8_t mon_flag = + post_policy ? BMP_MON_OUT_POSTPOLICY : BMP_MON_OUT_PREPOLICY; + + + if (post_policy || !withdraw) + locked_path = NULL; + + bool is_riboutmon_enabled = false; + frr_each (bmp_targets, &bmpbgp->targets, bt) { + if ((is_riboutmon_enabled |= + (CHECK_FLAG(bt->afimon[afi][safi], mon_flag)))) + break; + } + + if (!is_riboutmon_enabled) + goto out; + + struct peer_af *paf; + struct peer *peer; + struct bmp *bmp; + frr_each (bmp_targets, &bmpbgp->targets, bt) { + if (!CHECK_FLAG(bt->afimon[afi][safi], mon_flag)) + continue; + + SUBGRP_FOREACH_PEER (subgrp, paf) { + peer = PAF_PEER(paf); struct bmp_queue_entry *last_item = bmp_process_one( - bt, &bt->locupdhash, &bt->locupdlist, bgp, afi, - safi, bn, peer); + bt, &bt->mon_out_updhash, &bt->mon_out_updlist, + NULL, afi, safi, dest, peer, mon_flag, + locked_path); /* if bmp_process_one returns NULL * we don't have anything to do next @@ -2873,29 +3662,34 @@ static int bmp_route_update(struct bgp *bgp, afi_t afi, safi_t safi, continue; frr_each (bmp_session, &bt->sessions, bmp) { - if (!bmp->locrib_queuepos) - bmp->locrib_queuepos = last_item; + if (!bmp->mon_out_queuepos) + bmp->mon_out_queuepos = last_item; + // TODO avoid bumping for each update ? pullwr_bump(bmp->pullwr); }; } }; +out: + if (locked_path) // path is not allowed to be NULL for rib-out-pre + // withdraws only + bmp_mainunlock_bpi(locked_path); return 0; } - static int bgp_bmp_module_init(void) { hook_register(bgp_packet_dump, bmp_mirror_packet); hook_register(bgp_packet_send, bmp_outgoing_packet); hook_register(peer_status_changed, bmp_peer_status_changed); hook_register(peer_backward_transition, bmp_peer_backward); - hook_register(bgp_process, bmp_process); + hook_register(bgp_process, bmp_process_ribin); hook_register(bgp_inst_config_write, bmp_config_write); hook_register(bgp_inst_delete, bmp_bgp_del); hook_register(frr_late_init, bgp_bmp_init); hook_register(bgp_route_update, bmp_route_update); + hook_register(bgp_adj_out_updated, bmp_adj_out_changed); return 0; } diff --git a/bgpd/bgp_bmp.h b/bgpd/bgp_bmp.h index dadd99eb6d0a..0fabaac67559 100644 --- a/bgpd/bgp_bmp.h +++ b/bgpd/bgp_bmp.h @@ -12,6 +12,7 @@ #include "pullwr.h" #include "qobj.h" #include "resolver.h" +#include "bgp_updgrp.h" #define BMP_VERSION_3 3 @@ -29,9 +30,11 @@ #define BMP_READ_BUFSIZ 1024 /* bmp->state */ -#define BMP_None 0 -#define BMP_PeerUp 2 -#define BMP_Run 3 +enum BMP_State { + BMP_StartupIdle, + BMP_PeerUp, + BMP_Run, +}; /* This one is for BMP Route Monitoring messages, i.e. delivering updates * in somewhat processed (as opposed to fully raw, see mirroring below) form. @@ -60,6 +63,10 @@ struct bmp_queue_entry { struct bmp_qlist_item bli; struct bmp_qhash_item bhi; + struct bgp_path_info *locked_bpi; + +#define BMP_QUEUE_FLAGS_NONE (0 << 0) + uint8_t flags; struct prefix p; uint64_t peerid; afi_t afi; @@ -116,7 +123,7 @@ struct bmp { struct pullwr *pullwr; - int state; + enum BMP_State state; /* queue positions must remain synced with refcounts in the items. * Whenever appending a queue item, we need to know the correct number @@ -124,8 +131,10 @@ struct bmp { * ahead we need to make sure that refcount is decremented. Also, on * disconnects we need to walk the queue and drop our reference. */ - struct bmp_queue_entry *locrib_queuepos; - struct bmp_queue_entry *queuepos; + struct bmp_queue_entry *mon_in_queuepos; + struct bmp_queue_entry *mon_loc_queuepos; + struct bmp_queue_entry *mon_out_queuepos; + struct bmp_mirrorq *mirrorpos; bool mirror_lost; @@ -220,9 +229,12 @@ struct bmp_targets { * - IPv6 / unicast & multicast & VPN * - L2VPN / EVPN */ -#define BMP_MON_PREPOLICY (1 << 0) -#define BMP_MON_POSTPOLICY (1 << 1) +#define BMP_MON_IN_PREPOLICY (1 << 0) +#define BMP_MON_IN_POSTPOLICY (1 << 1) #define BMP_MON_LOC_RIB (1 << 2) +#define BMP_MON_OUT_PREPOLICY (1 << 3) +#define BMP_MON_OUT_POSTPOLICY (1 << 4) + uint8_t afimon[AFI_MAX][SAFI_MAX]; bool mirror; @@ -232,11 +244,14 @@ struct bmp_targets { struct event *t_stats; struct bmp_session_head sessions; - struct bmp_qhash_head updhash; - struct bmp_qlist_head updlist; + struct bmp_qhash_head mon_in_updhash; + struct bmp_qlist_head mon_in_updlist; - struct bmp_qhash_head locupdhash; - struct bmp_qlist_head locupdlist; + struct bmp_qhash_head mon_loc_updhash; + struct bmp_qlist_head mon_loc_updlist; + + struct bmp_qhash_head mon_out_updhash; + struct bmp_qlist_head mon_out_updlist; uint64_t cnt_accept, cnt_aclrefused; @@ -261,6 +276,33 @@ struct bmp_bgp_peer { size_t open_tx_len; }; +/* every bgp_path_info that bmp currently has locked for rib-out-prepolicy + * when this is allocated the bgp_path_info is locked using bgp_path_info_lock + * when freed unlocked using bpg_path_info_unlock + */ +PREDECL_HASH(bmp_lbpi); + +struct bmp_bpi_lock { + /* hashset field */ + struct bmp_lbpi_item lbpi; + + /* locked bgp_path_info */ + struct bgp_path_info *locked; + + /* main lock used to lock between loc-rib trigger (the one who locks) + * and bgp_adj_out_updated (the one who unlocks) + */ + int main; + + /* secondary lock, one for each bqe in the rib-out queue + * when each bqe is allocated we increment this lock + * when freed we decrement it + * after all bqe are processed, main should be 0 and lock 0 too + * so the bpi can be unlocked (and maybe freed) + */ + int lock; +}; + /* per struct bgp * data */ PREDECL_HASH(bmp_bgph); @@ -276,6 +318,8 @@ struct bmp_bgp { size_t mirror_qsize, mirror_qsizemax; size_t mirror_qsizelimit; + + uint32_t startup_delay_ms; }; enum { @@ -287,21 +331,25 @@ enum { }; enum { - BMP_STATS_PFX_REJECTED = 0, - BMP_STATS_PFX_DUP_ADV = 1, - BMP_STATS_PFX_DUP_WITHDRAW = 2, - BMP_STATS_UPD_LOOP_CLUSTER = 3, - BMP_STATS_UPD_LOOP_ASPATH = 4, - BMP_STATS_UPD_LOOP_ORIGINATOR = 5, - BMP_STATS_UPD_LOOP_CONFED = 6, - BMP_STATS_SIZE_ADJ_RIB_IN = 7, - BMP_STATS_SIZE_LOC_RIB = 8, - BMP_STATS_SIZE_ADJ_RIB_IN_SAFI = 9, - BMP_STATS_SIZE_LOC_RIB_IN_SAFI = 10, - BMP_STATS_UPD_7606_WITHDRAW = 11, - BMP_STATS_PFX_7606_WITHDRAW = 12, - BMP_STATS_UPD_DUP = 13, - BMP_STATS_FRR_NH_INVALID = 65531, + BMP_STATS_PFX_REJECTED = 0, + BMP_STATS_PFX_DUP_ADV = 1, + BMP_STATS_PFX_DUP_WITHDRAW = 2, + BMP_STATS_UPD_LOOP_CLUSTER = 3, + BMP_STATS_UPD_LOOP_ASPATH = 4, + BMP_STATS_UPD_LOOP_ORIGINATOR = 5, + BMP_STATS_UPD_LOOP_CONFED = 6, + BMP_STATS_SIZE_ADJ_RIB_IN = 7, + BMP_STATS_SIZE_LOC_RIB = 8, + BMP_STATS_SIZE_ADJ_RIB_IN_SAFI = 9, + BMP_STATS_SIZE_LOC_RIB_SAFI = 10, + BMP_STATS_UPD_7606_WITHDRAW = 11, + BMP_STATS_PFX_7606_WITHDRAW = 12, + BMP_STATS_UPD_DUP = 13, + BMP_STATS_SIZE_ADJ_RIB_OUT_PRE = 14, + BMP_STATS_SIZE_ADJ_RIB_OUT_POST = 15, + BMP_STATS_SIZE_ADJ_RIB_OUT_PRE_SAFI = 16, + BMP_STATS_SIZE_ADJ_RIB_OUT_POST_SAFI = 17, + BMP_STATS_FRR_NH_INVALID = 65531, }; DECLARE_MGROUP(BMP); diff --git a/bgpd/bgp_conditional_adv.c b/bgpd/bgp_conditional_adv.c index 3b5f5e986be4..5c857b053d23 100644 --- a/bgpd/bgp_conditional_adv.c +++ b/bgpd/bgp_conditional_adv.c @@ -98,6 +98,19 @@ static void bgp_conditional_adv_routes(struct peer *peer, afi_t afi, for (pi = bgp_dest_get_bgp_path_info(dest); pi; pi = pi->next) { advmap_attr = *pi->attr; + bool selected = bgp_check_selected( + pi, peer, addpath_capable, afi, safi); + + if (selected) { + bgp_adj_out_updated( + dest, subgrp, &attr, pi, false, + update_type == UPDATE_TYPE_ADVERTISE + ? false + : true, + __func__); + } + + /* Fill temp path_info */ prep_for_rmap_apply(&path, &path_extra, dest, pi, pi->peer, &advmap_attr); @@ -105,9 +118,7 @@ static void bgp_conditional_adv_routes(struct peer *peer, afi_t afi, RESET_FLAG(advmap_attr.rmap_change_flags); ret = route_map_apply(rmap, dest_p, &path); - if (ret != RMAP_PERMITMATCH || - !bgp_check_selected(pi, peer, addpath_capable, afi, - safi)) { + if (ret != RMAP_PERMITMATCH || !selected) { bgp_attr_flush(&advmap_attr); continue; } @@ -121,7 +132,7 @@ static void bgp_conditional_adv_routes(struct peer *peer, afi_t afi, */ if (update_type == UPDATE_TYPE_ADVERTISE && subgroup_announce_check(dest, pi, subgrp, dest_p, - &attr, &advmap_attr)) { + &attr, &advmap_attr, 0)) { bgp_adj_out_set_subgroup(dest, subgrp, &attr, pi); } else { diff --git a/bgpd/bgp_route.c b/bgpd/bgp_route.c index b4c97eb2eaf5..78eff2ba2765 100644 --- a/bgpd/bgp_route.c +++ b/bgpd/bgp_route.c @@ -2024,7 +2024,7 @@ void subgroup_announce_reset_nhop(uint8_t family, struct attr *attr) bool subgroup_announce_check(struct bgp_dest *dest, struct bgp_path_info *pi, struct update_subgroup *subgrp, const struct prefix *p, struct attr *attr, - struct attr *post_attr) + struct attr *post_attr, uint8_t special_cond) { struct bgp_filter *filter; struct peer *from; @@ -2057,6 +2057,11 @@ bool subgroup_announce_check(struct bgp_dest *dest, struct bgp_path_info *pi, bgp = SUBGRP_INST(subgrp); piattr = bgp_path_info_mpath_count(pi) ? bgp_path_info_mpath_attr(pi) : pi->attr; + bool ignore_policy = + CHECK_FLAG(special_cond, BGP_ANNCHK_SPECIAL_IGNORE_OUT_POLICY); + bool ignore_path_status = + CHECK_FLAG(special_cond, BGP_ANNCHK_SPECIAL_IGNORE_PATH_STATUS); + if (CHECK_FLAG(peer->af_flags[afi][safi], PEER_FLAG_MAX_PREFIX_OUT) && peer->pmax_out[afi][safi] != 0 && @@ -2097,21 +2102,22 @@ bool subgroup_announce_check(struct bgp_dest *dest, struct bgp_path_info *pi, /* With addpath we may be asked to TX all kinds of paths so make sure * pi is valid */ - if (!CHECK_FLAG(pi->flags, BGP_PATH_VALID) - || CHECK_FLAG(pi->flags, BGP_PATH_HISTORY) - || CHECK_FLAG(pi->flags, BGP_PATH_REMOVED)) { + if (!ignore_path_status && (!CHECK_FLAG(pi->flags, BGP_PATH_VALID) || + CHECK_FLAG(pi->flags, BGP_PATH_HISTORY) || + CHECK_FLAG(pi->flags, BGP_PATH_REMOVED))) { return false; } /* If this is not the bestpath then check to see if there is an enabled * addpath * feature that requires us to advertise it */ - if (!CHECK_FLAG(pi->flags, BGP_PATH_SELECTED)) + if (!ignore_path_status && !CHECK_FLAG(pi->flags, BGP_PATH_SELECTED)) if (!bgp_addpath_capable(pi, peer, afi, safi)) return false; /* Aggregate-address suppress check. */ - if (bgp_path_suppressed(pi) && !UNSUPPRESS_MAP_NAME(filter)) + if (!ignore_policy && bgp_path_suppressed(pi) && + !UNSUPPRESS_MAP_NAME(filter)) return false; /* @@ -2202,7 +2208,8 @@ bool subgroup_announce_check(struct bgp_dest *dest, struct bgp_path_info *pi, } /* ORF prefix-list filter check */ - if (CHECK_FLAG(peer->af_cap[afi][safi], PEER_CAP_ORF_PREFIX_RM_ADV) && + if (!ignore_policy && + CHECK_FLAG(peer->af_cap[afi][safi], PEER_CAP_ORF_PREFIX_RM_ADV) && CHECK_FLAG(peer->af_cap[afi][safi], PEER_CAP_ORF_PREFIX_SM_RCV)) if (peer->orf_plist[afi][safi]) { if (prefix_list_apply(peer->orf_plist[afi][safi], p) @@ -2217,7 +2224,8 @@ bool subgroup_announce_check(struct bgp_dest *dest, struct bgp_path_info *pi, } /* Output filter check. */ - if (bgp_output_filter(peer, p, piattr, afi, safi) == FILTER_DENY) { + if (!ignore_policy && + bgp_output_filter(peer, p, piattr, afi, safi) == FILTER_DENY) { if (bgp_debug_update(NULL, p, subgrp->update_group, 0)) zlog_debug("%pBP [Update:SEND] %pFX is filtered", peer, p); @@ -2385,7 +2393,11 @@ bool subgroup_announce_check(struct bgp_dest *dest, struct bgp_path_info *pi, bgp_otc_egress(peer, attr)) return false; - if (filter->advmap.update_type == UPDATE_TYPE_WITHDRAW && + bgp_peer_remove_private_as(bgp, afi, safi, peer, attr); + bgp_peer_as_override(bgp, afi, safi, peer, attr); + + if (!ignore_policy && + filter->advmap.update_type == UPDATE_TYPE_WITHDRAW && filter->advmap.aname && route_map_lookup_by_name(filter->advmap.aname)) { struct bgp_path_info rmap_path = {0}; @@ -2412,7 +2424,7 @@ bool subgroup_announce_check(struct bgp_dest *dest, struct bgp_path_info *pi, } /* Route map & unsuppress-map apply. */ - if (!post_attr && + if (!ignore_policy && !post_attr && (ROUTE_MAP_OUT_NAME(filter) || bgp_path_suppressed(pi))) { struct bgp_path_info rmap_path = {0}; struct bgp_path_info_extra dummy_rmap_path_extra = {0}; @@ -2993,9 +3005,12 @@ void subgroup_process_announce_selected(struct update_subgroup *subgrp, */ advertise = bgp_check_advertise(bgp, dest, safi); + bgp_adj_out_updated(dest, subgrp, &attr, selected, false, + selected && advertise ? false : true, __func__); + if (selected) { if (subgroup_announce_check(dest, selected, subgrp, p, &attr, - NULL)) { + NULL, 0)) { /* Route is selected, if the route is already installed * in FIB, then it is advertised */ @@ -3443,11 +3458,18 @@ static void bgp_process_main_one(struct bgp *bgp, struct bgp_dest *dest, } /* TODO BMP insert rib update hook */ - if (old_select) + if (old_select) { + if (old_select->peer) + old_select->peer->stat_loc_rib_count[afi][safi]--; bgp_path_info_unset_flag(dest, old_select, BGP_PATH_SELECTED); + } if (new_select) { if (debug) zlog_debug("%s: setting SELECTED flag", __func__); + + if (new_select->peer) + new_select->peer->stat_loc_rib_count[afi][safi]++; + bgp_path_info_set_flag(dest, new_select, BGP_PATH_SELECTED); bgp_path_info_unset_flag(dest, new_select, BGP_PATH_ATTR_CHANGED); @@ -4233,7 +4255,7 @@ void bgp_update(struct peer *peer, const struct prefix *p, uint32_t addpath_id, memcpy(&attr->evpn_overlay, evpn, sizeof(struct bgp_route_evpn)); } - bgp_adj_in_set(dest, peer, attr, addpath_id); + bgp_adj_in_set(dest, afi, safi, peer, attr, addpath_id); } /* Update permitted loop count */ @@ -5103,7 +5125,7 @@ void bgp_withdraw(struct peer *peer, const struct prefix *p, */ if (CHECK_FLAG(peer->af_flags[afi][safi], PEER_FLAG_SOFT_RECONFIG) && peer != bgp->peer_self) - if (!bgp_adj_in_unset(&dest, peer, addpath_id)) { + if (!bgp_adj_in_unset(&dest, afi, safi, peer, addpath_id)) { assert(dest); peer->stat_pfx_dup_withdraw++; @@ -5695,7 +5717,7 @@ static void bgp_clear_route_table(struct peer *peer, afi_t afi, safi_t safi, ain_next = ain->next; if (ain->peer == peer) - bgp_adj_in_remove(&dest, ain); + bgp_adj_in_remove(&dest, afi, safi, ain); ain = ain_next; @@ -5805,7 +5827,7 @@ void bgp_clear_adj_in(struct peer *peer, afi_t afi, safi_t safi) ain_next = ain->next; if (ain->peer == peer) - bgp_adj_in_remove(&dest, ain); + bgp_adj_in_remove(&dest, afi, safi, ain); ain = ain_next; diff --git a/bgpd/bgp_route.h b/bgpd/bgp_route.h index 3057a4259abe..4cf8b2ad0419 100644 --- a/bgpd/bgp_route.h +++ b/bgpd/bgp_route.h @@ -323,6 +323,7 @@ struct bgp_path_info { #define BGP_PATH_ACCEPT_OWN (1 << 16) #define BGP_PATH_MPLSVPN_LABEL_NH (1 << 17) #define BGP_PATH_MPLSVPN_NH_LABEL_BIND (1 << 18) +#define BGP_BMP_HELD (1 << 19) /* BGP route type. This can be static, RIP, OSPF, BGP etc. */ uint8_t type; @@ -853,11 +854,17 @@ extern void subgroup_process_announce_selected(struct update_subgroup *subgrp, safi_t safi, uint32_t addpath_tx_id); +#define BGP_ANNCHK_SPECIAL_IGNORE_OUT_POLICY (1 << 0) +#define BGP_ANNCHK_SPECIAL_IGNORE_PATH_STATUS (1 << 1) +#define BGP_ANNCHK_SPECIAL_PREPOLICY \ + (BGP_ANNCHK_SPECIAL_IGNORE_OUT_POLICY | \ + BGP_ANNCHK_SPECIAL_IGNORE_PATH_STATUS) extern bool subgroup_announce_check(struct bgp_dest *dest, struct bgp_path_info *pi, struct update_subgroup *subgrp, const struct prefix *p, struct attr *attr, - struct attr *post_attr); + struct attr *post_attr, + uint8_t special_cond); extern void bgp_peer_clear_node_queue_drain_immediate(struct peer *peer); extern void bgp_process_queues_drain_immediate(void); diff --git a/bgpd/bgp_updgrp.h b/bgpd/bgp_updgrp.h index 0d866f3be7e4..f160002b0528 100644 --- a/bgpd/bgp_updgrp.h +++ b/bgpd/bgp_updgrp.h @@ -58,6 +58,12 @@ (PEER_CAP_ORF_PREFIX_SM_RCV | PEER_CAP_ADDPATH_AF_TX_ADV | \ PEER_CAP_ADDPATH_AF_RX_RCV | PEER_CAP_ENHE_AF_NEGO) +DECLARE_HOOK(bgp_adj_out_updated, + (struct bgp_dest * dest, struct update_subgroup *subgrp, + struct attr *attr, struct bgp_path_info *path, bool post_policy, + bool withdraw), + (dest, subgrp, attr, path, post_policy, withdraw)); + enum bpacket_attr_vec_type { BGP_ATTR_VEC_NH = 0, BGP_ATTR_VEC_MAX }; typedef struct { @@ -446,9 +452,17 @@ extern void bgp_adj_out_set_subgroup(struct bgp_dest *dest, struct update_subgroup *subgrp, struct attr *attr, struct bgp_path_info *path); +extern void bgp_adj_out_updated(struct bgp_dest *dest, + struct update_subgroup *subgrp, + struct attr *attr, struct bgp_path_info *path, + bool post_policy, bool withdraw, + const char *caller); extern void bgp_adj_out_unset_subgroup(struct bgp_dest *dest, struct update_subgroup *subgrp, char withdraw, uint32_t addpath_tx_id); +extern struct bgp_adj_out *adj_lookup(struct bgp_dest *dest, + struct update_subgroup *subgrp, + uint32_t addpath_tx_id); void subgroup_announce_table(struct update_subgroup *subgrp, struct bgp_table *table); extern void subgroup_trigger_write(struct update_subgroup *subgrp); diff --git a/bgpd/bgp_updgrp_adv.c b/bgpd/bgp_updgrp_adv.c index ffdd39636b30..ed0628dda47d 100644 --- a/bgpd/bgp_updgrp_adv.c +++ b/bgpd/bgp_updgrp_adv.c @@ -1,4 +1,3 @@ -// SPDX-License-Identifier: GPL-2.0-or-later /** * bgp_updgrp_adv.c: BGP update group advertisement and adjacency * maintenance @@ -36,6 +35,11 @@ #include "bgpd/bgp_advertise.h" #include "bgpd/bgp_addpath.h" +DEFINE_HOOK(bgp_adj_out_updated, + (struct bgp_dest * dest, struct update_subgroup *subgrp, + struct attr *attr, struct bgp_path_info *path, bool post_policy, + bool withdraw), + (dest, subgrp, attr, path, post_policy, withdraw)); /******************** * PRIVATE FUNCTIONS @@ -59,9 +63,9 @@ static int bgp_adj_out_compare(const struct bgp_adj_out *o1, } RB_GENERATE(bgp_adj_out_rb, bgp_adj_out, adj_entry, bgp_adj_out_compare); -static inline struct bgp_adj_out *adj_lookup(struct bgp_dest *dest, - struct update_subgroup *subgrp, - uint32_t addpath_tx_id) +inline struct bgp_adj_out *adj_lookup(struct bgp_dest *dest, + struct update_subgroup *subgrp, + uint32_t addpath_tx_id) { struct bgp_adj_out lookup; @@ -252,6 +256,12 @@ static int group_announce_route_walkcb(struct update_group *updgrp, void *arg) adj->addpath_tx_id); } } + + if (!adj) + bgp_adj_out_updated( + ctx->dest, subgrp, NULL, + NULL, false, true, + __func__); } } } @@ -496,6 +506,47 @@ bgp_advertise_clean_subgroup(struct update_subgroup *subgrp, return next; } +void bgp_adj_out_updated(struct bgp_dest *dest, struct update_subgroup *subgrp, + struct attr *attr, struct bgp_path_info *path, + bool post_policy, bool withdraw, const char *caller) +{ + + if (post_policy) { + hook_call(bgp_adj_out_updated, dest, subgrp, attr, NULL, true, + withdraw); + return; + } + + if (!path && withdraw) { + for (path = dest ? bgp_dest_get_bgp_path_info(dest) : NULL; + path; path = path->next) { + if (CHECK_FLAG(path->flags, BGP_BMP_HELD)) + break; + } + + if (!path) + return; + } + + struct attr dummy_attr = {0}; + /* + * withdraw | pre_check | result + * true | true | withdraw + * true | false | nothing to do + * false | true | update + * false | false | nothing to do + */ + bool pre_check = subgroup_announce_check(dest, path, subgrp, bgp_dest_get_prefix(dest), + &dummy_attr, NULL, + BGP_ANNCHK_SPECIAL_PREPOLICY); + + if (!pre_check) + return; + + hook_call(bgp_adj_out_updated, dest, subgrp, attr, path, false, + withdraw); +} + void bgp_adj_out_set_subgroup(struct bgp_dest *dest, struct update_subgroup *subgrp, struct attr *attr, struct bgp_path_info *path) @@ -619,6 +670,8 @@ void bgp_adj_out_set_subgroup(struct bgp_dest *dest, bgp_adv_fifo_add_tail(&subgrp->sync->update, adv); subgrp->version = MAX(subgrp->version, dest->version); + + bgp_adj_out_updated(dest, subgrp, attr, path, true, false, __func__); } /* The only time 'withdraw' will be false is if we are sending @@ -668,6 +721,9 @@ void bgp_adj_out_unset_subgroup(struct bgp_dest *dest, if (trigger_write) subgroup_trigger_write(subgrp); + + bgp_adj_out_updated(dest, subgrp, NULL, NULL, true, + withdraw, __func__); } else { /* Free allocated information. */ adj_free(adj); @@ -953,6 +1009,8 @@ void subgroup_default_originate(struct update_subgroup *subgrp, int withdraw) dest = bgp_safi_node_lookup(bgp->rib[afi][safi_rib], safi_rib, &p, NULL); + // TODO BMP hook call for pre-policy + if (withdraw) { /* Withdraw the default route advertised using default * originate @@ -971,7 +1029,7 @@ void subgroup_default_originate(struct update_subgroup *subgrp, int withdraw) if (subgroup_announce_check( dest, pi, subgrp, bgp_dest_get_prefix(dest), - &attr, NULL)) { + &attr, NULL, 0)) { struct attr *default_attr = bgp_attr_intern(&attr); diff --git a/bgpd/bgpd.h b/bgpd/bgpd.h index a7860721e9e1..3b8f0ce43c06 100644 --- a/bgpd/bgpd.h +++ b/bgpd/bgpd.h @@ -1639,6 +1639,9 @@ struct peer { uint32_t stat_pfx_nh_invalid; uint32_t stat_pfx_dup_withdraw; uint32_t stat_upd_7606; /* RFC7606: treat-as-withdraw */ + uint32_t stat_adj_in_count[AFI_MAX][SAFI_MAX]; + uint32_t stat_loc_rib_count[AFI_MAX][SAFI_MAX]; + // no need for stat_adj_out_count here, it is in struct update_subgroup /* BGP state count */ uint32_t established; /* Established */ diff --git a/doc/user/bmp.rst b/doc/user/bmp.rst index 0f4683205978..f95c091d7e16 100644 --- a/doc/user/bmp.rst +++ b/doc/user/bmp.rst @@ -71,7 +71,7 @@ All of FRR's BMP configuration options are located inside the :clicmd:`router bgp ASN` block. Configure BGP first before proceeding to BMP setup. -There is one option that applies to the BGP instance as a whole: +There are two options that apply to the BGP instance as a whole: .. clicmd:: bmp mirror buffer-limit(0-4294967294) @@ -91,6 +91,33 @@ There is one option that applies to the BGP instance as a whole: BMP Route Monitoring is not affected by this option. +.. clicmd:: bmp startup-delay delay(0-4294967294) + + This sets the delay (in milliseconds) after module startup + for BMP sessions to become active. + + This setting is useful for BMP Monitoring because it allows BMP + to wait a bit for BGP to converge and the whole BGP state. It avoids + sending empty RIB content during synchronization on startup followed + by multiple incremental updates. It also avoids RIB-Out Pre-Policy Monitoring + to send duplicated messages on startup triggered by the reconfiguration of peer + policies. + + This setting applies to all sessions defined in the same BGP Instance. + On module initialization (at BGP Startup), the time is recorded. Then, + established sessions will wait "delay" after this recorded time to start + sending BMP Mirroring, the initial BMP Monitoring synchronization and + following BMP Monitoring Messages containing incremental updates. + + BMP Peer Up Messages are sent if the peer becomes available during this + period of time. + + The startup delay is applied for BMP startup only. BMP Sessions configured + while the daemon is running will only wait if this initial timer has not expired + yet. + + BMP Session Establishment is not affected by this option. + All other configuration is managed per targets: .. clicmd:: bmp targets NAME @@ -146,9 +173,9 @@ associated with a particular ``bmp targets``: Send BMP Statistics (counter) messages at the specified interval (in milliseconds.) -.. clicmd:: bmp monitor AFI SAFI +.. clicmd:: bmp monitor AFI SAFI - Perform Route Monitoring for the specified AFI and SAFI. Only IPv4 and + Perform Route Monitoring for the specified AFI, SAFI and RIB. Only IPv4 and IPv6 are currently valid for AFI. SAFI valid values are currently unicast, multicast, evpn and vpn. Other AFI/SAFI combinations may be added in the future. @@ -156,6 +183,11 @@ associated with a particular ``bmp targets``: All BGP neighbors are included in Route Monitoring. Options to select a subset of BGP sessions may be added in the future. + Pre-Policy and Post-Policy flags do not apply to Local-RIB monitoring. + + BMP Local-RIB Monitoring is defined in :rfc:`9069` + BMP RIB-Out Monitoring is defined in :rfc:`8671` + .. clicmd:: bmp mirror Perform Route Mirroring for all BGP neighbors. Since this provides a @@ -164,3 +196,49 @@ 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. + +BMP Troubleshooting +------------- + + +When encountering problems with BMP, it may be interesting to know the current +state of the latter. + +.. clicmd:: show bmp + + Displays information about the current state of BMP including targets, sessions, + configured modes, global settings, ... + +.. code-block:: frr +BMP Module started at Fri Feb 24 13:05:50 2023 + +BMP state for BGP VRF default: + + Route Mirroring 0 bytes (0 messages) pending + 0 bytes maximum buffer used + + Startup delay : 10000ms + + Targets "my_targets": + Route Mirroring disabled + Route Monitoring IPv4 unicast rib-out pre-policy rib-out post-policy + Listeners: + + Outbound connections: + remote state timer local + ---------------------------------------------------------------------- + 99.99.99.99:12345 Up 99.99.99.99:12345 00:00:04 (unspec) + + 1 connected clients: + remote uptime state MonSent MirrSent MirrLost ByteSent ByteQ ByteQKernel + --------------------------------------------------------------------------------------------------------------- + 99.99.99.99:12345 00:00:04 Startup-Wait 0 0 0 61 0 0 +:: + + Here we have a single BGP instance running on VRF default. No specific mirroring settings but a + startup delay of 10000ms. + This instance has a single target with rib-out pre-policy and post-policy monitoring, no mirroring. + This target has a single session open with client 99.99.99.99 on port 12345 which is in state Startup-Wait. + This session will start sending monitoring messages as soon as the current time is + "Fri Feb 24 13:05:50 2023" + 10000ms = "Fri Feb 24 13:06:00 2023" which explains why it is in + Startup-Wait mode and has not sent Monitoring Messages yet. diff --git a/lib/pullwr.c b/lib/pullwr.c index 3967eb587541..0c02bd85f3fb 100644 --- a/lib/pullwr.c +++ b/lib/pullwr.c @@ -82,6 +82,19 @@ void pullwr_bump(struct pullwr *pullwr) event_add_timer(pullwr->tm, pullwr_run, pullwr, 0, &pullwr->writer); } +/* call this function to schedule a pullwr_bump after a certain timeout + * + * uint32_t timeout is the wait time before pullwr_bump, in milliseconds + */ +void pullwr_timeout(struct pullwr *pullwr, uint32_t timeout) +{ + if (pullwr->writer) + return; + + event_add_timer_msec(pullwr->tm, pullwr_run, pullwr, timeout, + &pullwr->writer); +} + static size_t pullwr_iov(struct pullwr *pullwr, struct iovec *iov) { size_t len1; diff --git a/lib/pullwr.h b/lib/pullwr.h index ef2e01c04ede..9a58fdaf5671 100644 --- a/lib/pullwr.h +++ b/lib/pullwr.h @@ -86,6 +86,7 @@ extern void pullwr_cfg(struct pullwr *pullwr, int64_t max_spin_usec, size_t write_threshold); extern void pullwr_bump(struct pullwr *pullwr); +extern void pullwr_timeout(struct pullwr *pullwr, uint32_t timeout); extern void pullwr_write(struct pullwr *pullwr, const void *data, size_t len);