diff options
Diffstat (limited to 'bgpd/bgp_route.c')
-rw-r--r-- | bgpd/bgp_route.c | 15995 |
1 files changed, 15995 insertions, 0 deletions
diff --git a/bgpd/bgp_route.c b/bgpd/bgp_route.c new file mode 100644 index 0000000..3a850a4 --- /dev/null +++ b/bgpd/bgp_route.c @@ -0,0 +1,15995 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* BGP routing information + * Copyright (C) 1996, 97, 98, 99 Kunihiro Ishiguro + * Copyright (C) 2016 Job Snijders <job@instituut.net> + */ + +#include <zebra.h> +#include <math.h> + +#include "printfrr.h" +#include "frrstr.h" +#include "prefix.h" +#include "linklist.h" +#include "memory.h" +#include "command.h" +#include "stream.h" +#include "filter.h" +#include "log.h" +#include "routemap.h" +#include "buffer.h" +#include "sockunion.h" +#include "plist.h" +#include "frrevent.h" +#include "workqueue.h" +#include "queue.h" +#include "memory.h" +#include "srv6.h" +#include "lib/json.h" +#include "lib_errors.h" +#include "zclient.h" +#include "bgpd/bgpd.h" +#include "bgpd/bgp_table.h" +#include "bgpd/bgp_route.h" +#include "bgpd/bgp_attr.h" +#include "bgpd/bgp_debug.h" +#include "bgpd/bgp_errors.h" +#include "bgpd/bgp_aspath.h" +#include "bgpd/bgp_regex.h" +#include "bgpd/bgp_community.h" +#include "bgpd/bgp_community_alias.h" +#include "bgpd/bgp_ecommunity.h" +#include "bgpd/bgp_lcommunity.h" +#include "bgpd/bgp_clist.h" +#include "bgpd/bgp_packet.h" +#include "bgpd/bgp_filter.h" +#include "bgpd/bgp_fsm.h" +#include "bgpd/bgp_mplsvpn.h" +#include "bgpd/bgp_nexthop.h" +#include "bgpd/bgp_damp.h" +#include "bgpd/bgp_advertise.h" +#include "bgpd/bgp_zebra.h" +#include "bgpd/bgp_vty.h" +#include "bgpd/bgp_mpath.h" +#include "bgpd/bgp_nht.h" +#include "bgpd/bgp_updgrp.h" +#include "bgpd/bgp_label.h" +#include "bgpd/bgp_addpath.h" +#include "bgpd/bgp_mac.h" +#include "bgpd/bgp_network.h" +#include "bgpd/bgp_trace.h" +#include "bgpd/bgp_rpki.h" + +#ifdef ENABLE_BGP_VNC +#include "bgpd/rfapi/rfapi_backend.h" +#include "bgpd/rfapi/vnc_import_bgp.h" +#include "bgpd/rfapi/vnc_export_bgp.h" +#endif +#include "bgpd/bgp_encap_types.h" +#include "bgpd/bgp_encap_tlv.h" +#include "bgpd/bgp_evpn.h" +#include "bgpd/bgp_evpn_mh.h" +#include "bgpd/bgp_evpn_vty.h" +#include "bgpd/bgp_flowspec.h" +#include "bgpd/bgp_flowspec_util.h" +#include "bgpd/bgp_pbr.h" + +#include "bgpd/bgp_route_clippy.c" + +DEFINE_HOOK(bgp_snmp_update_stats, + (struct bgp_dest *rn, struct bgp_path_info *pi, bool added), + (rn, pi, added)); + +DEFINE_HOOK(bgp_rpki_prefix_status, + (struct peer *peer, struct attr *attr, + const struct prefix *prefix), + (peer, attr, prefix)); + +/* Extern from bgp_dump.c */ +extern const char *bgp_origin_str[]; +extern const char *bgp_origin_long_str[]; + +/* PMSI strings. */ +#define PMSI_TNLTYPE_STR_NO_INFO "No info" +#define PMSI_TNLTYPE_STR_DEFAULT PMSI_TNLTYPE_STR_NO_INFO +static const struct message bgp_pmsi_tnltype_str[] = { + {PMSI_TNLTYPE_NO_INFO, PMSI_TNLTYPE_STR_NO_INFO}, + {PMSI_TNLTYPE_RSVP_TE_P2MP, "RSVP-TE P2MP"}, + {PMSI_TNLTYPE_MLDP_P2MP, "mLDP P2MP"}, + {PMSI_TNLTYPE_PIM_SSM, "PIM-SSM"}, + {PMSI_TNLTYPE_PIM_SM, "PIM-SM"}, + {PMSI_TNLTYPE_PIM_BIDIR, "PIM-BIDIR"}, + {PMSI_TNLTYPE_INGR_REPL, "Ingress Replication"}, + {PMSI_TNLTYPE_MLDP_MP2MP, "mLDP MP2MP"}, + {0} +}; + +#define VRFID_NONE_STR "-" +#define SOFT_RECONFIG_TASK_MAX_PREFIX 25000 + +DEFINE_HOOK(bgp_process, + (struct bgp * bgp, afi_t afi, safi_t safi, struct bgp_dest *bn, + struct peer *peer, bool withdraw), + (bgp, afi, safi, bn, peer, withdraw)); + +/** Test if path is suppressed. */ +static bool bgp_path_suppressed(struct bgp_path_info *pi) +{ + if (pi->extra == NULL || pi->extra->aggr_suppressors == NULL) + return false; + + return listcount(pi->extra->aggr_suppressors) > 0; +} + +struct bgp_dest *bgp_afi_node_get(struct bgp_table *table, afi_t afi, + safi_t safi, const struct prefix *p, + struct prefix_rd *prd) +{ + struct bgp_dest *dest; + struct bgp_dest *pdest = NULL; + + assert(table); + + if ((safi == SAFI_MPLS_VPN) || (safi == SAFI_ENCAP) + || (safi == SAFI_EVPN)) { + pdest = bgp_node_get(table, (struct prefix *)prd); + + if (!bgp_dest_has_bgp_path_info_data(pdest)) + bgp_dest_set_bgp_table_info( + pdest, bgp_table_init(table->bgp, afi, safi)); + else + pdest = bgp_dest_unlock_node(pdest); + + assert(pdest); + table = bgp_dest_get_bgp_table_info(pdest); + } + + dest = bgp_node_get(table, p); + + if ((safi == SAFI_MPLS_VPN) || (safi == SAFI_ENCAP) + || (safi == SAFI_EVPN)) + dest->pdest = pdest; + + return dest; +} + +struct bgp_dest *bgp_safi_node_lookup(struct bgp_table *table, safi_t safi, + const struct prefix *p, + struct prefix_rd *prd) +{ + struct bgp_dest *dest; + struct bgp_dest *pdest = NULL; + + if (!table) + return NULL; + + if ((safi == SAFI_MPLS_VPN) || (safi == SAFI_ENCAP) + || (safi == SAFI_EVPN)) { + pdest = bgp_node_lookup(table, (struct prefix *)prd); + if (!pdest) + return NULL; + + if (!bgp_dest_has_bgp_path_info_data(pdest)) { + bgp_dest_unlock_node(pdest); + return NULL; + } + + table = bgp_dest_get_bgp_table_info(pdest); + } + + dest = bgp_node_lookup(table, p); + + return dest; +} + +/* Allocate bgp_path_info_extra */ +static struct bgp_path_info_extra *bgp_path_info_extra_new(void) +{ + struct bgp_path_info_extra *new; + new = XCALLOC(MTYPE_BGP_ROUTE_EXTRA, + sizeof(struct bgp_path_info_extra)); + new->label[0] = MPLS_INVALID_LABEL; + new->num_labels = 0; + new->flowspec = NULL; + return new; +} + +void bgp_path_info_extra_free(struct bgp_path_info_extra **extra) +{ + struct bgp_path_info_extra *e; + + if (!extra || !*extra) + return; + + e = *extra; + if (e->damp_info) + bgp_damp_info_free(e->damp_info, 0, e->damp_info->afi, + e->damp_info->safi); + + e->damp_info = NULL; + if (e->vrfleak && e->vrfleak->parent) { + struct bgp_path_info *bpi = + (struct bgp_path_info *)e->vrfleak->parent; + + if (bpi->net) { + /* FIXME: since multiple e may have the same e->parent + * and e->parent->net is holding a refcount for each + * of them, we need to do some fudging here. + * + * WARNING: if bpi->net->lock drops to 0, bpi may be + * freed as well (because bpi->net was holding the + * last reference to bpi) => write after free! + */ + unsigned refcount; + + bpi = bgp_path_info_lock(bpi); + refcount = bgp_dest_get_lock_count(bpi->net) - 1; + bgp_dest_unlock_node((struct bgp_dest *)bpi->net); + if (!refcount) + bpi->net = NULL; + bgp_path_info_unlock(bpi); + } + bgp_path_info_unlock(e->vrfleak->parent); + e->vrfleak->parent = NULL; + } + + if (e->vrfleak && e->vrfleak->bgp_orig) + bgp_unlock(e->vrfleak->bgp_orig); + + if (e->vrfleak && e->vrfleak->peer_orig) + peer_unlock(e->vrfleak->peer_orig); + + if (e->aggr_suppressors) + list_delete(&e->aggr_suppressors); + + if (e->evpn && e->evpn->mh_info) + bgp_evpn_path_mh_info_free(e->evpn->mh_info); + + if ((*extra)->flowspec && (*extra)->flowspec->bgp_fs_iprule) + list_delete(&((*extra)->flowspec->bgp_fs_iprule)); + if ((*extra)->flowspec && (*extra)->flowspec->bgp_fs_pbr) + list_delete(&((*extra)->flowspec->bgp_fs_pbr)); + + if (e->evpn) + XFREE(MTYPE_BGP_ROUTE_EXTRA_EVPN, e->evpn); + if (e->flowspec) + XFREE(MTYPE_BGP_ROUTE_EXTRA_FS, e->flowspec); + if (e->vrfleak) + XFREE(MTYPE_BGP_ROUTE_EXTRA_VRFLEAK, e->vrfleak); + + XFREE(MTYPE_BGP_ROUTE_EXTRA, *extra); +} + +/* Get bgp_path_info extra information for the given bgp_path_info, lazy + * allocated if required. + */ +struct bgp_path_info_extra *bgp_path_info_extra_get(struct bgp_path_info *pi) +{ + if (!pi->extra) + pi->extra = bgp_path_info_extra_new(); + if (!pi->extra->evpn && pi->net && pi->net->rn->p.family == AF_EVPN) + pi->extra->evpn = + XCALLOC(MTYPE_BGP_ROUTE_EXTRA_EVPN, + sizeof(struct bgp_path_info_extra_evpn)); + return pi->extra; +} + +/* Free bgp route information. */ +void bgp_path_info_free_with_caller(const char *name, + struct bgp_path_info *path) +{ + frrtrace(2, frr_bgp, bgp_path_info_free, path, name); + bgp_attr_unintern(&path->attr); + + bgp_unlink_nexthop(path); + bgp_path_info_extra_free(&path->extra); + bgp_path_info_mpath_free(&path->mpath); + if (path->net) + bgp_addpath_free_info_data(&path->tx_addpath, + &path->net->tx_addpath); + + peer_unlock(path->peer); /* bgp_path_info peer reference */ + + XFREE(MTYPE_BGP_ROUTE, path); +} + +struct bgp_path_info *bgp_path_info_lock(struct bgp_path_info *path) +{ + path->lock++; + return path; +} + +struct bgp_path_info *bgp_path_info_unlock(struct bgp_path_info *path) +{ + assert(path && path->lock > 0); + path->lock--; + + if (path->lock == 0) { + bgp_path_info_free(path); + return NULL; + } + + return path; +} + +bool bgp_path_info_nexthop_changed(struct bgp_path_info *pi, struct peer *to, + afi_t afi) +{ + if (pi->peer->sort == BGP_PEER_IBGP && to->sort == BGP_PEER_IBGP && + !CHECK_FLAG(to->af_flags[afi][SAFI_MPLS_VPN], + PEER_FLAG_FORCE_NEXTHOP_SELF)) + /* IBGP RR with no nexthop self force configured */ + return false; + + if (to->sort == BGP_PEER_IBGP && + !CHECK_FLAG(to->af_flags[afi][SAFI_MPLS_VPN], + PEER_FLAG_NEXTHOP_SELF)) + /* IBGP RR with no nexthop self configured */ + return false; + + if (CHECK_FLAG(to->af_flags[afi][SAFI_MPLS_VPN], + PEER_FLAG_NEXTHOP_UNCHANGED)) + /* IBGP or EBGP with nexthop attribute unchanged */ + return false; + + return true; +} + +/* This function sets flag BGP_NODE_SELECT_DEFER based on condition */ +static int bgp_dest_set_defer_flag(struct bgp_dest *dest, bool delete) +{ + struct peer *peer; + struct bgp_path_info *old_pi, *nextpi; + bool set_flag = false; + struct bgp *bgp = NULL; + struct bgp_table *table = NULL; + afi_t afi = 0; + safi_t safi = 0; + + /* If the flag BGP_NODE_SELECT_DEFER is set and new path is added + * then the route selection is deferred + */ + if (CHECK_FLAG(dest->flags, BGP_NODE_SELECT_DEFER) && (!delete)) + return 0; + + if (CHECK_FLAG(dest->flags, BGP_NODE_PROCESS_SCHEDULED)) { + if (BGP_DEBUG(update, UPDATE_OUT)) { + table = bgp_dest_table(dest); + if (table) + bgp = table->bgp; + + zlog_debug( + "Route %pBD(%s) is in workqueue and being processed, not deferred.", + dest, bgp ? bgp->name_pretty : "(Unknown)"); + } + + return 0; + } + + table = bgp_dest_table(dest); + if (table) { + bgp = table->bgp; + afi = table->afi; + safi = table->safi; + } + + for (old_pi = bgp_dest_get_bgp_path_info(dest); + (old_pi != NULL) && (nextpi = old_pi->next, 1); old_pi = nextpi) { + if (CHECK_FLAG(old_pi->flags, BGP_PATH_SELECTED)) + continue; + + /* Route selection is deferred if there is a stale path which + * which indicates peer is in restart mode + */ + if (CHECK_FLAG(old_pi->flags, BGP_PATH_STALE) + && (old_pi->sub_type == BGP_ROUTE_NORMAL)) { + set_flag = true; + } else { + /* If the peer is graceful restart capable and peer is + * restarting mode, set the flag BGP_NODE_SELECT_DEFER + */ + peer = old_pi->peer; + if (BGP_PEER_GRACEFUL_RESTART_CAPABLE(peer) + && BGP_PEER_RESTARTING_MODE(peer) + && (old_pi + && old_pi->sub_type == BGP_ROUTE_NORMAL)) { + set_flag = true; + } + } + if (set_flag) + break; + } + + /* Set the flag BGP_NODE_SELECT_DEFER if route selection deferral timer + * is active + */ + if (set_flag && table) { + if (bgp && (bgp->gr_info[afi][safi].t_select_deferral)) { + if (!CHECK_FLAG(dest->flags, BGP_NODE_SELECT_DEFER)) + bgp->gr_info[afi][safi].gr_deferred++; + SET_FLAG(dest->flags, BGP_NODE_SELECT_DEFER); + if (BGP_DEBUG(update, UPDATE_OUT)) + zlog_debug("DEFER route %pBD(%s), dest %p", + dest, bgp->name_pretty, dest); + return 0; + } + } + return -1; +} + +void bgp_path_info_add_with_caller(const char *name, struct bgp_dest *dest, + struct bgp_path_info *pi) +{ + frrtrace(3, frr_bgp, bgp_path_info_add, dest, pi, name); + struct bgp_path_info *top; + + top = bgp_dest_get_bgp_path_info(dest); + + pi->next = top; + pi->prev = NULL; + if (top) + top->prev = pi; + bgp_dest_set_bgp_path_info(dest, pi); + + bgp_path_info_lock(pi); + bgp_dest_lock_node(dest); + peer_lock(pi->peer); /* bgp_path_info peer reference */ + bgp_dest_set_defer_flag(dest, false); + hook_call(bgp_snmp_update_stats, dest, pi, true); +} + +/* Do the actual removal of info from RIB, for use by bgp_process + completion callback *only* */ +struct bgp_dest *bgp_path_info_reap(struct bgp_dest *dest, + struct bgp_path_info *pi) +{ + if (pi->next) + pi->next->prev = pi->prev; + if (pi->prev) + pi->prev->next = pi->next; + else + bgp_dest_set_bgp_path_info(dest, pi->next); + + bgp_path_info_mpath_dequeue(pi); + bgp_path_info_unlock(pi); + hook_call(bgp_snmp_update_stats, dest, pi, false); + + return bgp_dest_unlock_node(dest); +} + +void bgp_path_info_delete(struct bgp_dest *dest, struct bgp_path_info *pi) +{ + bgp_path_info_set_flag(dest, pi, BGP_PATH_REMOVED); + /* set of previous already took care of pcount */ + UNSET_FLAG(pi->flags, BGP_PATH_VALID); +} + +/* undo the effects of a previous call to bgp_path_info_delete; typically + called when a route is deleted and then quickly re-added before the + deletion has been processed */ +void bgp_path_info_restore(struct bgp_dest *dest, struct bgp_path_info *pi) +{ + bgp_path_info_unset_flag(dest, pi, BGP_PATH_REMOVED); + /* unset of previous already took care of pcount */ + SET_FLAG(pi->flags, BGP_PATH_VALID); +} + +/* Adjust pcount as required */ +static void bgp_pcount_adjust(struct bgp_dest *dest, struct bgp_path_info *pi) +{ + struct bgp_table *table; + + assert(dest && bgp_dest_table(dest)); + assert(pi && pi->peer && pi->peer->bgp); + + table = bgp_dest_table(dest); + + if (pi->peer == pi->peer->bgp->peer_self) + return; + + if (!BGP_PATH_COUNTABLE(pi) + && CHECK_FLAG(pi->flags, BGP_PATH_COUNTED)) { + + UNSET_FLAG(pi->flags, BGP_PATH_COUNTED); + + /* slight hack, but more robust against errors. */ + if (pi->peer->pcount[table->afi][table->safi]) + pi->peer->pcount[table->afi][table->safi]--; + else + flog_err(EC_LIB_DEVELOPMENT, + "Asked to decrement 0 prefix count for peer"); + } else if (BGP_PATH_COUNTABLE(pi) + && !CHECK_FLAG(pi->flags, BGP_PATH_COUNTED)) { + SET_FLAG(pi->flags, BGP_PATH_COUNTED); + pi->peer->pcount[table->afi][table->safi]++; + } +} + +static int bgp_label_index_differs(struct bgp_path_info *pi1, + struct bgp_path_info *pi2) +{ + return (!(pi1->attr->label_index == pi2->attr->label_index)); +} + +/* Set/unset bgp_path_info flags, adjusting any other state as needed. + * This is here primarily to keep prefix-count in check. + */ +void bgp_path_info_set_flag(struct bgp_dest *dest, struct bgp_path_info *pi, + uint32_t flag) +{ + SET_FLAG(pi->flags, flag); + + /* early bath if we know it's not a flag that changes countability state + */ + if (!CHECK_FLAG(flag, + BGP_PATH_VALID | BGP_PATH_HISTORY | BGP_PATH_REMOVED)) + return; + + bgp_pcount_adjust(dest, pi); +} + +void bgp_path_info_unset_flag(struct bgp_dest *dest, struct bgp_path_info *pi, + uint32_t flag) +{ + UNSET_FLAG(pi->flags, flag); + + /* early bath if we know it's not a flag that changes countability state + */ + if (!CHECK_FLAG(flag, + BGP_PATH_VALID | BGP_PATH_HISTORY | BGP_PATH_REMOVED)) + return; + + bgp_pcount_adjust(dest, pi); +} + +/* Get MED value. If MED value is missing and "bgp bestpath + missing-as-worst" is specified, treat it as the worst value. */ +static uint32_t bgp_med_value(struct attr *attr, struct bgp *bgp) +{ + if (attr->flag & ATTR_FLAG_BIT(BGP_ATTR_MULTI_EXIT_DISC)) + return attr->med; + else { + if (CHECK_FLAG(bgp->flags, BGP_FLAG_MED_MISSING_AS_WORST)) + return BGP_MED_MAX; + else + return 0; + } +} + +void bgp_path_info_path_with_addpath_rx_str(struct bgp_path_info *pi, char *buf, + size_t buf_len) +{ + struct peer *peer; + + if (pi->sub_type == BGP_ROUTE_IMPORTED && + bgp_get_imported_bpi_ultimate(pi)) + peer = bgp_get_imported_bpi_ultimate(pi)->peer; + else + peer = pi->peer; + + if (pi->addpath_rx_id) + snprintf(buf, buf_len, "path %s (addpath rxid %d)", peer->host, + pi->addpath_rx_id); + else + snprintf(buf, buf_len, "path %s", peer->host); +} + + +/* + * Get the ultimate path info. + */ +struct bgp_path_info *bgp_get_imported_bpi_ultimate(struct bgp_path_info *info) +{ + struct bgp_path_info *bpi_ultimate; + + if (info->sub_type != BGP_ROUTE_IMPORTED) + return info; + + for (bpi_ultimate = info; + bpi_ultimate->extra && bpi_ultimate->extra->vrfleak && + bpi_ultimate->extra->vrfleak->parent; + bpi_ultimate = bpi_ultimate->extra->vrfleak->parent) + ; + + return bpi_ultimate; +} + +/* Compare two bgp route entity. If 'new' is preferable over 'exist' return 1. + */ +int bgp_path_info_cmp(struct bgp *bgp, struct bgp_path_info *new, + struct bgp_path_info *exist, int *paths_eq, + struct bgp_maxpaths_cfg *mpath_cfg, bool debug, + char *pfx_buf, afi_t afi, safi_t safi, + enum bgp_path_selection_reason *reason) +{ + const struct prefix *new_p; + struct attr *newattr, *existattr; + enum bgp_peer_sort new_sort; + enum bgp_peer_sort exist_sort; + uint32_t new_pref; + uint32_t exist_pref; + uint32_t new_med; + uint32_t exist_med; + uint32_t new_weight; + uint32_t exist_weight; + uint32_t newm, existm; + struct in_addr new_id; + struct in_addr exist_id; + int new_cluster; + int exist_cluster; + int internal_as_route; + int confed_as_route; + int ret = 0; + int igp_metric_ret = 0; + int peer_sort_ret = -1; + char new_buf[PATH_ADDPATH_STR_BUFFER]; + char exist_buf[PATH_ADDPATH_STR_BUFFER]; + uint32_t new_mm_seq; + uint32_t exist_mm_seq; + int nh_cmp; + esi_t *exist_esi; + esi_t *new_esi; + bool same_esi; + bool old_proxy; + bool new_proxy; + bool new_origin, exist_origin; + struct bgp_path_info *bpi_ultimate; + struct peer *peer_new, *peer_exist; + + *paths_eq = 0; + + /* 0. Null check. */ + if (new == NULL) { + *reason = bgp_path_selection_none; + if (debug) + zlog_debug("%s: new is NULL", pfx_buf); + return 0; + } + + if (debug) { + bpi_ultimate = bgp_get_imported_bpi_ultimate(new); + bgp_path_info_path_with_addpath_rx_str(bpi_ultimate, new_buf, + sizeof(new_buf)); + } + + if (exist == NULL) { + *reason = bgp_path_selection_first; + if (debug) + zlog_debug("%s(%s): %s is the initial bestpath", + pfx_buf, bgp->name_pretty, new_buf); + return 1; + } + + if (debug) { + bpi_ultimate = bgp_get_imported_bpi_ultimate(exist); + bgp_path_info_path_with_addpath_rx_str(bpi_ultimate, exist_buf, + sizeof(exist_buf)); + zlog_debug("%s(%s): Comparing %s flags 0x%x with %s flags 0x%x", + pfx_buf, bgp->name_pretty, new_buf, new->flags, + exist_buf, exist->flags); + } + + newattr = new->attr; + existattr = exist->attr; + + /* A BGP speaker that has advertised the "Long-lived Graceful Restart + * Capability" to a neighbor MUST perform the following upon receiving + * a route from that neighbor with the "LLGR_STALE" community, or upon + * attaching the "LLGR_STALE" community itself per Section 4.2: + * + * Treat the route as the least-preferred in route selection (see + * below). See the Risks of Depreferencing Routes section (Section 5.2) + * for a discussion of potential risks inherent in doing this. + */ + if (bgp_attr_get_community(newattr) && + community_include(bgp_attr_get_community(newattr), + COMMUNITY_LLGR_STALE)) { + if (debug) + zlog_debug( + "%s: %s wins over %s due to LLGR_STALE community", + pfx_buf, new_buf, exist_buf); + return 0; + } + + if (bgp_attr_get_community(existattr) && + community_include(bgp_attr_get_community(existattr), + COMMUNITY_LLGR_STALE)) { + if (debug) + zlog_debug( + "%s: %s loses to %s due to LLGR_STALE community", + pfx_buf, new_buf, exist_buf); + return 1; + } + + new_p = bgp_dest_get_prefix(new->net); + + /* For EVPN routes, we cannot just go by local vs remote, we have to + * look at the MAC mobility sequence number, if present. + */ + if ((safi == SAFI_EVPN) + && (new_p->u.prefix_evpn.route_type == BGP_EVPN_MAC_IP_ROUTE)) { + /* This is an error condition described in RFC 7432 Section + * 15.2. The RFC + * states that in this scenario "the PE MUST alert the operator" + * but it + * does not state what other action to take. In order to provide + * some + * consistency in this scenario we are going to prefer the path + * with the + * sticky flag. + */ + if (newattr->sticky != existattr->sticky) { + if (newattr->sticky && !existattr->sticky) { + *reason = bgp_path_selection_evpn_sticky_mac; + if (debug) + zlog_debug( + "%s: %s wins over %s due to sticky MAC flag", + pfx_buf, new_buf, exist_buf); + return 1; + } + + if (!newattr->sticky && existattr->sticky) { + *reason = bgp_path_selection_evpn_sticky_mac; + if (debug) + zlog_debug( + "%s: %s loses to %s due to sticky MAC flag", + pfx_buf, new_buf, exist_buf); + return 0; + } + } + + new_esi = bgp_evpn_attr_get_esi(newattr); + exist_esi = bgp_evpn_attr_get_esi(existattr); + if (bgp_evpn_is_esi_valid(new_esi) && + !memcmp(new_esi, exist_esi, sizeof(esi_t))) { + same_esi = true; + } else { + same_esi = false; + } + + /* If both paths have the same non-zero ES and + * one path is local it wins. + * PS: Note the local path wins even if the remote + * has the higher MM seq. The local path's + * MM seq will be fixed up to match the highest + * rem seq, subsequently. + */ + if (same_esi) { + char esi_buf[ESI_STR_LEN]; + + if (bgp_evpn_is_path_local(bgp, new)) { + *reason = bgp_path_selection_evpn_local_path; + if (debug) + zlog_debug( + "%s: %s wins over %s as ES %s is same and local", + pfx_buf, new_buf, exist_buf, + esi_to_str(new_esi, esi_buf, + sizeof(esi_buf))); + return 1; + } + if (bgp_evpn_is_path_local(bgp, exist)) { + *reason = bgp_path_selection_evpn_local_path; + if (debug) + zlog_debug( + "%s: %s loses to %s as ES %s is same and local", + pfx_buf, new_buf, exist_buf, + esi_to_str(new_esi, esi_buf, + sizeof(esi_buf))); + return 0; + } + } + + new_mm_seq = mac_mobility_seqnum(newattr); + exist_mm_seq = mac_mobility_seqnum(existattr); + + if (new_mm_seq > exist_mm_seq) { + *reason = bgp_path_selection_evpn_seq; + if (debug) + zlog_debug( + "%s: %s wins over %s due to MM seq %u > %u", + pfx_buf, new_buf, exist_buf, new_mm_seq, + exist_mm_seq); + return 1; + } + + if (new_mm_seq < exist_mm_seq) { + *reason = bgp_path_selection_evpn_seq; + if (debug) + zlog_debug( + "%s: %s loses to %s due to MM seq %u < %u", + pfx_buf, new_buf, exist_buf, new_mm_seq, + exist_mm_seq); + return 0; + } + + /* if the sequence numbers and ESI are the same and one path + * is non-proxy it wins (over proxy) + */ + new_proxy = bgp_evpn_attr_is_proxy(newattr); + old_proxy = bgp_evpn_attr_is_proxy(existattr); + if (same_esi && bgp_evpn_attr_is_local_es(newattr) && + old_proxy != new_proxy) { + if (!new_proxy) { + *reason = bgp_path_selection_evpn_non_proxy; + if (debug) + zlog_debug( + "%s: %s wins over %s, same seq/es and non-proxy", + pfx_buf, new_buf, exist_buf); + return 1; + } + + *reason = bgp_path_selection_evpn_non_proxy; + if (debug) + zlog_debug( + "%s: %s loses to %s, same seq/es and non-proxy", + pfx_buf, new_buf, exist_buf); + return 0; + } + + /* + * if sequence numbers are the same path with the lowest IP + * wins + */ + nh_cmp = bgp_path_info_nexthop_cmp(new, exist); + if (nh_cmp < 0) { + *reason = bgp_path_selection_evpn_lower_ip; + if (debug) + zlog_debug( + "%s: %s wins over %s due to same MM seq %u and lower IP %pI4", + pfx_buf, new_buf, exist_buf, new_mm_seq, + &new->attr->nexthop); + return 1; + } + if (nh_cmp > 0) { + *reason = bgp_path_selection_evpn_lower_ip; + if (debug) + zlog_debug( + "%s: %s loses to %s due to same MM seq %u and higher IP %pI4", + pfx_buf, new_buf, exist_buf, new_mm_seq, + &new->attr->nexthop); + return 0; + } + } + + /* 1. Weight check. */ + new_weight = newattr->weight; + exist_weight = existattr->weight; + + if (new_weight > exist_weight) { + *reason = bgp_path_selection_weight; + if (debug) + zlog_debug("%s: %s wins over %s due to weight %d > %d", + pfx_buf, new_buf, exist_buf, new_weight, + exist_weight); + return 1; + } + + if (new_weight < exist_weight) { + *reason = bgp_path_selection_weight; + if (debug) + zlog_debug("%s: %s loses to %s due to weight %d < %d", + pfx_buf, new_buf, exist_buf, new_weight, + exist_weight); + return 0; + } + + /* 2. Local preference check. */ + new_pref = exist_pref = bgp->default_local_pref; + + if (newattr->flag & ATTR_FLAG_BIT(BGP_ATTR_LOCAL_PREF)) + new_pref = newattr->local_pref; + if (existattr->flag & ATTR_FLAG_BIT(BGP_ATTR_LOCAL_PREF)) + exist_pref = existattr->local_pref; + + if (new_pref > exist_pref) { + *reason = bgp_path_selection_local_pref; + if (debug) + zlog_debug( + "%s: %s wins over %s due to localpref %d > %d", + pfx_buf, new_buf, exist_buf, new_pref, + exist_pref); + return 1; + } + + if (new_pref < exist_pref) { + *reason = bgp_path_selection_local_pref; + if (debug) + zlog_debug( + "%s: %s loses to %s due to localpref %d < %d", + pfx_buf, new_buf, exist_buf, new_pref, + exist_pref); + return 0; + } + + /* If a BGP speaker supports ACCEPT_OWN and is configured for the + * extensions defined in this document, the following step is inserted + * after the LOCAL_PREF comparison step in the BGP decision process: + * When comparing a pair of routes for a BGP destination, the + * route with the ACCEPT_OWN community attached is preferred over + * the route that does not have the community. + * This extra step MUST only be invoked during the best path selection + * process of VPN-IP routes. + */ + if (safi == SAFI_MPLS_VPN && + (CHECK_FLAG(new->peer->af_flags[afi][safi], PEER_FLAG_ACCEPT_OWN) || + CHECK_FLAG(exist->peer->af_flags[afi][safi], + PEER_FLAG_ACCEPT_OWN))) { + bool new_accept_own = false; + bool exist_accept_own = false; + uint32_t accept_own = COMMUNITY_ACCEPT_OWN; + + if (newattr->flag & ATTR_FLAG_BIT(BGP_ATTR_COMMUNITIES)) + new_accept_own = community_include( + bgp_attr_get_community(newattr), accept_own); + if (existattr->flag & ATTR_FLAG_BIT(BGP_ATTR_COMMUNITIES)) + exist_accept_own = community_include( + bgp_attr_get_community(existattr), accept_own); + + if (new_accept_own && !exist_accept_own) { + *reason = bgp_path_selection_accept_own; + if (debug) + zlog_debug( + "%s: %s wins over %s due to accept-own", + pfx_buf, new_buf, exist_buf); + return 1; + } + + if (!new_accept_own && exist_accept_own) { + *reason = bgp_path_selection_accept_own; + if (debug) + zlog_debug( + "%s: %s loses to %s due to accept-own", + pfx_buf, new_buf, exist_buf); + return 0; + } + } + + /* Tie-breaker - AIGP (Metric TLV) attribute */ + if (CHECK_FLAG(newattr->flag, ATTR_FLAG_BIT(BGP_ATTR_AIGP)) && + CHECK_FLAG(existattr->flag, ATTR_FLAG_BIT(BGP_ATTR_AIGP)) && + CHECK_FLAG(bgp->flags, BGP_FLAG_COMPARE_AIGP)) { + uint64_t new_aigp = bgp_attr_get_aigp_metric(newattr); + uint64_t exist_aigp = bgp_attr_get_aigp_metric(existattr); + + if (new_aigp < exist_aigp) { + *reason = bgp_path_selection_aigp; + if (debug) + zlog_debug( + "%s: %s wins over %s due to AIGP %" PRIu64 + " < %" PRIu64, + pfx_buf, new_buf, exist_buf, new_aigp, + exist_aigp); + return 1; + } + + if (new_aigp > exist_aigp) { + *reason = bgp_path_selection_aigp; + if (debug) + zlog_debug( + "%s: %s loses to %s due to AIGP %" PRIu64 + " > %" PRIu64, + pfx_buf, new_buf, exist_buf, new_aigp, + exist_aigp); + return 0; + } + } + + /* 3. Local route check. We prefer: + * - BGP_ROUTE_STATIC + * - BGP_ROUTE_AGGREGATE + * - BGP_ROUTE_REDISTRIBUTE + */ + new_origin = !(new->sub_type == BGP_ROUTE_NORMAL || + new->sub_type == BGP_ROUTE_IMPORTED); + exist_origin = !(exist->sub_type == BGP_ROUTE_NORMAL || + exist->sub_type == BGP_ROUTE_IMPORTED); + + if (new_origin && !exist_origin) { + *reason = bgp_path_selection_local_route; + if (debug) + zlog_debug( + "%s: %s wins over %s due to preferred BGP_ROUTE type", + pfx_buf, new_buf, exist_buf); + return 1; + } + + if (!new_origin && exist_origin) { + *reason = bgp_path_selection_local_route; + if (debug) + zlog_debug( + "%s: %s loses to %s due to preferred BGP_ROUTE type", + pfx_buf, new_buf, exist_buf); + return 0; + } + + /* Here if these are imported routes then get ultimate pi for + * path compare. + */ + new = bgp_get_imported_bpi_ultimate(new); + exist = bgp_get_imported_bpi_ultimate(exist); + newattr = new->attr; + existattr = exist->attr; + + /* 4. AS path length check. */ + if (!CHECK_FLAG(bgp->flags, BGP_FLAG_ASPATH_IGNORE)) { + int exist_hops = aspath_count_hops(existattr->aspath); + int exist_confeds = aspath_count_confeds(existattr->aspath); + + if (CHECK_FLAG(bgp->flags, BGP_FLAG_ASPATH_CONFED)) { + int aspath_hops; + + aspath_hops = aspath_count_hops(newattr->aspath); + aspath_hops += aspath_count_confeds(newattr->aspath); + + if (aspath_hops < (exist_hops + exist_confeds)) { + *reason = bgp_path_selection_confed_as_path; + if (debug) + zlog_debug( + "%s: %s wins over %s due to aspath (with confeds) hopcount %d < %d", + pfx_buf, new_buf, exist_buf, + aspath_hops, + (exist_hops + exist_confeds)); + return 1; + } + + if (aspath_hops > (exist_hops + exist_confeds)) { + *reason = bgp_path_selection_confed_as_path; + if (debug) + zlog_debug( + "%s: %s loses to %s due to aspath (with confeds) hopcount %d > %d", + pfx_buf, new_buf, exist_buf, + aspath_hops, + (exist_hops + exist_confeds)); + return 0; + } + } else { + int newhops = aspath_count_hops(newattr->aspath); + + if (newhops < exist_hops) { + *reason = bgp_path_selection_as_path; + if (debug) + zlog_debug( + "%s: %s wins over %s due to aspath hopcount %d < %d", + pfx_buf, new_buf, exist_buf, + newhops, exist_hops); + return 1; + } + + if (newhops > exist_hops) { + *reason = bgp_path_selection_as_path; + if (debug) + zlog_debug( + "%s: %s loses to %s due to aspath hopcount %d > %d", + pfx_buf, new_buf, exist_buf, + newhops, exist_hops); + return 0; + } + } + } + + /* 5. Origin check. */ + if (newattr->origin < existattr->origin) { + *reason = bgp_path_selection_origin; + if (debug) + zlog_debug("%s: %s wins over %s due to ORIGIN %s < %s", + pfx_buf, new_buf, exist_buf, + bgp_origin_long_str[newattr->origin], + bgp_origin_long_str[existattr->origin]); + return 1; + } + + if (newattr->origin > existattr->origin) { + *reason = bgp_path_selection_origin; + if (debug) + zlog_debug("%s: %s loses to %s due to ORIGIN %s > %s", + pfx_buf, new_buf, exist_buf, + bgp_origin_long_str[newattr->origin], + bgp_origin_long_str[existattr->origin]); + return 0; + } + + /* 6. MED check. */ + internal_as_route = (aspath_count_hops(newattr->aspath) == 0 + && aspath_count_hops(existattr->aspath) == 0); + confed_as_route = (aspath_count_confeds(newattr->aspath) > 0 + && aspath_count_confeds(existattr->aspath) > 0 + && aspath_count_hops(newattr->aspath) == 0 + && aspath_count_hops(existattr->aspath) == 0); + + if (CHECK_FLAG(bgp->flags, BGP_FLAG_ALWAYS_COMPARE_MED) + || (CHECK_FLAG(bgp->flags, BGP_FLAG_MED_CONFED) && confed_as_route) + || aspath_cmp_left(newattr->aspath, existattr->aspath) + || aspath_cmp_left_confed(newattr->aspath, existattr->aspath) + || internal_as_route) { + new_med = bgp_med_value(new->attr, bgp); + exist_med = bgp_med_value(exist->attr, bgp); + + if (new_med < exist_med) { + *reason = bgp_path_selection_med; + if (debug) + zlog_debug( + "%s: %s wins over %s due to MED %d < %d", + pfx_buf, new_buf, exist_buf, new_med, + exist_med); + return 1; + } + + if (new_med > exist_med) { + *reason = bgp_path_selection_med; + if (debug) + zlog_debug( + "%s: %s loses to %s due to MED %d > %d", + pfx_buf, new_buf, exist_buf, new_med, + exist_med); + return 0; + } + } + + if (exist->sub_type == BGP_ROUTE_IMPORTED) { + bpi_ultimate = bgp_get_imported_bpi_ultimate(exist); + peer_exist = bpi_ultimate->peer; + } else + peer_exist = exist->peer; + + if (new->sub_type == BGP_ROUTE_IMPORTED) { + bpi_ultimate = bgp_get_imported_bpi_ultimate(new); + peer_new = bpi_ultimate->peer; + } else + peer_new = new->peer; + + /* 7. Peer type check. */ + new_sort = peer_new->sort; + exist_sort = peer_exist->sort; + + if (new_sort == BGP_PEER_EBGP + && (exist_sort == BGP_PEER_IBGP || exist_sort == BGP_PEER_CONFED)) { + *reason = bgp_path_selection_peer; + if (debug) + zlog_debug( + "%s: %s wins over %s due to eBGP peer > iBGP peer", + pfx_buf, new_buf, exist_buf); + if (!CHECK_FLAG(bgp->flags, BGP_FLAG_PEERTYPE_MULTIPATH_RELAX)) + return 1; + peer_sort_ret = 1; + } + + if (exist_sort == BGP_PEER_EBGP + && (new_sort == BGP_PEER_IBGP || new_sort == BGP_PEER_CONFED)) { + *reason = bgp_path_selection_peer; + if (debug) + zlog_debug( + "%s: %s loses to %s due to iBGP peer < eBGP peer", + pfx_buf, new_buf, exist_buf); + if (!CHECK_FLAG(bgp->flags, BGP_FLAG_PEERTYPE_MULTIPATH_RELAX)) + return 0; + peer_sort_ret = 0; + } + + /* 8. IGP metric check. */ + newm = existm = 0; + + if (new->extra) + newm = new->extra->igpmetric; + if (exist->extra) + existm = exist->extra->igpmetric; + + if (newm < existm) { + if (debug && peer_sort_ret < 0) + zlog_debug( + "%s: %s wins over %s due to IGP metric %u < %u", + pfx_buf, new_buf, exist_buf, newm, existm); + igp_metric_ret = 1; + } + + if (newm > existm) { + if (debug && peer_sort_ret < 0) + zlog_debug( + "%s: %s loses to %s due to IGP metric %u > %u", + pfx_buf, new_buf, exist_buf, newm, existm); + igp_metric_ret = 0; + } + + /* 9. Same IGP metric. Compare the cluster list length as + representative of IGP hops metric. Rewrite the metric value + pair (newm, existm) with the cluster list length. Prefer the + path with smaller cluster list length. */ + if (newm == existm) { + if (peer_sort_lookup(peer_new) == BGP_PEER_IBGP && + peer_sort_lookup(peer_exist) == BGP_PEER_IBGP && + (mpath_cfg == NULL || mpath_cfg->same_clusterlen)) { + newm = BGP_CLUSTER_LIST_LENGTH(new->attr); + existm = BGP_CLUSTER_LIST_LENGTH(exist->attr); + + if (newm < existm) { + if (debug && peer_sort_ret < 0) + zlog_debug( + "%s: %s wins over %s due to CLUSTER_LIST length %u < %u", + pfx_buf, new_buf, exist_buf, + newm, existm); + igp_metric_ret = 1; + } + + if (newm > existm) { + if (debug && peer_sort_ret < 0) + zlog_debug( + "%s: %s loses to %s due to CLUSTER_LIST length %u > %u", + pfx_buf, new_buf, exist_buf, + newm, existm); + igp_metric_ret = 0; + } + } + } + + /* 10. confed-external vs. confed-internal */ + if (CHECK_FLAG(bgp->config, BGP_CONFIG_CONFEDERATION)) { + if (new_sort == BGP_PEER_CONFED + && exist_sort == BGP_PEER_IBGP) { + *reason = bgp_path_selection_confed; + if (debug) + zlog_debug( + "%s: %s wins over %s due to confed-external peer > confed-internal peer", + pfx_buf, new_buf, exist_buf); + if (!CHECK_FLAG(bgp->flags, + BGP_FLAG_PEERTYPE_MULTIPATH_RELAX)) + return 1; + peer_sort_ret = 1; + } + + if (exist_sort == BGP_PEER_CONFED + && new_sort == BGP_PEER_IBGP) { + *reason = bgp_path_selection_confed; + if (debug) + zlog_debug( + "%s: %s loses to %s due to confed-internal peer < confed-external peer", + pfx_buf, new_buf, exist_buf); + if (!CHECK_FLAG(bgp->flags, + BGP_FLAG_PEERTYPE_MULTIPATH_RELAX)) + return 0; + peer_sort_ret = 0; + } + } + + /* 11. Maximum path check. */ + if (newm == existm) { + /* If one path has a label but the other does not, do not treat + * them as equals for multipath + */ + int newl, existl; + + newl = existl = 0; + + if (new->extra) + newl = new->extra->num_labels; + if (exist->extra) + existl = exist->extra->num_labels; + if (((new->extra &&bgp_is_valid_label(&new->extra->label[0])) != + (exist->extra && + bgp_is_valid_label(&exist->extra->label[0]))) || + (newl != existl)) { + if (debug) + zlog_debug( + "%s: %s and %s cannot be multipath, one has a label while the other does not", + pfx_buf, new_buf, exist_buf); + } else if (CHECK_FLAG(bgp->flags, + BGP_FLAG_ASPATH_MULTIPATH_RELAX)) { + + /* + * For the two paths, all comparison steps till IGP + * metric + * have succeeded - including AS_PATH hop count. Since + * 'bgp + * bestpath as-path multipath-relax' knob is on, we + * don't need + * an exact match of AS_PATH. Thus, mark the paths are + * equal. + * That will trigger both these paths to get into the + * multipath + * array. + */ + *paths_eq = 1; + + if (debug) + zlog_debug( + "%s: %s and %s are equal via multipath-relax", + pfx_buf, new_buf, exist_buf); + } else if (peer_new->sort == BGP_PEER_IBGP) { + if (aspath_cmp(new->attr->aspath, + exist->attr->aspath)) { + *paths_eq = 1; + + if (debug) + zlog_debug( + "%s: %s and %s are equal via matching aspaths", + pfx_buf, new_buf, exist_buf); + } + } else if (peer_new->as == peer_exist->as) { + *paths_eq = 1; + + if (debug) + zlog_debug( + "%s: %s and %s are equal via same remote-as", + pfx_buf, new_buf, exist_buf); + } + } else { + /* + * TODO: If unequal cost ibgp multipath is enabled we can + * mark the paths as equal here instead of returning + */ + + /* Prior to the addition of BGP_FLAG_PEERTYPE_MULTIPATH_RELAX, + * if either step 7 or 10 (peer type checks) yielded a winner, + * that result was returned immediately. Returning from step 10 + * ignored the return value computed in steps 8 and 9 (IGP + * metric checks). In order to preserve that behavior, if + * peer_sort_ret is set, return that rather than igp_metric_ret. + */ + ret = peer_sort_ret; + if (peer_sort_ret < 0) { + ret = igp_metric_ret; + if (debug) { + if (ret == 1) + zlog_debug( + "%s: %s wins over %s after IGP metric comparison", + pfx_buf, new_buf, exist_buf); + else + zlog_debug( + "%s: %s loses to %s after IGP metric comparison", + pfx_buf, new_buf, exist_buf); + } + *reason = bgp_path_selection_igp_metric; + } + return ret; + } + + /* + * At this point, the decision whether to set *paths_eq = 1 has been + * completed. If we deferred returning because of bestpath peer-type + * relax configuration, return now. + */ + if (peer_sort_ret >= 0) + return peer_sort_ret; + + /* 12. If both paths are external, prefer the path that was received + first (the oldest one). This step minimizes route-flap, since a + newer path won't displace an older one, even if it was the + preferred route based on the additional decision criteria below. */ + if (!CHECK_FLAG(bgp->flags, BGP_FLAG_COMPARE_ROUTER_ID) + && new_sort == BGP_PEER_EBGP && exist_sort == BGP_PEER_EBGP) { + if (CHECK_FLAG(new->flags, BGP_PATH_SELECTED)) { + *reason = bgp_path_selection_older; + if (debug) + zlog_debug( + "%s: %s wins over %s due to oldest external", + pfx_buf, new_buf, exist_buf); + return 1; + } + + if (CHECK_FLAG(exist->flags, BGP_PATH_SELECTED)) { + *reason = bgp_path_selection_older; + if (debug) + zlog_debug( + "%s: %s loses to %s due to oldest external", + pfx_buf, new_buf, exist_buf); + return 0; + } + } + + /* 13. Router-ID comparison. */ + /* If one of the paths is "stale", the corresponding peer router-id will + * be 0 and would always win over the other path. If originator id is + * used for the comparison, it will decide which path is better. + */ + if (newattr->flag & ATTR_FLAG_BIT(BGP_ATTR_ORIGINATOR_ID)) + new_id.s_addr = newattr->originator_id.s_addr; + else + new_id.s_addr = peer_new->remote_id.s_addr; + if (existattr->flag & ATTR_FLAG_BIT(BGP_ATTR_ORIGINATOR_ID)) + exist_id.s_addr = existattr->originator_id.s_addr; + else + exist_id.s_addr = peer_exist->remote_id.s_addr; + + if (ntohl(new_id.s_addr) < ntohl(exist_id.s_addr)) { + *reason = bgp_path_selection_router_id; + if (debug) + zlog_debug( + "%s: %s wins over %s due to Router-ID comparison", + pfx_buf, new_buf, exist_buf); + return 1; + } + + if (ntohl(new_id.s_addr) > ntohl(exist_id.s_addr)) { + *reason = bgp_path_selection_router_id; + if (debug) + zlog_debug( + "%s: %s loses to %s due to Router-ID comparison", + pfx_buf, new_buf, exist_buf); + return 0; + } + + /* 14. Cluster length comparison. */ + new_cluster = BGP_CLUSTER_LIST_LENGTH(new->attr); + exist_cluster = BGP_CLUSTER_LIST_LENGTH(exist->attr); + + if (new_cluster < exist_cluster) { + *reason = bgp_path_selection_cluster_length; + if (debug) + zlog_debug( + "%s: %s wins over %s due to CLUSTER_LIST length %d < %d", + pfx_buf, new_buf, exist_buf, new_cluster, + exist_cluster); + return 1; + } + + if (new_cluster > exist_cluster) { + *reason = bgp_path_selection_cluster_length; + if (debug) + zlog_debug( + "%s: %s loses to %s due to CLUSTER_LIST length %d > %d", + pfx_buf, new_buf, exist_buf, new_cluster, + exist_cluster); + return 0; + } + + /* 15. Neighbor address comparison. */ + /* Do this only if neither path is "stale" as stale paths do not have + * valid peer information (as the connection may or may not be up). + */ + if (CHECK_FLAG(exist->flags, BGP_PATH_STALE)) { + *reason = bgp_path_selection_stale; + if (debug) + zlog_debug( + "%s: %s wins over %s due to latter path being STALE", + pfx_buf, new_buf, exist_buf); + return 1; + } + + if (CHECK_FLAG(new->flags, BGP_PATH_STALE)) { + *reason = bgp_path_selection_stale; + if (debug) + zlog_debug( + "%s: %s loses to %s due to former path being STALE", + pfx_buf, new_buf, exist_buf); + return 0; + } + + /* locally configured routes to advertise do not have su_remote */ + if (peer_new->su_remote == NULL) { + *reason = bgp_path_selection_local_configured; + return 0; + } + + if (peer_exist->su_remote == NULL) { + *reason = bgp_path_selection_local_configured; + return 1; + } + + ret = sockunion_cmp(peer_new->su_remote, peer_exist->su_remote); + + if (ret == 1) { + *reason = bgp_path_selection_neighbor_ip; + if (debug) + zlog_debug( + "%s: %s loses to %s due to Neighor IP comparison", + pfx_buf, new_buf, exist_buf); + return 0; + } + + if (ret == -1) { + *reason = bgp_path_selection_neighbor_ip; + if (debug) + zlog_debug( + "%s: %s wins over %s due to Neighor IP comparison", + pfx_buf, new_buf, exist_buf); + return 1; + } + + *reason = bgp_path_selection_default; + if (debug) + zlog_debug("%s: %s wins over %s due to nothing left to compare", + pfx_buf, new_buf, exist_buf); + + return 1; +} + + +int bgp_evpn_path_info_cmp(struct bgp *bgp, struct bgp_path_info *new, + struct bgp_path_info *exist, int *paths_eq, + bool debug) +{ + enum bgp_path_selection_reason reason; + char pfx_buf[PREFIX2STR_BUFFER] = {}; + + if (debug) + prefix2str(bgp_dest_get_prefix(new->net), pfx_buf, + sizeof(pfx_buf)); + + return bgp_path_info_cmp(bgp, new, exist, paths_eq, NULL, debug, + pfx_buf, AFI_L2VPN, SAFI_EVPN, &reason); +} + +/* Compare two bgp route entity. Return -1 if new is preferred, 1 if exist + * is preferred, or 0 if they are the same (usually will only occur if + * multipath is enabled + * This version is compatible with */ +int bgp_path_info_cmp_compatible(struct bgp *bgp, struct bgp_path_info *new, + struct bgp_path_info *exist, char *pfx_buf, + afi_t afi, safi_t safi, + enum bgp_path_selection_reason *reason) +{ + int paths_eq; + int ret; + bool debug = false; + + ret = bgp_path_info_cmp(bgp, new, exist, &paths_eq, NULL, debug, + pfx_buf, afi, safi, reason); + + if (paths_eq) + ret = 0; + else { + if (ret == 1) + ret = -1; + else + ret = 1; + } + return ret; +} + +static enum filter_type bgp_input_filter(struct peer *peer, + const struct prefix *p, + struct attr *attr, afi_t afi, + safi_t safi) +{ + struct bgp_filter *filter; + enum filter_type ret = FILTER_PERMIT; + + filter = &peer->filter[afi][safi]; + +#define FILTER_EXIST_WARN(F, f, filter) \ + if (BGP_DEBUG(update, UPDATE_IN) && !(F##_IN(filter))) \ + zlog_debug("%s: Could not find configured input %s-list %s!", \ + peer->host, #f, F##_IN_NAME(filter)); + + if (DISTRIBUTE_IN_NAME(filter)) { + FILTER_EXIST_WARN(DISTRIBUTE, distribute, filter); + + if (access_list_apply(DISTRIBUTE_IN(filter), p) + == FILTER_DENY) { + ret = FILTER_DENY; + goto done; + } + } + + if (PREFIX_LIST_IN_NAME(filter)) { + FILTER_EXIST_WARN(PREFIX_LIST, prefix, filter); + + if (prefix_list_apply(PREFIX_LIST_IN(filter), p) + == PREFIX_DENY) { + ret = FILTER_DENY; + goto done; + } + } + + if (FILTER_LIST_IN_NAME(filter)) { + FILTER_EXIST_WARN(FILTER_LIST, as, filter); + + if (as_list_apply(FILTER_LIST_IN(filter), attr->aspath) + == AS_FILTER_DENY) { + ret = FILTER_DENY; + goto done; + } + } + +done: + if (frrtrace_enabled(frr_bgp, input_filter)) { + char pfxprint[PREFIX2STR_BUFFER]; + + prefix2str(p, pfxprint, sizeof(pfxprint)); + frrtrace(5, frr_bgp, input_filter, peer, pfxprint, afi, safi, + ret == FILTER_PERMIT ? "permit" : "deny"); + } + + return ret; +#undef FILTER_EXIST_WARN +} + +static enum filter_type bgp_output_filter(struct peer *peer, + const struct prefix *p, + struct attr *attr, afi_t afi, + safi_t safi) +{ + struct bgp_filter *filter; + enum filter_type ret = FILTER_PERMIT; + + filter = &peer->filter[afi][safi]; + +#define FILTER_EXIST_WARN(F, f, filter) \ + if (BGP_DEBUG(update, UPDATE_OUT) && !(F##_OUT(filter))) \ + zlog_debug("%s: Could not find configured output %s-list %s!", \ + peer->host, #f, F##_OUT_NAME(filter)); + + if (DISTRIBUTE_OUT_NAME(filter)) { + FILTER_EXIST_WARN(DISTRIBUTE, distribute, filter); + + if (access_list_apply(DISTRIBUTE_OUT(filter), p) + == FILTER_DENY) { + ret = FILTER_DENY; + goto done; + } + } + + if (PREFIX_LIST_OUT_NAME(filter)) { + FILTER_EXIST_WARN(PREFIX_LIST, prefix, filter); + + if (prefix_list_apply(PREFIX_LIST_OUT(filter), p) + == PREFIX_DENY) { + ret = FILTER_DENY; + goto done; + } + } + + if (FILTER_LIST_OUT_NAME(filter)) { + FILTER_EXIST_WARN(FILTER_LIST, as, filter); + + if (as_list_apply(FILTER_LIST_OUT(filter), attr->aspath) + == AS_FILTER_DENY) { + ret = FILTER_DENY; + goto done; + } + } + + if (frrtrace_enabled(frr_bgp, output_filter)) { + char pfxprint[PREFIX2STR_BUFFER]; + + prefix2str(p, pfxprint, sizeof(pfxprint)); + frrtrace(5, frr_bgp, output_filter, peer, pfxprint, afi, safi, + ret == FILTER_PERMIT ? "permit" : "deny"); + } + +done: + return ret; +#undef FILTER_EXIST_WARN +} + +/* If community attribute includes no_export then return 1. */ +static bool bgp_community_filter(struct peer *peer, struct attr *attr) +{ + if (bgp_attr_get_community(attr)) { + /* NO_ADVERTISE check. */ + if (community_include(bgp_attr_get_community(attr), + COMMUNITY_NO_ADVERTISE)) + return true; + + /* NO_EXPORT check. */ + if (peer->sort == BGP_PEER_EBGP && + community_include(bgp_attr_get_community(attr), + COMMUNITY_NO_EXPORT)) + return true; + + /* NO_EXPORT_SUBCONFED check. */ + if (peer->sort == BGP_PEER_EBGP + || peer->sort == BGP_PEER_CONFED) + if (community_include(bgp_attr_get_community(attr), + COMMUNITY_NO_EXPORT_SUBCONFED)) + return true; + } + return false; +} + +/* Route reflection loop check. */ +static bool bgp_cluster_filter(struct peer *peer, struct attr *attr) +{ + struct in_addr cluster_id; + struct cluster_list *cluster = bgp_attr_get_cluster(attr); + + if (cluster) { + if (peer->bgp->config & BGP_CONFIG_CLUSTER_ID) + cluster_id = peer->bgp->cluster_id; + else + cluster_id = peer->bgp->router_id; + + if (cluster_loop_check(cluster, cluster_id)) + return true; + } + return false; +} + +static bool bgp_otc_filter(struct peer *peer, struct attr *attr) +{ + if (attr->flag & ATTR_FLAG_BIT(BGP_ATTR_OTC)) { + if (peer->local_role == ROLE_PROVIDER || + peer->local_role == ROLE_RS_SERVER) + return true; + if (peer->local_role == ROLE_PEER && attr->otc != peer->as) + return true; + return false; + } + if (peer->local_role == ROLE_CUSTOMER || + peer->local_role == ROLE_PEER || + peer->local_role == ROLE_RS_CLIENT) { + attr->flag |= ATTR_FLAG_BIT(BGP_ATTR_OTC); + attr->otc = peer->as; + } + return false; +} + +static bool bgp_otc_egress(struct peer *peer, struct attr *attr) +{ + if (attr->flag & ATTR_FLAG_BIT(BGP_ATTR_OTC)) { + if (peer->local_role == ROLE_CUSTOMER || + peer->local_role == ROLE_RS_CLIENT || + peer->local_role == ROLE_PEER) + return true; + return false; + } + if (peer->local_role == ROLE_PROVIDER || + peer->local_role == ROLE_PEER || + peer->local_role == ROLE_RS_SERVER) { + attr->flag |= ATTR_FLAG_BIT(BGP_ATTR_OTC); + attr->otc = peer->bgp->as; + } + return false; +} + +static bool bgp_check_role_applicability(afi_t afi, safi_t safi) +{ + return ((afi == AFI_IP || afi == AFI_IP6) && safi == SAFI_UNICAST); +} + +static int bgp_input_modifier(struct peer *peer, const struct prefix *p, + struct attr *attr, afi_t afi, safi_t safi, + const char *rmap_name, mpls_label_t *label, + uint32_t num_labels, struct bgp_dest *dest) +{ + struct bgp_filter *filter; + struct bgp_path_info rmap_path = { 0 }; + struct bgp_path_info_extra extra = { 0 }; + route_map_result_t ret; + struct route_map *rmap = NULL; + + filter = &peer->filter[afi][safi]; + + /* Apply default weight value. */ + if (peer->weight[afi][safi]) + attr->weight = peer->weight[afi][safi]; + + if (rmap_name) { + rmap = route_map_lookup_by_name(rmap_name); + + if (rmap == NULL) + return RMAP_DENY; + } else { + if (ROUTE_MAP_IN_NAME(filter)) { + rmap = ROUTE_MAP_IN(filter); + + if (rmap == NULL) + return RMAP_DENY; + } + } + + /* Route map apply. */ + if (rmap) { + memset(&rmap_path, 0, sizeof(rmap_path)); + /* Duplicate current value to new structure for modification. */ + rmap_path.peer = peer; + rmap_path.attr = attr; + rmap_path.extra = &extra; + rmap_path.net = dest; + + extra.num_labels = num_labels; + if (label && num_labels && num_labels <= BGP_MAX_LABELS) + memcpy(extra.label, label, + num_labels * sizeof(mpls_label_t)); + + SET_FLAG(peer->rmap_type, PEER_RMAP_TYPE_IN); + + /* Apply BGP route map to the attribute. */ + ret = route_map_apply(rmap, p, &rmap_path); + + peer->rmap_type = 0; + + if (ret == RMAP_DENYMATCH) + return RMAP_DENY; + } + return RMAP_PERMIT; +} + +static int bgp_output_modifier(struct peer *peer, const struct prefix *p, + struct attr *attr, afi_t afi, safi_t safi, + const char *rmap_name) +{ + struct bgp_path_info rmap_path; + route_map_result_t ret; + struct route_map *rmap = NULL; + uint8_t rmap_type; + + /* + * So if we get to this point and have no rmap_name + * we want to just show the output as it currently + * exists. + */ + if (!rmap_name) + return RMAP_PERMIT; + + /* Apply default weight value. */ + if (peer->weight[afi][safi]) + attr->weight = peer->weight[afi][safi]; + + rmap = route_map_lookup_by_name(rmap_name); + + /* + * If we have a route map name and we do not find + * the routemap that means we have an implicit + * deny. + */ + if (rmap == NULL) + return RMAP_DENY; + + memset(&rmap_path, 0, sizeof(rmap_path)); + /* Route map apply. */ + /* Duplicate current value to new structure for modification. */ + rmap_path.peer = peer; + rmap_path.attr = attr; + + rmap_type = peer->rmap_type; + SET_FLAG(peer->rmap_type, PEER_RMAP_TYPE_OUT); + + /* Apply BGP route map to the attribute. */ + ret = route_map_apply(rmap, p, &rmap_path); + + peer->rmap_type = rmap_type; + + if (ret == RMAP_DENYMATCH) + /* + * caller has multiple error paths with bgp_attr_flush() + */ + return RMAP_DENY; + + return RMAP_PERMIT; +} + +/* If this is an EBGP peer with remove-private-AS */ +static void bgp_peer_remove_private_as(struct bgp *bgp, afi_t afi, safi_t safi, + struct peer *peer, struct attr *attr) +{ + if (peer->sort == BGP_PEER_EBGP + && (peer_af_flag_check(peer, afi, safi, + PEER_FLAG_REMOVE_PRIVATE_AS_ALL_REPLACE) + || peer_af_flag_check(peer, afi, safi, + PEER_FLAG_REMOVE_PRIVATE_AS_REPLACE) + || peer_af_flag_check(peer, afi, safi, + PEER_FLAG_REMOVE_PRIVATE_AS_ALL) + || peer_af_flag_check(peer, afi, safi, + PEER_FLAG_REMOVE_PRIVATE_AS))) { + // Take action on the entire aspath + if (peer_af_flag_check(peer, afi, safi, + PEER_FLAG_REMOVE_PRIVATE_AS_ALL_REPLACE) + || peer_af_flag_check(peer, afi, safi, + PEER_FLAG_REMOVE_PRIVATE_AS_ALL)) { + if (peer_af_flag_check( + peer, afi, safi, + PEER_FLAG_REMOVE_PRIVATE_AS_ALL_REPLACE)) + attr->aspath = aspath_replace_private_asns( + attr->aspath, bgp->as, peer->as); + + /* + * Even if the aspath consists of just private ASNs we + * need to walk the AS-Path to maintain all instances + * of the peer's ASN to break possible loops. + */ + else + attr->aspath = aspath_remove_private_asns( + attr->aspath, peer->as); + } + + // 'all' was not specified so the entire aspath must be private + // ASNs + // for us to do anything + else if (aspath_private_as_check(attr->aspath)) { + if (peer_af_flag_check( + peer, afi, safi, + PEER_FLAG_REMOVE_PRIVATE_AS_REPLACE)) + attr->aspath = aspath_replace_private_asns( + attr->aspath, bgp->as, peer->as); + else + /* + * Walk the aspath to retain any instances of + * the peer_asn + */ + attr->aspath = aspath_remove_private_asns( + attr->aspath, peer->as); + } + } +} + +/* If this is an EBGP peer with as-override */ +static void bgp_peer_as_override(struct bgp *bgp, afi_t afi, safi_t safi, + struct peer *peer, struct attr *attr) +{ + struct aspath *aspath; + + if (peer->sort == BGP_PEER_EBGP && + peer_af_flag_check(peer, afi, safi, PEER_FLAG_AS_OVERRIDE)) { + if (attr->aspath->refcnt) + aspath = aspath_dup(attr->aspath); + else + aspath = attr->aspath; + + attr->aspath = aspath_intern( + aspath_replace_specific_asn(aspath, peer->as, bgp->as)); + + aspath_free(aspath); + } +} + +void bgp_attr_add_llgr_community(struct attr *attr) +{ + struct community *old; + struct community *new; + struct community *merge; + struct community *llgr; + + old = bgp_attr_get_community(attr); + llgr = community_str2com("llgr-stale"); + + assert(llgr); + + if (old) { + merge = community_merge(community_dup(old), llgr); + + if (old->refcnt == 0) + community_free(&old); + + new = community_uniq_sort(merge); + community_free(&merge); + } else { + new = community_dup(llgr); + } + + community_free(&llgr); + + bgp_attr_set_community(attr, new); +} + +void bgp_attr_add_gshut_community(struct attr *attr) +{ + struct community *old; + struct community *new; + struct community *merge; + struct community *gshut; + + old = bgp_attr_get_community(attr); + gshut = community_str2com("graceful-shutdown"); + + assert(gshut); + + if (old) { + merge = community_merge(community_dup(old), gshut); + + if (old->refcnt == 0) + community_free(&old); + + new = community_uniq_sort(merge); + community_free(&merge); + } else { + new = community_dup(gshut); + } + + community_free(&gshut); + bgp_attr_set_community(attr, new); + + /* When we add the graceful-shutdown community we must also + * lower the local-preference */ + attr->flag |= ATTR_FLAG_BIT(BGP_ATTR_LOCAL_PREF); + attr->local_pref = BGP_GSHUT_LOCAL_PREF; +} + + +/* Notify BGP Conditional advertisement scanner process. */ +void bgp_notify_conditional_adv_scanner(struct update_subgroup *subgrp) +{ + struct peer *peer = SUBGRP_PEER(subgrp); + afi_t afi = SUBGRP_AFI(subgrp); + safi_t safi = SUBGRP_SAFI(subgrp); + struct bgp_filter *filter = &peer->filter[afi][safi]; + + if (!ADVERTISE_MAP_NAME(filter)) + return; + + if (!CHECK_FLAG(peer->flags, PEER_FLAG_CONFIG_NODE)) + return; + + peer->advmap_table_change = true; +} + + +void subgroup_announce_reset_nhop(uint8_t family, struct attr *attr) +{ + if (family == AF_INET) { + attr->nexthop.s_addr = INADDR_ANY; + attr->mp_nexthop_global_in.s_addr = INADDR_ANY; + } + if (family == AF_INET6) + memset(&attr->mp_nexthop_global, 0, IPV6_MAX_BYTELEN); + if (family == AF_EVPN) + memset(&attr->mp_nexthop_global_in, 0, BGP_ATTR_NHLEN_IPV4); +} + +bool subgroup_announce_check(struct bgp_dest *dest, struct bgp_path_info *pi, + struct update_subgroup *subgrp, + const struct prefix *p, struct attr *attr, + struct attr *post_attr) +{ + struct bgp_filter *filter; + struct peer *from; + struct peer *peer; + struct peer *onlypeer; + struct bgp *bgp; + struct attr *piattr; + route_map_result_t ret; + int transparent; + int reflect; + afi_t afi; + safi_t safi; + int samepeer_safe = 0; /* for synthetic mplsvpns routes */ + bool nh_reset = false; + uint64_t cum_bw; + mpls_label_t label; + + if (DISABLE_BGP_ANNOUNCE) + return false; + + afi = SUBGRP_AFI(subgrp); + safi = SUBGRP_SAFI(subgrp); + peer = SUBGRP_PEER(subgrp); + onlypeer = NULL; + if (CHECK_FLAG(peer->flags, PEER_FLAG_LONESOUL)) + onlypeer = SUBGRP_PFIRST(subgrp)->peer; + + from = pi->peer; + filter = &peer->filter[afi][safi]; + bgp = SUBGRP_INST(subgrp); + piattr = bgp_path_info_mpath_count(pi) ? bgp_path_info_mpath_attr(pi) + : pi->attr; + + if (CHECK_FLAG(peer->af_flags[afi][safi], PEER_FLAG_MAX_PREFIX_OUT) && + peer->pmax_out[afi][safi] != 0 && + subgrp->pscount >= peer->pmax_out[afi][safi]) { + if (BGP_DEBUG(update, UPDATE_OUT) || + BGP_DEBUG(update, UPDATE_PREFIX)) { + zlog_debug("%s reached maximum prefix to be send (%u)", + peer->host, peer->pmax_out[afi][safi]); + } + return false; + } + +#ifdef ENABLE_BGP_VNC + if (((afi == AFI_IP) || (afi == AFI_IP6)) && (safi == SAFI_MPLS_VPN) + && ((pi->type == ZEBRA_ROUTE_BGP_DIRECT) + || (pi->type == ZEBRA_ROUTE_BGP_DIRECT_EXT))) { + + /* + * direct and direct_ext type routes originate internally even + * though they can have peer pointers that reference other + * systems + */ + zlog_debug("%s: pfx %pFX bgp_direct->vpn route peer safe", + __func__, p); + samepeer_safe = 1; + } +#endif + + if (((afi == AFI_IP) || (afi == AFI_IP6)) + && ((safi == SAFI_MPLS_VPN) || (safi == SAFI_UNICAST)) + && (pi->type == ZEBRA_ROUTE_BGP) + && (pi->sub_type == BGP_ROUTE_IMPORTED)) { + + /* Applies to routes leaked vpn->vrf and vrf->vpn */ + + samepeer_safe = 1; + } + + /* With addpath we may be asked to TX all kinds of paths so make sure + * pi is valid */ + if (!CHECK_FLAG(pi->flags, BGP_PATH_VALID) + || CHECK_FLAG(pi->flags, BGP_PATH_HISTORY) + || CHECK_FLAG(pi->flags, BGP_PATH_REMOVED)) { + return false; + } + + /* If this is not the bestpath then check to see if there is an enabled + * addpath + * feature that requires us to advertise it */ + if (!CHECK_FLAG(pi->flags, BGP_PATH_SELECTED)) + if (!bgp_addpath_capable(pi, peer, afi, safi)) + return false; + + /* Aggregate-address suppress check. */ + if (bgp_path_suppressed(pi) && !UNSUPPRESS_MAP_NAME(filter)) + return false; + + /* + * If we are doing VRF 2 VRF leaking via the import + * statement, we want to prevent the route going + * off box as that the RT and RD created are localy + * significant and globaly useless. + */ + if (safi == SAFI_MPLS_VPN && pi->extra && pi->extra->num_labels + && pi->extra->label[0] == BGP_PREVENT_VRF_2_VRF_LEAK) + return false; + + /* If it's labeled safi, make sure the route has a valid label. */ + if (safi == SAFI_LABELED_UNICAST) { + label = bgp_adv_label(dest, pi, peer, afi, safi); + if (!bgp_is_valid_label(&label)) { + if (bgp_debug_update(NULL, p, subgrp->update_group, 0)) + zlog_debug("u%" PRIu64 ":s%" PRIu64 + " %pFX is filtered - no label (%p)", + subgrp->update_group->id, subgrp->id, + p, &label); + return false; + } + } else if (safi == SAFI_MPLS_VPN && + CHECK_FLAG(pi->flags, BGP_PATH_MPLSVPN_NH_LABEL_BIND) && + pi->mplsvpn.bmnc.nh_label_bind_cache && peer && + pi->peer != peer && pi->sub_type != BGP_ROUTE_IMPORTED && + pi->sub_type != BGP_ROUTE_STATIC && + bgp_mplsvpn_path_uses_valid_mpls_label(pi) && + bgp_path_info_nexthop_changed(pi, peer, afi)) { + /* Redistributed mpls vpn route between distinct + * peers from 'pi->peer' to 'to', + * and an mpls label is used in this path, + * and there is a nh label bind entry, + * then get appropriate mpls local label + * and check its validity + */ + label = bgp_mplsvpn_nh_label_bind_get_label(pi); + if (!bgp_is_valid_label(&label)) { + if (bgp_debug_update(NULL, p, subgrp->update_group, 0)) + zlog_debug("u%" PRIu64 ":s%" PRIu64 + " %pFX is filtered - no valid label", + subgrp->update_group->id, subgrp->id, + p); + return false; + } + } + + /* Do not send back route to sender. */ + if (onlypeer && from == onlypeer) { + return false; + } + + /* Do not send the default route in the BGP table if the neighbor is + * configured for default-originate */ + if (CHECK_FLAG(peer->af_flags[afi][safi], + PEER_FLAG_DEFAULT_ORIGINATE)) { + if (p->family == AF_INET && p->u.prefix4.s_addr == INADDR_ANY) + return false; + else if (p->family == AF_INET6 && p->prefixlen == 0) + return false; + } + + /* Transparency check. */ + if (CHECK_FLAG(peer->af_flags[afi][safi], PEER_FLAG_RSERVER_CLIENT) + && CHECK_FLAG(from->af_flags[afi][safi], PEER_FLAG_RSERVER_CLIENT)) + transparent = 1; + else + transparent = 0; + + /* If community is not disabled check the no-export and local. */ + if (!transparent && bgp_community_filter(peer, piattr)) { + if (bgp_debug_update(NULL, p, subgrp->update_group, 0)) + zlog_debug("%s: community filter check fail for %pFX", + __func__, p); + return false; + } + + /* If the attribute has originator-id and it is same as remote + peer's id. */ + if (onlypeer && piattr->flag & ATTR_FLAG_BIT(BGP_ATTR_ORIGINATOR_ID) + && (IPV4_ADDR_SAME(&onlypeer->remote_id, &piattr->originator_id))) { + if (bgp_debug_update(NULL, p, subgrp->update_group, 0)) + zlog_debug( + "%pBP [Update:SEND] %pFX originator-id is same as remote router-id", + onlypeer, p); + return false; + } + + /* ORF prefix-list filter check */ + if (CHECK_FLAG(peer->af_cap[afi][safi], PEER_CAP_ORF_PREFIX_RM_ADV) && + CHECK_FLAG(peer->af_cap[afi][safi], PEER_CAP_ORF_PREFIX_SM_RCV)) + if (peer->orf_plist[afi][safi]) { + if (prefix_list_apply(peer->orf_plist[afi][safi], p) + == PREFIX_DENY) { + if (bgp_debug_update(NULL, p, + subgrp->update_group, 0)) + zlog_debug( + "%pBP [Update:SEND] %pFX is filtered via ORF", + peer, p); + return false; + } + } + + /* Output filter check. */ + if (bgp_output_filter(peer, p, piattr, afi, safi) == FILTER_DENY) { + if (bgp_debug_update(NULL, p, subgrp->update_group, 0)) + zlog_debug("%pBP [Update:SEND] %pFX is filtered", peer, + p); + return false; + } + + /* AS path loop check. */ + if (peer->as_path_loop_detection && + aspath_loop_check(piattr->aspath, peer->as)) { + if (bgp_debug_update(NULL, p, subgrp->update_group, 0)) + zlog_debug( + "%pBP [Update:SEND] suppress announcement to peer AS %u that is part of AS path.", + peer, peer->as); + return false; + } + + /* If we're a CONFED we need to loop check the CONFED ID too */ + if (CHECK_FLAG(bgp->config, BGP_CONFIG_CONFEDERATION)) { + if (aspath_loop_check_confed(piattr->aspath, bgp->confed_id)) { + if (bgp_debug_update(NULL, p, subgrp->update_group, 0)) + zlog_debug( + "%pBP [Update:SEND] suppress announcement to peer AS %u is AS path.", + peer, bgp->confed_id); + return false; + } + } + + /* Route-Reflect check. */ + if (from->sort == BGP_PEER_IBGP && peer->sort == BGP_PEER_IBGP) + reflect = 1; + else + reflect = 0; + + /* IBGP reflection check. */ + if (reflect && !samepeer_safe) { + /* A route from a Client peer. */ + if (CHECK_FLAG(from->af_flags[afi][safi], + PEER_FLAG_REFLECTOR_CLIENT)) { + /* Reflect to all the Non-Client peers and also to the + Client peers other than the originator. Originator + check + is already done. So there is noting to do. */ + /* no bgp client-to-client reflection check. */ + if (CHECK_FLAG(bgp->flags, + BGP_FLAG_NO_CLIENT_TO_CLIENT)) + if (CHECK_FLAG(peer->af_flags[afi][safi], + PEER_FLAG_REFLECTOR_CLIENT)) + return false; + } else { + /* A route from a Non-client peer. Reflect to all other + clients. */ + if (!CHECK_FLAG(peer->af_flags[afi][safi], + PEER_FLAG_REFLECTOR_CLIENT)) + return false; + } + } + + /* For modify attribute, copy it to temporary structure. + * post_attr comes from BGP conditional advertisements, where + * attributes are already processed by advertise-map route-map, + * and this needs to be saved instead of overwriting from the + * path attributes. + */ + if (post_attr) + *attr = *post_attr; + else + *attr = *piattr; + + /* don't confuse inbound and outbound setting */ + RESET_FLAG(attr->rmap_change_flags); + + /* If local-preference is not set. */ + if ((peer->sort == BGP_PEER_IBGP || peer->sort == BGP_PEER_CONFED) + && (!(attr->flag & ATTR_FLAG_BIT(BGP_ATTR_LOCAL_PREF)))) { + attr->flag |= ATTR_FLAG_BIT(BGP_ATTR_LOCAL_PREF); + attr->local_pref = bgp->default_local_pref; + } + + /* If originator-id is not set and the route is to be reflected, + set the originator id */ + if (reflect + && (!(attr->flag & ATTR_FLAG_BIT(BGP_ATTR_ORIGINATOR_ID)))) { + IPV4_ADDR_COPY(&(attr->originator_id), &(from->remote_id)); + SET_FLAG(attr->flag, BGP_ATTR_ORIGINATOR_ID); + } + + /* Remove MED if its an EBGP peer - will get overwritten by route-maps + */ + if (peer->sort == BGP_PEER_EBGP + && attr->flag & ATTR_FLAG_BIT(BGP_ATTR_MULTI_EXIT_DISC)) { + if (from != bgp->peer_self && !transparent + && !CHECK_FLAG(peer->af_flags[afi][safi], + PEER_FLAG_MED_UNCHANGED)) + attr->flag &= + ~(ATTR_FLAG_BIT(BGP_ATTR_MULTI_EXIT_DISC)); + } + + /* Since the nexthop attribute can vary per peer, it is not explicitly + * set + * in announce check, only certain flags and length (or number of + * nexthops + * -- for IPv6/MP_REACH) are set here in order to guide the update + * formation + * code in setting the nexthop(s) on a per peer basis in + * reformat_peer(). + * Typically, the source nexthop in the attribute is preserved but in + * the + * scenarios where we know it will always be overwritten, we reset the + * nexthop to "0" in an attempt to achieve better Update packing. An + * example of this is when a prefix from each of 2 IBGP peers needs to + * be + * announced to an EBGP peer (and they have the same attributes barring + * their nexthop). + */ + if (reflect) + SET_FLAG(attr->rmap_change_flags, BATTR_REFLECTED); + +#define NEXTHOP_IS_V6 \ + ((safi != SAFI_ENCAP && safi != SAFI_MPLS_VPN \ + && (p->family == AF_INET6 || peer_cap_enhe(peer, afi, safi))) \ + || ((safi == SAFI_ENCAP || safi == SAFI_MPLS_VPN) \ + && attr->mp_nexthop_len >= IPV6_MAX_BYTELEN)) + + /* IPv6/MP starts with 1 nexthop. The link-local address is passed only + * if + * the peer (group) is configured to receive link-local nexthop + * unchanged + * and it is available in the prefix OR we're not reflecting the route, + * link-local nexthop address is valid and + * the peer (group) to whom we're going to announce is on a shared + * network + * and this is either a self-originated route or the peer is EBGP. + * By checking if nexthop LL address is valid we are sure that + * we do not announce LL address as `::`. + */ + if (NEXTHOP_IS_V6) { + attr->mp_nexthop_len = BGP_ATTR_NHLEN_IPV6_GLOBAL; + if ((CHECK_FLAG(peer->af_flags[afi][safi], + PEER_FLAG_NEXTHOP_LOCAL_UNCHANGED) + && IN6_IS_ADDR_LINKLOCAL(&attr->mp_nexthop_local)) + || (!reflect && !transparent + && IN6_IS_ADDR_LINKLOCAL(&peer->nexthop.v6_local) + && peer->shared_network + && (from == bgp->peer_self + || peer->sort == BGP_PEER_EBGP))) { + if (safi == SAFI_MPLS_VPN) + attr->mp_nexthop_len = + BGP_ATTR_NHLEN_VPNV6_GLOBAL_AND_LL; + else + attr->mp_nexthop_len = + BGP_ATTR_NHLEN_IPV6_GLOBAL_AND_LL; + } + + /* Clear off link-local nexthop in source, whenever it is not + * needed to + * ensure more prefixes share the same attribute for + * announcement. + */ + if (!(CHECK_FLAG(peer->af_flags[afi][safi], + PEER_FLAG_NEXTHOP_LOCAL_UNCHANGED))) + memset(&attr->mp_nexthop_local, 0, IPV6_MAX_BYTELEN); + } + + if (bgp_check_role_applicability(afi, safi) && + bgp_otc_egress(peer, attr)) + return false; + + if (filter->advmap.update_type == UPDATE_TYPE_WITHDRAW && + filter->advmap.aname && + route_map_lookup_by_name(filter->advmap.aname)) { + struct bgp_path_info rmap_path = {0}; + struct bgp_path_info_extra dummy_rmap_path_extra = {0}; + struct attr dummy_attr = *attr; + + /* Fill temp path_info */ + prep_for_rmap_apply(&rmap_path, &dummy_rmap_path_extra, dest, + pi, peer, &dummy_attr); + + struct route_map *amap = + route_map_lookup_by_name(filter->advmap.aname); + + ret = route_map_apply(amap, p, &rmap_path); + + bgp_attr_flush(&dummy_attr); + + /* + * The conditional advertisement mode is Withdraw and this + * prefix is a conditional prefix. Don't advertise it + */ + if (ret == RMAP_PERMITMATCH) + return false; + } + + /* Route map & unsuppress-map apply. */ + if (!post_attr && + (ROUTE_MAP_OUT_NAME(filter) || bgp_path_suppressed(pi))) { + struct bgp_path_info rmap_path = {0}; + struct bgp_path_info_extra dummy_rmap_path_extra = {0}; + struct attr dummy_attr = {0}; + + /* Fill temp path_info */ + prep_for_rmap_apply(&rmap_path, &dummy_rmap_path_extra, dest, + pi, peer, attr); + /* + * The route reflector is not allowed to modify the attributes + * of the reflected IBGP routes unless explicitly allowed. + */ + if ((from->sort == BGP_PEER_IBGP && peer->sort == BGP_PEER_IBGP) + && !CHECK_FLAG(bgp->flags, + BGP_FLAG_RR_ALLOW_OUTBOUND_POLICY)) { + dummy_attr = *attr; + rmap_path.attr = &dummy_attr; + } + + SET_FLAG(peer->rmap_type, PEER_RMAP_TYPE_OUT); + + if (bgp_path_suppressed(pi)) + ret = route_map_apply(UNSUPPRESS_MAP(filter), p, + &rmap_path); + else + ret = route_map_apply(ROUTE_MAP_OUT(filter), p, + &rmap_path); + + bgp_attr_flush(&dummy_attr); + peer->rmap_type = 0; + + if (ret == RMAP_DENYMATCH) { + if (bgp_debug_update(NULL, p, subgrp->update_group, 0)) + zlog_debug( + "%pBP [Update:SEND] %pFX is filtered by route-map '%s'", + peer, p, + bgp_path_suppressed(pi) + ? UNSUPPRESS_MAP_NAME(filter) + : ROUTE_MAP_OUT_NAME(filter)); + bgp_attr_flush(rmap_path.attr); + return false; + } + } + + bgp_peer_remove_private_as(bgp, afi, safi, peer, attr); + bgp_peer_as_override(bgp, afi, safi, peer, attr); + + /* RFC 8212 to prevent route leaks. + * This specification intends to improve this situation by requiring the + * explicit configuration of both BGP Import and Export Policies for any + * External BGP (EBGP) session such as customers, peers, or + * confederation boundaries for all enabled address families. Through + * codification of the aforementioned requirement, operators will + * benefit from consistent behavior across different BGP + * implementations. + */ + if (CHECK_FLAG(bgp->flags, BGP_FLAG_EBGP_REQUIRES_POLICY)) + if (!bgp_outbound_policy_exists(peer, filter)) { + if (monotime_since(&bgp->ebgprequirespolicywarning, + NULL) > FIFTEENMINUTE2USEC || + bgp->ebgprequirespolicywarning.tv_sec == 0) { + zlog_warn( + "EBGP inbound/outbound policy not properly setup, please configure in order for your peering to work correctly"); + monotime(&bgp->ebgprequirespolicywarning); + } + return false; + } + + /* draft-ietf-idr-deprecate-as-set-confed-set + * Filter routes having AS_SET or AS_CONFED_SET in the path. + * Eventually, This document (if approved) updates RFC 4271 + * and RFC 5065 by eliminating AS_SET and AS_CONFED_SET types, + * and obsoletes RFC 6472. + */ + if (peer->bgp->reject_as_sets) + if (aspath_check_as_sets(attr->aspath)) + return false; + + /* If neighbor soo is configured, then check if the route has + * SoO extended community and validate against the configured + * one. If they match, do not announce, to prevent routing + * loops. + */ + if ((attr->flag & ATTR_FLAG_BIT(BGP_ATTR_EXT_COMMUNITIES)) && + peer->soo[afi][safi]) { + struct ecommunity *ecomm_soo = peer->soo[afi][safi]; + struct ecommunity *ecomm = bgp_attr_get_ecommunity(attr); + + if ((ecommunity_lookup(ecomm, ECOMMUNITY_ENCODE_AS, + ECOMMUNITY_SITE_ORIGIN) || + ecommunity_lookup(ecomm, ECOMMUNITY_ENCODE_AS4, + ECOMMUNITY_SITE_ORIGIN) || + ecommunity_lookup(ecomm, ECOMMUNITY_ENCODE_IP, + ECOMMUNITY_SITE_ORIGIN)) && + ecommunity_include(ecomm, ecomm_soo)) { + if (bgp_debug_update(NULL, p, subgrp->update_group, 0)) + zlog_debug( + "%pBP [Update:SEND] %pFX is filtered by SoO extcommunity '%s'", + peer, p, ecommunity_str(ecomm_soo)); + return false; + } + } + + /* Codification of AS 0 Processing */ + if (aspath_check_as_zero(attr->aspath)) + return false; + + if (bgp_in_graceful_shutdown(bgp)) { + if (peer->sort == BGP_PEER_IBGP + || peer->sort == BGP_PEER_CONFED) { + attr->flag |= ATTR_FLAG_BIT(BGP_ATTR_LOCAL_PREF); + attr->local_pref = BGP_GSHUT_LOCAL_PREF; + } else { + bgp_attr_add_gshut_community(attr); + } + } + + /* A BGP speaker that has advertised the "Long-lived Graceful Restart + * Capability" to a neighbor MUST perform the following upon receiving + * a route from that neighbor with the "LLGR_STALE" community, or upon + * attaching the "LLGR_STALE" community itself per Section 4.2: + * + * The route SHOULD NOT be advertised to any neighbor from which the + * Long-lived Graceful Restart Capability has not been received. + */ + if (bgp_attr_get_community(attr) && + community_include(bgp_attr_get_community(attr), + COMMUNITY_LLGR_STALE) && + !CHECK_FLAG(peer->cap, PEER_CAP_LLGR_RCV) && + !CHECK_FLAG(peer->cap, PEER_CAP_LLGR_ADV)) + return false; + + /* After route-map has been applied, we check to see if the nexthop to + * be carried in the attribute (that is used for the announcement) can + * be cleared off or not. We do this in all cases where we would be + * setting the nexthop to "ourselves". For IPv6, we only need to + * consider + * the global nexthop here; the link-local nexthop would have been + * cleared + * already, and if not, it is required by the update formation code. + * Also see earlier comments in this function. + */ + /* + * If route-map has performed some operation on the nexthop or the peer + * configuration says to pass it unchanged, we cannot reset the nexthop + * here, so only attempt to do it if these aren't true. Note that the + * route-map handler itself might have cleared the nexthop, if for + * example, + * it is configured as 'peer-address'. + */ + if (!bgp_rmap_nhop_changed(attr->rmap_change_flags, + piattr->rmap_change_flags) + && !transparent + && !CHECK_FLAG(peer->af_flags[afi][safi], + PEER_FLAG_NEXTHOP_UNCHANGED)) { + /* We can reset the nexthop, if setting (or forcing) it to + * 'self' */ + if (CHECK_FLAG(peer->af_flags[afi][safi], + PEER_FLAG_NEXTHOP_SELF) + || CHECK_FLAG(peer->af_flags[afi][safi], + PEER_FLAG_FORCE_NEXTHOP_SELF)) { + if (!reflect + || CHECK_FLAG(peer->af_flags[afi][safi], + PEER_FLAG_FORCE_NEXTHOP_SELF)) { + subgroup_announce_reset_nhop( + (peer_cap_enhe(peer, afi, safi) + ? AF_INET6 + : p->family), + attr); + nh_reset = true; + } + } else if (peer->sort == BGP_PEER_EBGP) { + /* Can also reset the nexthop if announcing to EBGP, but + * only if + * no peer in the subgroup is on a shared subnet. + * Note: 3rd party nexthop currently implemented for + * IPv4 only. + */ + if ((p->family == AF_INET) && + (!bgp_subgrp_multiaccess_check_v4( + piattr->nexthop, + subgrp, from))) { + subgroup_announce_reset_nhop( + (peer_cap_enhe(peer, afi, safi) + ? AF_INET6 + : p->family), + attr); + nh_reset = true; + } + + if ((p->family == AF_INET6) && + (!bgp_subgrp_multiaccess_check_v6( + piattr->mp_nexthop_global, + subgrp, from))) { + subgroup_announce_reset_nhop( + (peer_cap_enhe(peer, afi, safi) + ? AF_INET6 + : p->family), + attr); + nh_reset = true; + } + + + + } else if (CHECK_FLAG(pi->flags, BGP_PATH_ANNC_NH_SELF)) { + /* + * This flag is used for leaked vpn-vrf routes + */ + int family = p->family; + + if (peer_cap_enhe(peer, afi, safi)) + family = AF_INET6; + + if (bgp_debug_update(NULL, p, subgrp->update_group, 0)) + zlog_debug( + "%s: %pFX BGP_PATH_ANNC_NH_SELF, family=%s", + __func__, p, family2str(family)); + subgroup_announce_reset_nhop(family, attr); + nh_reset = true; + } + } + + /* If IPv6/MP and nexthop does not have any override and happens + * to + * be a link-local address, reset it so that we don't pass along + * the + * source's link-local IPv6 address to recipients who may not be + * on + * the same interface. + */ + if (p->family == AF_INET6 || peer_cap_enhe(peer, afi, safi)) { + if (IN6_IS_ADDR_LINKLOCAL(&attr->mp_nexthop_global)) { + subgroup_announce_reset_nhop(AF_INET6, attr); + nh_reset = true; + } + } + + /* If this is an iBGP, send Origin Validation State (OVS) + * extended community (rfc8097). + */ + if (peer->sort == BGP_PEER_IBGP) { + enum rpki_states rpki_state = RPKI_NOT_BEING_USED; + + rpki_state = hook_call(bgp_rpki_prefix_status, peer, attr, p); + + if (rpki_state != RPKI_NOT_BEING_USED) + bgp_attr_set_ecommunity( + attr, ecommunity_add_origin_validation_state( + rpki_state, + bgp_attr_get_ecommunity(attr))); + } + + /* + * When the next hop is set to ourselves, if all multipaths have + * link-bandwidth announce the cumulative bandwidth as that makes + * the most sense. However, don't modify if the link-bandwidth has + * been explicitly set by user policy. + */ + if (nh_reset && + bgp_path_info_mpath_chkwtd(bgp, pi) && + (cum_bw = bgp_path_info_mpath_cumbw(pi)) != 0 && + !CHECK_FLAG(attr->rmap_change_flags, BATTR_RMAP_LINK_BW_SET)) + bgp_attr_set_ecommunity( + attr, + ecommunity_replace_linkbw( + bgp->as, bgp_attr_get_ecommunity(attr), cum_bw, + CHECK_FLAG( + peer->flags, + PEER_FLAG_DISABLE_LINK_BW_ENCODING_IEEE))); + + return true; +} + +static void bgp_route_select_timer_expire(struct event *thread) +{ + struct afi_safi_info *info; + afi_t afi; + safi_t safi; + struct bgp *bgp; + + info = EVENT_ARG(thread); + afi = info->afi; + safi = info->safi; + bgp = info->bgp; + + bgp->gr_info[afi][safi].t_route_select = NULL; + XFREE(MTYPE_TMP, info); + + /* Best path selection */ + bgp_best_path_select_defer(bgp, afi, safi); +} + +void bgp_best_selection(struct bgp *bgp, struct bgp_dest *dest, + struct bgp_maxpaths_cfg *mpath_cfg, + struct bgp_path_info_pair *result, afi_t afi, + safi_t safi) +{ + struct bgp_path_info *new_select; + struct bgp_path_info *old_select; + struct bgp_path_info *pi; + struct bgp_path_info *pi1; + struct bgp_path_info *pi2; + struct bgp_path_info *nextpi = NULL; + int paths_eq, do_mpath; + bool debug; + struct list mp_list; + char pfx_buf[PREFIX2STR_BUFFER] = {}; + char path_buf[PATH_ADDPATH_STR_BUFFER]; + + bgp_mp_list_init(&mp_list); + do_mpath = + (mpath_cfg->maxpaths_ebgp > 1 || mpath_cfg->maxpaths_ibgp > 1); + + debug = bgp_debug_bestpath(dest); + + if (debug) + prefix2str(bgp_dest_get_prefix(dest), pfx_buf, sizeof(pfx_buf)); + + dest->reason = bgp_path_selection_none; + /* bgp deterministic-med */ + new_select = NULL; + if (CHECK_FLAG(bgp->flags, BGP_FLAG_DETERMINISTIC_MED)) { + + /* Clear BGP_PATH_DMED_SELECTED for all paths */ + for (pi1 = bgp_dest_get_bgp_path_info(dest); pi1; + pi1 = pi1->next) + bgp_path_info_unset_flag(dest, pi1, + BGP_PATH_DMED_SELECTED); + + for (pi1 = bgp_dest_get_bgp_path_info(dest); pi1; + pi1 = pi1->next) { + if (CHECK_FLAG(pi1->flags, BGP_PATH_DMED_CHECK)) + continue; + if (BGP_PATH_HOLDDOWN(pi1)) + continue; + if (pi1->peer != bgp->peer_self && + !CHECK_FLAG(pi1->peer->sflags, + PEER_STATUS_NSF_WAIT)) { + if (!peer_established(pi1->peer->connection)) + continue; + } + + new_select = pi1; + if (pi1->next) { + for (pi2 = pi1->next; pi2; pi2 = pi2->next) { + if (CHECK_FLAG(pi2->flags, + BGP_PATH_DMED_CHECK)) + continue; + if (BGP_PATH_HOLDDOWN(pi2)) + continue; + if (pi2->peer != bgp->peer_self && + !CHECK_FLAG(pi2->peer->sflags, + PEER_STATUS_NSF_WAIT) && + !peer_established( + pi2->peer->connection)) + continue; + + if (!aspath_cmp_left(pi1->attr->aspath, + pi2->attr->aspath) + && !aspath_cmp_left_confed( + pi1->attr->aspath, + pi2->attr->aspath)) + continue; + + if (bgp_path_info_cmp( + bgp, pi2, new_select, + &paths_eq, mpath_cfg, debug, + pfx_buf, afi, safi, + &dest->reason)) { + bgp_path_info_unset_flag( + dest, new_select, + BGP_PATH_DMED_SELECTED); + new_select = pi2; + } + + bgp_path_info_set_flag( + dest, pi2, BGP_PATH_DMED_CHECK); + } + } + bgp_path_info_set_flag(dest, new_select, + BGP_PATH_DMED_CHECK); + bgp_path_info_set_flag(dest, new_select, + BGP_PATH_DMED_SELECTED); + + if (debug) { + bgp_path_info_path_with_addpath_rx_str( + new_select, path_buf, sizeof(path_buf)); + zlog_debug( + "%pBD(%s): %s is the bestpath from AS %u", + dest, bgp->name_pretty, path_buf, + aspath_get_first_as( + new_select->attr->aspath)); + } + } + } + + /* Check old selected route and new selected route. */ + old_select = NULL; + new_select = NULL; + for (pi = bgp_dest_get_bgp_path_info(dest); + (pi != NULL) && (nextpi = pi->next, 1); pi = nextpi) { + enum bgp_path_selection_reason reason; + + if (CHECK_FLAG(pi->flags, BGP_PATH_SELECTED)) + old_select = pi; + + if (BGP_PATH_HOLDDOWN(pi)) { + /* reap REMOVED routes, if needs be + * selected route must stay for a while longer though + */ + if (CHECK_FLAG(pi->flags, BGP_PATH_REMOVED) && + (pi != old_select)) { + dest = bgp_path_info_reap(dest, pi); + assert(dest); + } + + if (debug) + zlog_debug( + "%s: %pBD(%s) pi from %s in holddown", + __func__, dest, bgp->name_pretty, + pi->peer->host); + + continue; + } + + if (pi->peer && pi->peer != bgp->peer_self + && !CHECK_FLAG(pi->peer->sflags, PEER_STATUS_NSF_WAIT)) + if (!peer_established(pi->peer->connection)) { + if (debug) + zlog_debug( + "%s: %pBD(%s) non self peer %s not estab state", + __func__, dest, + bgp->name_pretty, + pi->peer->host); + + continue; + } + + if (CHECK_FLAG(bgp->flags, BGP_FLAG_DETERMINISTIC_MED) + && (!CHECK_FLAG(pi->flags, BGP_PATH_DMED_SELECTED))) { + bgp_path_info_unset_flag(dest, pi, BGP_PATH_DMED_CHECK); + if (debug) + zlog_debug("%s: %pBD(%s) pi %s dmed", __func__, + dest, bgp->name_pretty, + pi->peer->host); + continue; + } + + bgp_path_info_unset_flag(dest, pi, BGP_PATH_DMED_CHECK); + + reason = dest->reason; + if (bgp_path_info_cmp(bgp, pi, new_select, &paths_eq, mpath_cfg, + debug, pfx_buf, afi, safi, + &dest->reason)) { + if (new_select == NULL && + reason != bgp_path_selection_none) + dest->reason = reason; + new_select = pi; + } + } + + /* Now that we know which path is the bestpath see if any of the other + * paths + * qualify as multipaths + */ + if (debug) { + if (new_select) + bgp_path_info_path_with_addpath_rx_str( + new_select, path_buf, sizeof(path_buf)); + else + snprintf(path_buf, sizeof(path_buf), "NONE"); + zlog_debug( + "%pBD(%s): After path selection, newbest is %s oldbest was %s", + dest, bgp->name_pretty, path_buf, + old_select ? old_select->peer->host : "NONE"); + } + + if (do_mpath && new_select) { + for (pi = bgp_dest_get_bgp_path_info(dest); + (pi != NULL) && (nextpi = pi->next, 1); pi = nextpi) { + + if (debug) + bgp_path_info_path_with_addpath_rx_str( + pi, path_buf, sizeof(path_buf)); + + if (pi == new_select) { + if (debug) + zlog_debug( + "%pBD(%s): %s is the bestpath, add to the multipath list", + dest, bgp->name_pretty, + path_buf); + bgp_mp_list_add(&mp_list, pi); + continue; + } + + if (BGP_PATH_HOLDDOWN(pi)) + continue; + + if (pi->peer && pi->peer != bgp->peer_self + && !CHECK_FLAG(pi->peer->sflags, + PEER_STATUS_NSF_WAIT)) + if (!peer_established(pi->peer->connection)) + continue; + + if (!bgp_path_info_nexthop_cmp(pi, new_select)) { + if (debug) + zlog_debug( + "%pBD(%s): %s has the same nexthop as the bestpath, skip it", + dest, bgp->name_pretty, + path_buf); + continue; + } + + bgp_path_info_cmp(bgp, pi, new_select, &paths_eq, + mpath_cfg, debug, pfx_buf, afi, safi, + &dest->reason); + + if (paths_eq) { + if (debug) + zlog_debug( + "%pBD(%s): %s is equivalent to the bestpath, add to the multipath list", + dest, bgp->name_pretty, + path_buf); + bgp_mp_list_add(&mp_list, pi); + } + } + } + + bgp_path_info_mpath_update(bgp, dest, new_select, old_select, &mp_list, + mpath_cfg); + bgp_path_info_mpath_aggregate_update(new_select, old_select); + bgp_mp_list_clear(&mp_list); + + bgp_addpath_update_ids(bgp, dest, afi, safi); + + result->old = old_select; + result->new = new_select; + + return; +} + +/* + * A new route/change in bestpath of an existing route. Evaluate the path + * for advertisement to the subgroup. + */ +void subgroup_process_announce_selected(struct update_subgroup *subgrp, + struct bgp_path_info *selected, + struct bgp_dest *dest, afi_t afi, + safi_t safi, uint32_t addpath_tx_id) +{ + const struct prefix *p; + struct peer *onlypeer; + struct attr attr; + struct bgp *bgp; + bool advertise; + + p = bgp_dest_get_prefix(dest); + bgp = SUBGRP_INST(subgrp); + onlypeer = ((SUBGRP_PCOUNT(subgrp) == 1) ? (SUBGRP_PFIRST(subgrp))->peer + : NULL); + + if (BGP_DEBUG(update, UPDATE_OUT)) + zlog_debug("%s: p=%pFX, selected=%p", __func__, p, selected); + + /* First update is deferred until ORF or ROUTE-REFRESH is received */ + if (onlypeer && CHECK_FLAG(onlypeer->af_sflags[afi][safi], + PEER_STATUS_ORF_WAIT_REFRESH)) + return; + + memset(&attr, 0, sizeof(attr)); + /* It's initialized in bgp_announce_check() */ + + /* Announcement to the subgroup. If the route is filtered withdraw it. + * If BGP_NODE_FIB_INSTALL_PENDING is set and data plane install status + * is pending (BGP_NODE_FIB_INSTALL_PENDING), do not advertise the + * route + */ + advertise = bgp_check_advertise(bgp, dest, safi); + + if (selected) { + if (subgroup_announce_check(dest, selected, subgrp, p, &attr, + NULL)) { + /* Route is selected, if the route is already installed + * in FIB, then it is advertised + */ + if (advertise) { + if (!bgp_check_withdrawal(bgp, dest, safi)) { + struct attr *adv_attr = + bgp_attr_intern(&attr); + + bgp_adj_out_set_subgroup(dest, subgrp, + adv_attr, + selected); + } else + bgp_adj_out_unset_subgroup( + dest, subgrp, 1, addpath_tx_id); + } + } else + bgp_adj_out_unset_subgroup(dest, subgrp, 1, + addpath_tx_id); + } + + /* If selected is NULL we must withdraw the path using addpath_tx_id */ + else { + bgp_adj_out_unset_subgroup(dest, subgrp, 1, addpath_tx_id); + } +} + +/* + * Clear IGP changed flag and attribute changed flag for a route (all paths). + * This is called at the end of route processing. + */ +void bgp_zebra_clear_route_change_flags(struct bgp_dest *dest) +{ + struct bgp_path_info *pi; + + for (pi = bgp_dest_get_bgp_path_info(dest); pi; pi = pi->next) { + if (BGP_PATH_HOLDDOWN(pi)) + continue; + UNSET_FLAG(pi->flags, BGP_PATH_IGP_CHANGED); + UNSET_FLAG(pi->flags, BGP_PATH_ATTR_CHANGED); + } +} + +/* + * Has the route changed from the RIB's perspective? This is invoked only + * if the route selection returns the same best route as earlier - to + * determine if we need to update zebra or not. + */ +bool bgp_zebra_has_route_changed(struct bgp_path_info *selected) +{ + struct bgp_path_info *mpinfo; + + /* If this is multipath, check all selected paths for any nexthop + * change or attribute change. Some attribute changes (e.g., community) + * aren't of relevance to the RIB, but we'll update zebra to ensure + * we handle the case of BGP nexthop change. This is the behavior + * when the best path has an attribute change anyway. + */ + if (CHECK_FLAG(selected->flags, BGP_PATH_IGP_CHANGED) + || CHECK_FLAG(selected->flags, BGP_PATH_MULTIPATH_CHG) + || CHECK_FLAG(selected->flags, BGP_PATH_LINK_BW_CHG)) + return true; + + /* + * If this is multipath, check all selected paths for any nexthop change + */ + for (mpinfo = bgp_path_info_mpath_first(selected); mpinfo; + mpinfo = bgp_path_info_mpath_next(mpinfo)) { + if (CHECK_FLAG(mpinfo->flags, BGP_PATH_IGP_CHANGED) + || CHECK_FLAG(mpinfo->flags, BGP_PATH_ATTR_CHANGED)) + return true; + } + + /* Nothing has changed from the RIB's perspective. */ + return false; +} + +struct bgp_process_queue { + struct bgp *bgp; + STAILQ_HEAD(, bgp_dest) pqueue; +#define BGP_PROCESS_QUEUE_EOIU_MARKER (1 << 0) + unsigned int flags; + unsigned int queued; +}; + +static void bgp_process_evpn_route_injection(struct bgp *bgp, afi_t afi, + safi_t safi, struct bgp_dest *dest, + struct bgp_path_info *new_select, + struct bgp_path_info *old_select) +{ + const struct prefix *p = bgp_dest_get_prefix(dest); + + if ((afi != AFI_IP && afi != AFI_IP6) || (safi != SAFI_UNICAST)) + return; + + if (advertise_type5_routes(bgp, afi) && new_select + && is_route_injectable_into_evpn(new_select)) { + + /* apply the route-map */ + if (bgp->adv_cmd_rmap[afi][safi].map) { + route_map_result_t ret; + struct bgp_path_info rmap_path; + struct bgp_path_info_extra rmap_path_extra; + struct attr dummy_attr; + + dummy_attr = *new_select->attr; + + /* Fill temp path_info */ + prep_for_rmap_apply(&rmap_path, &rmap_path_extra, dest, + new_select, new_select->peer, + &dummy_attr); + + RESET_FLAG(dummy_attr.rmap_change_flags); + + ret = route_map_apply(bgp->adv_cmd_rmap[afi][safi].map, + p, &rmap_path); + + if (ret == RMAP_DENYMATCH) { + bgp_attr_flush(&dummy_attr); + bgp_evpn_withdraw_type5_route(bgp, p, afi, + safi); + } else + bgp_evpn_advertise_type5_route( + bgp, p, &dummy_attr, afi, safi); + } else { + bgp_evpn_advertise_type5_route(bgp, p, new_select->attr, + afi, safi); + } + } else if (advertise_type5_routes(bgp, afi) && old_select + && is_route_injectable_into_evpn(old_select)) + bgp_evpn_withdraw_type5_route(bgp, p, afi, safi); +} + +/* + * Utility to determine whether a particular path_info should use + * the IMPLICIT_NULL label. This is pretty specialized: it's only called + * in a path where we basically _know_ this is a BGP-LU route. + */ +static bool bgp_lu_need_null_label(struct bgp *bgp, + const struct bgp_path_info *new_select, + afi_t afi, mpls_label_t *label) +{ + /* Certain types get imp null; so do paths where the nexthop is + * not labeled. + */ + if (new_select->sub_type == BGP_ROUTE_STATIC + || new_select->sub_type == BGP_ROUTE_AGGREGATE + || new_select->sub_type == BGP_ROUTE_REDISTRIBUTE) + goto need_null_label; + else if (new_select->extra && + bgp_is_valid_label(&new_select->extra->label[0])) + return false; +need_null_label: + if (label == NULL) + return true; + /* Disable PHP : explicit-null */ + if (!!CHECK_FLAG(bgp->flags, BGP_FLAG_LU_IPV4_EXPLICIT_NULL) && + afi == AFI_IP) + *label = MPLS_LABEL_IPV4_EXPLICIT_NULL; + else if (!!CHECK_FLAG(bgp->flags, BGP_FLAG_LU_IPV6_EXPLICIT_NULL) && + afi == AFI_IP6) + *label = MPLS_LABEL_IPV6_EXPLICIT_NULL; + else + /* Enforced PHP popping: implicit-null */ + *label = MPLS_LABEL_IMPLICIT_NULL; + + return true; +} + +/* Right now, since we only deal with per-prefix labels, it is not + * necessary to do this upon changes to best path. Exceptions: + * - label index has changed -> recalculate resulting label + * - path_info sub_type changed -> switch to/from null label value + * - no valid label (due to removed static label binding) -> get new one + */ +static void bgp_lu_handle_label_allocation(struct bgp *bgp, + struct bgp_dest *dest, + struct bgp_path_info *new_select, + struct bgp_path_info *old_select, + afi_t afi) +{ + mpls_label_t mpls_label_null; + + if (bgp->allocate_mpls_labels[afi][SAFI_UNICAST]) { + if (new_select) { + if (!old_select || + bgp_label_index_differs(new_select, old_select) || + new_select->sub_type != old_select->sub_type || + !bgp_is_valid_label(&dest->local_label)) { + /* control label imposition for local + * routes, aggregate and redistributed + * routes + */ + mpls_label_null = MPLS_LABEL_IMPLICIT_NULL; + if (bgp_lu_need_null_label(bgp, new_select, afi, + &mpls_label_null)) { + if (CHECK_FLAG( + dest->flags, + BGP_NODE_REGISTERED_FOR_LABEL) || + CHECK_FLAG( + dest->flags, + BGP_NODE_LABEL_REQUESTED)) + bgp_unregister_for_label(dest); + dest->local_label = mpls_lse_encode( + mpls_label_null, 0, 0, 1); + bgp_set_valid_label(&dest->local_label); + } else + bgp_register_for_label(dest, + new_select); + } + } else if (CHECK_FLAG(dest->flags, + BGP_NODE_REGISTERED_FOR_LABEL) || + CHECK_FLAG(dest->flags, BGP_NODE_LABEL_REQUESTED)) { + bgp_unregister_for_label(dest); + } + } else if (CHECK_FLAG(dest->flags, BGP_NODE_REGISTERED_FOR_LABEL) || + CHECK_FLAG(dest->flags, BGP_NODE_LABEL_REQUESTED)) { + bgp_unregister_for_label(dest); + } +} + +static struct interface * +bgp_label_get_resolved_nh_iface(const struct bgp_path_info *pi) +{ + struct nexthop *nh; + + if (pi->nexthop == NULL || pi->nexthop->nexthop == NULL || + !CHECK_FLAG(pi->nexthop->flags, BGP_NEXTHOP_VALID)) + /* next-hop is not valid */ + return NULL; + + nh = pi->nexthop->nexthop; + if (nh->ifindex == IFINDEX_INTERNAL && + nh->type != NEXTHOP_TYPE_IPV4_IFINDEX && + nh->type != NEXTHOP_TYPE_IPV6_IFINDEX) + /* next-hop does not contain valid interface */ + return NULL; + + return if_lookup_by_index(nh->ifindex, nh->vrf_id); +} + +static void +bgp_mplsvpn_handle_label_allocation(struct bgp *bgp, struct bgp_dest *dest, + struct bgp_path_info *new_select, + struct bgp_path_info *old_select, afi_t afi) +{ + struct interface *ifp; + struct bgp_interface *bgp_ifp; + + if (bgp->allocate_mpls_labels[afi][SAFI_MPLS_VPN] && new_select) { + ifp = bgp_label_get_resolved_nh_iface(new_select); + if (ifp) + bgp_ifp = (struct bgp_interface *)(ifp->info); + else + bgp_ifp = NULL; + if (bgp_ifp && + CHECK_FLAG(bgp_ifp->flags, + BGP_INTERFACE_MPLS_L3VPN_SWITCHING) && + bgp_mplsvpn_path_uses_valid_mpls_label(new_select) && + new_select->sub_type != BGP_ROUTE_IMPORTED && + new_select->sub_type != BGP_ROUTE_STATIC) + bgp_mplsvpn_nh_label_bind_register_local_label( + bgp, dest, new_select); + else + bgp_mplsvpn_path_nh_label_bind_unlink(new_select); + } else { + if (new_select) + /* no mpls vpn allocation */ + bgp_mplsvpn_path_nh_label_bind_unlink(new_select); + else if (old_select) + /* unlink old selection if any */ + bgp_mplsvpn_path_nh_label_bind_unlink(old_select); + } +} + +/* + * old_select = The old best path + * new_select = the new best path + * + * if (!old_select && new_select) + * We are sending new information on. + * + * if (old_select && new_select) { + * if (new_select != old_select) + * We have a new best path send a change + * else + * We've received a update with new attributes that needs + * to be passed on. + * } + * + * if (old_select && !new_select) + * We have no eligible route that we can announce or the rn + * is being removed. + */ +static void bgp_process_main_one(struct bgp *bgp, struct bgp_dest *dest, + afi_t afi, safi_t safi) +{ + struct bgp_path_info *new_select; + struct bgp_path_info *old_select; + struct bgp_path_info_pair old_and_new; + int debug = 0; + + if (CHECK_FLAG(bgp->flags, BGP_FLAG_DELETE_IN_PROGRESS)) { + if (dest) + debug = bgp_debug_bestpath(dest); + if (debug) + zlog_debug( + "%s: bgp delete in progress, ignoring event, p=%pBD(%s)", + __func__, dest, bgp->name_pretty); + return; + } + /* Is it end of initial update? (after startup) */ + if (!dest) { + frr_timestamp(3, bgp->update_delay_zebra_resume_time, + sizeof(bgp->update_delay_zebra_resume_time)); + + bgp->main_zebra_update_hold = 0; + FOREACH_AFI_SAFI (afi, safi) { + if (bgp_fibupd_safi(safi)) + bgp_zebra_announce_table(bgp, afi, safi); + } + bgp->main_peers_update_hold = 0; + + bgp_start_routeadv(bgp); + return; + } + + const struct prefix *p = bgp_dest_get_prefix(dest); + + debug = bgp_debug_bestpath(dest); + if (debug) + zlog_debug("%s: p=%pBD(%s) afi=%s, safi=%s start", __func__, + dest, bgp->name_pretty, afi2str(afi), + safi2str(safi)); + + /* The best path calculation for the route is deferred if + * BGP_NODE_SELECT_DEFER is set + */ + if (CHECK_FLAG(dest->flags, BGP_NODE_SELECT_DEFER)) { + if (BGP_DEBUG(update, UPDATE_OUT)) + zlog_debug("SELECT_DEFER flag set for route %p(%s)", + dest, bgp->name_pretty); + return; + } + + /* Best path selection. */ + bgp_best_selection(bgp, dest, &bgp->maxpaths[afi][safi], &old_and_new, + afi, safi); + old_select = old_and_new.old; + new_select = old_and_new.new; + + if (safi == SAFI_UNICAST || safi == SAFI_LABELED_UNICAST) + /* label unicast path : + * Do we need to allocate or free labels? + */ + bgp_lu_handle_label_allocation(bgp, dest, new_select, + old_select, afi); + else if (safi == SAFI_MPLS_VPN) + /* mpls vpn path: + * Do we need to allocate or free labels? + */ + bgp_mplsvpn_handle_label_allocation(bgp, dest, new_select, + old_select, afi); + + if (debug) + zlog_debug( + "%s: p=%pBD(%s) afi=%s, safi=%s, old_select=%p, new_select=%p", + __func__, dest, bgp->name_pretty, afi2str(afi), + safi2str(safi), old_select, new_select); + + /* If best route remains the same and this is not due to user-initiated + * clear, see exactly what needs to be done. + */ + if (old_select && old_select == new_select && + !CHECK_FLAG(dest->flags, BGP_NODE_USER_CLEAR) && + !CHECK_FLAG(dest->flags, BGP_NODE_PROCESS_CLEAR) && + !CHECK_FLAG(old_select->flags, BGP_PATH_ATTR_CHANGED) && + !bgp_addpath_is_addpath_used(&bgp->tx_addpath, afi, safi)) { + if (bgp_zebra_has_route_changed(old_select)) { +#ifdef ENABLE_BGP_VNC + vnc_import_bgp_add_route(bgp, p, old_select); + vnc_import_bgp_exterior_add_route(bgp, p, old_select); +#endif + if (bgp_fibupd_safi(safi) + && !bgp_option_check(BGP_OPT_NO_FIB)) { + + if (new_select->type == ZEBRA_ROUTE_BGP + && (new_select->sub_type == BGP_ROUTE_NORMAL + || new_select->sub_type + == BGP_ROUTE_IMPORTED)) + + bgp_zebra_announce(dest, p, old_select, + bgp, afi, safi); + } + } + + /* If there is a change of interest to peers, reannounce the + * route. */ + if (CHECK_FLAG(old_select->flags, BGP_PATH_ATTR_CHANGED) + || CHECK_FLAG(old_select->flags, BGP_PATH_LINK_BW_CHG) + || CHECK_FLAG(dest->flags, BGP_NODE_LABEL_CHANGED)) { + group_announce_route(bgp, afi, safi, dest, new_select); + + /* unicast routes must also be annouced to + * labeled-unicast update-groups */ + if (safi == SAFI_UNICAST) + group_announce_route(bgp, afi, + SAFI_LABELED_UNICAST, dest, + new_select); + + UNSET_FLAG(old_select->flags, BGP_PATH_ATTR_CHANGED); + UNSET_FLAG(dest->flags, BGP_NODE_LABEL_CHANGED); + } + + /* advertise/withdraw type-5 routes */ + if (CHECK_FLAG(old_select->flags, BGP_PATH_LINK_BW_CHG) + || CHECK_FLAG(old_select->flags, BGP_PATH_MULTIPATH_CHG)) + bgp_process_evpn_route_injection( + bgp, afi, safi, dest, old_select, old_select); + + UNSET_FLAG(old_select->flags, BGP_PATH_MULTIPATH_CHG); + UNSET_FLAG(old_select->flags, BGP_PATH_LINK_BW_CHG); + bgp_zebra_clear_route_change_flags(dest); + UNSET_FLAG(dest->flags, BGP_NODE_PROCESS_SCHEDULED); + return; + } + + /* If the user did "clear ip bgp prefix x.x.x.x" this flag will be set + */ + UNSET_FLAG(dest->flags, BGP_NODE_USER_CLEAR); + + /* If the process wants to force deletion this flag will be set + */ + UNSET_FLAG(dest->flags, BGP_NODE_PROCESS_CLEAR); + + /* bestpath has changed; bump version */ + if (old_select || new_select) { + bgp_bump_version(dest); + + if (!bgp->t_rmap_def_originate_eval) + event_add_timer( + bm->master, + update_group_refresh_default_originate_route_map, + bgp, bgp->rmap_def_originate_eval_timer, + &bgp->t_rmap_def_originate_eval); + } + + if (old_select) + bgp_path_info_unset_flag(dest, old_select, BGP_PATH_SELECTED); + if (new_select) { + if (debug) + zlog_debug("%s: setting SELECTED flag", __func__); + bgp_path_info_set_flag(dest, new_select, BGP_PATH_SELECTED); + bgp_path_info_unset_flag(dest, new_select, + BGP_PATH_ATTR_CHANGED); + UNSET_FLAG(new_select->flags, BGP_PATH_MULTIPATH_CHG); + UNSET_FLAG(new_select->flags, BGP_PATH_LINK_BW_CHG); + } + +#ifdef ENABLE_BGP_VNC + if ((afi == AFI_IP || afi == AFI_IP6) && (safi == SAFI_UNICAST)) { + if (old_select != new_select) { + if (old_select) { + vnc_import_bgp_exterior_del_route(bgp, p, + old_select); + vnc_import_bgp_del_route(bgp, p, old_select); + } + if (new_select) { + vnc_import_bgp_exterior_add_route(bgp, p, + new_select); + vnc_import_bgp_add_route(bgp, p, new_select); + } + } + } +#endif + + group_announce_route(bgp, afi, safi, dest, new_select); + + /* unicast routes must also be annouced to labeled-unicast update-groups + */ + if (safi == SAFI_UNICAST) + group_announce_route(bgp, afi, SAFI_LABELED_UNICAST, dest, + new_select); + + /* FIB update. */ + if (bgp_fibupd_safi(safi) && (bgp->inst_type != BGP_INSTANCE_TYPE_VIEW) + && !bgp_option_check(BGP_OPT_NO_FIB)) { + + if (new_select && new_select->type == ZEBRA_ROUTE_BGP + && (new_select->sub_type == BGP_ROUTE_NORMAL + || new_select->sub_type == BGP_ROUTE_AGGREGATE + || new_select->sub_type == BGP_ROUTE_IMPORTED)) { + + /* if this is an evpn imported type-5 prefix, + * we need to withdraw the route first to clear + * the nh neigh and the RMAC entry. + */ + if (old_select && + is_route_parent_evpn(old_select)) + bgp_zebra_withdraw(p, old_select, bgp, safi); + + bgp_zebra_announce(dest, p, new_select, bgp, afi, safi); + } else { + /* Withdraw the route from the kernel. */ + if (old_select && old_select->type == ZEBRA_ROUTE_BGP + && (old_select->sub_type == BGP_ROUTE_NORMAL + || old_select->sub_type == BGP_ROUTE_AGGREGATE + || old_select->sub_type == BGP_ROUTE_IMPORTED)) + + bgp_zebra_withdraw(p, old_select, bgp, safi); + } + } + + bgp_process_evpn_route_injection(bgp, afi, safi, dest, new_select, + old_select); + + /* Clear any route change flags. */ + bgp_zebra_clear_route_change_flags(dest); + + UNSET_FLAG(dest->flags, BGP_NODE_PROCESS_SCHEDULED); + + /* Reap old select bgp_path_info, if it has been removed */ + if (old_select && CHECK_FLAG(old_select->flags, BGP_PATH_REMOVED)) + bgp_path_info_reap(dest, old_select); + + return; +} + +/* Process the routes with the flag BGP_NODE_SELECT_DEFER set */ +void bgp_best_path_select_defer(struct bgp *bgp, afi_t afi, safi_t safi) +{ + struct bgp_dest *dest; + int cnt = 0; + struct afi_safi_info *thread_info; + + if (bgp->gr_info[afi][safi].t_route_select) { + struct event *t = bgp->gr_info[afi][safi].t_route_select; + + thread_info = EVENT_ARG(t); + XFREE(MTYPE_TMP, thread_info); + EVENT_OFF(bgp->gr_info[afi][safi].t_route_select); + } + + if (BGP_DEBUG(update, UPDATE_OUT)) { + zlog_debug("%s: processing route for %s : cnt %d", __func__, + get_afi_safi_str(afi, safi, false), + bgp->gr_info[afi][safi].gr_deferred); + } + + /* Process the route list */ + for (dest = bgp_table_top(bgp->rib[afi][safi]); + dest && bgp->gr_info[afi][safi].gr_deferred != 0 && + cnt < BGP_MAX_BEST_ROUTE_SELECT; + dest = bgp_route_next(dest)) { + if (!CHECK_FLAG(dest->flags, BGP_NODE_SELECT_DEFER)) + continue; + + UNSET_FLAG(dest->flags, BGP_NODE_SELECT_DEFER); + bgp->gr_info[afi][safi].gr_deferred--; + bgp_process_main_one(bgp, dest, afi, safi); + cnt++; + } + /* If iteration stopped before the entire table was traversed then the + * node needs to be unlocked. + */ + if (dest) { + bgp_dest_unlock_node(dest); + dest = NULL; + } + + /* Send EOR message when all routes are processed */ + if (!bgp->gr_info[afi][safi].gr_deferred) { + bgp_send_delayed_eor(bgp); + /* Send route processing complete message to RIB */ + bgp_zebra_update(bgp, afi, safi, + ZEBRA_CLIENT_ROUTE_UPDATE_COMPLETE); + return; + } + + thread_info = XMALLOC(MTYPE_TMP, sizeof(struct afi_safi_info)); + + thread_info->afi = afi; + thread_info->safi = safi; + thread_info->bgp = bgp; + + /* If there are more routes to be processed, start the + * selection timer + */ + event_add_timer(bm->master, bgp_route_select_timer_expire, thread_info, + BGP_ROUTE_SELECT_DELAY, + &bgp->gr_info[afi][safi].t_route_select); +} + +static wq_item_status bgp_process_wq(struct work_queue *wq, void *data) +{ + struct bgp_process_queue *pqnode = data; + struct bgp *bgp = pqnode->bgp; + struct bgp_table *table; + struct bgp_dest *dest; + + /* eoiu marker */ + if (CHECK_FLAG(pqnode->flags, BGP_PROCESS_QUEUE_EOIU_MARKER)) { + bgp_process_main_one(bgp, NULL, 0, 0); + /* should always have dedicated wq call */ + assert(STAILQ_FIRST(&pqnode->pqueue) == NULL); + return WQ_SUCCESS; + } + + while (!STAILQ_EMPTY(&pqnode->pqueue)) { + dest = STAILQ_FIRST(&pqnode->pqueue); + STAILQ_REMOVE_HEAD(&pqnode->pqueue, pq); + STAILQ_NEXT(dest, pq) = NULL; /* complete unlink */ + table = bgp_dest_table(dest); + /* note, new DESTs may be added as part of processing */ + bgp_process_main_one(bgp, dest, table->afi, table->safi); + + bgp_dest_unlock_node(dest); + bgp_table_unlock(table); + } + + return WQ_SUCCESS; +} + +static void bgp_processq_del(struct work_queue *wq, void *data) +{ + struct bgp_process_queue *pqnode = data; + + bgp_unlock(pqnode->bgp); + + XFREE(MTYPE_BGP_PROCESS_QUEUE, pqnode); +} + +void bgp_process_queue_init(struct bgp *bgp) +{ + if (!bgp->process_queue) { + char name[BUFSIZ]; + + snprintf(name, BUFSIZ, "process_queue %s", bgp->name_pretty); + bgp->process_queue = work_queue_new(bm->master, name); + } + + bgp->process_queue->spec.workfunc = &bgp_process_wq; + bgp->process_queue->spec.del_item_data = &bgp_processq_del; + bgp->process_queue->spec.max_retries = 0; + bgp->process_queue->spec.hold = 50; + /* Use a higher yield value of 50ms for main queue processing */ + bgp->process_queue->spec.yield = 50 * 1000L; +} + +static struct bgp_process_queue *bgp_processq_alloc(struct bgp *bgp) +{ + struct bgp_process_queue *pqnode; + + pqnode = XCALLOC(MTYPE_BGP_PROCESS_QUEUE, + sizeof(struct bgp_process_queue)); + + /* unlocked in bgp_processq_del */ + pqnode->bgp = bgp_lock(bgp); + STAILQ_INIT(&pqnode->pqueue); + + return pqnode; +} + +void bgp_process(struct bgp *bgp, struct bgp_dest *dest, afi_t afi, safi_t safi) +{ +#define ARBITRARY_PROCESS_QLEN 10000 + struct work_queue *wq = bgp->process_queue; + struct bgp_process_queue *pqnode; + int pqnode_reuse = 0; + + /* already scheduled for processing? */ + if (CHECK_FLAG(dest->flags, BGP_NODE_PROCESS_SCHEDULED)) + return; + + /* If the flag BGP_NODE_SELECT_DEFER is set, do not add route to + * the workqueue + */ + if (CHECK_FLAG(dest->flags, BGP_NODE_SELECT_DEFER)) { + if (BGP_DEBUG(update, UPDATE_OUT)) + zlog_debug("BGP_NODE_SELECT_DEFER set for route %p", + dest); + return; + } + + if (CHECK_FLAG(dest->flags, BGP_NODE_SOFT_RECONFIG)) { + if (BGP_DEBUG(update, UPDATE_OUT)) + zlog_debug( + "Soft reconfigure table in progress for route %p", + dest); + return; + } + + if (wq == NULL) + return; + + /* Add route nodes to an existing work queue item until reaching the + limit only if is from the same BGP view and it's not an EOIU marker + */ + if (work_queue_item_count(wq)) { + struct work_queue_item *item = work_queue_last_item(wq); + pqnode = item->data; + + if (CHECK_FLAG(pqnode->flags, BGP_PROCESS_QUEUE_EOIU_MARKER) + || pqnode->bgp != bgp + || pqnode->queued >= ARBITRARY_PROCESS_QLEN) + pqnode = bgp_processq_alloc(bgp); + else + pqnode_reuse = 1; + } else + pqnode = bgp_processq_alloc(bgp); + /* all unlocked in bgp_process_wq */ + bgp_table_lock(bgp_dest_table(dest)); + + SET_FLAG(dest->flags, BGP_NODE_PROCESS_SCHEDULED); + bgp_dest_lock_node(dest); + + /* can't be enqueued twice */ + assert(STAILQ_NEXT(dest, pq) == NULL); + STAILQ_INSERT_TAIL(&pqnode->pqueue, dest, pq); + pqnode->queued++; + + if (!pqnode_reuse) + work_queue_add(wq, pqnode); + + return; +} + +void bgp_add_eoiu_mark(struct bgp *bgp) +{ + struct bgp_process_queue *pqnode; + + if (bgp->process_queue == NULL) + return; + + pqnode = bgp_processq_alloc(bgp); + + SET_FLAG(pqnode->flags, BGP_PROCESS_QUEUE_EOIU_MARKER); + work_queue_add(bgp->process_queue, pqnode); +} + +static void bgp_maximum_prefix_restart_timer(struct event *thread) +{ + struct peer_connection *connection = EVENT_ARG(thread); + struct peer *peer = connection->peer; + + if (bgp_debug_neighbor_events(peer)) + zlog_debug( + "%s Maximum-prefix restart timer expired, restore peering", + peer->host); + + if ((peer_clear(peer, NULL) < 0) && bgp_debug_neighbor_events(peer)) + zlog_debug("%s: %s peer_clear failed", __func__, peer->host); +} + +static uint32_t bgp_filtered_routes_count(struct peer *peer, afi_t afi, + safi_t safi) +{ + uint32_t count = 0; + bool filtered = false; + struct bgp_dest *dest; + struct bgp_adj_in *ain; + struct attr attr = {}; + struct bgp_table *table = peer->bgp->rib[afi][safi]; + + for (dest = bgp_table_top(table); dest; dest = bgp_route_next(dest)) { + for (ain = dest->adj_in; ain; ain = ain->next) { + const struct prefix *rn_p = bgp_dest_get_prefix(dest); + + attr = *ain->attr; + + if (bgp_input_filter(peer, rn_p, &attr, afi, safi) + == FILTER_DENY) + filtered = true; + + if (bgp_input_modifier( + peer, rn_p, &attr, afi, safi, + ROUTE_MAP_IN_NAME(&peer->filter[afi][safi]), + NULL, 0, NULL) + == RMAP_DENY) + filtered = true; + + if (filtered) + count++; + + bgp_attr_flush(&attr); + } + } + + return count; +} + +bool bgp_maximum_prefix_overflow(struct peer *peer, afi_t afi, safi_t safi, + int always) +{ + iana_afi_t pkt_afi; + iana_safi_t pkt_safi; + uint32_t pcount = (CHECK_FLAG(peer->af_flags[afi][safi], + PEER_FLAG_MAX_PREFIX_FORCE)) + ? bgp_filtered_routes_count(peer, afi, safi) + + peer->pcount[afi][safi] + : peer->pcount[afi][safi]; + struct peer_connection *connection = peer->connection; + + if (!CHECK_FLAG(peer->af_flags[afi][safi], PEER_FLAG_MAX_PREFIX)) + return false; + + if (pcount > peer->pmax[afi][safi]) { + if (CHECK_FLAG(peer->af_sflags[afi][safi], + PEER_STATUS_PREFIX_LIMIT) + && !always) + return false; + + zlog_info( + "%%MAXPFXEXCEED: No. of %s prefix received from %pBP %u exceed, limit %u", + get_afi_safi_str(afi, safi, false), peer, pcount, + peer->pmax[afi][safi]); + SET_FLAG(peer->af_sflags[afi][safi], PEER_STATUS_PREFIX_LIMIT); + + if (CHECK_FLAG(peer->af_flags[afi][safi], + PEER_FLAG_MAX_PREFIX_WARNING)) + return false; + + /* Convert AFI, SAFI to values for packet. */ + pkt_afi = afi_int2iana(afi); + pkt_safi = safi_int2iana(safi); + { + uint8_t ndata[7]; + + ndata[0] = (pkt_afi >> 8); + ndata[1] = pkt_afi; + ndata[2] = pkt_safi; + ndata[3] = (peer->pmax[afi][safi] >> 24); + ndata[4] = (peer->pmax[afi][safi] >> 16); + ndata[5] = (peer->pmax[afi][safi] >> 8); + ndata[6] = (peer->pmax[afi][safi]); + + SET_FLAG(peer->sflags, PEER_STATUS_PREFIX_OVERFLOW); + bgp_notify_send_with_data(connection, BGP_NOTIFY_CEASE, + BGP_NOTIFY_CEASE_MAX_PREFIX, + ndata, 7); + } + + /* Dynamic peers will just close their connection. */ + if (peer_dynamic_neighbor(peer)) + return true; + + /* restart timer start */ + if (peer->pmax_restart[afi][safi]) { + peer->v_pmax_restart = + peer->pmax_restart[afi][safi] * 60; + + if (bgp_debug_neighbor_events(peer)) + zlog_debug( + "%pBP Maximum-prefix restart timer started for %d secs", + peer, peer->v_pmax_restart); + + BGP_TIMER_ON(connection->t_pmax_restart, + bgp_maximum_prefix_restart_timer, + peer->v_pmax_restart); + } + + return true; + } else + UNSET_FLAG(peer->af_sflags[afi][safi], + PEER_STATUS_PREFIX_LIMIT); + + if (pcount + > (peer->pmax[afi][safi] * peer->pmax_threshold[afi][safi] / 100)) { + if (CHECK_FLAG(peer->af_sflags[afi][safi], + PEER_STATUS_PREFIX_THRESHOLD) + && !always) + return false; + + zlog_info( + "%%MAXPFX: No. of %s prefix received from %pBP reaches %u, max %u", + get_afi_safi_str(afi, safi, false), peer, pcount, + peer->pmax[afi][safi]); + SET_FLAG(peer->af_sflags[afi][safi], + PEER_STATUS_PREFIX_THRESHOLD); + } else + UNSET_FLAG(peer->af_sflags[afi][safi], + PEER_STATUS_PREFIX_THRESHOLD); + return false; +} + +/* Unconditionally remove the route from the RIB, without taking + * damping into consideration (eg, because the session went down) + */ +void bgp_rib_remove(struct bgp_dest *dest, struct bgp_path_info *pi, + struct peer *peer, afi_t afi, safi_t safi) +{ + + struct bgp *bgp = NULL; + bool delete_route = false; + + bgp_aggregate_decrement(peer->bgp, bgp_dest_get_prefix(dest), pi, afi, + safi); + + if (!CHECK_FLAG(pi->flags, BGP_PATH_HISTORY)) { + bgp_path_info_delete(dest, pi); /* keep historical info */ + + /* If the selected path is removed, reset BGP_NODE_SELECT_DEFER + * flag + */ + if (CHECK_FLAG(pi->flags, BGP_PATH_SELECTED)) + delete_route = true; + else if (bgp_dest_set_defer_flag(dest, true) < 0) + delete_route = true; + if (delete_route) { + if (CHECK_FLAG(dest->flags, BGP_NODE_SELECT_DEFER)) { + UNSET_FLAG(dest->flags, BGP_NODE_SELECT_DEFER); + bgp = pi->peer->bgp; + bgp->gr_info[afi][safi].gr_deferred--; + } + } + } + + hook_call(bgp_process, peer->bgp, afi, safi, dest, peer, true); + bgp_process(peer->bgp, dest, afi, safi); +} + +static void bgp_rib_withdraw(struct bgp_dest *dest, struct bgp_path_info *pi, + struct peer *peer, afi_t afi, safi_t safi, + struct prefix_rd *prd) +{ + const struct prefix *p = bgp_dest_get_prefix(dest); + + /* apply dampening, if result is suppressed, we'll be retaining + * the bgp_path_info in the RIB for historical reference. + */ + if (CHECK_FLAG(peer->bgp->af_flags[afi][safi], BGP_CONFIG_DAMPENING) + && peer->sort == BGP_PEER_EBGP) + if ((bgp_damp_withdraw(pi, dest, afi, safi, 0)) + == BGP_DAMP_SUPPRESSED) { + bgp_aggregate_decrement(peer->bgp, p, pi, afi, + safi); + return; + } + +#ifdef ENABLE_BGP_VNC + if (safi == SAFI_MPLS_VPN) { + struct bgp_dest *pdest = NULL; + struct bgp_table *table = NULL; + + pdest = bgp_node_get(peer->bgp->rib[afi][safi], + (struct prefix *)prd); + if (bgp_dest_has_bgp_path_info_data(pdest)) { + table = bgp_dest_get_bgp_table_info(pdest); + + vnc_import_bgp_del_vnc_host_route_mode_resolve_nve( + peer->bgp, prd, table, p, pi); + } + bgp_dest_unlock_node(pdest); + } + if ((afi == AFI_IP || afi == AFI_IP6) && (safi == SAFI_UNICAST)) { + if (CHECK_FLAG(pi->flags, BGP_PATH_SELECTED)) { + + vnc_import_bgp_del_route(peer->bgp, p, pi); + vnc_import_bgp_exterior_del_route(peer->bgp, p, pi); + } + } +#endif + + /* If this is an EVPN route, process for un-import. */ + if (safi == SAFI_EVPN) + bgp_evpn_unimport_route(peer->bgp, afi, safi, p, pi); + + bgp_rib_remove(dest, pi, peer, afi, safi); +} + +struct bgp_path_info *info_make(int type, int sub_type, unsigned short instance, + struct peer *peer, struct attr *attr, + struct bgp_dest *dest) +{ + struct bgp_path_info *new; + + /* Make new BGP info. */ + new = XCALLOC(MTYPE_BGP_ROUTE, sizeof(struct bgp_path_info)); + new->type = type; + new->instance = instance; + new->sub_type = sub_type; + new->peer = peer; + new->attr = attr; + new->uptime = monotime(NULL); + new->net = dest; + return new; +} + +/* Check if received nexthop is valid or not. */ +bool bgp_update_martian_nexthop(struct bgp *bgp, afi_t afi, safi_t safi, + uint8_t type, uint8_t stype, struct attr *attr, + struct bgp_dest *dest) +{ + bool ret = false; + bool is_bgp_static_route = + (type == ZEBRA_ROUTE_BGP && stype == BGP_ROUTE_STATIC) ? true + : false; + + /* If `bgp allow-martian-nexthop` is turned on, return next-hop + * as good. + */ + if (bgp->allow_martian) + return false; + + /* + * Only validated for unicast and multicast currently. + * Also valid for EVPN where the nexthop is an IP address. + * If we are a bgp static route being checked then there is + * no need to check to see if the nexthop is martian as + * that it should be ok. + */ + if (is_bgp_static_route || + (safi != SAFI_UNICAST && safi != SAFI_MULTICAST && safi != SAFI_EVPN)) + return false; + + /* If NEXT_HOP is present, validate it. */ + if (attr->flag & ATTR_FLAG_BIT(BGP_ATTR_NEXT_HOP)) { + if (attr->nexthop.s_addr == INADDR_ANY || + !ipv4_unicast_valid(&attr->nexthop) || + bgp_nexthop_self(bgp, afi, type, stype, attr, dest)) + return true; + } + + /* If MP_NEXTHOP is present, validate it. */ + /* Note: For IPv6 nexthops, we only validate the global (1st) nexthop; + * there is code in bgp_attr.c to ignore the link-local (2nd) nexthop if + * it is not an IPv6 link-local address. + * + * If we receive an UPDATE with nexthop length set to 32 bytes + * we shouldn't discard an UPDATE if it's set to (::). + * The link-local (2st) is validated along the code path later. + */ + if (attr->mp_nexthop_len) { + switch (attr->mp_nexthop_len) { + case BGP_ATTR_NHLEN_IPV4: + case BGP_ATTR_NHLEN_VPNV4: + ret = (attr->mp_nexthop_global_in.s_addr == + INADDR_ANY || + !ipv4_unicast_valid( + &attr->mp_nexthop_global_in) || + bgp_nexthop_self(bgp, afi, type, stype, attr, + dest)); + break; + + case BGP_ATTR_NHLEN_IPV6_GLOBAL: + case BGP_ATTR_NHLEN_VPNV6_GLOBAL: + ret = (IN6_IS_ADDR_UNSPECIFIED( + &attr->mp_nexthop_global) + || IN6_IS_ADDR_LOOPBACK(&attr->mp_nexthop_global) + || IN6_IS_ADDR_MULTICAST( + &attr->mp_nexthop_global) + || bgp_nexthop_self(bgp, afi, type, stype, attr, + dest)); + break; + case BGP_ATTR_NHLEN_IPV6_GLOBAL_AND_LL: + ret = (IN6_IS_ADDR_LOOPBACK(&attr->mp_nexthop_global) + || IN6_IS_ADDR_MULTICAST( + &attr->mp_nexthop_global) + || bgp_nexthop_self(bgp, afi, type, stype, attr, + dest)); + break; + + default: + ret = true; + break; + } + } + + return ret; +} + +static void bgp_attr_add_no_export_community(struct attr *attr) +{ + struct community *old; + struct community *new; + struct community *merge; + struct community *no_export; + + old = bgp_attr_get_community(attr); + no_export = community_str2com("no-export"); + + assert(no_export); + + if (old) { + merge = community_merge(community_dup(old), no_export); + + if (!old->refcnt) + community_free(&old); + + new = community_uniq_sort(merge); + community_free(&merge); + } else { + new = community_dup(no_export); + } + + community_free(&no_export); + + bgp_attr_set_community(attr, new); +} + +static bool bgp_accept_own(struct peer *peer, afi_t afi, safi_t safi, + struct attr *attr, const struct prefix *prefix, + int *sub_type) +{ + struct listnode *node, *nnode; + struct bgp *bgp; + bool accept_own_found = false; + + if (safi != SAFI_MPLS_VPN) + return false; + + /* Processing of the ACCEPT_OWN community is enabled by configuration */ + if (!CHECK_FLAG(peer->af_flags[afi][safi], PEER_FLAG_ACCEPT_OWN)) + return false; + + /* The route in question carries the ACCEPT_OWN community */ + if (attr->flag & ATTR_FLAG_BIT(BGP_ATTR_COMMUNITIES)) { + struct community *comm = bgp_attr_get_community(attr); + + if (community_include(comm, COMMUNITY_ACCEPT_OWN)) + accept_own_found = true; + } + + /* The route in question is targeted to one or more destination VRFs + * on the router (as determined by inspecting the Route Target(s)). + */ + for (ALL_LIST_ELEMENTS(bm->bgp, node, nnode, bgp)) { + if (bgp->inst_type != BGP_INSTANCE_TYPE_VRF) + continue; + + if (accept_own_found && + ecommunity_include( + bgp->vpn_policy[afi] + .rtlist[BGP_VPN_POLICY_DIR_TOVPN], + bgp_attr_get_ecommunity(attr))) { + if (bgp_debug_update(peer, prefix, NULL, 1)) + zlog_debug( + "%pBP prefix %pFX has ORIGINATOR_ID, but it's accepted due to ACCEPT_OWN", + peer, prefix); + + /* Treat this route as imported, because it's leaked + * already from another VRF, and we got an updated + * version from route-reflector with ACCEPT_OWN + * community. + */ + *sub_type = BGP_ROUTE_IMPORTED; + + return true; + } + } + + return false; +} + +void bgp_update(struct peer *peer, const struct prefix *p, uint32_t addpath_id, + struct attr *attr, afi_t afi, safi_t safi, int type, + int sub_type, struct prefix_rd *prd, mpls_label_t *label, + uint32_t num_labels, int soft_reconfig, + struct bgp_route_evpn *evpn) +{ + int ret; + int aspath_loop_count = 0; + struct bgp_dest *dest; + struct bgp *bgp; + struct attr new_attr; + struct attr *attr_new; + struct bgp_path_info *pi; + struct bgp_path_info *new = NULL; + struct bgp_path_info_extra *extra; + const char *reason; + char pfx_buf[BGP_PRD_PATH_STRLEN]; + int connected = 0; + int do_loop_check = 1; + int has_valid_label = 0; + afi_t nh_afi; + bool force_evpn_import = false; + safi_t orig_safi = safi; + int allowas_in = 0; + + if (frrtrace_enabled(frr_bgp, process_update)) { + char pfxprint[PREFIX2STR_BUFFER]; + + prefix2str(p, pfxprint, sizeof(pfxprint)); + frrtrace(6, frr_bgp, process_update, peer, pfxprint, addpath_id, + afi, safi, attr); + } + +#ifdef ENABLE_BGP_VNC + int vnc_implicit_withdraw = 0; +#endif + int same_attr = 0; + const struct prefix *bgp_nht_param_prefix; + + /* Special case for BGP-LU - map LU safi to ordinary unicast safi */ + if (orig_safi == SAFI_LABELED_UNICAST) + safi = SAFI_UNICAST; + + memset(&new_attr, 0, sizeof(new_attr)); + new_attr.label_index = BGP_INVALID_LABEL_INDEX; + new_attr.label = MPLS_INVALID_LABEL; + + bgp = peer->bgp; + dest = bgp_afi_node_get(bgp->rib[afi][safi], afi, safi, p, prd); + /* TODO: Check to see if we can get rid of "is_valid_label" */ + if (afi == AFI_L2VPN && safi == SAFI_EVPN) + has_valid_label = (num_labels > 0) ? 1 : 0; + else + has_valid_label = bgp_is_valid_label(label); + + if (has_valid_label) + assert(label != NULL); + + + /* When peer's soft reconfiguration enabled. Record input packet in + Adj-RIBs-In. */ + if (!soft_reconfig && + CHECK_FLAG(peer->af_flags[afi][safi], PEER_FLAG_SOFT_RECONFIG) && + peer != bgp->peer_self) { + /* + * If the trigger is not from soft_reconfig and if + * PEER_FLAG_SOFT_RECONFIG is enabled for the peer, then attr + * will not be interned. In which case, it is ok to update the + * attr->evpn_overlay, so that, this can be stored in adj_in. + */ + if ((afi == AFI_L2VPN) && evpn) { + memcpy(&attr->evpn_overlay, evpn, + sizeof(struct bgp_route_evpn)); + } + bgp_adj_in_set(dest, peer, attr, addpath_id); + } + + /* Update permitted loop count */ + if (CHECK_FLAG(peer->af_flags[afi][safi], PEER_FLAG_ALLOWAS_IN)) + allowas_in = peer->allowas_in[afi][safi]; + + /* Check previously received route. */ + for (pi = bgp_dest_get_bgp_path_info(dest); pi; pi = pi->next) + if (pi->peer == peer && pi->type == type + && pi->sub_type == sub_type + && pi->addpath_rx_id == addpath_id) + break; + + /* AS path local-as loop check. */ + if (peer->change_local_as) { + if (allowas_in) + aspath_loop_count = allowas_in; + else if (!CHECK_FLAG(peer->flags, + PEER_FLAG_LOCAL_AS_NO_PREPEND)) + aspath_loop_count = 1; + + if (aspath_loop_check(attr->aspath, peer->change_local_as) + > aspath_loop_count) { + peer->stat_pfx_aspath_loop++; + reason = "as-path contains our own AS;"; + goto filtered; + } + } + + /* If the peer is configured for "allowas-in origin" and the last ASN in + * the + * as-path is our ASN then we do not need to call aspath_loop_check + */ + if (CHECK_FLAG(peer->af_flags[afi][safi], PEER_FLAG_ALLOWAS_IN_ORIGIN)) + if (aspath_get_last_as(attr->aspath) == bgp->as) + do_loop_check = 0; + + if (CHECK_FLAG(peer->af_flags[afi][safi], PEER_FLAG_REFLECTOR_CLIENT)) + bgp_nht_param_prefix = NULL; + else + bgp_nht_param_prefix = p; + + /* AS path loop check. */ + if (do_loop_check) { + if (aspath_loop_check(attr->aspath, bgp->as) > + peer->allowas_in[afi][safi]) { + peer->stat_pfx_aspath_loop++; + reason = "as-path contains our own AS;"; + goto filtered; + } + } + + /* If we're a CONFED we need to loop check the CONFED ID too */ + if (CHECK_FLAG(bgp->config, BGP_CONFIG_CONFEDERATION) && do_loop_check) + if (aspath_loop_check_confed(attr->aspath, bgp->confed_id) > + peer->allowas_in[afi][safi]) { + peer->stat_pfx_aspath_loop++; + reason = "as-path contains our own confed AS;"; + goto filtered; + } + + /* Route reflector originator ID check. If ACCEPT_OWN mechanism is + * enabled, then take care of that too. + */ + bool accept_own = false; + + if (attr->flag & ATTR_FLAG_BIT(BGP_ATTR_ORIGINATOR_ID) + && IPV4_ADDR_SAME(&bgp->router_id, &attr->originator_id)) { + accept_own = + bgp_accept_own(peer, afi, safi, attr, p, &sub_type); + if (!accept_own) { + peer->stat_pfx_originator_loop++; + reason = "originator is us;"; + goto filtered; + } + } + + /* Route reflector cluster ID check. */ + if (bgp_cluster_filter(peer, attr)) { + peer->stat_pfx_cluster_loop++; + reason = "reflected from the same cluster;"; + goto filtered; + } + + /* Apply incoming filter. */ + if (bgp_input_filter(peer, p, attr, afi, orig_safi) == FILTER_DENY) { + peer->stat_pfx_filter++; + reason = "filter;"; + goto filtered; + } + + if ((afi == AFI_IP || afi == AFI_IP6) && safi == SAFI_MPLS_VPN && + bgp->inst_type == BGP_INSTANCE_TYPE_DEFAULT && + !CHECK_FLAG(bgp->af_flags[afi][safi], + BGP_VPNVX_RETAIN_ROUTE_TARGET_ALL) && + vpn_leak_to_vrf_no_retain_filter_check(bgp, attr, afi)) { + reason = + "no import. Filtered by no bgp retain route-target all"; + goto filtered; + } + + /* If the route has Node Target Extended Communities, check + * if it's allowed to be installed locally. + */ + if ((attr->flag & ATTR_FLAG_BIT(BGP_ATTR_EXT_COMMUNITIES))) { + struct ecommunity *ecomm = bgp_attr_get_ecommunity(attr); + + if (ecommunity_lookup(ecomm, ECOMMUNITY_ENCODE_IP, + ECOMMUNITY_NODE_TARGET) && + !ecommunity_node_target_match(ecomm, &peer->local_id)) { + reason = + "Node-Target Extended Communities do not contain own BGP Identifier;"; + goto filtered; + } + } + + /* RFC 8212 to prevent route leaks. + * This specification intends to improve this situation by requiring the + * explicit configuration of both BGP Import and Export Policies for any + * External BGP (EBGP) session such as customers, peers, or + * confederation boundaries for all enabled address families. Through + * codification of the aforementioned requirement, operators will + * benefit from consistent behavior across different BGP + * implementations. + */ + if (CHECK_FLAG(bgp->flags, BGP_FLAG_EBGP_REQUIRES_POLICY)) + if (!bgp_inbound_policy_exists(peer, + &peer->filter[afi][safi])) { + reason = "inbound policy missing"; + if (monotime_since(&bgp->ebgprequirespolicywarning, + NULL) > FIFTEENMINUTE2USEC || + bgp->ebgprequirespolicywarning.tv_sec == 0) { + zlog_warn( + "EBGP inbound/outbound policy not properly setup, please configure in order for your peering to work correctly"); + monotime(&bgp->ebgprequirespolicywarning); + } + goto filtered; + } + + /* draft-ietf-idr-deprecate-as-set-confed-set + * Filter routes having AS_SET or AS_CONFED_SET in the path. + * Eventually, This document (if approved) updates RFC 4271 + * and RFC 5065 by eliminating AS_SET and AS_CONFED_SET types, + * and obsoletes RFC 6472. + */ + if (peer->bgp->reject_as_sets) + if (aspath_check_as_sets(attr->aspath)) { + reason = + "as-path contains AS_SET or AS_CONFED_SET type;"; + goto filtered; + } + + new_attr = *attr; + /* + * If bgp_update is called with soft_reconfig set then + * attr is interned. In this case, do not overwrite the + * attr->evpn_overlay with evpn directly. Instead memcpy + * evpn to new_atr.evpn_overlay before it is interned. + */ + if (soft_reconfig && (afi == AFI_L2VPN) && evpn) + memcpy(&new_attr.evpn_overlay, evpn, + sizeof(struct bgp_route_evpn)); + + /* Apply incoming route-map. + * NB: new_attr may now contain newly allocated values from route-map + * "set" + * commands, so we need bgp_attr_flush in the error paths, until we + * intern + * the attr (which takes over the memory references) */ + if (bgp_input_modifier(peer, p, &new_attr, afi, orig_safi, NULL, label, + num_labels, dest) + == RMAP_DENY) { + peer->stat_pfx_filter++; + reason = "route-map;"; + bgp_attr_flush(&new_attr); + goto filtered; + } + + if (pi && pi->attr->rmap_table_id != new_attr.rmap_table_id) { + if (CHECK_FLAG(pi->flags, BGP_PATH_SELECTED)) + /* remove from RIB previous entry */ + bgp_zebra_withdraw(p, pi, bgp, safi); + } + + if (peer->sort == BGP_PEER_EBGP) { + + /* rfc7999: + * A BGP speaker receiving an announcement tagged with the + * BLACKHOLE community SHOULD add the NO_ADVERTISE or + * NO_EXPORT community as defined in RFC1997, or a + * similar community, to prevent propagation of the + * prefix outside the local AS. The community to prevent + * propagation SHOULD be chosen according to the operator's + * routing policy. + */ + if (bgp_attr_get_community(&new_attr) && + community_include(bgp_attr_get_community(&new_attr), + COMMUNITY_BLACKHOLE)) + bgp_attr_add_no_export_community(&new_attr); + + /* If we receive the graceful-shutdown community from an eBGP + * peer we must lower local-preference */ + if (bgp_attr_get_community(&new_attr) && + community_include(bgp_attr_get_community(&new_attr), + COMMUNITY_GSHUT)) { + new_attr.flag |= ATTR_FLAG_BIT(BGP_ATTR_LOCAL_PREF); + new_attr.local_pref = BGP_GSHUT_LOCAL_PREF; + + /* If graceful-shutdown is configured globally or + * per neighbor, then add the GSHUT community to + * all paths received from eBGP peers. */ + } else if (bgp_in_graceful_shutdown(peer->bgp) || + CHECK_FLAG(peer->flags, PEER_FLAG_GRACEFUL_SHUTDOWN)) + bgp_attr_add_gshut_community(&new_attr); + } + + /* next hop check. */ + if (!CHECK_FLAG(peer->flags, PEER_FLAG_IS_RFAPI_HD) && + bgp_update_martian_nexthop(bgp, afi, safi, type, sub_type, + &new_attr, dest)) { + peer->stat_pfx_nh_invalid++; + reason = "martian or self next-hop;"; + bgp_attr_flush(&new_attr); + goto filtered; + } + + if (bgp_mac_entry_exists(p) || bgp_mac_exist(&attr->rmac)) { + peer->stat_pfx_nh_invalid++; + reason = "self mac;"; + bgp_attr_flush(&new_attr); + goto filtered; + } + + if (bgp_check_role_applicability(afi, safi) && + bgp_otc_filter(peer, &new_attr)) { + reason = "failing otc validation"; + bgp_attr_flush(&new_attr); + goto filtered; + } + + /* If neighbor soo is configured, tag all incoming routes with + * this SoO tag and then filter out advertisements in + * subgroup_announce_check() if it matches the configured SoO + * on the other peer. + */ + if (peer->soo[afi][safi]) { + struct ecommunity *old_ecomm = + bgp_attr_get_ecommunity(&new_attr); + struct ecommunity *ecomm_soo = peer->soo[afi][safi]; + struct ecommunity *new_ecomm; + + if (old_ecomm) { + new_ecomm = ecommunity_merge(ecommunity_dup(old_ecomm), + ecomm_soo); + + if (!old_ecomm->refcnt) + ecommunity_free(&old_ecomm); + } else { + new_ecomm = ecommunity_dup(ecomm_soo); + } + + bgp_attr_set_ecommunity(&new_attr, new_ecomm); + } + + attr_new = bgp_attr_intern(&new_attr); + + /* If the update is implicit withdraw. */ + if (pi) { + pi->uptime = monotime(NULL); + same_attr = attrhash_cmp(pi->attr, attr_new); + + hook_call(bgp_process, bgp, afi, safi, dest, peer, true); + + /* Same attribute comes in. */ + if (!CHECK_FLAG(pi->flags, BGP_PATH_REMOVED) + && same_attr + && (!has_valid_label + || memcmp(&(bgp_path_info_extra_get(pi))->label, label, + num_labels * sizeof(mpls_label_t)) + == 0)) { + if (CHECK_FLAG(bgp->af_flags[afi][safi], + BGP_CONFIG_DAMPENING) + && peer->sort == BGP_PEER_EBGP + && CHECK_FLAG(pi->flags, BGP_PATH_HISTORY)) { + if (bgp_debug_update(peer, p, NULL, 1)) { + bgp_debug_rdpfxpath2str( + afi, safi, prd, p, label, + num_labels, addpath_id ? 1 : 0, + addpath_id, evpn, pfx_buf, + sizeof(pfx_buf)); + zlog_debug("%pBP rcvd %s", peer, + pfx_buf); + } + + if (bgp_damp_update(pi, dest, afi, safi) + != BGP_DAMP_SUPPRESSED) { + bgp_aggregate_increment(bgp, p, pi, afi, + safi); + bgp_process(bgp, dest, afi, safi); + } + } else /* Duplicate - odd */ + { + if (bgp_debug_update(peer, p, NULL, 1)) { + if (!peer->rcvd_attr_printed) { + zlog_debug( + "%pBP rcvd UPDATE w/ attr: %s", + peer, + peer->rcvd_attr_str); + peer->rcvd_attr_printed = 1; + } + + bgp_debug_rdpfxpath2str( + afi, safi, prd, p, label, + num_labels, addpath_id ? 1 : 0, + addpath_id, evpn, pfx_buf, + sizeof(pfx_buf)); + zlog_debug( + "%pBP rcvd %s...duplicate ignored", + peer, pfx_buf); + } + + /* graceful restart STALE flag unset. */ + if (CHECK_FLAG(pi->flags, BGP_PATH_STALE)) { + bgp_path_info_unset_flag( + dest, pi, BGP_PATH_STALE); + bgp_dest_set_defer_flag(dest, false); + bgp_process(bgp, dest, afi, safi); + } + } + + bgp_dest_unlock_node(dest); + bgp_attr_unintern(&attr_new); + + return; + } + + /* Withdraw/Announce before we fully processed the withdraw */ + if (CHECK_FLAG(pi->flags, BGP_PATH_REMOVED)) { + if (bgp_debug_update(peer, p, NULL, 1)) { + bgp_debug_rdpfxpath2str( + afi, safi, prd, p, label, num_labels, + addpath_id ? 1 : 0, addpath_id, evpn, + pfx_buf, sizeof(pfx_buf)); + zlog_debug( + "%pBP rcvd %s, flapped quicker than processing", + peer, pfx_buf); + } + + bgp_path_info_restore(dest, pi); + + /* + * If the BGP_PATH_REMOVED flag is set, then EVPN + * routes would have been unimported already when a + * prior BGP withdraw processing happened. Such routes + * need to be imported again, so flag accordingly. + */ + force_evpn_import = true; + } else { + /* implicit withdraw, decrement aggregate and pcount + * here. only if update is accepted, they'll increment + * below. + */ + bgp_aggregate_decrement(bgp, p, pi, afi, safi); + } + + /* Received Logging. */ + if (bgp_debug_update(peer, p, NULL, 1)) { + bgp_debug_rdpfxpath2str(afi, safi, prd, p, label, + num_labels, addpath_id ? 1 : 0, + addpath_id, evpn, pfx_buf, + sizeof(pfx_buf)); + zlog_debug("%pBP rcvd %s", peer, pfx_buf); + } + + /* graceful restart STALE flag unset. */ + if (CHECK_FLAG(pi->flags, BGP_PATH_STALE)) { + bgp_path_info_unset_flag(dest, pi, BGP_PATH_STALE); + bgp_dest_set_defer_flag(dest, false); + } + + /* The attribute is changed. */ + bgp_path_info_set_flag(dest, pi, BGP_PATH_ATTR_CHANGED); + + /* Update bgp route dampening information. */ + if (CHECK_FLAG(bgp->af_flags[afi][safi], BGP_CONFIG_DAMPENING) + && peer->sort == BGP_PEER_EBGP) { + /* This is implicit withdraw so we should update + dampening + information. */ + if (!CHECK_FLAG(pi->flags, BGP_PATH_HISTORY)) + bgp_damp_withdraw(pi, dest, afi, safi, 1); + } +#ifdef ENABLE_BGP_VNC + if (safi == SAFI_MPLS_VPN) { + struct bgp_dest *pdest = NULL; + struct bgp_table *table = NULL; + + pdest = bgp_node_get(bgp->rib[afi][safi], + (struct prefix *)prd); + if (bgp_dest_has_bgp_path_info_data(pdest)) { + table = bgp_dest_get_bgp_table_info(pdest); + + vnc_import_bgp_del_vnc_host_route_mode_resolve_nve( + bgp, prd, table, p, pi); + } + bgp_dest_unlock_node(pdest); + } + if ((afi == AFI_IP || afi == AFI_IP6) + && (safi == SAFI_UNICAST)) { + if (CHECK_FLAG(pi->flags, BGP_PATH_SELECTED)) { + /* + * Implicit withdraw case. + */ + ++vnc_implicit_withdraw; + vnc_import_bgp_del_route(bgp, p, pi); + vnc_import_bgp_exterior_del_route(bgp, p, pi); + } + } +#endif + + /* Special handling for EVPN update of an existing route. If the + * extended community attribute has changed, we need to + * un-import + * the route using its existing extended community. It will be + * subsequently processed for import with the new extended + * community. + */ + if (((safi == SAFI_EVPN) || (safi == SAFI_MPLS_VPN)) + && !same_attr) { + if ((pi->attr->flag + & ATTR_FLAG_BIT(BGP_ATTR_EXT_COMMUNITIES)) + && (attr_new->flag + & ATTR_FLAG_BIT(BGP_ATTR_EXT_COMMUNITIES))) { + int cmp; + + cmp = ecommunity_cmp( + bgp_attr_get_ecommunity(pi->attr), + bgp_attr_get_ecommunity(attr_new)); + if (!cmp) { + if (bgp_debug_update(peer, p, NULL, 1)) + zlog_debug( + "Change in EXT-COMM, existing %s new %s", + ecommunity_str( + bgp_attr_get_ecommunity( + pi->attr)), + ecommunity_str( + bgp_attr_get_ecommunity( + attr_new))); + if (safi == SAFI_EVPN) + bgp_evpn_unimport_route( + bgp, afi, safi, p, pi); + else /* SAFI_MPLS_VPN */ + vpn_leak_to_vrf_withdraw(pi); + } + } + } + + /* Update to new attribute. */ + bgp_attr_unintern(&pi->attr); + pi->attr = attr_new; + + /* Update MPLS label */ + if (has_valid_label) { + extra = bgp_path_info_extra_get(pi); + if (extra->label != label) { + memcpy(&extra->label, label, + num_labels * sizeof(mpls_label_t)); + extra->num_labels = num_labels; + } + if (!(afi == AFI_L2VPN && safi == SAFI_EVPN)) + bgp_set_valid_label(&extra->label[0]); + } + +#ifdef ENABLE_BGP_VNC + if ((afi == AFI_IP || afi == AFI_IP6) + && (safi == SAFI_UNICAST)) { + if (vnc_implicit_withdraw) { + /* + * Add back the route with its new attributes + * (e.g., nexthop). + * The route is still selected, until the route + * selection + * queued by bgp_process actually runs. We have + * to make this + * update to the VNC side immediately to avoid + * racing against + * configuration changes (e.g., route-map + * changes) which + * trigger re-importation of the entire RIB. + */ + vnc_import_bgp_add_route(bgp, p, pi); + vnc_import_bgp_exterior_add_route(bgp, p, pi); + } + } +#endif + + /* Update bgp route dampening information. */ + if (CHECK_FLAG(bgp->af_flags[afi][safi], BGP_CONFIG_DAMPENING) + && peer->sort == BGP_PEER_EBGP) { + /* Now we do normal update dampening. */ + ret = bgp_damp_update(pi, dest, afi, safi); + if (ret == BGP_DAMP_SUPPRESSED) { + bgp_dest_unlock_node(dest); + return; + } + } + + /* Nexthop reachability check - for unicast and + * labeled-unicast.. */ + if (((afi == AFI_IP || afi == AFI_IP6) && + (safi == SAFI_UNICAST || safi == SAFI_LABELED_UNICAST || + (safi == SAFI_MPLS_VPN && + pi->sub_type != BGP_ROUTE_IMPORTED))) || + (safi == SAFI_EVPN && + bgp_evpn_is_prefix_nht_supported(p))) { + if (safi != SAFI_EVPN && peer->sort == BGP_PEER_EBGP + && peer->ttl == BGP_DEFAULT_TTL + && !CHECK_FLAG(peer->flags, + PEER_FLAG_DISABLE_CONNECTED_CHECK) + && !CHECK_FLAG(bgp->flags, + BGP_FLAG_DISABLE_NH_CONNECTED_CHK)) + connected = 1; + else + connected = 0; + + struct bgp *bgp_nexthop = bgp; + + if (pi->extra && pi->extra->vrfleak && + pi->extra->vrfleak->bgp_orig) + bgp_nexthop = pi->extra->vrfleak->bgp_orig; + + nh_afi = BGP_ATTR_NH_AFI(afi, pi->attr); + + if (bgp_find_or_add_nexthop(bgp, bgp_nexthop, nh_afi, + safi, pi, NULL, connected, + bgp_nht_param_prefix) || + CHECK_FLAG(peer->flags, PEER_FLAG_IS_RFAPI_HD)) { + if (accept_own) + bgp_path_info_set_flag( + dest, pi, BGP_PATH_ACCEPT_OWN); + + bgp_path_info_set_flag(dest, pi, + BGP_PATH_VALID); + } else { + if (BGP_DEBUG(nht, NHT)) { + zlog_debug("%s(%pI4): NH unresolved", + __func__, + (in_addr_t *)&attr_new->nexthop); + } + bgp_path_info_unset_flag(dest, pi, + BGP_PATH_VALID); + } + } else { + /* case mpls-vpn routes with accept-own community + * (which have the BGP_ROUTE_IMPORTED subtype) + * case other afi/safi not supporting nexthop tracking + */ + if (accept_own) + bgp_path_info_set_flag(dest, pi, + BGP_PATH_ACCEPT_OWN); + bgp_path_info_set_flag(dest, pi, BGP_PATH_VALID); + } + +#ifdef ENABLE_BGP_VNC + if (safi == SAFI_MPLS_VPN) { + struct bgp_dest *pdest = NULL; + struct bgp_table *table = NULL; + + pdest = bgp_node_get(bgp->rib[afi][safi], + (struct prefix *)prd); + if (bgp_dest_has_bgp_path_info_data(pdest)) { + table = bgp_dest_get_bgp_table_info(pdest); + + vnc_import_bgp_add_vnc_host_route_mode_resolve_nve( + bgp, prd, table, p, pi); + } + bgp_dest_unlock_node(pdest); + } +#endif + + /* If this is an EVPN route and some attribute has changed, + * or we are explicitly told to perform a route import, process + * route for import. If the extended community has changed, we + * would + * have done the un-import earlier and the import would result + * in the + * route getting injected into appropriate L2 VNIs. If it is + * just + * some other attribute change, the import will result in + * updating + * the attributes for the route in the VNI(s). + */ + if (safi == SAFI_EVPN && + (!same_attr || force_evpn_import) && + CHECK_FLAG(pi->flags, BGP_PATH_VALID)) + bgp_evpn_import_route(bgp, afi, safi, p, pi); + + /* Process change. */ + bgp_aggregate_increment(bgp, p, pi, afi, safi); + + bgp_process(bgp, dest, afi, safi); + bgp_dest_unlock_node(dest); + + if (SAFI_UNICAST == safi + && (bgp->inst_type == BGP_INSTANCE_TYPE_VRF + || bgp->inst_type == BGP_INSTANCE_TYPE_DEFAULT)) { + + vpn_leak_from_vrf_update(bgp_get_default(), bgp, pi); + } + if ((SAFI_MPLS_VPN == safi) + && (bgp->inst_type == BGP_INSTANCE_TYPE_DEFAULT)) { + vpn_leak_to_vrf_update(bgp, pi, prd); + } + +#ifdef ENABLE_BGP_VNC + if (SAFI_MPLS_VPN == safi) { + mpls_label_t label_decoded = decode_label(label); + + rfapiProcessUpdate(peer, NULL, p, prd, attr, afi, safi, + type, sub_type, &label_decoded); + } + if (SAFI_ENCAP == safi) { + rfapiProcessUpdate(peer, NULL, p, prd, attr, afi, safi, + type, sub_type, NULL); + } +#endif + return; + } // End of implicit withdraw + + /* Received Logging. */ + if (bgp_debug_update(peer, p, NULL, 1)) { + if (!peer->rcvd_attr_printed) { + zlog_debug("%pBP rcvd UPDATE w/ attr: %s", peer, + peer->rcvd_attr_str); + peer->rcvd_attr_printed = 1; + } + + bgp_debug_rdpfxpath2str(afi, safi, prd, p, label, num_labels, + addpath_id ? 1 : 0, addpath_id, evpn, + pfx_buf, sizeof(pfx_buf)); + zlog_debug("%pBP rcvd %s", peer, pfx_buf); + } + + /* Make new BGP info. */ + new = info_make(type, sub_type, 0, peer, attr_new, dest); + + /* Update MPLS label */ + if (has_valid_label) { + extra = bgp_path_info_extra_get(new); + if (extra->label != label) { + memcpy(&extra->label, label, + num_labels * sizeof(mpls_label_t)); + extra->num_labels = num_labels; + } + if (!(afi == AFI_L2VPN && safi == SAFI_EVPN)) + bgp_set_valid_label(&extra->label[0]); + } + + /* Nexthop reachability check. */ + if (((afi == AFI_IP || afi == AFI_IP6) && + (safi == SAFI_UNICAST || safi == SAFI_LABELED_UNICAST || + (safi == SAFI_MPLS_VPN && + new->sub_type != BGP_ROUTE_IMPORTED))) || + (safi == SAFI_EVPN && bgp_evpn_is_prefix_nht_supported(p))) { + if (safi != SAFI_EVPN && peer->sort == BGP_PEER_EBGP + && peer->ttl == BGP_DEFAULT_TTL + && !CHECK_FLAG(peer->flags, + PEER_FLAG_DISABLE_CONNECTED_CHECK) + && !CHECK_FLAG(bgp->flags, + BGP_FLAG_DISABLE_NH_CONNECTED_CHK)) + connected = 1; + else + connected = 0; + + nh_afi = BGP_ATTR_NH_AFI(afi, new->attr); + + if (bgp_find_or_add_nexthop(bgp, bgp, nh_afi, safi, new, NULL, + connected, bgp_nht_param_prefix) || + CHECK_FLAG(peer->flags, PEER_FLAG_IS_RFAPI_HD)) { + if (accept_own) + bgp_path_info_set_flag(dest, new, + BGP_PATH_ACCEPT_OWN); + + bgp_path_info_set_flag(dest, new, BGP_PATH_VALID); + } else { + if (BGP_DEBUG(nht, NHT)) + zlog_debug("%s(%pI4): NH unresolved", __func__, + &attr_new->nexthop); + bgp_path_info_unset_flag(dest, new, BGP_PATH_VALID); + } + } else { + /* case mpls-vpn routes with accept-own community + * (which have the BGP_ROUTE_IMPORTED subtype) + * case other afi/safi not supporting nexthop tracking + */ + if (accept_own) + bgp_path_info_set_flag(dest, new, BGP_PATH_ACCEPT_OWN); + bgp_path_info_set_flag(dest, new, BGP_PATH_VALID); + } + + /* If maximum prefix count is configured and current prefix + * count exeed it. + */ + if (bgp_maximum_prefix_overflow(peer, afi, safi, 0)) { + reason = "maximum-prefix overflow"; + bgp_attr_flush(&new_attr); + goto filtered; + } + + /* Addpath ID */ + new->addpath_rx_id = addpath_id; + + /* Increment prefix */ + bgp_aggregate_increment(bgp, p, new, afi, safi); + + /* Register new BGP information. */ + bgp_path_info_add(dest, new); + + /* route_node_get lock */ + bgp_dest_unlock_node(dest); + +#ifdef ENABLE_BGP_VNC + if (safi == SAFI_MPLS_VPN) { + struct bgp_dest *pdest = NULL; + struct bgp_table *table = NULL; + + pdest = bgp_node_get(bgp->rib[afi][safi], (struct prefix *)prd); + if (bgp_dest_has_bgp_path_info_data(pdest)) { + table = bgp_dest_get_bgp_table_info(pdest); + + vnc_import_bgp_add_vnc_host_route_mode_resolve_nve( + bgp, prd, table, p, new); + } + bgp_dest_unlock_node(pdest); + } +#endif + + /* If this is an EVPN route, process for import. */ + if (safi == SAFI_EVPN && CHECK_FLAG(new->flags, BGP_PATH_VALID)) + bgp_evpn_import_route(bgp, afi, safi, p, new); + + hook_call(bgp_process, bgp, afi, safi, dest, peer, false); + + /* Process change. */ + bgp_process(bgp, dest, afi, safi); + + if (SAFI_UNICAST == safi + && (bgp->inst_type == BGP_INSTANCE_TYPE_VRF + || bgp->inst_type == BGP_INSTANCE_TYPE_DEFAULT)) { + vpn_leak_from_vrf_update(bgp_get_default(), bgp, new); + } + if ((SAFI_MPLS_VPN == safi) + && (bgp->inst_type == BGP_INSTANCE_TYPE_DEFAULT)) { + vpn_leak_to_vrf_update(bgp, new, prd); + } +#ifdef ENABLE_BGP_VNC + if (SAFI_MPLS_VPN == safi) { + mpls_label_t label_decoded = decode_label(label); + + rfapiProcessUpdate(peer, NULL, p, prd, attr, afi, safi, type, + sub_type, &label_decoded); + } + if (SAFI_ENCAP == safi) { + rfapiProcessUpdate(peer, NULL, p, prd, attr, afi, safi, type, + sub_type, NULL); + } +#endif + + return; + +/* This BGP update is filtered. Log the reason then update BGP + entry. */ +filtered: + if (new) { + bgp_unlink_nexthop(new); + bgp_path_info_delete(dest, new); + bgp_path_info_extra_free(&new->extra); + XFREE(MTYPE_BGP_ROUTE, new); + } + + hook_call(bgp_process, bgp, afi, safi, dest, peer, true); + + if (bgp_debug_update(peer, p, NULL, 1)) { + if (!peer->rcvd_attr_printed) { + zlog_debug("%pBP rcvd UPDATE w/ attr: %s", peer, + peer->rcvd_attr_str); + peer->rcvd_attr_printed = 1; + } + + bgp_debug_rdpfxpath2str(afi, safi, prd, p, label, num_labels, + addpath_id ? 1 : 0, addpath_id, evpn, + pfx_buf, sizeof(pfx_buf)); + zlog_debug("%pBP rcvd UPDATE about %s -- DENIED due to: %s", + peer, pfx_buf, reason); + } + + if (pi) { + /* If this is an EVPN route, un-import it as it is now filtered. + */ + if (safi == SAFI_EVPN) + bgp_evpn_unimport_route(bgp, afi, safi, p, pi); + + if (SAFI_UNICAST == safi + && (bgp->inst_type == BGP_INSTANCE_TYPE_VRF + || bgp->inst_type == BGP_INSTANCE_TYPE_DEFAULT)) { + + vpn_leak_from_vrf_withdraw(bgp_get_default(), bgp, pi); + } + if ((SAFI_MPLS_VPN == safi) + && (bgp->inst_type == BGP_INSTANCE_TYPE_DEFAULT)) { + + vpn_leak_to_vrf_withdraw(pi); + } + + bgp_rib_remove(dest, pi, peer, afi, safi); + } + + bgp_dest_unlock_node(dest); + +#ifdef ENABLE_BGP_VNC + /* + * Filtered update is treated as an implicit withdrawal (see + * bgp_rib_remove() + * a few lines above) + */ + if ((SAFI_MPLS_VPN == safi) || (SAFI_ENCAP == safi)) { + rfapiProcessWithdraw(peer, NULL, p, prd, NULL, afi, safi, type, + 0); + } +#endif + + return; +} + +void bgp_withdraw(struct peer *peer, const struct prefix *p, + uint32_t addpath_id, afi_t afi, safi_t safi, int type, + int sub_type, struct prefix_rd *prd, mpls_label_t *label, + uint32_t num_labels, struct bgp_route_evpn *evpn) +{ + struct bgp *bgp; + char pfx_buf[BGP_PRD_PATH_STRLEN]; + struct bgp_dest *dest; + struct bgp_path_info *pi; + +#ifdef ENABLE_BGP_VNC + if ((SAFI_MPLS_VPN == safi) || (SAFI_ENCAP == safi)) { + rfapiProcessWithdraw(peer, NULL, p, prd, NULL, afi, safi, type, + 0); + } +#endif + + bgp = peer->bgp; + + /* Lookup node. */ + dest = bgp_afi_node_get(bgp->rib[afi][safi], afi, safi, p, prd); + + /* If peer is soft reconfiguration enabled. Record input packet for + * further calculation. + * + * Cisco IOS 12.4(24)T4 on session establishment sends withdraws for all + * routes that are filtered. This tanks out Quagga RS pretty badly due + * to + * the iteration over all RS clients. + * Since we need to remove the entry from adj_in anyway, do that first + * and + * if there was no entry, we don't need to do anything more. + */ + if (CHECK_FLAG(peer->af_flags[afi][safi], PEER_FLAG_SOFT_RECONFIG) + && peer != bgp->peer_self) + if (!bgp_adj_in_unset(&dest, peer, addpath_id)) { + assert(dest); + peer->stat_pfx_dup_withdraw++; + + if (bgp_debug_update(peer, p, NULL, 1)) { + bgp_debug_rdpfxpath2str( + afi, safi, prd, p, label, num_labels, + addpath_id ? 1 : 0, addpath_id, NULL, + pfx_buf, sizeof(pfx_buf)); + zlog_debug( + "%s withdrawing route %s not in adj-in", + peer->host, pfx_buf); + } + bgp_dest_unlock_node(dest); + return; + } + + /* Lookup withdrawn route. */ + assert(dest); + for (pi = bgp_dest_get_bgp_path_info(dest); pi; pi = pi->next) + if (pi->peer == peer && pi->type == type + && pi->sub_type == sub_type + && pi->addpath_rx_id == addpath_id) + break; + + /* Logging. */ + if (bgp_debug_update(peer, p, NULL, 1)) { + bgp_debug_rdpfxpath2str(afi, safi, prd, p, label, num_labels, + addpath_id ? 1 : 0, addpath_id, NULL, + pfx_buf, sizeof(pfx_buf)); + zlog_debug("%pBP rcvd UPDATE about %s -- withdrawn", peer, + pfx_buf); + } + + /* Withdraw specified route from routing table. */ + if (pi && !CHECK_FLAG(pi->flags, BGP_PATH_HISTORY)) { + bgp_rib_withdraw(dest, pi, peer, afi, safi, prd); + if (SAFI_UNICAST == safi + && (bgp->inst_type == BGP_INSTANCE_TYPE_VRF + || bgp->inst_type == BGP_INSTANCE_TYPE_DEFAULT)) { + vpn_leak_from_vrf_withdraw(bgp_get_default(), bgp, pi); + } + if ((SAFI_MPLS_VPN == safi) + && (bgp->inst_type == BGP_INSTANCE_TYPE_DEFAULT)) { + + vpn_leak_to_vrf_withdraw(pi); + } + } else if (bgp_debug_update(peer, p, NULL, 1)) { + bgp_debug_rdpfxpath2str(afi, safi, prd, p, label, num_labels, + addpath_id ? 1 : 0, addpath_id, NULL, + pfx_buf, sizeof(pfx_buf)); + zlog_debug("%s Can't find the route %s", peer->host, pfx_buf); + } + + /* Unlock bgp_node_get() lock. */ + bgp_dest_unlock_node(dest); + + return; +} + +void bgp_default_originate(struct peer *peer, afi_t afi, safi_t safi, + int withdraw) +{ + struct update_subgroup *subgrp; + subgrp = peer_subgroup(peer, afi, safi); + subgroup_default_originate(subgrp, withdraw); +} + + +/* + * bgp_stop_announce_route_timer + */ +void bgp_stop_announce_route_timer(struct peer_af *paf) +{ + if (!paf->t_announce_route) + return; + + EVENT_OFF(paf->t_announce_route); +} + +/* + * bgp_announce_route_timer_expired + * + * Callback that is invoked when the route announcement timer for a + * peer_af expires. + */ +static void bgp_announce_route_timer_expired(struct event *t) +{ + struct peer_af *paf; + struct peer *peer; + + paf = EVENT_ARG(t); + peer = paf->peer; + + if (!peer_established(peer->connection)) + return; + + if (!peer->afc_nego[paf->afi][paf->safi]) + return; + + peer_af_announce_route(paf, 1); + + /* Notify BGP conditional advertisement scanner percess */ + peer->advmap_config_change[paf->afi][paf->safi] = true; +} + +/* + * bgp_announce_route + * + * *Triggers* announcement of routes of a given AFI/SAFI to a peer. + * + * if force is true we will force an update even if the update + * limiting code is attempted to kick in. + */ +void bgp_announce_route(struct peer *peer, afi_t afi, safi_t safi, bool force) +{ + struct peer_af *paf; + struct update_subgroup *subgrp; + + paf = peer_af_find(peer, afi, safi); + if (!paf) + return; + subgrp = PAF_SUBGRP(paf); + + /* + * Ignore if subgroup doesn't exist (implies AF is not negotiated) + * or a refresh has already been triggered. + */ + if (!subgrp || paf->t_announce_route) + return; + + if (force) + SET_FLAG(subgrp->sflags, SUBGRP_STATUS_FORCE_UPDATES); + + /* + * Start a timer to stagger/delay the announce. This serves + * two purposes - announcement can potentially be combined for + * multiple peers and the announcement doesn't happen in the + * vty context. + */ + event_add_timer_msec(bm->master, bgp_announce_route_timer_expired, paf, + (subgrp->peer_count == 1) + ? BGP_ANNOUNCE_ROUTE_SHORT_DELAY_MS + : BGP_ANNOUNCE_ROUTE_DELAY_MS, + &paf->t_announce_route); +} + +/* + * Announce routes from all AF tables to a peer. + * + * This should ONLY be called when there is a need to refresh the + * routes to the peer based on a policy change for this peer alone + * or a route refresh request received from the peer. + * The operation will result in splitting the peer from its existing + * subgroups and putting it in new subgroups. + */ +void bgp_announce_route_all(struct peer *peer) +{ + afi_t afi; + safi_t safi; + + FOREACH_AFI_SAFI (afi, safi) + bgp_announce_route(peer, afi, safi, false); +} + +/* Flag or unflag bgp_dest to determine whether it should be treated by + * bgp_soft_reconfig_table_task. + * Flag if flag is true. Unflag if flag is false. + */ +static void bgp_soft_reconfig_table_flag(struct bgp_table *table, bool flag) +{ + struct bgp_dest *dest; + struct bgp_adj_in *ain; + + if (!table) + return; + + for (dest = bgp_table_top(table); dest; dest = bgp_route_next(dest)) { + for (ain = dest->adj_in; ain; ain = ain->next) { + if (ain->peer != NULL) + break; + } + if (flag && ain != NULL && ain->peer != NULL) + SET_FLAG(dest->flags, BGP_NODE_SOFT_RECONFIG); + else + UNSET_FLAG(dest->flags, BGP_NODE_SOFT_RECONFIG); + } +} + +static void bgp_soft_reconfig_table_update(struct peer *peer, + struct bgp_dest *dest, + struct bgp_adj_in *ain, afi_t afi, + safi_t safi, struct prefix_rd *prd) +{ + struct bgp_path_info *pi; + uint32_t num_labels = 0; + mpls_label_t *label_pnt = NULL; + struct bgp_route_evpn evpn; + + for (pi = bgp_dest_get_bgp_path_info(dest); pi; pi = pi->next) + if (pi->peer == peer) + break; + + if (pi && pi->extra) + num_labels = pi->extra->num_labels; + if (num_labels) + label_pnt = &pi->extra->label[0]; + if (pi) + memcpy(&evpn, bgp_attr_get_evpn_overlay(pi->attr), + sizeof(evpn)); + else + memset(&evpn, 0, sizeof(evpn)); + + bgp_update(peer, bgp_dest_get_prefix(dest), ain->addpath_rx_id, + ain->attr, afi, safi, ZEBRA_ROUTE_BGP, BGP_ROUTE_NORMAL, prd, + label_pnt, num_labels, 1, &evpn); +} + +static void bgp_soft_reconfig_table(struct peer *peer, afi_t afi, safi_t safi, + struct bgp_table *table, + struct prefix_rd *prd) +{ + struct bgp_dest *dest; + struct bgp_adj_in *ain; + + if (!table) + table = peer->bgp->rib[afi][safi]; + + for (dest = bgp_table_top(table); dest; dest = bgp_route_next(dest)) + for (ain = dest->adj_in; ain; ain = ain->next) { + if (ain->peer != peer) + continue; + + bgp_soft_reconfig_table_update(peer, dest, ain, afi, + safi, prd); + } +} + +/* Do soft reconfig table per bgp table. + * Walk on SOFT_RECONFIG_TASK_MAX_PREFIX bgp_dest, + * when BGP_NODE_SOFT_RECONFIG is set, + * reconfig bgp_dest for list of table->soft_reconfig_peers peers. + * Schedule a new thread to continue the job. + * Without splitting the full job into several part, + * vtysh waits for the job to finish before responding to a BGP command + */ +static void bgp_soft_reconfig_table_task(struct event *thread) +{ + uint32_t iter, max_iter; + struct bgp_dest *dest; + struct bgp_adj_in *ain; + struct peer *peer; + struct bgp_table *table; + struct prefix_rd *prd; + struct listnode *node, *nnode; + + table = EVENT_ARG(thread); + prd = NULL; + + max_iter = SOFT_RECONFIG_TASK_MAX_PREFIX; + if (table->soft_reconfig_init) { + /* first call of the function with a new srta structure. + * Don't do any treatment this time on nodes + * in order vtysh to respond quickly + */ + max_iter = 0; + } + + for (iter = 0, dest = bgp_table_top(table); (dest && iter < max_iter); + dest = bgp_route_next(dest)) { + if (!CHECK_FLAG(dest->flags, BGP_NODE_SOFT_RECONFIG)) + continue; + + UNSET_FLAG(dest->flags, BGP_NODE_SOFT_RECONFIG); + + for (ain = dest->adj_in; ain; ain = ain->next) { + for (ALL_LIST_ELEMENTS(table->soft_reconfig_peers, node, + nnode, peer)) { + if (ain->peer != peer) + continue; + + bgp_soft_reconfig_table_update( + peer, dest, ain, table->afi, + table->safi, prd); + iter++; + } + } + } + + /* we're either starting the initial iteration, + * or we're going to continue an ongoing iteration + */ + if (dest || table->soft_reconfig_init) { + table->soft_reconfig_init = false; + event_add_event(bm->master, bgp_soft_reconfig_table_task, table, + 0, &table->soft_reconfig_thread); + return; + } + /* we're done, clean up the background iteration context info and + schedule route annoucement + */ + for (ALL_LIST_ELEMENTS(table->soft_reconfig_peers, node, nnode, peer)) { + listnode_delete(table->soft_reconfig_peers, peer); + bgp_announce_route(peer, table->afi, table->safi, false); + } + + list_delete(&table->soft_reconfig_peers); +} + + +/* Cancel soft_reconfig_table task matching bgp instance, bgp_table + * and peer. + * - bgp cannot be NULL + * - if table and peer are NULL, cancel all threads within the bgp instance + * - if table is NULL and peer is not, + * remove peer in all threads within the bgp instance + * - if peer is NULL, cancel all threads matching table within the bgp instance + */ +void bgp_soft_reconfig_table_task_cancel(const struct bgp *bgp, + const struct bgp_table *table, + const struct peer *peer) +{ + struct peer *npeer; + struct listnode *node, *nnode; + int afi, safi; + struct bgp_table *ntable; + + if (!bgp) + return; + + FOREACH_AFI_SAFI (afi, safi) { + ntable = bgp->rib[afi][safi]; + if (!ntable) + continue; + if (table && table != ntable) + continue; + + for (ALL_LIST_ELEMENTS(ntable->soft_reconfig_peers, node, nnode, + npeer)) { + if (peer && peer != npeer) + continue; + listnode_delete(ntable->soft_reconfig_peers, npeer); + } + + if (!ntable->soft_reconfig_peers + || !list_isempty(ntable->soft_reconfig_peers)) + continue; + + list_delete(&ntable->soft_reconfig_peers); + bgp_soft_reconfig_table_flag(ntable, false); + EVENT_OFF(ntable->soft_reconfig_thread); + } +} + +/* + * Returns false if the peer is not configured for soft reconfig in + */ +bool bgp_soft_reconfig_in(struct peer *peer, afi_t afi, safi_t safi) +{ + struct bgp_dest *dest; + struct bgp_table *table; + struct listnode *node, *nnode; + struct peer *npeer; + struct peer_af *paf; + + if (!CHECK_FLAG(peer->af_flags[afi][safi], PEER_FLAG_SOFT_RECONFIG)) + return false; + + if ((safi != SAFI_MPLS_VPN) && (safi != SAFI_ENCAP) + && (safi != SAFI_EVPN)) { + table = peer->bgp->rib[afi][safi]; + if (!table) + return true; + + table->soft_reconfig_init = true; + + if (!table->soft_reconfig_peers) + table->soft_reconfig_peers = list_new(); + npeer = NULL; + /* add peer to the table soft_reconfig_peers if not already + * there + */ + for (ALL_LIST_ELEMENTS(table->soft_reconfig_peers, node, nnode, + npeer)) { + if (peer == npeer) + break; + } + if (peer != npeer) + listnode_add(table->soft_reconfig_peers, peer); + + /* (re)flag all bgp_dest in table. Existing soft_reconfig_in job + * on table would start back at the beginning. + */ + bgp_soft_reconfig_table_flag(table, true); + + if (!table->soft_reconfig_thread) + event_add_event(bm->master, + bgp_soft_reconfig_table_task, table, 0, + &table->soft_reconfig_thread); + /* Cancel bgp_announce_route_timer_expired threads. + * bgp_announce_route_timer_expired threads have been scheduled + * to announce routes as soon as the soft_reconfigure process + * finishes. + * In this case, soft_reconfigure is also scheduled by using + * a thread but is planned after the + * bgp_announce_route_timer_expired threads. It means that, + * without cancelling the threads, the route announcement task + * would run before the soft reconfiguration one. That would + * useless and would block vtysh during several seconds. Route + * announcements are rescheduled as soon as the soft_reconfigure + * process finishes. + */ + paf = peer_af_find(peer, afi, safi); + if (paf) + bgp_stop_announce_route_timer(paf); + } else + for (dest = bgp_table_top(peer->bgp->rib[afi][safi]); dest; + dest = bgp_route_next(dest)) { + table = bgp_dest_get_bgp_table_info(dest); + + if (table == NULL) + continue; + + const struct prefix *p = bgp_dest_get_prefix(dest); + struct prefix_rd prd; + + prd.family = AF_UNSPEC; + prd.prefixlen = 64; + memcpy(&prd.val, p->u.val, 8); + + bgp_soft_reconfig_table(peer, afi, safi, table, &prd); + } + + return true; +} + + +struct bgp_clear_node_queue { + struct bgp_dest *dest; +}; + +static wq_item_status bgp_clear_route_node(struct work_queue *wq, void *data) +{ + struct bgp_clear_node_queue *cnq = data; + struct bgp_dest *dest = cnq->dest; + struct peer *peer = wq->spec.data; + struct bgp_path_info *pi; + struct bgp *bgp; + afi_t afi = bgp_dest_table(dest)->afi; + safi_t safi = bgp_dest_table(dest)->safi; + + assert(dest && peer); + bgp = peer->bgp; + + /* It is possible that we have multiple paths for a prefix from a peer + * if that peer is using AddPath. + */ + for (pi = bgp_dest_get_bgp_path_info(dest); pi; pi = pi->next) { + if (pi->peer != peer) + continue; + + /* graceful restart STALE flag set. */ + if (((CHECK_FLAG(peer->sflags, PEER_STATUS_NSF_WAIT) + && peer->nsf[afi][safi]) + || CHECK_FLAG(peer->af_sflags[afi][safi], + PEER_STATUS_ENHANCED_REFRESH)) + && !CHECK_FLAG(pi->flags, BGP_PATH_STALE) + && !CHECK_FLAG(pi->flags, BGP_PATH_UNUSEABLE)) + bgp_path_info_set_flag(dest, pi, BGP_PATH_STALE); + else { + /* If this is an EVPN route, process for + * un-import. */ + if (safi == SAFI_EVPN) + bgp_evpn_unimport_route( + bgp, afi, safi, + bgp_dest_get_prefix(dest), pi); + /* Handle withdraw for VRF route-leaking and L3VPN */ + if (SAFI_UNICAST == safi + && (bgp->inst_type == BGP_INSTANCE_TYPE_VRF || + bgp->inst_type == BGP_INSTANCE_TYPE_DEFAULT)) { + vpn_leak_from_vrf_withdraw(bgp_get_default(), + bgp, pi); + } + if (SAFI_MPLS_VPN == safi && + bgp->inst_type == BGP_INSTANCE_TYPE_DEFAULT) { + vpn_leak_to_vrf_withdraw(pi); + } + + bgp_rib_remove(dest, pi, peer, afi, safi); + } + } + return WQ_SUCCESS; +} + +static void bgp_clear_node_queue_del(struct work_queue *wq, void *data) +{ + struct bgp_clear_node_queue *cnq = data; + struct bgp_dest *dest = cnq->dest; + struct bgp_table *table = bgp_dest_table(dest); + + bgp_dest_unlock_node(dest); + bgp_table_unlock(table); + XFREE(MTYPE_BGP_CLEAR_NODE_QUEUE, cnq); +} + +static void bgp_clear_node_complete(struct work_queue *wq) +{ + struct peer *peer = wq->spec.data; + + /* Tickle FSM to start moving again */ + BGP_EVENT_ADD(peer->connection, Clearing_Completed); + + peer_unlock(peer); /* bgp_clear_route */ +} + +static void bgp_clear_node_queue_init(struct peer *peer) +{ + char wname[sizeof("clear xxxx:xxxx:xxxx:xxxx:xxxx:xxxx:xxxx:xxxx")]; + + snprintf(wname, sizeof(wname), "clear %s", peer->host); +#undef CLEAR_QUEUE_NAME_LEN + + peer->clear_node_queue = work_queue_new(bm->master, wname); + peer->clear_node_queue->spec.hold = 10; + peer->clear_node_queue->spec.workfunc = &bgp_clear_route_node; + peer->clear_node_queue->spec.del_item_data = &bgp_clear_node_queue_del; + peer->clear_node_queue->spec.completion_func = &bgp_clear_node_complete; + peer->clear_node_queue->spec.max_retries = 0; + + /* we only 'lock' this peer reference when the queue is actually active + */ + peer->clear_node_queue->spec.data = peer; +} + +static void bgp_clear_route_table(struct peer *peer, afi_t afi, safi_t safi, + struct bgp_table *table) +{ + struct bgp_dest *dest; + int force = peer->bgp->process_queue ? 0 : 1; + + if (!table) + table = peer->bgp->rib[afi][safi]; + + /* If still no table => afi/safi isn't configured at all or smth. */ + if (!table) + return; + + for (dest = bgp_table_top(table); dest; dest = bgp_route_next(dest)) { + struct bgp_path_info *pi, *next; + struct bgp_adj_in *ain; + struct bgp_adj_in *ain_next; + + /* XXX:TODO: This is suboptimal, every non-empty route_node is + * queued for every clearing peer, regardless of whether it is + * relevant to the peer at hand. + * + * Overview: There are 3 different indices which need to be + * scrubbed, potentially, when a peer is removed: + * + * 1 peer's routes visible via the RIB (ie accepted routes) + * 2 peer's routes visible by the (optional) peer's adj-in index + * 3 other routes visible by the peer's adj-out index + * + * 3 there is no hurry in scrubbing, once the struct peer is + * removed from bgp->peer, we could just GC such deleted peer's + * adj-outs at our leisure. + * + * 1 and 2 must be 'scrubbed' in some way, at least made + * invisible via RIB index before peer session is allowed to be + * brought back up. So one needs to know when such a 'search' is + * complete. + * + * Ideally: + * + * - there'd be a single global queue or a single RIB walker + * - rather than tracking which route_nodes still need to be + * examined on a peer basis, we'd track which peers still + * aren't cleared + * + * Given that our per-peer prefix-counts now should be reliable, + * this may actually be achievable. It doesn't seem to be a huge + * problem at this time, + * + * It is possible that we have multiple paths for a prefix from + * a peer + * if that peer is using AddPath. + */ + ain = dest->adj_in; + while (ain) { + ain_next = ain->next; + + if (ain->peer == peer) + bgp_adj_in_remove(&dest, ain); + + ain = ain_next; + + assert(dest); + } + + for (pi = bgp_dest_get_bgp_path_info(dest); pi; pi = next) { + next = pi->next; + if (pi->peer != peer) + continue; + + if (force) { + dest = bgp_path_info_reap(dest, pi); + assert(dest); + } else { + struct bgp_clear_node_queue *cnq; + + /* both unlocked in bgp_clear_node_queue_del */ + bgp_table_lock(bgp_dest_table(dest)); + bgp_dest_lock_node(dest); + cnq = XCALLOC( + MTYPE_BGP_CLEAR_NODE_QUEUE, + sizeof(struct bgp_clear_node_queue)); + cnq->dest = dest; + work_queue_add(peer->clear_node_queue, cnq); + break; + } + } + } + return; +} + +void bgp_clear_route(struct peer *peer, afi_t afi, safi_t safi) +{ + struct bgp_dest *dest; + struct bgp_table *table; + + if (peer->clear_node_queue == NULL) + bgp_clear_node_queue_init(peer); + + /* bgp_fsm.c keeps sessions in state Clearing, not transitioning to + * Idle until it receives a Clearing_Completed event. This protects + * against peers which flap faster than we can we clear, which could + * lead to: + * + * a) race with routes from the new session being installed before + * clear_route_node visits the node (to delete the route of that + * peer) + * b) resource exhaustion, clear_route_node likely leads to an entry + * on the process_main queue. Fast-flapping could cause that queue + * to grow and grow. + */ + + /* lock peer in assumption that clear-node-queue will get nodes; if so, + * the unlock will happen upon work-queue completion; other wise, the + * unlock happens at the end of this function. + */ + if (!peer->clear_node_queue->thread) + peer_lock(peer); + + if (safi != SAFI_MPLS_VPN && safi != SAFI_ENCAP && safi != SAFI_EVPN) + bgp_clear_route_table(peer, afi, safi, NULL); + else + for (dest = bgp_table_top(peer->bgp->rib[afi][safi]); dest; + dest = bgp_route_next(dest)) { + table = bgp_dest_get_bgp_table_info(dest); + if (!table) + continue; + + bgp_clear_route_table(peer, afi, safi, table); + } + + /* unlock if no nodes got added to the clear-node-queue. */ + if (!peer->clear_node_queue->thread) + peer_unlock(peer); +} + +void bgp_clear_route_all(struct peer *peer) +{ + afi_t afi; + safi_t safi; + + FOREACH_AFI_SAFI (afi, safi) + bgp_clear_route(peer, afi, safi); + +#ifdef ENABLE_BGP_VNC + rfapiProcessPeerDown(peer); +#endif +} + +void bgp_clear_adj_in(struct peer *peer, afi_t afi, safi_t safi) +{ + struct bgp_table *table; + struct bgp_dest *dest; + struct bgp_adj_in *ain; + struct bgp_adj_in *ain_next; + + table = peer->bgp->rib[afi][safi]; + + /* It is possible that we have multiple paths for a prefix from a peer + * if that peer is using AddPath. + */ + for (dest = bgp_table_top(table); dest; dest = bgp_route_next(dest)) { + ain = dest->adj_in; + + while (ain) { + ain_next = ain->next; + + if (ain->peer == peer) + bgp_adj_in_remove(&dest, ain); + + ain = ain_next; + + assert(dest); + } + } +} + +/* If any of the routes from the peer have been marked with the NO_LLGR + * community, either as sent by the peer, or as the result of a configured + * policy, they MUST NOT be retained, but MUST be removed as per the normal + * operation of [RFC4271]. + */ +void bgp_clear_stale_route(struct peer *peer, afi_t afi, safi_t safi) +{ + struct bgp_dest *dest; + struct bgp_path_info *pi; + struct bgp_table *table; + + if (safi == SAFI_MPLS_VPN || safi == SAFI_ENCAP || safi == SAFI_EVPN) { + for (dest = bgp_table_top(peer->bgp->rib[afi][safi]); dest; + dest = bgp_route_next(dest)) { + struct bgp_dest *rm; + + /* look for neighbor in tables */ + table = bgp_dest_get_bgp_table_info(dest); + if (!table) + continue; + + for (rm = bgp_table_top(table); rm; + rm = bgp_route_next(rm)) + for (pi = bgp_dest_get_bgp_path_info(rm); pi; + pi = pi->next) { + if (pi->peer != peer) + continue; + if (CHECK_FLAG( + peer->af_sflags[afi][safi], + PEER_STATUS_LLGR_WAIT) && + bgp_attr_get_community(pi->attr) && + !community_include( + bgp_attr_get_community( + pi->attr), + COMMUNITY_NO_LLGR)) + continue; + if (!CHECK_FLAG(pi->flags, + BGP_PATH_STALE)) + continue; + + /* + * If this is VRF leaked route + * process for withdraw. + */ + if (pi->sub_type == + BGP_ROUTE_IMPORTED && + peer->bgp->inst_type == + BGP_INSTANCE_TYPE_DEFAULT) + vpn_leak_to_vrf_withdraw(pi); + + bgp_rib_remove(rm, pi, peer, afi, safi); + break; + } + } + } else { + for (dest = bgp_table_top(peer->bgp->rib[afi][safi]); dest; + dest = bgp_route_next(dest)) + for (pi = bgp_dest_get_bgp_path_info(dest); pi; + pi = pi->next) { + if (pi->peer != peer) + continue; + if (CHECK_FLAG(peer->af_sflags[afi][safi], + PEER_STATUS_LLGR_WAIT) && + bgp_attr_get_community(pi->attr) && + !community_include( + bgp_attr_get_community(pi->attr), + COMMUNITY_NO_LLGR)) + continue; + if (!CHECK_FLAG(pi->flags, BGP_PATH_STALE)) + continue; + if (safi == SAFI_UNICAST && + (peer->bgp->inst_type == + BGP_INSTANCE_TYPE_VRF || + peer->bgp->inst_type == + BGP_INSTANCE_TYPE_DEFAULT)) + vpn_leak_from_vrf_withdraw( + bgp_get_default(), peer->bgp, + pi); + + bgp_rib_remove(dest, pi, peer, afi, safi); + break; + } + } +} + +void bgp_set_stale_route(struct peer *peer, afi_t afi, safi_t safi) +{ + struct bgp_dest *dest, *ndest; + struct bgp_path_info *pi; + struct bgp_table *table; + + if (safi == SAFI_MPLS_VPN || safi == SAFI_ENCAP || safi == SAFI_EVPN) { + for (dest = bgp_table_top(peer->bgp->rib[afi][safi]); dest; + dest = bgp_route_next(dest)) { + table = bgp_dest_get_bgp_table_info(dest); + if (!table) + continue; + + for (ndest = bgp_table_top(table); ndest; + ndest = bgp_route_next(ndest)) { + for (pi = bgp_dest_get_bgp_path_info(ndest); pi; + pi = pi->next) { + if (pi->peer != peer) + continue; + + if ((CHECK_FLAG( + peer->af_sflags[afi][safi], + PEER_STATUS_ENHANCED_REFRESH)) + && !CHECK_FLAG(pi->flags, + BGP_PATH_STALE) + && !CHECK_FLAG( + pi->flags, + BGP_PATH_UNUSEABLE)) { + if (bgp_debug_neighbor_events( + peer)) + zlog_debug( + "%pBP route-refresh for %s/%s, marking prefix %pFX as stale", + peer, + afi2str(afi), + safi2str(safi), + bgp_dest_get_prefix( + ndest)); + + bgp_path_info_set_flag( + ndest, pi, + BGP_PATH_STALE); + } + } + } + } + } else { + for (dest = bgp_table_top(peer->bgp->rib[afi][safi]); dest; + dest = bgp_route_next(dest)) { + for (pi = bgp_dest_get_bgp_path_info(dest); pi; + pi = pi->next) { + if (pi->peer != peer) + continue; + + if ((CHECK_FLAG(peer->af_sflags[afi][safi], + PEER_STATUS_ENHANCED_REFRESH)) + && !CHECK_FLAG(pi->flags, BGP_PATH_STALE) + && !CHECK_FLAG(pi->flags, + BGP_PATH_UNUSEABLE)) { + if (bgp_debug_neighbor_events(peer)) + zlog_debug( + "%pBP route-refresh for %s/%s, marking prefix %pFX as stale", + peer, afi2str(afi), + safi2str(safi), + bgp_dest_get_prefix( + dest)); + + bgp_path_info_set_flag(dest, pi, + BGP_PATH_STALE); + } + } + } + } +} + +bool bgp_outbound_policy_exists(struct peer *peer, struct bgp_filter *filter) +{ + if (peer->sort == BGP_PEER_IBGP) + return true; + + if (peer->sort == BGP_PEER_EBGP + && (ROUTE_MAP_OUT_NAME(filter) || PREFIX_LIST_OUT_NAME(filter) + || FILTER_LIST_OUT_NAME(filter) + || DISTRIBUTE_OUT_NAME(filter))) + return true; + return false; +} + +bool bgp_inbound_policy_exists(struct peer *peer, struct bgp_filter *filter) +{ + if (peer->sort == BGP_PEER_IBGP) + return true; + + if (peer->sort == BGP_PEER_EBGP + && (ROUTE_MAP_IN_NAME(filter) || PREFIX_LIST_IN_NAME(filter) + || FILTER_LIST_IN_NAME(filter) + || DISTRIBUTE_IN_NAME(filter))) + return true; + return false; +} + +static void bgp_cleanup_table(struct bgp *bgp, struct bgp_table *table, + safi_t safi) +{ + struct bgp_dest *dest; + struct bgp_path_info *pi; + struct bgp_path_info *next; + + for (dest = bgp_table_top(table); dest; dest = bgp_route_next(dest)) + for (pi = bgp_dest_get_bgp_path_info(dest); pi; pi = next) { + const struct prefix *p = bgp_dest_get_prefix(dest); + + next = pi->next; + + /* Unimport EVPN routes from VRFs */ + if (safi == SAFI_EVPN) + bgp_evpn_unimport_route(bgp, AFI_L2VPN, + SAFI_EVPN, p, pi); + + if (CHECK_FLAG(pi->flags, BGP_PATH_SELECTED) + && pi->type == ZEBRA_ROUTE_BGP + && (pi->sub_type == BGP_ROUTE_NORMAL + || pi->sub_type == BGP_ROUTE_AGGREGATE + || pi->sub_type == BGP_ROUTE_IMPORTED)) { + + if (bgp_fibupd_safi(safi)) + bgp_zebra_withdraw(p, pi, bgp, safi); + } + + dest = bgp_path_info_reap(dest, pi); + assert(dest); + } +} + +/* Delete all kernel routes. */ +void bgp_cleanup_routes(struct bgp *bgp) +{ + afi_t afi; + struct bgp_dest *dest; + struct bgp_table *table; + + for (afi = AFI_IP; afi < AFI_MAX; ++afi) { + if (afi == AFI_L2VPN) + continue; + bgp_cleanup_table(bgp, bgp->rib[afi][SAFI_UNICAST], + SAFI_UNICAST); + /* + * VPN and ENCAP and EVPN tables are two-level (RD is top level) + */ + if (afi != AFI_L2VPN) { + safi_t safi; + safi = SAFI_MPLS_VPN; + for (dest = bgp_table_top(bgp->rib[afi][safi]); dest; + dest = bgp_route_next(dest)) { + table = bgp_dest_get_bgp_table_info(dest); + if (table != NULL) { + bgp_cleanup_table(bgp, table, safi); + bgp_table_finish(&table); + bgp_dest_set_bgp_table_info(dest, NULL); + dest = bgp_dest_unlock_node(dest); + + assert(dest); + } + } + safi = SAFI_ENCAP; + for (dest = bgp_table_top(bgp->rib[afi][safi]); dest; + dest = bgp_route_next(dest)) { + table = bgp_dest_get_bgp_table_info(dest); + if (table != NULL) { + bgp_cleanup_table(bgp, table, safi); + bgp_table_finish(&table); + bgp_dest_set_bgp_table_info(dest, NULL); + dest = bgp_dest_unlock_node(dest); + + assert(dest); + } + } + } + } + for (dest = bgp_table_top(bgp->rib[AFI_L2VPN][SAFI_EVPN]); dest; + dest = bgp_route_next(dest)) { + table = bgp_dest_get_bgp_table_info(dest); + if (table != NULL) { + bgp_cleanup_table(bgp, table, SAFI_EVPN); + bgp_table_finish(&table); + bgp_dest_set_bgp_table_info(dest, NULL); + dest = bgp_dest_unlock_node(dest); + + assert(dest); + } + } +} + +void bgp_reset(void) +{ + vty_reset(); + bgp_zclient_reset(); + access_list_reset(); + prefix_list_reset(); +} + +bool bgp_addpath_encode_rx(struct peer *peer, afi_t afi, safi_t safi) +{ + return (CHECK_FLAG(peer->af_cap[afi][safi], PEER_CAP_ADDPATH_AF_RX_ADV) + && CHECK_FLAG(peer->af_cap[afi][safi], + PEER_CAP_ADDPATH_AF_TX_RCV)); +} + +/* Parse NLRI stream. Withdraw NLRI is recognized by NULL attr + value. */ +int bgp_nlri_parse_ip(struct peer *peer, struct attr *attr, + struct bgp_nlri *packet) +{ + uint8_t *pnt; + uint8_t *lim; + struct prefix p; + int psize; + afi_t afi; + safi_t safi; + bool addpath_capable; + uint32_t addpath_id; + + pnt = packet->nlri; + lim = pnt + packet->length; + afi = packet->afi; + safi = packet->safi; + addpath_id = 0; + addpath_capable = bgp_addpath_encode_rx(peer, afi, safi); + + /* RFC4271 6.3 The NLRI field in the UPDATE message is checked for + syntactic validity. If the field is syntactically incorrect, + then the Error Subcode is set to Invalid Network Field. */ + for (; pnt < lim; pnt += psize) { + /* Clear prefix structure. */ + memset(&p, 0, sizeof(p)); + + if (addpath_capable) { + + /* When packet overflow occurs return immediately. */ + if (pnt + BGP_ADDPATH_ID_LEN >= lim) + return BGP_NLRI_PARSE_ERROR_PACKET_OVERFLOW; + + memcpy(&addpath_id, pnt, BGP_ADDPATH_ID_LEN); + addpath_id = ntohl(addpath_id); + pnt += BGP_ADDPATH_ID_LEN; + } + + /* Fetch prefix length. */ + p.prefixlen = *pnt++; + /* afi/safi validity already verified by caller, + * bgp_update_receive */ + p.family = afi2family(afi); + + /* Prefix length check. */ + if (p.prefixlen > prefix_blen(&p) * 8) { + flog_err( + EC_BGP_UPDATE_RCV, + "%s [Error] Update packet error (wrong prefix length %d for afi %u)", + peer->host, p.prefixlen, packet->afi); + return BGP_NLRI_PARSE_ERROR_PREFIX_LENGTH; + } + + /* Packet size overflow check. */ + psize = PSIZE(p.prefixlen); + + /* When packet overflow occur return immediately. */ + if (pnt + psize > lim) { + flog_err( + EC_BGP_UPDATE_RCV, + "%s [Error] Update packet error (prefix length %d overflows packet)", + peer->host, p.prefixlen); + return BGP_NLRI_PARSE_ERROR_PACKET_OVERFLOW; + } + + /* Defensive coding, double-check the psize fits in a struct + * prefix for the v4 and v6 afi's and unicast/multicast */ + if (psize > (ssize_t)sizeof(p.u.val)) { + flog_err( + EC_BGP_UPDATE_RCV, + "%s [Error] Update packet error (prefix length %d too large for prefix storage %zu)", + peer->host, p.prefixlen, sizeof(p.u.val)); + return BGP_NLRI_PARSE_ERROR_PACKET_LENGTH; + } + + /* Fetch prefix from NLRI packet. */ + memcpy(p.u.val, pnt, psize); + + /* Check address. */ + if (afi == AFI_IP && safi == SAFI_UNICAST) { + if (IN_CLASSD(ntohl(p.u.prefix4.s_addr))) { + /* From RFC4271 Section 6.3: + * + * If a prefix in the NLRI field is semantically + * incorrect + * (e.g., an unexpected multicast IP address), + * an error SHOULD + * be logged locally, and the prefix SHOULD be + * ignored. + */ + flog_err( + EC_BGP_UPDATE_RCV, + "%s: IPv4 unicast NLRI is multicast address %pI4, ignoring", + peer->host, &p.u.prefix4); + continue; + } + } + + /* Check address. */ + if (afi == AFI_IP6 && safi == SAFI_UNICAST) { + if (IN6_IS_ADDR_LINKLOCAL(&p.u.prefix6)) { + flog_err( + EC_BGP_UPDATE_RCV, + "%s: IPv6 unicast NLRI is link-local address %pI6, ignoring", + peer->host, &p.u.prefix6); + + continue; + } + if (IN6_IS_ADDR_MULTICAST(&p.u.prefix6)) { + flog_err( + EC_BGP_UPDATE_RCV, + "%s: IPv6 unicast NLRI is multicast address %pI6, ignoring", + peer->host, &p.u.prefix6); + + continue; + } + } + + /* Normal process. */ + if (attr) + bgp_update(peer, &p, addpath_id, attr, afi, safi, + ZEBRA_ROUTE_BGP, BGP_ROUTE_NORMAL, NULL, + NULL, 0, 0, NULL); + else + bgp_withdraw(peer, &p, addpath_id, afi, safi, + ZEBRA_ROUTE_BGP, BGP_ROUTE_NORMAL, NULL, + NULL, 0, NULL); + + /* Do not send BGP notification twice when maximum-prefix count + * overflow. */ + if (CHECK_FLAG(peer->sflags, PEER_STATUS_PREFIX_OVERFLOW)) + return BGP_NLRI_PARSE_ERROR_PREFIX_OVERFLOW; + } + + /* Packet length consistency check. */ + if (pnt != lim) { + flog_err( + EC_BGP_UPDATE_RCV, + "%s [Error] Update packet error (prefix length mismatch with total length)", + peer->host); + return BGP_NLRI_PARSE_ERROR_PACKET_LENGTH; + } + + return BGP_NLRI_PARSE_OK; +} + +static void bgp_nexthop_reachability_check(afi_t afi, safi_t safi, + struct bgp_path_info *bpi, + const struct prefix *p, + struct bgp_dest *dest, + struct bgp *bgp) +{ + /* Nexthop reachability check. */ + if (safi == SAFI_UNICAST || safi == SAFI_LABELED_UNICAST) { + if (CHECK_FLAG(bgp->flags, BGP_FLAG_IMPORT_CHECK)) { + if (bgp_find_or_add_nexthop(bgp, bgp, afi, safi, bpi, + NULL, 0, p)) + bgp_path_info_set_flag(dest, bpi, + BGP_PATH_VALID); + else { + if (BGP_DEBUG(nht, NHT)) { + char buf1[INET6_ADDRSTRLEN]; + + inet_ntop(p->family, &p->u.prefix, buf1, + sizeof(buf1)); + zlog_debug("%s(%s): Route not in table, not advertising", + __func__, buf1); + } + bgp_path_info_unset_flag(dest, bpi, + BGP_PATH_VALID); + } + } else { + /* Delete the NHT structure if any, if we're toggling between + * enabling/disabling import check. We deregister the route + * from NHT to avoid overloading NHT and the process interaction + */ + bgp_unlink_nexthop(bpi); + + bgp_path_info_set_flag(dest, bpi, BGP_PATH_VALID); + } + } +} + +static struct bgp_static *bgp_static_new(void) +{ + return XCALLOC(MTYPE_BGP_STATIC, sizeof(struct bgp_static)); +} + +static void bgp_static_free(struct bgp_static *bgp_static) +{ + XFREE(MTYPE_ROUTE_MAP_NAME, bgp_static->rmap.name); + route_map_counter_decrement(bgp_static->rmap.map); + + if (bgp_static->prd_pretty) + XFREE(MTYPE_BGP, bgp_static->prd_pretty); + XFREE(MTYPE_ATTR, bgp_static->eth_s_id); + XFREE(MTYPE_BGP_STATIC, bgp_static); +} + +void bgp_static_update(struct bgp *bgp, const struct prefix *p, + struct bgp_static *bgp_static, afi_t afi, safi_t safi) +{ + struct bgp_dest *dest; + struct bgp_path_info *pi; + struct bgp_path_info *new; + struct bgp_path_info rmap_path; + struct attr attr; + struct attr *attr_new; + route_map_result_t ret; +#ifdef ENABLE_BGP_VNC + int vnc_implicit_withdraw = 0; + mpls_label_t label = 0; +#endif + uint32_t num_labels = 0; + + assert(bgp_static); + + if ((safi == SAFI_MPLS_VPN || safi == SAFI_ENCAP || safi == SAFI_EVPN) && + bgp_static->label != MPLS_INVALID_LABEL) + num_labels = 1; + + dest = bgp_afi_node_get(bgp->rib[afi][safi], afi, safi, p, + &bgp_static->prd); + + bgp_attr_default_set(&attr, bgp, BGP_ORIGIN_IGP); + + attr.nexthop = bgp_static->igpnexthop; + attr.med = bgp_static->igpmetric; + attr.flag |= ATTR_FLAG_BIT(BGP_ATTR_MULTI_EXIT_DISC); + + if (afi == AFI_IP) + attr.mp_nexthop_len = BGP_ATTR_NHLEN_IPV4; + + if (bgp_static->igpmetric) + bgp_attr_set_aigp_metric(&attr, bgp_static->igpmetric); + + if (bgp_static->atomic) + attr.flag |= ATTR_FLAG_BIT(BGP_ATTR_ATOMIC_AGGREGATE); + + /* Store label index, if required. */ + if (bgp_static->label_index != BGP_INVALID_LABEL_INDEX) { + attr.label_index = bgp_static->label_index; + attr.flag |= ATTR_FLAG_BIT(BGP_ATTR_PREFIX_SID); + } + + if (safi == SAFI_EVPN || safi == SAFI_MPLS_VPN || safi == SAFI_ENCAP) { + if (afi == AFI_IP) { + attr.mp_nexthop_global_in = bgp_static->igpnexthop; + attr.mp_nexthop_len = IPV4_MAX_BYTELEN; + } + } + + if (afi == AFI_L2VPN) { + if (bgp_static->gatewayIp.family == AF_INET) { + SET_IPADDR_V4(&attr.evpn_overlay.gw_ip); + memcpy(&attr.evpn_overlay.gw_ip.ipaddr_v4, + &bgp_static->gatewayIp.u.prefix4, + IPV4_MAX_BYTELEN); + } else if (bgp_static->gatewayIp.family == AF_INET6) { + SET_IPADDR_V6(&attr.evpn_overlay.gw_ip); + memcpy(&attr.evpn_overlay.gw_ip.ipaddr_v6, + &bgp_static->gatewayIp.u.prefix6, + IPV6_MAX_BYTELEN); + } + memcpy(&attr.esi, bgp_static->eth_s_id, sizeof(esi_t)); + if (bgp_static->encap_tunneltype == BGP_ENCAP_TYPE_VXLAN) { + struct bgp_encap_type_vxlan bet; + memset(&bet, 0, sizeof(bet)); + bet.vnid = p->u.prefix_evpn.prefix_addr.eth_tag; + bgp_encap_type_vxlan_to_tlv(&bet, &attr); + } + if (bgp_static->router_mac) { + bgp_add_routermac_ecom(&attr, bgp_static->router_mac); + } + } + + /* Apply route-map. */ + if (bgp_static->rmap.name) { + struct attr attr_tmp = attr; + + memset(&rmap_path, 0, sizeof(rmap_path)); + rmap_path.peer = bgp->peer_self; + rmap_path.attr = &attr_tmp; + + SET_FLAG(bgp->peer_self->rmap_type, PEER_RMAP_TYPE_NETWORK); + + ret = route_map_apply(bgp_static->rmap.map, p, &rmap_path); + + bgp->peer_self->rmap_type = 0; + + if (ret == RMAP_DENYMATCH) { + /* Free uninterned attribute. */ + bgp_attr_flush(&attr_tmp); + + /* Unintern original. */ + aspath_unintern(&attr.aspath); + bgp_static_withdraw(bgp, p, afi, safi, &bgp_static->prd); + bgp_dest_unlock_node(dest); + return; + } + + if (bgp_in_graceful_shutdown(bgp)) + bgp_attr_add_gshut_community(&attr_tmp); + + attr_new = bgp_attr_intern(&attr_tmp); + } else { + + if (bgp_in_graceful_shutdown(bgp)) + bgp_attr_add_gshut_community(&attr); + + attr_new = bgp_attr_intern(&attr); + } + + for (pi = bgp_dest_get_bgp_path_info(dest); pi; pi = pi->next) + if (pi->peer == bgp->peer_self && pi->type == ZEBRA_ROUTE_BGP + && pi->sub_type == BGP_ROUTE_STATIC) + break; + + if (pi) { + if (attrhash_cmp(pi->attr, attr_new) + && !CHECK_FLAG(pi->flags, BGP_PATH_REMOVED) + && !CHECK_FLAG(bgp->flags, BGP_FLAG_FORCE_STATIC_PROCESS)) { + bgp_dest_unlock_node(dest); + bgp_attr_unintern(&attr_new); + aspath_unintern(&attr.aspath); + return; + } else { + /* The attribute is changed. */ + bgp_path_info_set_flag(dest, pi, BGP_PATH_ATTR_CHANGED); + + /* Rewrite BGP route information. */ + if (CHECK_FLAG(pi->flags, BGP_PATH_REMOVED)) + bgp_path_info_restore(dest, pi); + else + bgp_aggregate_decrement(bgp, p, pi, afi, safi); +#ifdef ENABLE_BGP_VNC + if ((afi == AFI_IP || afi == AFI_IP6) && + safi == SAFI_UNICAST) { + if (CHECK_FLAG(pi->flags, BGP_PATH_SELECTED)) { + /* + * Implicit withdraw case. + * We have to do this before pi is + * changed + */ + ++vnc_implicit_withdraw; + vnc_import_bgp_del_route(bgp, p, pi); + vnc_import_bgp_exterior_del_route( + bgp, p, pi); + } + } +#endif + bgp_attr_unintern(&pi->attr); + pi->attr = attr_new; + pi->uptime = monotime(NULL); +#ifdef ENABLE_BGP_VNC + if ((afi == AFI_IP || afi == AFI_IP6) && + safi == SAFI_UNICAST) { + if (vnc_implicit_withdraw) { + vnc_import_bgp_add_route(bgp, p, pi); + vnc_import_bgp_exterior_add_route( + bgp, p, pi); + } + } else { + if (pi->extra) + label = decode_label( + &pi->extra->label[0]); + } +#endif + + bgp_nexthop_reachability_check(afi, safi, pi, p, dest, + bgp); + + /* Process change. */ + bgp_aggregate_increment(bgp, p, pi, afi, safi); + bgp_process(bgp, dest, afi, safi); + + if (SAFI_MPLS_VPN == safi && + bgp->inst_type == BGP_INSTANCE_TYPE_DEFAULT) { + vpn_leak_to_vrf_update(bgp, pi, + &bgp_static->prd); + } +#ifdef ENABLE_BGP_VNC + if (safi == SAFI_MPLS_VPN || safi == SAFI_ENCAP || + safi == SAFI_EVPN) + rfapiProcessUpdate(pi->peer, NULL, p, + &bgp_static->prd, pi->attr, + afi, safi, pi->type, + pi->sub_type, &label); +#endif + + if (SAFI_UNICAST == safi + && (bgp->inst_type == BGP_INSTANCE_TYPE_VRF + || bgp->inst_type + == BGP_INSTANCE_TYPE_DEFAULT)) { + vpn_leak_from_vrf_update(bgp_get_default(), bgp, + pi); + } + + bgp_dest_unlock_node(dest); + aspath_unintern(&attr.aspath); + return; + } + } + + /* Make new BGP info. */ + new = info_make(ZEBRA_ROUTE_BGP, BGP_ROUTE_STATIC, 0, bgp->peer_self, + attr_new, dest); + + if (safi == SAFI_MPLS_VPN || safi == SAFI_ENCAP || safi == SAFI_EVPN) { + SET_FLAG(new->flags, BGP_PATH_VALID); + bgp_path_info_extra_get(new); + if (num_labels) { + new->extra->label[0] = bgp_static->label; + new->extra->num_labels = num_labels; + } +#ifdef ENABLE_BGP_VNC + label = decode_label(&bgp_static->label); +#endif + } + + bgp_nexthop_reachability_check(afi, safi, new, p, dest, bgp); + + /* Aggregate address increment. */ + bgp_aggregate_increment(bgp, p, new, afi, safi); + + /* Register new BGP information. */ + bgp_path_info_add(dest, new); + + /* route_node_get lock */ + bgp_dest_unlock_node(dest); + + /* Process change. */ + bgp_process(bgp, dest, afi, safi); + + if (SAFI_UNICAST == safi && + (bgp->inst_type == BGP_INSTANCE_TYPE_VRF || + bgp->inst_type == BGP_INSTANCE_TYPE_DEFAULT)) { + vpn_leak_from_vrf_update(bgp_get_default(), bgp, new); + } + + if (SAFI_MPLS_VPN == safi && + bgp->inst_type == BGP_INSTANCE_TYPE_DEFAULT) { + vpn_leak_to_vrf_update(bgp, new, &bgp_static->prd); + } +#ifdef ENABLE_BGP_VNC + if (safi == SAFI_MPLS_VPN || safi == SAFI_ENCAP || safi == SAFI_EVPN) + rfapiProcessUpdate(new->peer, NULL, p, &bgp_static->prd, + new->attr, afi, safi, new->type, + new->sub_type, &label); +#endif + + /* Unintern original. */ + aspath_unintern(&attr.aspath); +} + +void bgp_static_withdraw(struct bgp *bgp, const struct prefix *p, afi_t afi, + safi_t safi, struct prefix_rd *prd) +{ + struct bgp_dest *dest; + struct bgp_path_info *pi; + + dest = bgp_afi_node_get(bgp->rib[afi][safi], afi, safi, p, prd); + + /* Check selected route and self inserted route. */ + for (pi = bgp_dest_get_bgp_path_info(dest); pi; pi = pi->next) + if (pi->peer == bgp->peer_self && pi->type == ZEBRA_ROUTE_BGP + && pi->sub_type == BGP_ROUTE_STATIC) + break; + + /* Withdraw static BGP route from routing table. */ + if (pi) { +#ifdef ENABLE_BGP_VNC + if (safi == SAFI_MPLS_VPN || safi == SAFI_ENCAP) + rfapiProcessWithdraw(pi->peer, NULL, p, prd, pi->attr, + afi, safi, pi->type, + 1); /* Kill, since it is an administrative change */ +#endif + if (SAFI_UNICAST == safi && + (bgp->inst_type == BGP_INSTANCE_TYPE_VRF || + bgp->inst_type == BGP_INSTANCE_TYPE_DEFAULT)) { + vpn_leak_from_vrf_withdraw(bgp_get_default(), bgp, pi); + } + if (SAFI_MPLS_VPN == safi + && bgp->inst_type == BGP_INSTANCE_TYPE_DEFAULT) { + vpn_leak_to_vrf_withdraw(pi); + } + bgp_aggregate_decrement(bgp, p, pi, afi, safi); + bgp_unlink_nexthop(pi); + bgp_path_info_delete(dest, pi); + bgp_process(bgp, dest, afi, safi); + } + + /* Unlock bgp_node_lookup. */ + bgp_dest_unlock_node(dest); +} + +/* Configure static BGP network. When user don't run zebra, static + route should be installed as valid. */ +int bgp_static_set(struct vty *vty, bool negate, const char *ip_str, + const char *rd_str, const char *label_str, afi_t afi, + safi_t safi, const char *rmap, int backdoor, + uint32_t label_index, int evpn_type, const char *esi, + const char *gwip, const char *ethtag, const char *routermac) +{ + VTY_DECLVAR_CONTEXT(bgp, bgp); + int ret; + struct prefix p; + struct bgp_static *bgp_static; + struct prefix_rd prd = {}; + struct bgp_dest *pdest; + struct bgp_dest *dest; + struct bgp_table *table; + uint8_t need_update = 0; + mpls_label_t label = MPLS_INVALID_LABEL; + struct prefix gw_ip; + + /* Convert IP prefix string to struct prefix. */ + ret = str2prefix(ip_str, &p); + if (!ret) { + vty_out(vty, "%% Malformed prefix\n"); + return CMD_WARNING_CONFIG_FAILED; + } + if (afi == AFI_IP6 && IN6_IS_ADDR_LINKLOCAL(&p.u.prefix6)) { + vty_out(vty, "%% Malformed prefix (link-local address)\n"); + return CMD_WARNING_CONFIG_FAILED; + } + + apply_mask(&p); + + if (afi == AFI_L2VPN && + (bgp_build_evpn_prefix(evpn_type, ethtag != NULL ? atol(ethtag) : 0, + &p))) { + vty_out(vty, "%% L2VPN prefix could not be forged\n"); + return CMD_WARNING_CONFIG_FAILED; + } + + if (safi == SAFI_MPLS_VPN || safi == SAFI_EVPN) { + ret = str2prefix_rd(rd_str, &prd); + if (!ret) { + vty_out(vty, "%% Malformed rd\n"); + return CMD_WARNING_CONFIG_FAILED; + } + + if (label_str) { + unsigned long label_val; + + label_val = strtoul(label_str, NULL, 10); + encode_label(label_val, &label); + } + } + + if (safi == SAFI_EVPN) { + if (esi && str2esi(esi, NULL) == 0) { + vty_out(vty, "%% Malformed ESI\n"); + return CMD_WARNING_CONFIG_FAILED; + } + if (routermac && prefix_str2mac(routermac, NULL) == 0) { + vty_out(vty, "%% Malformed Router MAC\n"); + return CMD_WARNING_CONFIG_FAILED; + } + if (gwip) { + memset(&gw_ip, 0, sizeof(gw_ip)); + ret = str2prefix(gwip, &gw_ip); + if (!ret) { + vty_out(vty, "%% Malformed GatewayIp\n"); + return CMD_WARNING_CONFIG_FAILED; + } + if ((gw_ip.family == AF_INET && + is_evpn_prefix_ipaddr_v6((struct prefix_evpn *)&p)) || + (gw_ip.family == AF_INET6 && + is_evpn_prefix_ipaddr_v4( + (struct prefix_evpn *)&p))) { + vty_out(vty, + "%% GatewayIp family differs with IP prefix\n"); + return CMD_WARNING_CONFIG_FAILED; + } + } + } + + if (safi == SAFI_MPLS_VPN || safi == SAFI_EVPN) { + pdest = bgp_node_get(bgp->route[afi][safi], + (struct prefix *)&prd); + if (!bgp_dest_has_bgp_path_info_data(pdest)) + bgp_dest_set_bgp_table_info(pdest, + bgp_table_init(bgp, afi, + safi)); + table = bgp_dest_get_bgp_table_info(pdest); + } else { + table = bgp->route[afi][safi]; + } + + if (negate) { + /* Set BGP static route configuration. */ + dest = bgp_node_lookup(bgp->route[afi][safi], &p); + + if (!dest) { + vty_out(vty, "%% Can't find static route specified\n"); + return CMD_WARNING_CONFIG_FAILED; + } + + bgp_static = bgp_dest_get_bgp_static_info(dest); + if (bgp_static) { + if ((label_index != BGP_INVALID_LABEL_INDEX) && + (label_index != bgp_static->label_index)) { + vty_out(vty, + "%% label-index doesn't match static route\n"); + bgp_dest_unlock_node(dest); + return CMD_WARNING_CONFIG_FAILED; + } + + if ((rmap && bgp_static->rmap.name) && + strcmp(rmap, bgp_static->rmap.name)) { + vty_out(vty, + "%% route-map name doesn't match static route\n"); + bgp_dest_unlock_node(dest); + return CMD_WARNING_CONFIG_FAILED; + } + + /* Update BGP RIB. */ + if (!bgp_static->backdoor) + bgp_static_withdraw(bgp, &p, afi, safi, NULL); + + /* Clear configuration. */ + bgp_static_free(bgp_static); + } + + bgp_dest_set_bgp_static_info(dest, NULL); + dest = bgp_dest_unlock_node(dest); + assert(dest); + bgp_dest_unlock_node(dest); + } else { + dest = bgp_node_get(table, &p); + + bgp_static = bgp_dest_get_bgp_static_info(dest); + if (bgp_static) { + /* Configuration change. */ + /* Label index cannot be changed. */ + if (bgp_static->label_index != label_index) { + vty_out(vty, "%% cannot change label-index\n"); + bgp_dest_unlock_node(dest); + return CMD_WARNING_CONFIG_FAILED; + } + + /* Check previous routes are installed into BGP. */ + if (bgp_static->valid + && bgp_static->backdoor != backdoor) + need_update = 1; + + bgp_static->backdoor = backdoor; + + if (rmap) { + XFREE(MTYPE_ROUTE_MAP_NAME, + bgp_static->rmap.name); + route_map_counter_decrement( + bgp_static->rmap.map); + bgp_static->rmap.name = + XSTRDUP(MTYPE_ROUTE_MAP_NAME, rmap); + bgp_static->rmap.map = + route_map_lookup_by_name(rmap); + route_map_counter_increment( + bgp_static->rmap.map); + } else { + XFREE(MTYPE_ROUTE_MAP_NAME, + bgp_static->rmap.name); + route_map_counter_decrement( + bgp_static->rmap.map); + bgp_static->rmap.map = NULL; + bgp_static->valid = 0; + } + bgp_dest_unlock_node(dest); + } else { + /* New configuration. */ + bgp_static = bgp_static_new(); + bgp_static->backdoor = backdoor; + bgp_static->valid = 0; + bgp_static->igpmetric = 0; + bgp_static->igpnexthop.s_addr = INADDR_ANY; + bgp_static->label_index = label_index; + bgp_static->label = label; + bgp_static->prd = prd; + + if (rmap) { + XFREE(MTYPE_ROUTE_MAP_NAME, + bgp_static->rmap.name); + route_map_counter_decrement( + bgp_static->rmap.map); + bgp_static->rmap.name = + XSTRDUP(MTYPE_ROUTE_MAP_NAME, rmap); + bgp_static->rmap.map = + route_map_lookup_by_name(rmap); + route_map_counter_increment( + bgp_static->rmap.map); + } + + if (safi == SAFI_EVPN) { + if (esi) { + bgp_static->eth_s_id = + XCALLOC(MTYPE_ATTR, + sizeof(esi_t)); + str2esi(esi, bgp_static->eth_s_id); + } + if (routermac) { + bgp_static->router_mac = + XCALLOC(MTYPE_ATTR, + ETH_ALEN + 1); + (void)prefix_str2mac(routermac, + bgp_static->router_mac); + } + if (gwip) + prefix_copy(&bgp_static->gatewayIp, + &gw_ip); + } + + bgp_dest_set_bgp_static_info(dest, bgp_static); + } + + bgp_static->valid = 1; + if (need_update) + bgp_static_withdraw(bgp, &p, afi, safi, NULL); + + if (!bgp_static->backdoor) + bgp_static_update(bgp, &p, bgp_static, afi, safi); + } + + return CMD_SUCCESS; +} + +void bgp_static_add(struct bgp *bgp) +{ + afi_t afi; + safi_t safi; + struct bgp_dest *dest; + struct bgp_dest *rm; + struct bgp_table *table; + struct bgp_static *bgp_static; + + SET_FLAG(bgp->flags, BGP_FLAG_FORCE_STATIC_PROCESS); + FOREACH_AFI_SAFI (afi, safi) + for (dest = bgp_table_top(bgp->route[afi][safi]); dest; + dest = bgp_route_next(dest)) { + if (!bgp_dest_has_bgp_path_info_data(dest)) + continue; + + if ((safi == SAFI_MPLS_VPN) || (safi == SAFI_ENCAP) + || (safi == SAFI_EVPN)) { + table = bgp_dest_get_bgp_table_info(dest); + + for (rm = bgp_table_top(table); rm; + rm = bgp_route_next(rm)) { + bgp_static = + bgp_dest_get_bgp_static_info( + rm); + bgp_static_update(bgp, + bgp_dest_get_prefix(rm), + bgp_static, afi, safi); + } + } else { + bgp_static_update( + bgp, bgp_dest_get_prefix(dest), + bgp_dest_get_bgp_static_info(dest), afi, + safi); + } + } + UNSET_FLAG(bgp->flags, BGP_FLAG_FORCE_STATIC_PROCESS); +} + +/* Called from bgp_delete(). Delete all static routes from the BGP + instance. */ +void bgp_static_delete(struct bgp *bgp) +{ + afi_t afi; + safi_t safi; + struct bgp_dest *dest; + struct bgp_dest *rm; + struct bgp_table *table; + struct bgp_static *bgp_static; + + FOREACH_AFI_SAFI (afi, safi) + for (dest = bgp_table_top(bgp->route[afi][safi]); dest; + dest = bgp_route_next(dest)) { + if (!bgp_dest_has_bgp_path_info_data(dest)) + continue; + + if ((safi == SAFI_MPLS_VPN) || (safi == SAFI_ENCAP) + || (safi == SAFI_EVPN)) { + table = bgp_dest_get_bgp_table_info(dest); + + for (rm = bgp_table_top(table); rm; + rm = bgp_route_next(rm)) { + bgp_static = + bgp_dest_get_bgp_static_info( + rm); + if (!bgp_static) + continue; + + bgp_static_withdraw(bgp, + bgp_dest_get_prefix( + rm), + AFI_IP, safi, + (struct prefix_rd *) + bgp_dest_get_prefix( + dest)); + bgp_static_free(bgp_static); + bgp_dest_set_bgp_static_info(rm, + NULL); + rm = bgp_dest_unlock_node(rm); + assert(rm); + } + } else { + bgp_static = bgp_dest_get_bgp_static_info(dest); + bgp_static_withdraw(bgp, + bgp_dest_get_prefix(dest), + afi, safi, NULL); + bgp_static_free(bgp_static); + bgp_dest_set_bgp_static_info(dest, NULL); + dest = bgp_dest_unlock_node(dest); + assert(dest); + } + } +} + +void bgp_static_redo_import_check(struct bgp *bgp) +{ + afi_t afi; + safi_t safi; + struct bgp_dest *dest; + struct bgp_dest *rm; + struct bgp_table *table; + struct bgp_static *bgp_static; + + /* Use this flag to force reprocessing of the route */ + SET_FLAG(bgp->flags, BGP_FLAG_FORCE_STATIC_PROCESS); + FOREACH_AFI_SAFI (afi, safi) { + for (dest = bgp_table_top(bgp->route[afi][safi]); dest; + dest = bgp_route_next(dest)) { + if (!bgp_dest_has_bgp_path_info_data(dest)) + continue; + + if ((safi == SAFI_MPLS_VPN) || (safi == SAFI_ENCAP) + || (safi == SAFI_EVPN)) { + table = bgp_dest_get_bgp_table_info(dest); + + for (rm = bgp_table_top(table); rm; + rm = bgp_route_next(rm)) { + bgp_static = + bgp_dest_get_bgp_static_info( + rm); + bgp_static_update(bgp, + bgp_dest_get_prefix(rm), + bgp_static, afi, safi); + } + } else { + bgp_static = bgp_dest_get_bgp_static_info(dest); + bgp_static_update(bgp, + bgp_dest_get_prefix(dest), + bgp_static, afi, safi); + } + } + } + UNSET_FLAG(bgp->flags, BGP_FLAG_FORCE_STATIC_PROCESS); +} + +static void bgp_purge_af_static_redist_routes(struct bgp *bgp, afi_t afi, + safi_t safi) +{ + struct bgp_table *table; + struct bgp_dest *dest; + struct bgp_path_info *pi; + + /* Do not install the aggregate route if BGP is in the + * process of termination. + */ + if (CHECK_FLAG(bgp->flags, BGP_FLAG_DELETE_IN_PROGRESS) + || (bgp->peer_self == NULL)) + return; + + table = bgp->rib[afi][safi]; + for (dest = bgp_table_top(table); dest; dest = bgp_route_next(dest)) { + for (pi = bgp_dest_get_bgp_path_info(dest); pi; pi = pi->next) { + if (pi->peer == bgp->peer_self + && ((pi->type == ZEBRA_ROUTE_BGP + && pi->sub_type == BGP_ROUTE_STATIC) + || (pi->type != ZEBRA_ROUTE_BGP + && pi->sub_type + == BGP_ROUTE_REDISTRIBUTE))) { + bgp_aggregate_decrement( + bgp, bgp_dest_get_prefix(dest), pi, afi, + safi); + bgp_unlink_nexthop(pi); + bgp_path_info_delete(dest, pi); + bgp_process(bgp, dest, afi, safi); + } + } + } +} + +/* + * Purge all networks and redistributed routes from routing table. + * Invoked upon the instance going down. + */ +void bgp_purge_static_redist_routes(struct bgp *bgp) +{ + afi_t afi; + safi_t safi; + + FOREACH_AFI_SAFI (afi, safi) + bgp_purge_af_static_redist_routes(bgp, afi, safi); +} + +static int bgp_table_map_set(struct vty *vty, afi_t afi, safi_t safi, + const char *rmap_name) +{ + VTY_DECLVAR_CONTEXT(bgp, bgp); + struct bgp_rmap *rmap; + + rmap = &bgp->table_map[afi][safi]; + if (rmap_name) { + XFREE(MTYPE_ROUTE_MAP_NAME, rmap->name); + route_map_counter_decrement(rmap->map); + rmap->name = XSTRDUP(MTYPE_ROUTE_MAP_NAME, rmap_name); + rmap->map = route_map_lookup_by_name(rmap_name); + route_map_counter_increment(rmap->map); + } else { + XFREE(MTYPE_ROUTE_MAP_NAME, rmap->name); + route_map_counter_decrement(rmap->map); + rmap->map = NULL; + } + + if (bgp_fibupd_safi(safi)) + bgp_zebra_announce_table(bgp, afi, safi); + + return CMD_SUCCESS; +} + +static int bgp_table_map_unset(struct vty *vty, afi_t afi, safi_t safi, + const char *rmap_name) +{ + VTY_DECLVAR_CONTEXT(bgp, bgp); + struct bgp_rmap *rmap; + + rmap = &bgp->table_map[afi][safi]; + XFREE(MTYPE_ROUTE_MAP_NAME, rmap->name); + route_map_counter_decrement(rmap->map); + rmap->map = NULL; + + if (bgp_fibupd_safi(safi)) + bgp_zebra_announce_table(bgp, afi, safi); + + return CMD_SUCCESS; +} + +void bgp_config_write_table_map(struct vty *vty, struct bgp *bgp, afi_t afi, + safi_t safi) +{ + if (bgp->table_map[afi][safi].name) { + vty_out(vty, " table-map %s\n", + bgp->table_map[afi][safi].name); + } +} + +DEFUN (bgp_table_map, + bgp_table_map_cmd, + "table-map WORD", + "BGP table to RIB route download filter\n" + "Name of the route map\n") +{ + int idx_word = 1; + return bgp_table_map_set(vty, bgp_node_afi(vty), bgp_node_safi(vty), + argv[idx_word]->arg); +} +DEFUN (no_bgp_table_map, + no_bgp_table_map_cmd, + "no table-map WORD", + NO_STR + "BGP table to RIB route download filter\n" + "Name of the route map\n") +{ + int idx_word = 2; + return bgp_table_map_unset(vty, bgp_node_afi(vty), bgp_node_safi(vty), + argv[idx_word]->arg); +} + +DEFPY(bgp_network, + bgp_network_cmd, + "[no] network \ + <A.B.C.D/M$prefix|A.B.C.D$address [mask A.B.C.D$netmask]> \ + [{route-map RMAP_NAME$map_name|label-index (0-1048560)$label_index| \ + backdoor$backdoor}]", + NO_STR + "Specify a network to announce via BGP\n" + "IPv4 prefix\n" + "Network number\n" + "Network mask\n" + "Network mask\n" + "Route-map to modify the attributes\n" + "Name of the route map\n" + "Label index to associate with the prefix\n" + "Label index value\n" + "Specify a BGP backdoor route\n") +{ + char addr_prefix_str[BUFSIZ]; + + if (address_str) { + int ret; + + ret = netmask_str2prefix_str(address_str, netmask_str, + addr_prefix_str, + sizeof(addr_prefix_str)); + if (!ret) { + vty_out(vty, "%% Inconsistent address and mask\n"); + return CMD_WARNING_CONFIG_FAILED; + } + } + + return bgp_static_set(vty, no, + address_str ? addr_prefix_str : prefix_str, NULL, + NULL, AFI_IP, bgp_node_safi(vty), map_name, + backdoor ? 1 : 0, + label_index ? (uint32_t)label_index + : BGP_INVALID_LABEL_INDEX, + 0, NULL, NULL, NULL, NULL); +} + +DEFPY(ipv6_bgp_network, + ipv6_bgp_network_cmd, + "[no] network X:X::X:X/M$prefix \ + [{route-map RMAP_NAME$map_name|label-index (0-1048560)$label_index}]", + NO_STR + "Specify a network to announce via BGP\n" + "IPv6 prefix\n" + "Route-map to modify the attributes\n" + "Name of the route map\n" + "Label index to associate with the prefix\n" + "Label index value\n") +{ + return bgp_static_set(vty, no, prefix_str, NULL, NULL, AFI_IP6, + bgp_node_safi(vty), map_name, 0, + label_index ? (uint32_t)label_index + : BGP_INVALID_LABEL_INDEX, + 0, NULL, NULL, NULL, NULL); +} + +static struct bgp_aggregate *bgp_aggregate_new(void) +{ + return XCALLOC(MTYPE_BGP_AGGREGATE, sizeof(struct bgp_aggregate)); +} + +void bgp_aggregate_free(struct bgp_aggregate *aggregate) +{ + XFREE(MTYPE_ROUTE_MAP_NAME, aggregate->suppress_map_name); + route_map_counter_decrement(aggregate->suppress_map); + XFREE(MTYPE_ROUTE_MAP_NAME, aggregate->rmap.name); + route_map_counter_decrement(aggregate->rmap.map); + XFREE(MTYPE_BGP_AGGREGATE, aggregate); +} + +/** + * Helper function to avoid repeated code: prepare variables for a + * `route_map_apply` call. + * + * \returns `true` on route map match, otherwise `false`. + */ +static bool aggr_suppress_map_test(struct bgp *bgp, + struct bgp_aggregate *aggregate, + struct bgp_path_info *pi) +{ + const struct prefix *p = bgp_dest_get_prefix(pi->net); + route_map_result_t rmr = RMAP_DENYMATCH; + struct bgp_path_info rmap_path = {}; + struct attr attr = {}; + + /* No route map entries created, just don't match. */ + if (aggregate->suppress_map == NULL) + return false; + + /* Call route map matching and return result. */ + attr.aspath = aspath_empty(bgp->asnotation); + rmap_path.peer = bgp->peer_self; + rmap_path.attr = &attr; + + SET_FLAG(bgp->peer_self->rmap_type, PEER_RMAP_TYPE_AGGREGATE); + rmr = route_map_apply(aggregate->suppress_map, p, &rmap_path); + bgp->peer_self->rmap_type = 0; + + bgp_attr_flush(&attr); + aspath_unintern(&attr.aspath); + + return rmr == RMAP_PERMITMATCH; +} + +/** Test whether the aggregation has suppressed this path or not. */ +static bool aggr_suppress_exists(struct bgp_aggregate *aggregate, + struct bgp_path_info *pi) +{ + if (pi->extra == NULL || pi->extra->aggr_suppressors == NULL) + return false; + + return listnode_lookup(pi->extra->aggr_suppressors, aggregate) != NULL; +} + +/** + * Suppress this path and keep the reference. + * + * \returns `true` if needs processing otherwise `false`. + */ +static bool aggr_suppress_path(struct bgp_aggregate *aggregate, + struct bgp_path_info *pi) +{ + struct bgp_path_info_extra *pie; + + /* Path is already suppressed by this aggregation. */ + if (aggr_suppress_exists(aggregate, pi)) + return false; + + pie = bgp_path_info_extra_get(pi); + + /* This is the first suppression, allocate memory and list it. */ + if (pie->aggr_suppressors == NULL) + pie->aggr_suppressors = list_new(); + + listnode_add(pie->aggr_suppressors, aggregate); + + /* Only mark for processing if suppressed. */ + if (listcount(pie->aggr_suppressors) == 1) { + if (BGP_DEBUG(update, UPDATE_OUT)) + zlog_debug("aggregate-address suppressing: %pFX", + bgp_dest_get_prefix(pi->net)); + + bgp_path_info_set_flag(pi->net, pi, BGP_PATH_ATTR_CHANGED); + return true; + } + + return false; +} + +/** + * Unsuppress this path and remove the reference. + * + * \returns `true` if needs processing otherwise `false`. + */ +static bool aggr_unsuppress_path(struct bgp_aggregate *aggregate, + struct bgp_path_info *pi) +{ + /* Path wasn't suppressed. */ + if (!aggr_suppress_exists(aggregate, pi)) + return false; + + listnode_delete(pi->extra->aggr_suppressors, aggregate); + + /* Unsuppress and free extra memory if last item. */ + if (listcount(pi->extra->aggr_suppressors) == 0) { + if (BGP_DEBUG(update, UPDATE_OUT)) + zlog_debug("aggregate-address unsuppressing: %pFX", + bgp_dest_get_prefix(pi->net)); + + list_delete(&pi->extra->aggr_suppressors); + bgp_path_info_set_flag(pi->net, pi, BGP_PATH_ATTR_CHANGED); + return true; + } + + return false; +} + +static bool bgp_aggregate_info_same(struct bgp_path_info *pi, uint8_t origin, + struct aspath *aspath, + struct community *comm, + struct ecommunity *ecomm, + struct lcommunity *lcomm) +{ + static struct aspath *ae = NULL; + enum asnotation_mode asnotation; + + asnotation = bgp_get_asnotation(NULL); + + if (!aspath) + ae = aspath_empty(asnotation); + + if (!pi) + return false; + + if (origin != pi->attr->origin) + return false; + + if (!aspath_cmp(pi->attr->aspath, (aspath) ? aspath : ae)) + return false; + + if (!community_cmp(bgp_attr_get_community(pi->attr), comm)) + return false; + + if (!ecommunity_cmp(bgp_attr_get_ecommunity(pi->attr), ecomm)) + return false; + + if (!lcommunity_cmp(bgp_attr_get_lcommunity(pi->attr), lcomm)) + return false; + + if (!CHECK_FLAG(pi->flags, BGP_PATH_VALID)) + return false; + + return true; +} + +static void bgp_aggregate_install( + struct bgp *bgp, afi_t afi, safi_t safi, const struct prefix *p, + uint8_t origin, struct aspath *aspath, struct community *community, + struct ecommunity *ecommunity, struct lcommunity *lcommunity, + uint8_t atomic_aggregate, struct bgp_aggregate *aggregate) +{ + struct bgp_dest *dest; + struct bgp_table *table; + struct bgp_path_info *pi, *orig, *new; + struct attr *attr; + + table = bgp->rib[afi][safi]; + + dest = bgp_node_get(table, p); + + for (orig = pi = bgp_dest_get_bgp_path_info(dest); pi; pi = pi->next) + if (pi->peer == bgp->peer_self && pi->type == ZEBRA_ROUTE_BGP + && pi->sub_type == BGP_ROUTE_AGGREGATE) + break; + + /* + * If we have paths with different MEDs, then don't install + * (or uninstall) the aggregate route. + */ + if (aggregate->match_med && aggregate->med_mismatched) + goto uninstall_aggregate_route; + + if (aggregate->count > 0) { + /* + * If the aggregate information has not changed + * no need to re-install it again. + */ + if (pi && bgp_aggregate_info_same(pi, origin, aspath, community, + ecommunity, lcommunity)) { + bgp_dest_unlock_node(dest); + + if (aspath) + aspath_free(aspath); + if (community) + community_free(&community); + if (ecommunity) + ecommunity_free(&ecommunity); + if (lcommunity) + lcommunity_free(&lcommunity); + + return; + } + + /* + * Mark the old as unusable + */ + if (pi) + bgp_path_info_delete(dest, pi); + + attr = bgp_attr_aggregate_intern( + bgp, origin, aspath, community, ecommunity, lcommunity, + aggregate, atomic_aggregate, p); + + if (!attr) { + aspath_free(aspath); + community_free(&community); + ecommunity_free(&ecommunity); + lcommunity_free(&lcommunity); + bgp_dest_unlock_node(dest); + bgp_aggregate_delete(bgp, p, afi, safi, aggregate); + if (BGP_DEBUG(update_groups, UPDATE_GROUPS)) + zlog_debug("%s: %pFX null attribute", __func__, + p); + return; + } + + new = info_make(ZEBRA_ROUTE_BGP, BGP_ROUTE_AGGREGATE, 0, + bgp->peer_self, attr, dest); + + SET_FLAG(new->flags, BGP_PATH_VALID); + + bgp_path_info_add(dest, new); + bgp_process(bgp, dest, afi, safi); + } else { + uninstall_aggregate_route: + for (pi = orig; pi; pi = pi->next) + if (pi->peer == bgp->peer_self + && pi->type == ZEBRA_ROUTE_BGP + && pi->sub_type == BGP_ROUTE_AGGREGATE) + break; + + /* Withdraw static BGP route from routing table. */ + if (pi) { + bgp_path_info_delete(dest, pi); + bgp_process(bgp, dest, afi, safi); + } + } + + bgp_dest_unlock_node(dest); +} + +/** + * Check if the current path has different MED than other known paths. + * + * \returns `true` if the MED matched the others else `false`. + */ +static bool bgp_aggregate_med_match(struct bgp_aggregate *aggregate, + struct bgp *bgp, struct bgp_path_info *pi) +{ + uint32_t cur_med = bgp_med_value(pi->attr, bgp); + + /* This is the first route being analyzed. */ + if (!aggregate->med_initialized) { + aggregate->med_initialized = true; + aggregate->med_mismatched = false; + aggregate->med_matched_value = cur_med; + } else { + /* Check if routes with different MED showed up. */ + if (cur_med != aggregate->med_matched_value) + aggregate->med_mismatched = true; + } + + return !aggregate->med_mismatched; +} + +/** + * Initializes and tests all routes in the aggregate address path for MED + * values. + * + * \returns `true` if all MEDs are the same otherwise `false`. + */ +static bool bgp_aggregate_test_all_med(struct bgp_aggregate *aggregate, + struct bgp *bgp, const struct prefix *p, + afi_t afi, safi_t safi) +{ + struct bgp_table *table = bgp->rib[afi][safi]; + const struct prefix *dest_p; + struct bgp_dest *dest, *top; + struct bgp_path_info *pi; + bool med_matched = true; + + aggregate->med_initialized = false; + + top = bgp_node_get(table, p); + for (dest = bgp_node_get(table, p); dest; + dest = bgp_route_next_until(dest, top)) { + dest_p = bgp_dest_get_prefix(dest); + if (dest_p->prefixlen <= p->prefixlen) + continue; + + for (pi = bgp_dest_get_bgp_path_info(dest); pi; pi = pi->next) { + if (BGP_PATH_HOLDDOWN(pi)) + continue; + if (pi->sub_type == BGP_ROUTE_AGGREGATE) + continue; + if (!bgp_aggregate_med_match(aggregate, bgp, pi)) { + med_matched = false; + break; + } + } + if (!med_matched) + break; + } + bgp_dest_unlock_node(top); + + return med_matched; +} + +/** + * Toggles the route suppression status for this aggregate address + * configuration. + */ +void bgp_aggregate_toggle_suppressed(struct bgp_aggregate *aggregate, + struct bgp *bgp, const struct prefix *p, + afi_t afi, safi_t safi, bool suppress) +{ + struct bgp_table *table = bgp->rib[afi][safi]; + const struct prefix *dest_p; + struct bgp_dest *dest, *top; + struct bgp_path_info *pi; + bool toggle_suppression; + + /* We've found a different MED we must revert any suppressed routes. */ + top = bgp_node_get(table, p); + for (dest = bgp_node_get(table, p); dest; + dest = bgp_route_next_until(dest, top)) { + dest_p = bgp_dest_get_prefix(dest); + if (dest_p->prefixlen <= p->prefixlen) + continue; + + toggle_suppression = false; + for (pi = bgp_dest_get_bgp_path_info(dest); pi; pi = pi->next) { + if (BGP_PATH_HOLDDOWN(pi)) + continue; + if (pi->sub_type == BGP_ROUTE_AGGREGATE) + continue; + + /* We are toggling suppression back. */ + if (suppress) { + /* Suppress route if not suppressed already. */ + if (aggr_suppress_path(aggregate, pi)) + toggle_suppression = true; + continue; + } + + /* Install route if there is no more suppression. */ + if (aggr_unsuppress_path(aggregate, pi)) + toggle_suppression = true; + } + + if (toggle_suppression) + bgp_process(bgp, dest, afi, safi); + } + bgp_dest_unlock_node(top); +} + +/** + * Aggregate address MED matching incremental test: this function is called + * when the initial aggregation occurred and we are only testing a single + * new path. + * + * In addition to testing and setting the MED validity it also installs back + * suppressed routes (if summary is configured). + * + * Must not be called in `bgp_aggregate_route`. + */ +static void bgp_aggregate_med_update(struct bgp_aggregate *aggregate, + struct bgp *bgp, const struct prefix *p, + afi_t afi, safi_t safi, + struct bgp_path_info *pi) +{ + /* MED matching disabled. */ + if (!aggregate->match_med) + return; + + /* Aggregation with different MED, recheck if we have got equal MEDs + * now. + */ + if (aggregate->med_mismatched && + bgp_aggregate_test_all_med(aggregate, bgp, p, afi, safi) && + aggregate->summary_only) + bgp_aggregate_toggle_suppressed(aggregate, bgp, p, afi, safi, + true); + else + bgp_aggregate_med_match(aggregate, bgp, pi); + + /* No mismatches, just quit. */ + if (!aggregate->med_mismatched) + return; + + /* Route summarization is disabled. */ + if (!aggregate->summary_only) + return; + + bgp_aggregate_toggle_suppressed(aggregate, bgp, p, afi, safi, false); +} + +/* Update an aggregate as routes are added/removed from the BGP table */ +bool bgp_aggregate_route(struct bgp *bgp, const struct prefix *p, afi_t afi, + safi_t safi, struct bgp_aggregate *aggregate) +{ + struct bgp_table *table; + struct bgp_dest *top; + struct bgp_dest *dest; + uint8_t origin; + struct aspath *aspath = NULL; + struct community *community = NULL; + struct ecommunity *ecommunity = NULL; + struct lcommunity *lcommunity = NULL; + struct bgp_path_info *pi; + unsigned long match = 0; + uint8_t atomic_aggregate = 0; + + /* If the bgp instance is being deleted or self peer is deleted + * then do not create aggregate route + */ + if (CHECK_FLAG(bgp->flags, BGP_FLAG_DELETE_IN_PROGRESS) || + bgp->peer_self == NULL) + return false; + + /* Initialize and test routes for MED difference. */ + if (aggregate->match_med) + bgp_aggregate_test_all_med(aggregate, bgp, p, afi, safi); + + /* + * Reset aggregate count: we might've been called from route map + * update so in that case we must retest all more specific routes. + * + * \see `bgp_route_map_process_update`. + */ + aggregate->count = 0; + aggregate->incomplete_origin_count = 0; + aggregate->incomplete_origin_count = 0; + aggregate->egp_origin_count = 0; + + /* ORIGIN attribute: If at least one route among routes that are + aggregated has ORIGIN with the value INCOMPLETE, then the + aggregated route must have the ORIGIN attribute with the value + INCOMPLETE. Otherwise, if at least one route among routes that + are aggregated has ORIGIN with the value EGP, then the aggregated + route must have the origin attribute with the value EGP. In all + other case the value of the ORIGIN attribute of the aggregated + route is INTERNAL. */ + origin = BGP_ORIGIN_IGP; + + table = bgp->rib[afi][safi]; + + top = bgp_node_get(table, p); + for (dest = bgp_node_get(table, p); dest; + dest = bgp_route_next_until(dest, top)) { + const struct prefix *dest_p = bgp_dest_get_prefix(dest); + + if (dest_p->prefixlen <= p->prefixlen) + continue; + + /* If suppress fib is enabled and route not installed + * in FIB, skip the route + */ + if (!bgp_check_advertise(bgp, dest, safi)) + continue; + + match = 0; + + for (pi = bgp_dest_get_bgp_path_info(dest); pi; pi = pi->next) { + if (BGP_PATH_HOLDDOWN(pi)) + continue; + + if (pi->attr->flag + & ATTR_FLAG_BIT(BGP_ATTR_ATOMIC_AGGREGATE)) + atomic_aggregate = 1; + + if (pi->sub_type == BGP_ROUTE_AGGREGATE) + continue; + + /* + * summary-only aggregate route suppress + * aggregated route announcements. + * + * MED matching: + * Don't create summaries if MED didn't match + * otherwise neither the specific routes and the + * aggregation will be announced. + */ + if (aggregate->summary_only + && AGGREGATE_MED_VALID(aggregate)) { + if (aggr_suppress_path(aggregate, pi)) + match++; + } + + /* + * Suppress more specific routes that match the route + * map results. + * + * MED matching: + * Don't suppress routes if MED matching is enabled and + * it mismatched otherwise we might end up with no + * routes for this path. + */ + if (aggregate->suppress_map_name + && AGGREGATE_MED_VALID(aggregate) + && aggr_suppress_map_test(bgp, aggregate, pi)) { + if (aggr_suppress_path(aggregate, pi)) + match++; + } + + aggregate->count++; + + /* + * If at least one route among routes that are + * aggregated has ORIGIN with the value INCOMPLETE, + * then the aggregated route MUST have the ORIGIN + * attribute with the value INCOMPLETE. Otherwise, if + * at least one route among routes that are aggregated + * has ORIGIN with the value EGP, then the aggregated + * route MUST have the ORIGIN attribute with the value + * EGP. + */ + switch (pi->attr->origin) { + case BGP_ORIGIN_INCOMPLETE: + aggregate->incomplete_origin_count++; + break; + case BGP_ORIGIN_EGP: + aggregate->egp_origin_count++; + break; + default: + /*Do nothing. + */ + break; + } + + if (!aggregate->as_set) + continue; + + /* + * as-set aggregate route generate origin, as path, + * and community aggregation. + */ + /* Compute aggregate route's as-path. + */ + bgp_compute_aggregate_aspath_hash(aggregate, + pi->attr->aspath); + + /* Compute aggregate route's community. + */ + if (bgp_attr_get_community(pi->attr)) + bgp_compute_aggregate_community_hash( + aggregate, + bgp_attr_get_community(pi->attr)); + + /* Compute aggregate route's extended community. + */ + if (bgp_attr_get_ecommunity(pi->attr)) + bgp_compute_aggregate_ecommunity_hash( + aggregate, + bgp_attr_get_ecommunity(pi->attr)); + + /* Compute aggregate route's large community. + */ + if (bgp_attr_get_lcommunity(pi->attr)) + bgp_compute_aggregate_lcommunity_hash( + aggregate, + bgp_attr_get_lcommunity(pi->attr)); + } + if (match) + bgp_process(bgp, dest, afi, safi); + } + if (aggregate->as_set) { + bgp_compute_aggregate_aspath_val(aggregate); + bgp_compute_aggregate_community_val(aggregate); + bgp_compute_aggregate_ecommunity_val(aggregate); + bgp_compute_aggregate_lcommunity_val(aggregate); + } + + + bgp_dest_unlock_node(top); + + + if (aggregate->incomplete_origin_count > 0) + origin = BGP_ORIGIN_INCOMPLETE; + else if (aggregate->egp_origin_count > 0) + origin = BGP_ORIGIN_EGP; + + if (aggregate->origin != BGP_ORIGIN_UNSPECIFIED) + origin = aggregate->origin; + + if (aggregate->as_set) { + if (aggregate->aspath) + /* Retrieve aggregate route's as-path. + */ + aspath = aspath_dup(aggregate->aspath); + + if (aggregate->community) + /* Retrieve aggregate route's community. + */ + community = community_dup(aggregate->community); + + if (aggregate->ecommunity) + /* Retrieve aggregate route's ecommunity. + */ + ecommunity = ecommunity_dup(aggregate->ecommunity); + + if (aggregate->lcommunity) + /* Retrieve aggregate route's lcommunity. + */ + lcommunity = lcommunity_dup(aggregate->lcommunity); + } + + bgp_aggregate_install(bgp, afi, safi, p, origin, aspath, community, + ecommunity, lcommunity, atomic_aggregate, + aggregate); + + return true; +} + +void bgp_aggregate_delete(struct bgp *bgp, const struct prefix *p, afi_t afi, + safi_t safi, struct bgp_aggregate *aggregate) +{ + struct bgp_table *table; + struct bgp_dest *top; + struct bgp_dest *dest; + struct bgp_path_info *pi; + unsigned long match; + + table = bgp->rib[afi][safi]; + + /* If routes exists below this node, generate aggregate routes. */ + top = bgp_node_get(table, p); + for (dest = bgp_node_get(table, p); dest; + dest = bgp_route_next_until(dest, top)) { + const struct prefix *dest_p = bgp_dest_get_prefix(dest); + + if (dest_p->prefixlen <= p->prefixlen) + continue; + match = 0; + + for (pi = bgp_dest_get_bgp_path_info(dest); pi; pi = pi->next) { + if (BGP_PATH_HOLDDOWN(pi)) + continue; + + if (pi->sub_type == BGP_ROUTE_AGGREGATE) + continue; + + /* + * This route is suppressed: attempt to unsuppress it. + * + * `aggr_unsuppress_path` will fail if this particular + * aggregate route was not the suppressor. + */ + if (pi->extra && pi->extra->aggr_suppressors && + listcount(pi->extra->aggr_suppressors)) { + if (aggr_unsuppress_path(aggregate, pi)) + match++; + } + + aggregate->count--; + + if (pi->attr->origin == BGP_ORIGIN_INCOMPLETE) + aggregate->incomplete_origin_count--; + else if (pi->attr->origin == BGP_ORIGIN_EGP) + aggregate->egp_origin_count--; + + if (aggregate->as_set) { + /* Remove as-path from aggregate. + */ + bgp_remove_aspath_from_aggregate_hash( + aggregate, + pi->attr->aspath); + + if (bgp_attr_get_community(pi->attr)) + /* Remove community from aggregate. + */ + bgp_remove_comm_from_aggregate_hash( + aggregate, + bgp_attr_get_community( + pi->attr)); + + if (bgp_attr_get_ecommunity(pi->attr)) + /* Remove ecommunity from aggregate. + */ + bgp_remove_ecomm_from_aggregate_hash( + aggregate, + bgp_attr_get_ecommunity( + pi->attr)); + + if (bgp_attr_get_lcommunity(pi->attr)) + /* Remove lcommunity from aggregate. + */ + bgp_remove_lcomm_from_aggregate_hash( + aggregate, + bgp_attr_get_lcommunity( + pi->attr)); + } + } + + /* If this node was suppressed, process the change. */ + if (match) + bgp_process(bgp, dest, afi, safi); + } + if (aggregate->as_set) { + aspath_free(aggregate->aspath); + aggregate->aspath = NULL; + if (aggregate->community) + community_free(&aggregate->community); + if (aggregate->ecommunity) + ecommunity_free(&aggregate->ecommunity); + if (aggregate->lcommunity) + lcommunity_free(&aggregate->lcommunity); + } + + bgp_dest_unlock_node(top); +} + +static void bgp_add_route_to_aggregate(struct bgp *bgp, + const struct prefix *aggr_p, + struct bgp_path_info *pinew, afi_t afi, + safi_t safi, + struct bgp_aggregate *aggregate) +{ + uint8_t origin; + struct aspath *aspath = NULL; + uint8_t atomic_aggregate = 0; + struct community *community = NULL; + struct ecommunity *ecommunity = NULL; + struct lcommunity *lcommunity = NULL; + + /* If the bgp instance is being deleted or self peer is deleted + * then do not create aggregate route + */ + if (CHECK_FLAG(bgp->flags, BGP_FLAG_DELETE_IN_PROGRESS) + || (bgp->peer_self == NULL)) + return; + + /* ORIGIN attribute: If at least one route among routes that are + * aggregated has ORIGIN with the value INCOMPLETE, then the + * aggregated route must have the ORIGIN attribute with the value + * INCOMPLETE. Otherwise, if at least one route among routes that + * are aggregated has ORIGIN with the value EGP, then the aggregated + * route must have the origin attribute with the value EGP. In all + * other case the value of the ORIGIN attribute of the aggregated + * route is INTERNAL. + */ + origin = BGP_ORIGIN_IGP; + + aggregate->count++; + + /* + * This must be called before `summary` check to avoid + * "suppressing" twice. + */ + if (aggregate->match_med) + bgp_aggregate_med_update(aggregate, bgp, aggr_p, afi, safi, + pinew); + + if (aggregate->summary_only && AGGREGATE_MED_VALID(aggregate)) + aggr_suppress_path(aggregate, pinew); + + if (aggregate->suppress_map_name && AGGREGATE_MED_VALID(aggregate) + && aggr_suppress_map_test(bgp, aggregate, pinew)) + aggr_suppress_path(aggregate, pinew); + + switch (pinew->attr->origin) { + case BGP_ORIGIN_INCOMPLETE: + aggregate->incomplete_origin_count++; + break; + case BGP_ORIGIN_EGP: + aggregate->egp_origin_count++; + break; + default: + /* Do nothing. + */ + break; + } + + if (aggregate->incomplete_origin_count > 0) + origin = BGP_ORIGIN_INCOMPLETE; + else if (aggregate->egp_origin_count > 0) + origin = BGP_ORIGIN_EGP; + + if (aggregate->origin != BGP_ORIGIN_UNSPECIFIED) + origin = aggregate->origin; + + if (aggregate->as_set) { + /* Compute aggregate route's as-path. + */ + bgp_compute_aggregate_aspath(aggregate, + pinew->attr->aspath); + + /* Compute aggregate route's community. + */ + if (bgp_attr_get_community(pinew->attr)) + bgp_compute_aggregate_community( + aggregate, bgp_attr_get_community(pinew->attr)); + + /* Compute aggregate route's extended community. + */ + if (bgp_attr_get_ecommunity(pinew->attr)) + bgp_compute_aggregate_ecommunity( + aggregate, + bgp_attr_get_ecommunity(pinew->attr)); + + /* Compute aggregate route's large community. + */ + if (bgp_attr_get_lcommunity(pinew->attr)) + bgp_compute_aggregate_lcommunity( + aggregate, + bgp_attr_get_lcommunity(pinew->attr)); + + /* Retrieve aggregate route's as-path. + */ + if (aggregate->aspath) + aspath = aspath_dup(aggregate->aspath); + + /* Retrieve aggregate route's community. + */ + if (aggregate->community) + community = community_dup(aggregate->community); + + /* Retrieve aggregate route's ecommunity. + */ + if (aggregate->ecommunity) + ecommunity = ecommunity_dup(aggregate->ecommunity); + + /* Retrieve aggregate route's lcommunity. + */ + if (aggregate->lcommunity) + lcommunity = lcommunity_dup(aggregate->lcommunity); + } + + bgp_aggregate_install(bgp, afi, safi, aggr_p, origin, + aspath, community, ecommunity, + lcommunity, atomic_aggregate, aggregate); +} + +static void bgp_remove_route_from_aggregate(struct bgp *bgp, afi_t afi, + safi_t safi, + struct bgp_path_info *pi, + struct bgp_aggregate *aggregate, + const struct prefix *aggr_p) +{ + uint8_t origin; + struct aspath *aspath = NULL; + uint8_t atomic_aggregate = 0; + struct community *community = NULL; + struct ecommunity *ecommunity = NULL; + struct lcommunity *lcommunity = NULL; + unsigned long match = 0; + + /* If the bgp instance is being deleted or self peer is deleted + * then do not create aggregate route + */ + if (CHECK_FLAG(bgp->flags, BGP_FLAG_DELETE_IN_PROGRESS) + || (bgp->peer_self == NULL)) + return; + + if (BGP_PATH_HOLDDOWN(pi)) + return; + + if (pi->sub_type == BGP_ROUTE_AGGREGATE) + return; + + if (aggregate->summary_only && AGGREGATE_MED_VALID(aggregate)) + if (aggr_unsuppress_path(aggregate, pi)) + match++; + + if (aggregate->suppress_map_name && AGGREGATE_MED_VALID(aggregate) + && aggr_suppress_map_test(bgp, aggregate, pi)) + if (aggr_unsuppress_path(aggregate, pi)) + match++; + + /* + * This must be called after `summary`, `suppress-map` check to avoid + * "unsuppressing" twice. + */ + if (aggregate->match_med) + bgp_aggregate_med_update(aggregate, bgp, aggr_p, afi, safi, pi); + + if (aggregate->count > 0) + aggregate->count--; + + if (pi->attr->origin == BGP_ORIGIN_INCOMPLETE) + aggregate->incomplete_origin_count--; + else if (pi->attr->origin == BGP_ORIGIN_EGP) + aggregate->egp_origin_count--; + + if (aggregate->as_set) { + /* Remove as-path from aggregate. + */ + bgp_remove_aspath_from_aggregate(aggregate, + pi->attr->aspath); + + if (bgp_attr_get_community(pi->attr)) + /* Remove community from aggregate. + */ + bgp_remove_community_from_aggregate( + aggregate, bgp_attr_get_community(pi->attr)); + + if (bgp_attr_get_ecommunity(pi->attr)) + /* Remove ecommunity from aggregate. + */ + bgp_remove_ecommunity_from_aggregate( + aggregate, bgp_attr_get_ecommunity(pi->attr)); + + if (bgp_attr_get_lcommunity(pi->attr)) + /* Remove lcommunity from aggregate. + */ + bgp_remove_lcommunity_from_aggregate( + aggregate, bgp_attr_get_lcommunity(pi->attr)); + } + + /* If this node was suppressed, process the change. */ + if (match) + bgp_process(bgp, pi->net, afi, safi); + + origin = BGP_ORIGIN_IGP; + if (aggregate->incomplete_origin_count > 0) + origin = BGP_ORIGIN_INCOMPLETE; + else if (aggregate->egp_origin_count > 0) + origin = BGP_ORIGIN_EGP; + + if (aggregate->origin != BGP_ORIGIN_UNSPECIFIED) + origin = aggregate->origin; + + if (aggregate->as_set) { + /* Retrieve aggregate route's as-path. + */ + if (aggregate->aspath) + aspath = aspath_dup(aggregate->aspath); + + /* Retrieve aggregate route's community. + */ + if (aggregate->community) + community = community_dup(aggregate->community); + + /* Retrieve aggregate route's ecommunity. + */ + if (aggregate->ecommunity) + ecommunity = ecommunity_dup(aggregate->ecommunity); + + /* Retrieve aggregate route's lcommunity. + */ + if (aggregate->lcommunity) + lcommunity = lcommunity_dup(aggregate->lcommunity); + } + + bgp_aggregate_install(bgp, afi, safi, aggr_p, origin, + aspath, community, ecommunity, + lcommunity, atomic_aggregate, aggregate); +} + +void bgp_aggregate_increment(struct bgp *bgp, const struct prefix *p, + struct bgp_path_info *pi, afi_t afi, safi_t safi) +{ + struct bgp_dest *child; + struct bgp_dest *dest; + struct bgp_aggregate *aggregate; + struct bgp_table *table; + + table = bgp->aggregate[afi][safi]; + + /* No aggregates configured. */ + if (bgp_table_top_nolock(table) == NULL) + return; + + if (p->prefixlen == 0) + return; + + if (BGP_PATH_HOLDDOWN(pi)) + return; + + /* If suppress fib is enabled and route not installed + * in FIB, do not update the aggregate route + */ + if (!bgp_check_advertise(bgp, pi->net, safi)) + return; + + child = bgp_node_get(table, p); + + /* Aggregate address configuration check. */ + for (dest = child; dest; dest = bgp_dest_parent_nolock(dest)) { + const struct prefix *dest_p = bgp_dest_get_prefix(dest); + + aggregate = bgp_dest_get_bgp_aggregate_info(dest); + if (aggregate != NULL && dest_p->prefixlen < p->prefixlen) { + bgp_add_route_to_aggregate(bgp, dest_p, pi, afi, safi, + aggregate); + } + } + bgp_dest_unlock_node(child); +} + +void bgp_aggregate_decrement(struct bgp *bgp, const struct prefix *p, + struct bgp_path_info *del, afi_t afi, safi_t safi) +{ + struct bgp_dest *child; + struct bgp_dest *dest; + struct bgp_aggregate *aggregate; + struct bgp_table *table; + + table = bgp->aggregate[afi][safi]; + + /* No aggregates configured. */ + if (bgp_table_top_nolock(table) == NULL) + return; + + if (p->prefixlen == 0) + return; + + child = bgp_node_get(table, p); + + /* Aggregate address configuration check. */ + for (dest = child; dest; dest = bgp_dest_parent_nolock(dest)) { + const struct prefix *dest_p = bgp_dest_get_prefix(dest); + + aggregate = bgp_dest_get_bgp_aggregate_info(dest); + if (aggregate != NULL && dest_p->prefixlen < p->prefixlen) { + bgp_remove_route_from_aggregate(bgp, afi, safi, del, + aggregate, dest_p); + } + } + bgp_dest_unlock_node(child); +} + +/* Aggregate route attribute. */ +#define AGGREGATE_SUMMARY_ONLY 1 +#define AGGREGATE_AS_SET 1 +#define AGGREGATE_AS_UNSET 0 + +static const char *bgp_origin2str(uint8_t origin) +{ + switch (origin) { + case BGP_ORIGIN_IGP: + return "igp"; + case BGP_ORIGIN_EGP: + return "egp"; + case BGP_ORIGIN_INCOMPLETE: + return "incomplete"; + } + return "n/a"; +} + +static const char *bgp_rpki_validation2str(enum rpki_states v_state) +{ + switch (v_state) { + case RPKI_NOT_BEING_USED: + return "not used"; + case RPKI_VALID: + return "valid"; + case RPKI_NOTFOUND: + return "not found"; + case RPKI_INVALID: + return "invalid"; + } + + assert(!"We should never get here this is a dev escape"); + return "ERROR"; +} + +static int bgp_aggregate_unset(struct vty *vty, const char *prefix_str, + afi_t afi, safi_t safi) +{ + VTY_DECLVAR_CONTEXT(bgp, bgp); + int ret; + struct prefix p; + struct bgp_dest *dest; + struct bgp_aggregate *aggregate; + + /* Convert string to prefix structure. */ + ret = str2prefix(prefix_str, &p); + if (!ret) { + vty_out(vty, "Malformed prefix\n"); + return CMD_WARNING_CONFIG_FAILED; + } + apply_mask(&p); + + /* Old configuration check. */ + dest = bgp_node_lookup(bgp->aggregate[afi][safi], &p); + if (!dest) { + vty_out(vty, + "%% There is no aggregate-address configuration.\n"); + return CMD_WARNING_CONFIG_FAILED; + } + + aggregate = bgp_dest_get_bgp_aggregate_info(dest); + bgp_aggregate_delete(bgp, &p, afi, safi, aggregate); + bgp_aggregate_install(bgp, afi, safi, &p, 0, NULL, NULL, + NULL, NULL, 0, aggregate); + + /* Unlock aggregate address configuration. */ + bgp_dest_set_bgp_aggregate_info(dest, NULL); + + bgp_free_aggregate_info(aggregate); + dest = bgp_dest_unlock_node(dest); + assert(dest); + bgp_dest_unlock_node(dest); + + return CMD_SUCCESS; +} + +static int bgp_aggregate_set(struct vty *vty, const char *prefix_str, afi_t afi, + safi_t safi, const char *rmap, + uint8_t summary_only, uint8_t as_set, + uint8_t origin, bool match_med, + const char *suppress_map) +{ + VTY_DECLVAR_CONTEXT(bgp, bgp); + int ret; + struct prefix p; + struct bgp_dest *dest; + struct bgp_aggregate *aggregate; + uint8_t as_set_new = as_set; + + if (suppress_map && summary_only) { + vty_out(vty, + "'summary-only' and 'suppress-map' can't be used at the same time\n"); + return CMD_WARNING_CONFIG_FAILED; + } + + /* Convert string to prefix structure. */ + ret = str2prefix(prefix_str, &p); + if (!ret) { + vty_out(vty, "Malformed prefix\n"); + return CMD_WARNING_CONFIG_FAILED; + } + apply_mask(&p); + + if ((afi == AFI_IP && p.prefixlen == IPV4_MAX_BITLEN) || + (afi == AFI_IP6 && p.prefixlen == IPV6_MAX_BITLEN)) { + vty_out(vty, "Specified prefix: %s will not result in any useful aggregation, disallowing\n", + prefix_str); + return CMD_WARNING_CONFIG_FAILED; + } + + /* Old configuration check. */ + dest = bgp_node_get(bgp->aggregate[afi][safi], &p); + aggregate = bgp_dest_get_bgp_aggregate_info(dest); + + if (aggregate) { + vty_out(vty, "There is already same aggregate network.\n"); + /* try to remove the old entry */ + ret = bgp_aggregate_unset(vty, prefix_str, afi, safi); + if (ret) { + vty_out(vty, "Error deleting aggregate.\n"); + bgp_dest_unlock_node(dest); + return CMD_WARNING_CONFIG_FAILED; + } + } + + /* Make aggregate address structure. */ + aggregate = bgp_aggregate_new(); + aggregate->summary_only = summary_only; + aggregate->match_med = match_med; + + /* Network operators MUST NOT locally generate any new + * announcements containing AS_SET or AS_CONFED_SET. If they have + * announced routes with AS_SET or AS_CONFED_SET in them, then they + * SHOULD withdraw those routes and re-announce routes for the + * aggregate or component prefixes (i.e., the more-specific routes + * subsumed by the previously aggregated route) without AS_SET + * or AS_CONFED_SET in the updates. + */ + if (bgp->reject_as_sets) { + if (as_set == AGGREGATE_AS_SET) { + as_set_new = AGGREGATE_AS_UNSET; + zlog_warn( + "%s: Ignoring as-set because `bgp reject-as-sets` is enabled.", + __func__); + vty_out(vty, + "Ignoring as-set because `bgp reject-as-sets` is enabled.\n"); + } + } + + aggregate->as_set = as_set_new; + aggregate->safi = safi; + /* Override ORIGIN attribute if defined. + * E.g.: Cisco and Juniper set ORIGIN for aggregated address + * to IGP which is not what rfc4271 says. + * This enables the same behavior, optionally. + */ + aggregate->origin = origin; + + if (rmap) { + XFREE(MTYPE_ROUTE_MAP_NAME, aggregate->rmap.name); + route_map_counter_decrement(aggregate->rmap.map); + aggregate->rmap.name = + XSTRDUP(MTYPE_ROUTE_MAP_NAME, rmap); + aggregate->rmap.map = route_map_lookup_by_name(rmap); + route_map_counter_increment(aggregate->rmap.map); + } + + if (suppress_map) { + XFREE(MTYPE_ROUTE_MAP_NAME, aggregate->suppress_map_name); + route_map_counter_decrement(aggregate->suppress_map); + + aggregate->suppress_map_name = + XSTRDUP(MTYPE_ROUTE_MAP_NAME, suppress_map); + aggregate->suppress_map = + route_map_lookup_by_name(aggregate->suppress_map_name); + route_map_counter_increment(aggregate->suppress_map); + } + + bgp_dest_set_bgp_aggregate_info(dest, aggregate); + + /* Aggregate address insert into BGP routing table. */ + if (!bgp_aggregate_route(bgp, &p, afi, safi, aggregate)) { + bgp_aggregate_free(aggregate); + bgp_dest_unlock_node(dest); + } + + return CMD_SUCCESS; +} + +DEFPY(aggregate_addressv4, aggregate_addressv4_cmd, + "[no] aggregate-address <A.B.C.D/M$prefix|A.B.C.D$addr A.B.C.D$mask> [{" + "as-set$as_set_s" + "|summary-only$summary_only" + "|route-map RMAP_NAME$rmap_name" + "|origin <egp|igp|incomplete>$origin_s" + "|matching-MED-only$match_med" + "|suppress-map RMAP_NAME$suppress_map" + "}]", + NO_STR + "Configure BGP aggregate entries\n" + "Aggregate prefix\n" + "Aggregate address\n" + "Aggregate mask\n" + "Generate AS set path information\n" + "Filter more specific routes from updates\n" + "Apply route map to aggregate network\n" + "Route map name\n" + "BGP origin code\n" + "Remote EGP\n" + "Local IGP\n" + "Unknown heritage\n" + "Only aggregate routes with matching MED\n" + "Suppress the selected more specific routes\n" + "Route map with the route selectors\n") +{ + const char *prefix_s = NULL; + safi_t safi = bgp_node_safi(vty); + uint8_t origin = BGP_ORIGIN_UNSPECIFIED; + int as_set = AGGREGATE_AS_UNSET; + char prefix_buf[PREFIX2STR_BUFFER]; + + if (addr_str) { + if (netmask_str2prefix_str(addr_str, mask_str, prefix_buf, + sizeof(prefix_buf)) + == 0) { + vty_out(vty, "%% Inconsistent address and mask\n"); + return CMD_WARNING_CONFIG_FAILED; + } + prefix_s = prefix_buf; + } else + prefix_s = prefix_str; + + if (origin_s) { + if (strcmp(origin_s, "egp") == 0) + origin = BGP_ORIGIN_EGP; + else if (strcmp(origin_s, "igp") == 0) + origin = BGP_ORIGIN_IGP; + else if (strcmp(origin_s, "incomplete") == 0) + origin = BGP_ORIGIN_INCOMPLETE; + } + + if (as_set_s) + as_set = AGGREGATE_AS_SET; + + /* Handle configuration removal, otherwise installation. */ + if (no) + return bgp_aggregate_unset(vty, prefix_s, AFI_IP, safi); + + return bgp_aggregate_set(vty, prefix_s, AFI_IP, safi, rmap_name, + summary_only != NULL, as_set, origin, + match_med != NULL, suppress_map); +} + +void bgp_free_aggregate_info(struct bgp_aggregate *aggregate) +{ + if (aggregate->community) + community_free(&aggregate->community); + + hash_clean_and_free(&aggregate->community_hash, + bgp_aggr_community_remove); + + if (aggregate->ecommunity) + ecommunity_free(&aggregate->ecommunity); + + hash_clean_and_free(&aggregate->ecommunity_hash, + bgp_aggr_ecommunity_remove); + + if (aggregate->lcommunity) + lcommunity_free(&aggregate->lcommunity); + + hash_clean_and_free(&aggregate->lcommunity_hash, + bgp_aggr_lcommunity_remove); + + if (aggregate->aspath) + aspath_free(aggregate->aspath); + + hash_clean_and_free(&aggregate->aspath_hash, bgp_aggr_aspath_remove); + + bgp_aggregate_free(aggregate); +} + +DEFPY(aggregate_addressv6, aggregate_addressv6_cmd, + "[no] aggregate-address X:X::X:X/M$prefix [{" + "as-set$as_set_s" + "|summary-only$summary_only" + "|route-map RMAP_NAME$rmap_name" + "|origin <egp|igp|incomplete>$origin_s" + "|matching-MED-only$match_med" + "|suppress-map RMAP_NAME$suppress_map" + "}]", + NO_STR + "Configure BGP aggregate entries\n" + "Aggregate prefix\n" + "Generate AS set path information\n" + "Filter more specific routes from updates\n" + "Apply route map to aggregate network\n" + "Route map name\n" + "BGP origin code\n" + "Remote EGP\n" + "Local IGP\n" + "Unknown heritage\n" + "Only aggregate routes with matching MED\n" + "Suppress the selected more specific routes\n" + "Route map with the route selectors\n") +{ + uint8_t origin = BGP_ORIGIN_UNSPECIFIED; + int as_set = AGGREGATE_AS_UNSET; + + if (origin_s) { + if (strcmp(origin_s, "egp") == 0) + origin = BGP_ORIGIN_EGP; + else if (strcmp(origin_s, "igp") == 0) + origin = BGP_ORIGIN_IGP; + else if (strcmp(origin_s, "incomplete") == 0) + origin = BGP_ORIGIN_INCOMPLETE; + } + + if (as_set_s) + as_set = AGGREGATE_AS_SET; + + /* Handle configuration removal, otherwise installation. */ + if (no) + return bgp_aggregate_unset(vty, prefix_str, AFI_IP6, + SAFI_UNICAST); + + return bgp_aggregate_set(vty, prefix_str, AFI_IP6, SAFI_UNICAST, + rmap_name, summary_only != NULL, as_set, + origin, match_med != NULL, suppress_map); +} + +/* Redistribute route treatment. */ +void bgp_redistribute_add(struct bgp *bgp, struct prefix *p, + const union g_addr *nexthop, ifindex_t ifindex, + enum nexthop_types_t nhtype, uint8_t distance, + enum blackhole_type bhtype, uint32_t metric, + uint8_t type, unsigned short instance, + route_tag_t tag) +{ + struct bgp_path_info *new; + struct bgp_path_info *bpi; + struct bgp_path_info rmap_path; + struct bgp_dest *bn; + struct attr attr; + struct attr *new_attr; + afi_t afi; + route_map_result_t ret; + struct bgp_redist *red; + + if (CHECK_FLAG(bgp->flags, BGP_FLAG_DELETE_IN_PROGRESS) || + bgp->peer_self == NULL) + return; + + /* Make default attribute. */ + bgp_attr_default_set(&attr, bgp, BGP_ORIGIN_INCOMPLETE); + /* + * This must not be NULL to satisfy Coverity SA + */ + assert(attr.aspath); + + if (p->family == AF_INET6) + UNSET_FLAG(attr.flag, ATTR_FLAG_BIT(BGP_ATTR_NEXT_HOP)); + + switch (nhtype) { + case NEXTHOP_TYPE_IFINDEX: + switch (p->family) { + case AF_INET: + attr.nexthop.s_addr = INADDR_ANY; + attr.mp_nexthop_len = BGP_ATTR_NHLEN_IPV4; + attr.mp_nexthop_global_in.s_addr = INADDR_ANY; + break; + case AF_INET6: + memset(&attr.mp_nexthop_global, 0, + sizeof(attr.mp_nexthop_global)); + attr.mp_nexthop_len = BGP_ATTR_NHLEN_IPV6_GLOBAL; + break; + } + break; + case NEXTHOP_TYPE_IPV4: + case NEXTHOP_TYPE_IPV4_IFINDEX: + attr.nexthop = nexthop->ipv4; + attr.mp_nexthop_len = BGP_ATTR_NHLEN_IPV4; + attr.mp_nexthop_global_in = nexthop->ipv4; + break; + case NEXTHOP_TYPE_IPV6: + case NEXTHOP_TYPE_IPV6_IFINDEX: + attr.mp_nexthop_global = nexthop->ipv6; + attr.mp_nexthop_len = BGP_ATTR_NHLEN_IPV6_GLOBAL; + break; + case NEXTHOP_TYPE_BLACKHOLE: + switch (p->family) { + case AF_INET: + attr.nexthop.s_addr = INADDR_ANY; + attr.mp_nexthop_len = BGP_ATTR_NHLEN_IPV4; + attr.mp_nexthop_global_in.s_addr = INADDR_ANY; + break; + case AF_INET6: + memset(&attr.mp_nexthop_global, 0, + sizeof(attr.mp_nexthop_global)); + attr.mp_nexthop_len = BGP_ATTR_NHLEN_IPV6_GLOBAL; + break; + } + attr.bh_type = bhtype; + break; + } + attr.nh_type = nhtype; + attr.nh_ifindex = ifindex; + + attr.med = metric; + attr.distance = distance; + attr.flag |= ATTR_FLAG_BIT(BGP_ATTR_MULTI_EXIT_DISC); + attr.tag = tag; + + if (metric) + bgp_attr_set_aigp_metric(&attr, metric); + + afi = family2afi(p->family); + + red = bgp_redist_lookup(bgp, afi, type, instance); + if (red) { + struct attr attr_new; + + /* Copy attribute for modification. */ + attr_new = attr; + + if (red->redist_metric_flag) { + attr_new.med = red->redist_metric; + bgp_attr_set_aigp_metric(&attr_new, red->redist_metric); + } + + /* Apply route-map. */ + if (red->rmap.name) { + memset(&rmap_path, 0, sizeof(rmap_path)); + rmap_path.peer = bgp->peer_self; + rmap_path.attr = &attr_new; + + SET_FLAG(bgp->peer_self->rmap_type, + PEER_RMAP_TYPE_REDISTRIBUTE); + + ret = route_map_apply(red->rmap.map, p, &rmap_path); + + bgp->peer_self->rmap_type = 0; + + if (ret == RMAP_DENYMATCH) { + /* Free uninterned attribute. */ + bgp_attr_flush(&attr_new); + + /* Unintern original. */ + aspath_unintern(&attr.aspath); + bgp_redistribute_delete(bgp, p, type, instance); + return; + } + } + + if (bgp_in_graceful_shutdown(bgp)) + bgp_attr_add_gshut_community(&attr_new); + + bn = bgp_afi_node_get(bgp->rib[afi][SAFI_UNICAST], afi, + SAFI_UNICAST, p, NULL); + + new_attr = bgp_attr_intern(&attr_new); + + for (bpi = bgp_dest_get_bgp_path_info(bn); bpi; bpi = bpi->next) + if (bpi->peer == bgp->peer_self + && bpi->sub_type == BGP_ROUTE_REDISTRIBUTE) + break; + + if (bpi) { + /* Ensure the (source route) type is updated. */ + bpi->type = type; + if (attrhash_cmp(bpi->attr, new_attr) + && !CHECK_FLAG(bpi->flags, BGP_PATH_REMOVED)) { + bgp_attr_unintern(&new_attr); + aspath_unintern(&attr.aspath); + bgp_dest_unlock_node(bn); + return; + } else { + /* The attribute is changed. */ + bgp_path_info_set_flag(bn, bpi, + BGP_PATH_ATTR_CHANGED); + + /* Rewrite BGP route information. */ + if (CHECK_FLAG(bpi->flags, BGP_PATH_REMOVED)) + bgp_path_info_restore(bn, bpi); + else + bgp_aggregate_decrement( + bgp, p, bpi, afi, SAFI_UNICAST); + bgp_attr_unintern(&bpi->attr); + bpi->attr = new_attr; + bpi->uptime = monotime(NULL); + + /* Process change. */ + bgp_aggregate_increment(bgp, p, bpi, afi, + SAFI_UNICAST); + bgp_process(bgp, bn, afi, SAFI_UNICAST); + bgp_dest_unlock_node(bn); + aspath_unintern(&attr.aspath); + + if ((bgp->inst_type == BGP_INSTANCE_TYPE_VRF) + || (bgp->inst_type + == BGP_INSTANCE_TYPE_DEFAULT)) { + + vpn_leak_from_vrf_update( + bgp_get_default(), bgp, bpi); + } + return; + } + } + + new = info_make(type, BGP_ROUTE_REDISTRIBUTE, instance, + bgp->peer_self, new_attr, bn); + SET_FLAG(new->flags, BGP_PATH_VALID); + + bgp_aggregate_increment(bgp, p, new, afi, SAFI_UNICAST); + bgp_path_info_add(bn, new); + bgp_dest_unlock_node(bn); + SET_FLAG(bn->flags, BGP_NODE_FIB_INSTALLED); + bgp_process(bgp, bn, afi, SAFI_UNICAST); + + if ((bgp->inst_type == BGP_INSTANCE_TYPE_VRF) + || (bgp->inst_type == BGP_INSTANCE_TYPE_DEFAULT)) { + + vpn_leak_from_vrf_update(bgp_get_default(), bgp, new); + } + } + + /* Unintern original. */ + aspath_unintern(&attr.aspath); +} + +void bgp_redistribute_delete(struct bgp *bgp, struct prefix *p, uint8_t type, + unsigned short instance) +{ + afi_t afi; + struct bgp_dest *dest; + struct bgp_path_info *pi; + struct bgp_redist *red; + + afi = family2afi(p->family); + + red = bgp_redist_lookup(bgp, afi, type, instance); + if (red) { + dest = bgp_afi_node_get(bgp->rib[afi][SAFI_UNICAST], afi, + SAFI_UNICAST, p, NULL); + + for (pi = bgp_dest_get_bgp_path_info(dest); pi; pi = pi->next) + if (pi->peer == bgp->peer_self && pi->type == type) + break; + + if (pi) { + if ((bgp->inst_type == BGP_INSTANCE_TYPE_VRF) + || (bgp->inst_type == BGP_INSTANCE_TYPE_DEFAULT)) { + + vpn_leak_from_vrf_withdraw(bgp_get_default(), + bgp, pi); + } + bgp_aggregate_decrement(bgp, p, pi, afi, SAFI_UNICAST); + bgp_path_info_delete(dest, pi); + bgp_process(bgp, dest, afi, SAFI_UNICAST); + } + bgp_dest_unlock_node(dest); + } +} + +/* Withdraw specified route type's route. */ +void bgp_redistribute_withdraw(struct bgp *bgp, afi_t afi, int type, + unsigned short instance) +{ + struct bgp_dest *dest; + struct bgp_path_info *pi; + struct bgp_table *table; + + table = bgp->rib[afi][SAFI_UNICAST]; + + for (dest = bgp_table_top(table); dest; dest = bgp_route_next(dest)) { + for (pi = bgp_dest_get_bgp_path_info(dest); pi; pi = pi->next) + if (pi->peer == bgp->peer_self && pi->type == type + && pi->instance == instance) + break; + + if (pi) { + if ((bgp->inst_type == BGP_INSTANCE_TYPE_VRF) + || (bgp->inst_type == BGP_INSTANCE_TYPE_DEFAULT)) { + + vpn_leak_from_vrf_withdraw(bgp_get_default(), + bgp, pi); + } + bgp_aggregate_decrement(bgp, bgp_dest_get_prefix(dest), + pi, afi, SAFI_UNICAST); + bgp_path_info_delete(dest, pi); + if (!CHECK_FLAG(bgp->flags, + BGP_FLAG_DELETE_IN_PROGRESS)) + bgp_process(bgp, dest, afi, SAFI_UNICAST); + else { + dest = bgp_path_info_reap(dest, pi); + assert(dest); + } + } + } +} + +/* Static function to display route. */ +static void route_vty_out_route(struct bgp_dest *dest, const struct prefix *p, + struct vty *vty, json_object *json, bool wide) +{ + int len = 0; + char buf[INET6_ADDRSTRLEN]; + + if (p->family == AF_INET) { + if (!json) { + len = vty_out(vty, "%pFX", p); + } else { + json_object_string_add(json, "prefix", + inet_ntop(p->family, + &p->u.prefix, buf, + sizeof(buf))); + json_object_int_add(json, "prefixLen", p->prefixlen); + json_object_string_addf(json, "network", "%pFX", p); + json_object_int_add(json, "version", dest->version); + } + } else if (p->family == AF_ETHERNET) { + len = vty_out(vty, "%pFX", p); + } else if (p->family == AF_EVPN) { + if (!json) + len = vty_out(vty, "%pFX", (struct prefix_evpn *)p); + else + bgp_evpn_route2json((struct prefix_evpn *)p, json); + } else if (p->family == AF_FLOWSPEC) { + route_vty_out_flowspec(vty, p, NULL, + json ? + NLRI_STRING_FORMAT_JSON_SIMPLE : + NLRI_STRING_FORMAT_MIN, json); + } else { + if (!json) + len = vty_out(vty, "%pFX", p); + else { + json_object_string_add(json, "prefix", + inet_ntop(p->family, + &p->u.prefix, buf, + sizeof(buf))); + json_object_int_add(json, "prefixLen", p->prefixlen); + json_object_string_addf(json, "network", "%pFX", p); + json_object_int_add(json, "version", dest->version); + } + } + + if (!json) { + len = wide ? (45 - len) : (17 - len); + if (len < 1) + vty_out(vty, "\n%*s", 20, " "); + else + vty_out(vty, "%*s", len, " "); + } +} + +enum bgp_display_type { + normal_list, +}; + +const char *bgp_path_selection_reason2str(enum bgp_path_selection_reason reason) +{ + switch (reason) { + case bgp_path_selection_none: + return "Nothing to Select"; + case bgp_path_selection_first: + return "First path received"; + case bgp_path_selection_evpn_sticky_mac: + return "EVPN Sticky Mac"; + case bgp_path_selection_evpn_seq: + return "EVPN sequence number"; + case bgp_path_selection_evpn_lower_ip: + return "EVPN lower IP"; + case bgp_path_selection_evpn_local_path: + return "EVPN local ES path"; + case bgp_path_selection_evpn_non_proxy: + return "EVPN non proxy"; + case bgp_path_selection_weight: + return "Weight"; + case bgp_path_selection_local_pref: + return "Local Pref"; + case bgp_path_selection_accept_own: + return "Accept Own"; + case bgp_path_selection_local_route: + return "Local Route"; + case bgp_path_selection_aigp: + return "AIGP"; + case bgp_path_selection_confed_as_path: + return "Confederation based AS Path"; + case bgp_path_selection_as_path: + return "AS Path"; + case bgp_path_selection_origin: + return "Origin"; + case bgp_path_selection_med: + return "MED"; + case bgp_path_selection_peer: + return "Peer Type"; + case bgp_path_selection_confed: + return "Confed Peer Type"; + case bgp_path_selection_igp_metric: + return "IGP Metric"; + case bgp_path_selection_older: + return "Older Path"; + case bgp_path_selection_router_id: + return "Router ID"; + case bgp_path_selection_cluster_length: + return "Cluster length"; + case bgp_path_selection_stale: + return "Path Staleness"; + case bgp_path_selection_local_configured: + return "Locally configured route"; + case bgp_path_selection_neighbor_ip: + return "Neighbor IP"; + case bgp_path_selection_default: + return "Nothing left to compare"; + } + return "Invalid (internal error)"; +} + +/* Print the short form route status for a bgp_path_info */ +static void route_vty_short_status_out(struct vty *vty, + struct bgp_path_info *path, + const struct prefix *p, + json_object *json_path) +{ + enum rpki_states rpki_state = RPKI_NOT_BEING_USED; + + if (json_path) { + + /* Route status display. */ + if (CHECK_FLAG(path->flags, BGP_PATH_REMOVED)) + json_object_boolean_true_add(json_path, "removed"); + + if (CHECK_FLAG(path->flags, BGP_PATH_STALE)) + json_object_boolean_true_add(json_path, "stale"); + + if (path->extra && bgp_path_suppressed(path)) + json_object_boolean_true_add(json_path, "suppressed"); + + if (CHECK_FLAG(path->flags, BGP_PATH_VALID) + && !CHECK_FLAG(path->flags, BGP_PATH_HISTORY)) + json_object_boolean_true_add(json_path, "valid"); + + /* Selected */ + if (CHECK_FLAG(path->flags, BGP_PATH_HISTORY)) + json_object_boolean_true_add(json_path, "history"); + + if (CHECK_FLAG(path->flags, BGP_PATH_DAMPED)) + json_object_boolean_true_add(json_path, "damped"); + + if (CHECK_FLAG(path->flags, BGP_PATH_SELECTED)) { + json_object_boolean_true_add(json_path, "bestpath"); + json_object_string_add(json_path, "selectionReason", + bgp_path_selection_reason2str( + path->net->reason)); + } + + if (CHECK_FLAG(path->flags, BGP_PATH_MULTIPATH)) + json_object_boolean_true_add(json_path, "multipath"); + + /* Internal route. */ + if ((path->peer->as) + && (path->peer->as == path->peer->local_as)) + json_object_string_add(json_path, "pathFrom", + "internal"); + else + json_object_string_add(json_path, "pathFrom", + "external"); + + return; + } + + /* RPKI validation state */ + rpki_state = + hook_call(bgp_rpki_prefix_status, path->peer, path->attr, p); + + if (rpki_state == RPKI_VALID) + vty_out(vty, "V"); + else if (rpki_state == RPKI_INVALID) + vty_out(vty, "I"); + else if (rpki_state == RPKI_NOTFOUND) + vty_out(vty, "N"); + else + vty_out(vty, " "); + + /* Route status display. */ + if (CHECK_FLAG(path->flags, BGP_PATH_REMOVED)) + vty_out(vty, "R"); + else if (CHECK_FLAG(path->flags, BGP_PATH_STALE)) + vty_out(vty, "S"); + else if (bgp_path_suppressed(path)) + vty_out(vty, "s"); + else if (CHECK_FLAG(path->flags, BGP_PATH_VALID) + && !CHECK_FLAG(path->flags, BGP_PATH_HISTORY)) + vty_out(vty, "*"); + else + vty_out(vty, " "); + + /* Selected */ + if (CHECK_FLAG(path->flags, BGP_PATH_HISTORY)) + vty_out(vty, "h"); + else if (CHECK_FLAG(path->flags, BGP_PATH_DAMPED)) + vty_out(vty, "d"); + else if (CHECK_FLAG(path->flags, BGP_PATH_SELECTED)) + vty_out(vty, ">"); + else if (CHECK_FLAG(path->flags, BGP_PATH_MULTIPATH)) + vty_out(vty, "="); + else + vty_out(vty, " "); + + /* Internal route. */ + if (path->peer && (path->peer->as) + && (path->peer->as == path->peer->local_as)) + vty_out(vty, "i"); + else + vty_out(vty, " "); +} + +static char *bgp_nexthop_hostname(struct peer *peer, + struct bgp_nexthop_cache *bnc) +{ + if (peer->hostname + && CHECK_FLAG(peer->bgp->flags, BGP_FLAG_SHOW_NEXTHOP_HOSTNAME)) + return peer->hostname; + return NULL; +} + +/* called from terminal list command */ +void route_vty_out(struct vty *vty, const struct prefix *p, + struct bgp_path_info *path, int display, safi_t safi, + json_object *json_paths, bool wide) +{ + int len; + struct attr *attr = path->attr; + json_object *json_path = NULL; + json_object *json_nexthops = NULL; + json_object *json_nexthop_global = NULL; + json_object *json_nexthop_ll = NULL; + json_object *json_ext_community = NULL; + char vrf_id_str[VRF_NAMSIZ] = {0}; + bool nexthop_self = + CHECK_FLAG(path->flags, BGP_PATH_ANNC_NH_SELF) ? true : false; + bool nexthop_othervrf = false; + vrf_id_t nexthop_vrfid = VRF_DEFAULT; + const char *nexthop_vrfname = VRF_DEFAULT_NAME; + char *nexthop_hostname = + bgp_nexthop_hostname(path->peer, path->nexthop); + char esi_buf[ESI_STR_LEN]; + + if (json_paths) + json_path = json_object_new_object(); + + /* short status lead text */ + route_vty_short_status_out(vty, path, p, json_path); + + if (!json_paths) { + /* print prefix and mask */ + if (!display) + route_vty_out_route(path->net, p, vty, json_path, wide); + else + vty_out(vty, "%*s", (wide ? 45 : 17), " "); + } else { + route_vty_out_route(path->net, p, vty, json_path, wide); + } + + /* + * If vrf id of nexthop is different from that of prefix, + * set up printable string to append + */ + if (path->extra && path->extra->vrfleak && + path->extra->vrfleak->bgp_orig) { + const char *self = ""; + + if (nexthop_self) + self = "<"; + + nexthop_othervrf = true; + nexthop_vrfid = path->extra->vrfleak->bgp_orig->vrf_id; + + if (path->extra->vrfleak->bgp_orig->vrf_id == VRF_UNKNOWN) + snprintf(vrf_id_str, sizeof(vrf_id_str), + "@%s%s", VRFID_NONE_STR, self); + else + snprintf(vrf_id_str, sizeof(vrf_id_str), "@%u%s", + path->extra->vrfleak->bgp_orig->vrf_id, self); + + if (path->extra->vrfleak->bgp_orig->inst_type != + BGP_INSTANCE_TYPE_DEFAULT) + + nexthop_vrfname = path->extra->vrfleak->bgp_orig->name; + } else { + const char *self = ""; + + if (nexthop_self) + self = "<"; + + snprintf(vrf_id_str, sizeof(vrf_id_str), "%s", self); + } + + /* + * For ENCAP and EVPN routes, nexthop address family is not + * neccessarily the same as the prefix address family. + * Both SAFI_MPLS_VPN and SAFI_ENCAP use the MP nexthop field + * EVPN routes are also exchanged with a MP nexthop. Currently, + * this + * is only IPv4, the value will be present in either + * attr->nexthop or + * attr->mp_nexthop_global_in + */ + if ((safi == SAFI_ENCAP) || (safi == SAFI_MPLS_VPN)) { + char nexthop[128]; + int af = NEXTHOP_FAMILY(attr->mp_nexthop_len); + + switch (af) { + case AF_INET: + snprintfrr(nexthop, sizeof(nexthop), "%pI4", + &attr->mp_nexthop_global_in); + break; + case AF_INET6: + snprintfrr(nexthop, sizeof(nexthop), "%pI6", + &attr->mp_nexthop_global); + break; + default: + snprintf(nexthop, sizeof(nexthop), "?"); + break; + } + + if (json_paths) { + json_nexthop_global = json_object_new_object(); + + json_object_string_add(json_nexthop_global, "ip", + nexthop); + + if (path->peer->hostname) + json_object_string_add(json_nexthop_global, + "hostname", + path->peer->hostname); + + json_object_string_add(json_nexthop_global, "afi", + (af == AF_INET) ? "ipv4" + : "ipv6"); + json_object_boolean_true_add(json_nexthop_global, + "used"); + } else { + if (nexthop_hostname) + len = vty_out(vty, "%s(%s)%s", nexthop, + nexthop_hostname, vrf_id_str); + else + len = vty_out(vty, "%s%s", nexthop, vrf_id_str); + + len = wide ? (41 - len) : (16 - len); + if (len < 1) + vty_out(vty, "\n%*s", 36, " "); + else + vty_out(vty, "%*s", len, " "); + } + } else if (safi == SAFI_EVPN) { + if (json_paths) { + json_nexthop_global = json_object_new_object(); + + json_object_string_addf(json_nexthop_global, "ip", + "%pI4", + &attr->mp_nexthop_global_in); + + if (path->peer->hostname) + json_object_string_add(json_nexthop_global, + "hostname", + path->peer->hostname); + + json_object_string_add(json_nexthop_global, "afi", + "ipv4"); + json_object_boolean_true_add(json_nexthop_global, + "used"); + } else { + if (nexthop_hostname) + len = vty_out(vty, "%pI4(%s)%s", + &attr->mp_nexthop_global_in, + nexthop_hostname, vrf_id_str); + else + len = vty_out(vty, "%pI4%s", + &attr->mp_nexthop_global_in, + vrf_id_str); + + len = wide ? (41 - len) : (16 - len); + if (len < 1) + vty_out(vty, "\n%*s", 36, " "); + else + vty_out(vty, "%*s", len, " "); + } + } else if (safi == SAFI_FLOWSPEC) { + if (attr->nexthop.s_addr != INADDR_ANY) { + if (json_paths) { + json_nexthop_global = json_object_new_object(); + + json_object_string_add(json_nexthop_global, + "afi", "ipv4"); + json_object_string_addf(json_nexthop_global, + "ip", "%pI4", + &attr->nexthop); + + if (path->peer->hostname) + json_object_string_add( + json_nexthop_global, "hostname", + path->peer->hostname); + + json_object_boolean_true_add( + json_nexthop_global, + "used"); + } else { + if (nexthop_hostname) + len = vty_out(vty, "%pI4(%s)%s", + &attr->nexthop, + nexthop_hostname, + vrf_id_str); + else + len = vty_out(vty, "%pI4%s", + &attr->nexthop, + vrf_id_str); + + len = wide ? (41 - len) : (16 - len); + if (len < 1) + vty_out(vty, "\n%*s", 36, " "); + else + vty_out(vty, "%*s", len, " "); + } + } + } else if (p->family == AF_INET && !BGP_ATTR_MP_NEXTHOP_LEN_IP6(attr)) { + if (json_paths) { + json_nexthop_global = json_object_new_object(); + + json_object_string_addf(json_nexthop_global, "ip", + "%pI4", &attr->nexthop); + + if (path->peer->hostname) + json_object_string_add(json_nexthop_global, + "hostname", + path->peer->hostname); + + json_object_string_add(json_nexthop_global, "afi", + "ipv4"); + json_object_boolean_true_add(json_nexthop_global, + "used"); + } else { + if (nexthop_hostname) + len = vty_out(vty, "%pI4(%s)%s", &attr->nexthop, + nexthop_hostname, vrf_id_str); + else + len = vty_out(vty, "%pI4%s", &attr->nexthop, + vrf_id_str); + + len = wide ? (41 - len) : (16 - len); + if (len < 1) + vty_out(vty, "\n%*s", 36, " "); + else + vty_out(vty, "%*s", len, " "); + } + } + + /* IPv6 Next Hop */ + else if (p->family == AF_INET6 || BGP_ATTR_MP_NEXTHOP_LEN_IP6(attr)) { + if (json_paths) { + json_nexthop_global = json_object_new_object(); + json_object_string_addf(json_nexthop_global, "ip", + "%pI6", + &attr->mp_nexthop_global); + + if (path->peer->hostname) + json_object_string_add(json_nexthop_global, + "hostname", + path->peer->hostname); + + json_object_string_add(json_nexthop_global, "afi", + "ipv6"); + json_object_string_add(json_nexthop_global, "scope", + "global"); + + /* We display both LL & GL if both have been + * received */ + if ((attr->mp_nexthop_len + == BGP_ATTR_NHLEN_IPV6_GLOBAL_AND_LL) + || (path->peer->conf_if)) { + json_nexthop_ll = json_object_new_object(); + json_object_string_addf( + json_nexthop_ll, "ip", "%pI6", + &attr->mp_nexthop_local); + + if (path->peer->hostname) + json_object_string_add( + json_nexthop_ll, "hostname", + path->peer->hostname); + + json_object_string_add(json_nexthop_ll, "afi", + "ipv6"); + json_object_string_add(json_nexthop_ll, "scope", + "link-local"); + + if ((IPV6_ADDR_CMP(&attr->mp_nexthop_global, + &attr->mp_nexthop_local) + != 0) + && !attr->mp_nexthop_prefer_global) + json_object_boolean_true_add( + json_nexthop_ll, "used"); + else + json_object_boolean_true_add( + json_nexthop_global, "used"); + } else + json_object_boolean_true_add( + json_nexthop_global, "used"); + } else { + /* Display LL if LL/Global both in table unless + * prefer-global is set */ + if (((attr->mp_nexthop_len + == BGP_ATTR_NHLEN_IPV6_GLOBAL_AND_LL) + && !attr->mp_nexthop_prefer_global) + || (path->peer->conf_if)) { + if (path->peer->conf_if) { + len = vty_out(vty, "%s", + path->peer->conf_if); + /* len of IPv6 addr + max len of def + * ifname */ + len = wide ? (41 - len) : (16 - len); + + if (len < 1) + vty_out(vty, "\n%*s", 36, " "); + else + vty_out(vty, "%*s", len, " "); + } else { + if (nexthop_hostname) + len = vty_out( + vty, "%pI6(%s)%s", + &attr->mp_nexthop_local, + nexthop_hostname, + vrf_id_str); + else + len = vty_out( + vty, "%pI6%s", + &attr->mp_nexthop_local, + vrf_id_str); + + len = wide ? (41 - len) : (16 - len); + + if (len < 1) + vty_out(vty, "\n%*s", 36, " "); + else + vty_out(vty, "%*s", len, " "); + } + } else { + if (nexthop_hostname) + len = vty_out(vty, "%pI6(%s)%s", + &attr->mp_nexthop_global, + nexthop_hostname, + vrf_id_str); + else + len = vty_out(vty, "%pI6%s", + &attr->mp_nexthop_global, + vrf_id_str); + + len = wide ? (41 - len) : (16 - len); + + if (len < 1) + vty_out(vty, "\n%*s", 36, " "); + else + vty_out(vty, "%*s", len, " "); + } + } + } + + /* MED/Metric */ + if (attr->flag & ATTR_FLAG_BIT(BGP_ATTR_MULTI_EXIT_DISC)) + if (json_paths) + json_object_int_add(json_path, "metric", attr->med); + else if (wide) + vty_out(vty, "%7u", attr->med); + else + vty_out(vty, "%10u", attr->med); + else if (!json_paths) { + if (wide) + vty_out(vty, "%*s", 7, " "); + else + vty_out(vty, "%*s", 10, " "); + } + + /* Local Pref */ + if (attr->flag & ATTR_FLAG_BIT(BGP_ATTR_LOCAL_PREF)) + if (json_paths) + json_object_int_add(json_path, "locPrf", + attr->local_pref); + else + vty_out(vty, "%7u", attr->local_pref); + else if (!json_paths) + vty_out(vty, " "); + + if (json_paths) + json_object_int_add(json_path, "weight", attr->weight); + else + vty_out(vty, "%7u ", attr->weight); + + if (json_paths) + json_object_string_addf(json_path, "peerId", "%pSU", + &path->peer->connection->su); + + /* Print aspath */ + if (attr->aspath) { + if (json_paths) + json_object_string_add(json_path, "path", + attr->aspath->str); + else + aspath_print_vty(vty, attr->aspath); + } + + /* Print origin */ + if (json_paths) + json_object_string_add(json_path, "origin", + bgp_origin_long_str[attr->origin]); + else + vty_out(vty, "%s", bgp_origin_str[attr->origin]); + + if (json_paths) { + if (bgp_evpn_is_esi_valid(&attr->esi)) { + json_object_string_add(json_path, "esi", + esi_to_str(&attr->esi, + esi_buf, sizeof(esi_buf))); + } + if (safi == SAFI_EVPN && + attr->flag & ATTR_FLAG_BIT(BGP_ATTR_EXT_COMMUNITIES)) { + json_ext_community = json_object_new_object(); + json_object_string_add( + json_ext_community, "string", + bgp_attr_get_ecommunity(attr)->str); + json_object_object_add(json_path, + "extendedCommunity", + json_ext_community); + } + + if (nexthop_self) + json_object_boolean_true_add(json_path, + "announceNexthopSelf"); + if (nexthop_othervrf) { + json_object_string_add(json_path, "nhVrfName", + nexthop_vrfname); + + json_object_int_add(json_path, "nhVrfId", + ((nexthop_vrfid == VRF_UNKNOWN) + ? -1 + : (int)nexthop_vrfid)); + } + } + + if (json_paths) { + if (json_nexthop_global || json_nexthop_ll) { + json_nexthops = json_object_new_array(); + + if (json_nexthop_global) + json_object_array_add(json_nexthops, + json_nexthop_global); + + if (json_nexthop_ll) + json_object_array_add(json_nexthops, + json_nexthop_ll); + + json_object_object_add(json_path, "nexthops", + json_nexthops); + } + + json_object_array_add(json_paths, json_path); + } else { + vty_out(vty, "\n"); + + if (safi == SAFI_EVPN) { + if (bgp_evpn_is_esi_valid(&attr->esi)) { + /* XXX - add these params to the json out */ + vty_out(vty, "%*s", 20, " "); + vty_out(vty, "ESI:%s", + esi_to_str(&attr->esi, esi_buf, + sizeof(esi_buf))); + + vty_out(vty, "\n"); + } + if (attr->flag & + ATTR_FLAG_BIT(BGP_ATTR_EXT_COMMUNITIES)) { + vty_out(vty, "%*s", 20, " "); + vty_out(vty, "%s\n", + bgp_attr_get_ecommunity(attr)->str); + } + } + +#ifdef ENABLE_BGP_VNC + /* prints an additional line, indented, with VNC info, if + * present */ + if ((safi == SAFI_MPLS_VPN) || (safi == SAFI_ENCAP)) + rfapi_vty_out_vncinfo(vty, p, path, safi); +#endif + } +} + +/* called from terminal list command */ +void route_vty_out_tmp(struct vty *vty, struct bgp_dest *dest, + const struct prefix *p, struct attr *attr, safi_t safi, + bool use_json, json_object *json_ar, bool wide) +{ + json_object *json_status = NULL; + json_object *json_net = NULL; + int len; + char buff[BUFSIZ]; + + /* Route status display. */ + if (use_json) { + json_status = json_object_new_object(); + json_net = json_object_new_object(); + } else { + vty_out(vty, " *"); + vty_out(vty, ">"); + vty_out(vty, " "); + } + + /* print prefix and mask */ + if (use_json) { + if (safi == SAFI_EVPN) + bgp_evpn_route2json((struct prefix_evpn *)p, json_net); + else if (p->family == AF_INET || p->family == AF_INET6) { + json_object_string_add( + json_net, "addrPrefix", + inet_ntop(p->family, &p->u.prefix, buff, + BUFSIZ)); + json_object_int_add(json_net, "prefixLen", + p->prefixlen); + json_object_string_addf(json_net, "network", "%pFX", p); + } + } else + route_vty_out_route(dest, p, vty, NULL, wide); + + /* Print attribute */ + if (attr) { + if (use_json) { + if (p->family == AF_INET && + (safi == SAFI_MPLS_VPN || safi == SAFI_ENCAP || + !BGP_ATTR_MP_NEXTHOP_LEN_IP6(attr))) { + if (safi == SAFI_MPLS_VPN || safi == SAFI_ENCAP) + json_object_string_addf( + json_net, "nextHop", "%pI4", + &attr->mp_nexthop_global_in); + else + json_object_string_addf( + json_net, "nextHop", "%pI4", + &attr->nexthop); + } else if (p->family == AF_INET6 || + BGP_ATTR_MP_NEXTHOP_LEN_IP6(attr)) { + json_object_string_addf( + json_net, "nextHopGlobal", "%pI6", + &attr->mp_nexthop_global); + } else if (p->family == AF_EVPN && + !BGP_ATTR_NEXTHOP_AFI_IP6(attr)) { + json_object_string_addf( + json_net, "nextHop", "%pI4", + &attr->mp_nexthop_global_in); + } + + if (attr->flag + & ATTR_FLAG_BIT(BGP_ATTR_MULTI_EXIT_DISC)) + json_object_int_add(json_net, "metric", + attr->med); + + if (attr->flag & ATTR_FLAG_BIT(BGP_ATTR_LOCAL_PREF)) + json_object_int_add(json_net, "locPrf", + attr->local_pref); + + json_object_int_add(json_net, "weight", attr->weight); + + /* Print aspath */ + if (attr->aspath) + json_object_string_add(json_net, "path", + attr->aspath->str); + + /* Print origin */ +#if CONFDATE > 20231208 +CPP_NOTICE("Drop `bgpOriginCodes` from JSON outputs") +#endif + json_object_string_add(json_net, "bgpOriginCode", + bgp_origin_str[attr->origin]); + json_object_string_add( + json_net, "origin", + bgp_origin_long_str[attr->origin]); + } else { + if (p->family == AF_INET && + (safi == SAFI_MPLS_VPN || safi == SAFI_ENCAP || + safi == SAFI_EVPN || + !BGP_ATTR_MP_NEXTHOP_LEN_IP6(attr))) { + if (safi == SAFI_MPLS_VPN || safi == SAFI_ENCAP + || safi == SAFI_EVPN) + vty_out(vty, "%-16pI4", + &attr->mp_nexthop_global_in); + else if (wide) + vty_out(vty, "%-41pI4", &attr->nexthop); + else + vty_out(vty, "%-16pI4", &attr->nexthop); + } else if (p->family == AF_INET6 || + BGP_ATTR_MP_NEXTHOP_LEN_IP6(attr)) { + len = vty_out(vty, "%pI6", + &attr->mp_nexthop_global); + len = wide ? (41 - len) : (16 - len); + if (len < 1) + vty_out(vty, "\n%*s", 36, " "); + else + vty_out(vty, "%*s", len, " "); + } + if (attr->flag + & ATTR_FLAG_BIT(BGP_ATTR_MULTI_EXIT_DISC)) + if (wide) + vty_out(vty, "%7u", attr->med); + else + vty_out(vty, "%10u", attr->med); + else if (wide) + vty_out(vty, " "); + else + vty_out(vty, " "); + + if (attr->flag & ATTR_FLAG_BIT(BGP_ATTR_LOCAL_PREF)) + vty_out(vty, "%7u", attr->local_pref); + else + vty_out(vty, " "); + + vty_out(vty, "%7u ", attr->weight); + + /* Print aspath */ + if (attr->aspath) + aspath_print_vty(vty, attr->aspath); + + /* Print origin */ + vty_out(vty, "%s", bgp_origin_str[attr->origin]); + } + } + if (use_json) { + struct bgp_path_info *bpi = bgp_dest_get_bgp_path_info(dest); + +#if CONFDATE > 20231208 +CPP_NOTICE("Drop `bgpStatusCodes` from JSON outputs") +#endif + json_object_boolean_true_add(json_status, "*"); + json_object_boolean_true_add(json_status, ">"); + json_object_boolean_true_add(json_net, "valid"); + json_object_boolean_true_add(json_net, "best"); + + if (bpi && CHECK_FLAG(bpi->flags, BGP_PATH_MULTIPATH)) { + json_object_boolean_true_add(json_status, "="); + json_object_boolean_true_add(json_net, "multipath"); + } + json_object_object_add(json_net, "appliedStatusSymbols", + json_status); + json_object_object_addf(json_ar, json_net, "%pFX", p); + } else + vty_out(vty, "\n"); +} + +void route_vty_out_tag(struct vty *vty, const struct prefix *p, + struct bgp_path_info *path, int display, safi_t safi, + json_object *json) +{ + json_object *json_out = NULL; + struct attr *attr; + mpls_label_t label = MPLS_INVALID_LABEL; + + if (!path->extra) + return; + + if (json) + json_out = json_object_new_object(); + + /* short status lead text */ + route_vty_short_status_out(vty, path, p, json_out); + + /* print prefix and mask */ + if (json == NULL) { + if (!display) + route_vty_out_route(path->net, p, vty, NULL, false); + else + vty_out(vty, "%*s", 17, " "); + } + + /* Print attribute */ + attr = path->attr; + if (((p->family == AF_INET) && + ((safi == SAFI_MPLS_VPN || safi == SAFI_ENCAP))) || + (safi == SAFI_EVPN && !BGP_ATTR_NEXTHOP_AFI_IP6(attr)) || + (!BGP_ATTR_MP_NEXTHOP_LEN_IP6(attr))) { + if (safi == SAFI_MPLS_VPN || safi == SAFI_ENCAP + || safi == SAFI_EVPN) { + if (json) + json_object_string_addf( + json_out, "mpNexthopGlobalIn", "%pI4", + &attr->mp_nexthop_global_in); + else + vty_out(vty, "%-16pI4", + &attr->mp_nexthop_global_in); + } else { + if (json) + json_object_string_addf(json_out, "nexthop", + "%pI4", &attr->nexthop); + else + vty_out(vty, "%-16pI4", &attr->nexthop); + } + } else if (((p->family == AF_INET6) && + ((safi == SAFI_MPLS_VPN || safi == SAFI_ENCAP))) || + (safi == SAFI_EVPN && BGP_ATTR_MP_NEXTHOP_LEN_IP6(attr)) || + (BGP_ATTR_MP_NEXTHOP_LEN_IP6(attr))) { + char buf_a[512]; + + if (attr->mp_nexthop_len == BGP_ATTR_NHLEN_IPV6_GLOBAL) { + if (json) + json_object_string_addf( + json_out, "mpNexthopGlobalIn", "%pI6", + &attr->mp_nexthop_global); + else + vty_out(vty, "%s", + inet_ntop(AF_INET6, + &attr->mp_nexthop_global, + buf_a, sizeof(buf_a))); + } else if (attr->mp_nexthop_len + == BGP_ATTR_NHLEN_IPV6_GLOBAL_AND_LL) { + snprintfrr(buf_a, sizeof(buf_a), "%pI6(%pI6)", + &attr->mp_nexthop_global, + &attr->mp_nexthop_local); + if (json) + json_object_string_add(json_out, + "mpNexthopGlobalLocal", + buf_a); + else + vty_out(vty, "%s", buf_a); + } + } + + if (bgp_is_valid_label(&path->extra->label[0])) { + label = decode_label(&path->extra->label[0]); + if (json) { + json_object_int_add(json_out, "notag", label); + json_object_array_add(json, json_out); + } else { + vty_out(vty, "notag/%d", label); + vty_out(vty, "\n"); + } + } else if (!json) + vty_out(vty, "\n"); +} + +void route_vty_out_overlay(struct vty *vty, const struct prefix *p, + struct bgp_path_info *path, int display, + json_object *json_paths) +{ + struct attr *attr; + json_object *json_path = NULL; + json_object *json_nexthop = NULL; + json_object *json_overlay = NULL; + + if (!path->extra) + return; + + if (json_paths) { + json_path = json_object_new_object(); + json_overlay = json_object_new_object(); + json_nexthop = json_object_new_object(); + } + + /* short status lead text */ + route_vty_short_status_out(vty, path, p, json_path); + + /* print prefix and mask */ + if (!display) + route_vty_out_route(path->net, p, vty, json_path, false); + else + vty_out(vty, "%*s", 17, " "); + + /* Print attribute */ + attr = path->attr; + int af = NEXTHOP_FAMILY(attr->mp_nexthop_len); + + switch (af) { + case AF_INET: + if (!json_path) { + vty_out(vty, "%-16pI4", &attr->mp_nexthop_global_in); + } else { + json_object_string_addf(json_nexthop, "ip", "%pI4", + &attr->mp_nexthop_global_in); + + json_object_string_add(json_nexthop, "afi", "ipv4"); + + json_object_object_add(json_path, "nexthop", + json_nexthop); + } + break; + case AF_INET6: + if (!json_path) { + vty_out(vty, "%pI6(%pI6)", &attr->mp_nexthop_global, + &attr->mp_nexthop_local); + } else { + json_object_string_addf(json_nexthop, "ipv6Global", + "%pI6", + &attr->mp_nexthop_global); + + json_object_string_addf(json_nexthop, "ipv6LinkLocal", + "%pI6", + &attr->mp_nexthop_local); + + json_object_string_add(json_nexthop, "afi", "ipv6"); + + json_object_object_add(json_path, "nexthop", + json_nexthop); + } + break; + default: + if (!json_path) { + vty_out(vty, "?"); + } else { + json_object_string_add(json_nexthop, "error", + "Unsupported address-family"); + } + } + + const struct bgp_route_evpn *eo = bgp_attr_get_evpn_overlay(attr); + + if (!json_path) + vty_out(vty, "/%pIA", &eo->gw_ip); + else + json_object_string_addf(json_overlay, "gw", "%pIA", &eo->gw_ip); + + if (bgp_attr_get_ecommunity(attr)) { + char *mac = NULL; + struct ecommunity_val *routermac = ecommunity_lookup( + bgp_attr_get_ecommunity(attr), ECOMMUNITY_ENCODE_EVPN, + ECOMMUNITY_EVPN_SUBTYPE_ROUTERMAC); + + if (routermac) + mac = ecom_mac2str((char *)routermac->val); + if (mac) { + if (!json_path) { + vty_out(vty, "/%s", mac); + } else { + json_object_string_add(json_overlay, "rmac", + mac); + } + XFREE(MTYPE_TMP, mac); + } + } + + if (!json_path) { + vty_out(vty, "\n"); + } else { + json_object_object_add(json_path, "overlay", json_overlay); + + json_object_array_add(json_paths, json_path); + } +} + +/* dampening route */ +static void damp_route_vty_out(struct vty *vty, const struct prefix *p, + struct bgp_path_info *path, int display, + afi_t afi, safi_t safi, bool use_json, + json_object *json_paths) +{ + struct attr *attr = path->attr; + int len; + char timebuf[BGP_UPTIME_LEN] = {}; + json_object *json_path = NULL; + + if (use_json) + json_path = json_object_new_object(); + + /* short status lead text */ + route_vty_short_status_out(vty, path, p, json_path); + + /* print prefix and mask */ + if (!use_json) { + if (!display) + route_vty_out_route(path->net, p, vty, NULL, false); + else + vty_out(vty, "%*s", 17, " "); + + len = vty_out(vty, "%s", path->peer->host); + len = 17 - len; + + if (len < 1) + vty_out(vty, "\n%*s", 34, " "); + else + vty_out(vty, "%*s", len, " "); + + vty_out(vty, "%s ", + bgp_damp_reuse_time_vty(vty, path, timebuf, + BGP_UPTIME_LEN, afi, safi, + use_json, NULL)); + + if (attr->aspath) + aspath_print_vty(vty, attr->aspath); + + vty_out(vty, "%s", bgp_origin_str[attr->origin]); + + vty_out(vty, "\n"); + } else { + bgp_damp_reuse_time_vty(vty, path, timebuf, BGP_UPTIME_LEN, afi, + safi, use_json, json_path); + + if (attr->aspath) + json_object_string_add(json_path, "asPath", + attr->aspath->str); + + json_object_string_add(json_path, "origin", + bgp_origin_str[attr->origin]); + json_object_string_add(json_path, "peerHost", path->peer->host); + + json_object_array_add(json_paths, json_path); + } +} + +/* flap route */ +static void flap_route_vty_out(struct vty *vty, const struct prefix *p, + struct bgp_path_info *path, int display, + afi_t afi, safi_t safi, bool use_json, + json_object *json_paths) +{ + struct attr *attr = path->attr; + struct bgp_damp_info *bdi; + char timebuf[BGP_UPTIME_LEN] = {}; + int len; + json_object *json_path = NULL; + + if (!path->extra) + return; + + if (use_json) + json_path = json_object_new_object(); + + bdi = path->extra->damp_info; + + /* short status lead text */ + route_vty_short_status_out(vty, path, p, json_path); + + if (!use_json) { + if (!display) + route_vty_out_route(path->net, p, vty, NULL, false); + else + vty_out(vty, "%*s", 17, " "); + + len = vty_out(vty, "%s", path->peer->host); + len = 16 - len; + if (len < 1) + vty_out(vty, "\n%*s", 33, " "); + else + vty_out(vty, "%*s", len, " "); + + len = vty_out(vty, "%d", bdi->flap); + len = 5 - len; + if (len < 1) + vty_out(vty, " "); + else + vty_out(vty, "%*s", len, " "); + + vty_out(vty, "%s ", peer_uptime(bdi->start_time, timebuf, + BGP_UPTIME_LEN, 0, NULL)); + + if (CHECK_FLAG(path->flags, BGP_PATH_DAMPED) + && !CHECK_FLAG(path->flags, BGP_PATH_HISTORY)) + vty_out(vty, "%s ", + bgp_damp_reuse_time_vty(vty, path, timebuf, + BGP_UPTIME_LEN, afi, + safi, use_json, NULL)); + else + vty_out(vty, "%*s ", 8, " "); + + if (attr->aspath) + aspath_print_vty(vty, attr->aspath); + + vty_out(vty, "%s", bgp_origin_str[attr->origin]); + + vty_out(vty, "\n"); + } else { + json_object_string_add(json_path, "peerHost", path->peer->host); + json_object_int_add(json_path, "bdiFlap", bdi->flap); + + peer_uptime(bdi->start_time, timebuf, BGP_UPTIME_LEN, use_json, + json_path); + + if (CHECK_FLAG(path->flags, BGP_PATH_DAMPED) + && !CHECK_FLAG(path->flags, BGP_PATH_HISTORY)) + bgp_damp_reuse_time_vty(vty, path, timebuf, + BGP_UPTIME_LEN, afi, safi, + use_json, json_path); + + if (attr->aspath) + json_object_string_add(json_path, "asPath", + attr->aspath->str); + + json_object_string_add(json_path, "origin", + bgp_origin_str[attr->origin]); + + json_object_array_add(json_paths, json_path); + } +} + +static void route_vty_out_advertised_to(struct vty *vty, struct peer *peer, + int *first, const char *header, + json_object *json_adv_to) +{ + json_object *json_peer = NULL; + + if (json_adv_to) { + /* 'advertised-to' is a dictionary of peers we have advertised + * this + * prefix too. The key is the peer's IP or swpX, the value is + * the + * hostname if we know it and "" if not. + */ + json_peer = json_object_new_object(); + + if (peer->hostname) + json_object_string_add(json_peer, "hostname", + peer->hostname); + + if (peer->conf_if) + json_object_object_add(json_adv_to, peer->conf_if, + json_peer); + else + json_object_object_addf(json_adv_to, json_peer, "%pSU", + &peer->connection->su); + } else { + if (*first) { + vty_out(vty, "%s", header); + *first = 0; + } + + if (peer->hostname + && CHECK_FLAG(peer->bgp->flags, BGP_FLAG_SHOW_HOSTNAME)) { + if (peer->conf_if) + vty_out(vty, " %s(%s)", peer->hostname, + peer->conf_if); + else + vty_out(vty, " %s(%pSU)", peer->hostname, + &peer->connection->su); + } else { + if (peer->conf_if) + vty_out(vty, " %s", peer->conf_if); + else + vty_out(vty, " %pSU", &peer->connection->su); + } + } +} + +static void route_vty_out_tx_ids(struct vty *vty, + struct bgp_addpath_info_data *d) +{ + int i; + + for (i = 0; i < BGP_ADDPATH_MAX; i++) { + vty_out(vty, "TX-%s %u%s", bgp_addpath_names(i)->human_name, + d->addpath_tx_id[i], + i < BGP_ADDPATH_MAX - 1 ? " " : "\n"); + } +} + +static void route_vty_out_detail_es_info(struct vty *vty, + struct bgp_path_info *pi, + struct attr *attr, + json_object *json_path) +{ + char esi_buf[ESI_STR_LEN]; + bool es_local = !!CHECK_FLAG(attr->es_flags, ATTR_ES_IS_LOCAL); + bool peer_router = !!CHECK_FLAG(attr->es_flags, + ATTR_ES_PEER_ROUTER); + bool peer_active = !!CHECK_FLAG(attr->es_flags, + ATTR_ES_PEER_ACTIVE); + bool peer_proxy = !!CHECK_FLAG(attr->es_flags, + ATTR_ES_PEER_PROXY); + esi_to_str(&attr->esi, esi_buf, sizeof(esi_buf)); + if (json_path) { + json_object *json_es_info = NULL; + + json_object_string_add( + json_path, "esi", + esi_buf); + if (es_local || bgp_evpn_attr_is_sync(attr)) { + json_es_info = json_object_new_object(); + if (es_local) + json_object_boolean_true_add( + json_es_info, "localEs"); + if (peer_active) + json_object_boolean_true_add( + json_es_info, "peerActive"); + if (peer_proxy) + json_object_boolean_true_add( + json_es_info, "peerProxy"); + if (peer_router) + json_object_boolean_true_add( + json_es_info, "peerRouter"); + if (attr->mm_sync_seqnum) + json_object_int_add( + json_es_info, "peerSeq", + attr->mm_sync_seqnum); + json_object_object_add( + json_path, "es_info", + json_es_info); + } + } else { + if (bgp_evpn_attr_is_sync(attr)) + vty_out(vty, + " ESI %s %s peer-info: (%s%s%sMM: %d)\n", + esi_buf, + es_local ? "local-es":"", + peer_proxy ? "proxy " : "", + peer_active ? "active ":"", + peer_router ? "router ":"", + attr->mm_sync_seqnum); + else + vty_out(vty, " ESI %s %s\n", + esi_buf, + es_local ? "local-es":""); + } +} + +void route_vty_out_detail(struct vty *vty, struct bgp *bgp, struct bgp_dest *bn, + const struct prefix *p, struct bgp_path_info *path, + afi_t afi, safi_t safi, + enum rpki_states rpki_curr_state, + json_object *json_paths) +{ + char buf[INET6_ADDRSTRLEN]; + char tag_buf[30]; + struct attr *attr = path->attr; + time_t tbuf; + char timebuf[32]; + json_object *json_bestpath = NULL; + json_object *json_cluster_list = NULL; + json_object *json_cluster_list_list = NULL; + json_object *json_ext_community = NULL; + json_object *json_last_update = NULL; + json_object *json_pmsi = NULL; + json_object *json_nexthop_global = NULL; + json_object *json_nexthop_ll = NULL; + json_object *json_nexthops = NULL; + json_object *json_path = NULL; + json_object *json_peer = NULL; + json_object *json_string = NULL; + json_object *json_adv_to = NULL; + int first = 0; + struct listnode *node, *nnode; + struct peer *peer; + bool addpath_capable; + int has_adj; + unsigned int first_as; + bool nexthop_self = + CHECK_FLAG(path->flags, BGP_PATH_ANNC_NH_SELF) ? true : false; + int i; + char *nexthop_hostname = + bgp_nexthop_hostname(path->peer, path->nexthop); + uint32_t ttl = 0; + uint32_t bos = 0; + uint32_t exp = 0; + mpls_label_t label = MPLS_INVALID_LABEL; + tag_buf[0] = '\0'; + struct bgp_path_info *bpi_ultimate = + bgp_get_imported_bpi_ultimate(path); + + if (json_paths) { + json_path = json_object_new_object(); + json_peer = json_object_new_object(); + json_nexthop_global = json_object_new_object(); + } + + if (safi == SAFI_EVPN) { + if (!json_paths) + vty_out(vty, " Route %pFX", p); + } + + if (path->extra) { + if (path->extra && path->extra->num_labels) { + bgp_evpn_label2str(path->extra->label, + path->extra->num_labels, tag_buf, + sizeof(tag_buf)); + } + if (safi == SAFI_EVPN) { + if (!json_paths) { + if (tag_buf[0] != '\0') + vty_out(vty, " VNI %s", tag_buf); + } else { + if (tag_buf[0]) + json_object_string_add(json_path, "vni", + tag_buf); + } + } + } + + if (safi == SAFI_EVPN + && attr->evpn_overlay.type == OVERLAY_INDEX_GATEWAY_IP) { + char gwip_buf[INET6_ADDRSTRLEN]; + + ipaddr2str(&attr->evpn_overlay.gw_ip, gwip_buf, + sizeof(gwip_buf)); + + if (json_paths) + json_object_string_add(json_path, "gatewayIP", + gwip_buf); + else + vty_out(vty, " Gateway IP %s", gwip_buf); + } + + if (safi == SAFI_EVPN && !json_path) + vty_out(vty, "\n"); + + + if (path->extra && path->extra->vrfleak && + path->extra->vrfleak->parent && !json_paths) { + struct bgp_path_info *parent_ri; + struct bgp_dest *dest, *pdest; + + parent_ri = + (struct bgp_path_info *)path->extra->vrfleak->parent; + dest = parent_ri->net; + if (dest && dest->pdest) { + pdest = dest->pdest; + if (is_pi_family_evpn(parent_ri)) { + vty_out(vty, " Imported from "); + vty_out(vty, BGP_RD_AS_FORMAT(bgp->asnotation), + (struct prefix_rd *)bgp_dest_get_prefix( + pdest)); + vty_out(vty, ":%pFX, VNI %s", + (struct prefix_evpn *) + bgp_dest_get_prefix(dest), + tag_buf); + if (CHECK_FLAG(attr->es_flags, ATTR_ES_L3_NHG)) + vty_out(vty, ", L3NHG %s", + CHECK_FLAG( + attr->es_flags, + ATTR_ES_L3_NHG_ACTIVE) + ? "active" + : "inactive"); + vty_out(vty, "\n"); + + } else { + vty_out(vty, " Imported from "); + vty_out(vty, BGP_RD_AS_FORMAT(bgp->asnotation), + (struct prefix_rd *)bgp_dest_get_prefix( + pdest)); + vty_out(vty, ":%pFX\n", + (struct prefix_evpn *) + bgp_dest_get_prefix(dest)); + } + } + } + + /* Line1 display AS-path, Aggregator */ + if (attr->aspath) { + if (json_paths) { + if (!attr->aspath->json) + aspath_str_update(attr->aspath, true); + json_object_lock(attr->aspath->json); + json_object_object_add(json_path, "aspath", + attr->aspath->json); + } else { + if (attr->aspath->segments) + vty_out(vty, " %s", attr->aspath->str); + else + vty_out(vty, " Local"); + } + } + + if (CHECK_FLAG(path->flags, BGP_PATH_REMOVED)) { + if (json_paths) + json_object_boolean_true_add(json_path, "removed"); + else + vty_out(vty, ", (removed)"); + } + + if (CHECK_FLAG(path->flags, BGP_PATH_STALE)) { + if (json_paths) + json_object_boolean_true_add(json_path, "stale"); + else + vty_out(vty, ", (stale)"); + } + + if (CHECK_FLAG(attr->flag, ATTR_FLAG_BIT(BGP_ATTR_AGGREGATOR))) { + if (json_paths) { + json_object_int_add(json_path, "aggregatorAs", + attr->aggregator_as); + json_object_string_addf(json_path, "aggregatorId", + "%pI4", &attr->aggregator_addr); + } else { + vty_out(vty, ", (aggregated by %u %pI4)", + attr->aggregator_as, &attr->aggregator_addr); + } + } + + if (CHECK_FLAG(path->peer->af_flags[afi][safi], + PEER_FLAG_REFLECTOR_CLIENT)) { + if (json_paths) + json_object_boolean_true_add(json_path, + "rxedFromRrClient"); + else + vty_out(vty, ", (Received from a RR-client)"); + } + + if (CHECK_FLAG(path->peer->af_flags[afi][safi], + PEER_FLAG_RSERVER_CLIENT)) { + if (json_paths) + json_object_boolean_true_add(json_path, + "rxedFromRsClient"); + else + vty_out(vty, ", (Received from a RS-client)"); + } + + if (CHECK_FLAG(path->flags, BGP_PATH_HISTORY)) { + if (json_paths) + json_object_boolean_true_add(json_path, + "dampeningHistoryEntry"); + else + vty_out(vty, ", (history entry)"); + } else if (CHECK_FLAG(path->flags, BGP_PATH_DAMPED)) { + if (json_paths) + json_object_boolean_true_add(json_path, + "dampeningSuppressed"); + else + vty_out(vty, ", (suppressed due to dampening)"); + } + + if (!json_paths) + vty_out(vty, "\n"); + + /* Line2 display Next-hop, Neighbor, Router-id */ + /* Display the nexthop */ + + if ((p->family == AF_INET || p->family == AF_ETHERNET || + p->family == AF_EVPN) && + (safi == SAFI_MPLS_VPN || safi == SAFI_ENCAP || safi == SAFI_EVPN || + !BGP_ATTR_MP_NEXTHOP_LEN_IP6(attr))) { + if (safi == SAFI_MPLS_VPN || safi == SAFI_ENCAP + || safi == SAFI_EVPN) { + if (json_paths) { + json_object_string_addf( + json_nexthop_global, "ip", "%pI4", + &attr->mp_nexthop_global_in); + + if (path->peer->hostname) + json_object_string_add( + json_nexthop_global, "hostname", + path->peer->hostname); + } else { + if (nexthop_hostname) + vty_out(vty, " %pI4(%s)", + &attr->mp_nexthop_global_in, + nexthop_hostname); + else + vty_out(vty, " %pI4", + &attr->mp_nexthop_global_in); + } + } else { + if (json_paths) { + json_object_string_addf(json_nexthop_global, + "ip", "%pI4", + &attr->nexthop); + + if (path->peer->hostname) + json_object_string_add( + json_nexthop_global, "hostname", + path->peer->hostname); + } else { + if (nexthop_hostname) + vty_out(vty, " %pI4(%s)", + &attr->nexthop, + nexthop_hostname); + else + vty_out(vty, " %pI4", + &attr->nexthop); + } + } + + if (json_paths) + json_object_string_add(json_nexthop_global, "afi", + "ipv4"); + } else { + if (json_paths) { + json_object_string_addf(json_nexthop_global, "ip", + "%pI6", + &attr->mp_nexthop_global); + + if (path->peer->hostname) + json_object_string_add(json_nexthop_global, + "hostname", + path->peer->hostname); + + json_object_string_add(json_nexthop_global, "afi", + "ipv6"); + json_object_string_add(json_nexthop_global, "scope", + "global"); + } else { + if (nexthop_hostname) + vty_out(vty, " %pI6(%s)", + &attr->mp_nexthop_global, + nexthop_hostname); + else + vty_out(vty, " %pI6", + &attr->mp_nexthop_global); + } + } + + /* Display the IGP cost or 'inaccessible' */ + if (!CHECK_FLAG(bpi_ultimate->flags, BGP_PATH_VALID)) { + bool import = CHECK_FLAG(bgp->flags, BGP_FLAG_IMPORT_CHECK); + + if (json_paths) { + json_object_boolean_false_add(json_nexthop_global, + "accessible"); + json_object_boolean_add(json_nexthop_global, + "importCheckEnabled", import); + } else { + vty_out(vty, " (inaccessible%s)", + import ? ", import-check enabled" : ""); + } + } else { + if (bpi_ultimate->extra && bpi_ultimate->extra->igpmetric) { + if (json_paths) + json_object_int_add( + json_nexthop_global, "metric", + bpi_ultimate->extra->igpmetric); + else + vty_out(vty, " (metric %u)", + bpi_ultimate->extra->igpmetric); + } + + /* IGP cost is 0, display this only for json */ + else { + if (json_paths) + json_object_int_add(json_nexthop_global, + "metric", 0); + } + + if (json_paths) + json_object_boolean_true_add(json_nexthop_global, + "accessible"); + } + + /* Display peer "from" output */ + /* This path was originated locally */ + if (path->peer == bgp->peer_self) { + + if (safi == SAFI_EVPN || (p->family == AF_INET && + !BGP_ATTR_MP_NEXTHOP_LEN_IP6(attr))) { + if (json_paths) + json_object_string_add(json_peer, "peerId", + "0.0.0.0"); + else + vty_out(vty, " from 0.0.0.0 "); + } else { + if (json_paths) + json_object_string_add(json_peer, "peerId", + "::"); + else + vty_out(vty, " from :: "); + } + + if (json_paths) + json_object_string_addf(json_peer, "routerId", "%pI4", + &bgp->router_id); + else + vty_out(vty, "(%pI4)", &bgp->router_id); + } + + /* We RXed this path from one of our peers */ + else { + + if (json_paths) { + json_object_string_addf(json_peer, "peerId", "%pSU", + &path->peer->connection->su); + json_object_string_addf(json_peer, "routerId", "%pI4", + &path->peer->remote_id); + + if (path->peer->hostname) + json_object_string_add(json_peer, "hostname", + path->peer->hostname); + + if (path->peer->domainname) + json_object_string_add(json_peer, "domainname", + path->peer->domainname); + + if (path->peer->conf_if) + json_object_string_add(json_peer, "interface", + path->peer->conf_if); + } else { + if (path->peer->conf_if) { + if (path->peer->hostname + && CHECK_FLAG(path->peer->bgp->flags, + BGP_FLAG_SHOW_HOSTNAME)) + vty_out(vty, " from %s(%s)", + path->peer->hostname, + path->peer->conf_if); + else + vty_out(vty, " from %s", + path->peer->conf_if); + } else { + if (path->peer->hostname + && CHECK_FLAG(path->peer->bgp->flags, + BGP_FLAG_SHOW_HOSTNAME)) + vty_out(vty, " from %s(%s)", + path->peer->hostname, + path->peer->host); + else + vty_out(vty, " from %pSU", + &path->peer->connection->su); + } + + if (attr->flag & ATTR_FLAG_BIT(BGP_ATTR_ORIGINATOR_ID)) + vty_out(vty, " (%pI4)", &attr->originator_id); + else + vty_out(vty, " (%pI4)", &path->peer->remote_id); + } + } + + /* + * Note when vrfid of nexthop is different from that of prefix + */ + if (path->extra && path->extra->vrfleak && + path->extra->vrfleak->bgp_orig) { + vrf_id_t nexthop_vrfid = path->extra->vrfleak->bgp_orig->vrf_id; + + if (json_paths) { + const char *vn; + + if (path->extra->vrfleak->bgp_orig->inst_type == + BGP_INSTANCE_TYPE_DEFAULT) + vn = VRF_DEFAULT_NAME; + else + vn = path->extra->vrfleak->bgp_orig->name; + + json_object_string_add(json_path, "nhVrfName", vn); + + if (nexthop_vrfid == VRF_UNKNOWN) { + json_object_int_add(json_path, "nhVrfId", -1); + } else { + json_object_int_add(json_path, "nhVrfId", + (int)nexthop_vrfid); + } + } else { + if (nexthop_vrfid == VRF_UNKNOWN) + vty_out(vty, " vrf ?"); + else { + struct vrf *vrf; + + vrf = vrf_lookup_by_id(nexthop_vrfid); + vty_out(vty, " vrf %s(%u)", + VRF_LOGNAME(vrf), nexthop_vrfid); + } + } + } + + if (nexthop_self) { + if (json_paths) { + json_object_boolean_true_add(json_path, + "announceNexthopSelf"); + } else { + vty_out(vty, " announce-nh-self"); + } + } + + if (!json_paths) + vty_out(vty, "\n"); + + /* display the link-local nexthop */ + if (attr->mp_nexthop_len == BGP_ATTR_NHLEN_IPV6_GLOBAL_AND_LL) { + if (json_paths) { + json_nexthop_ll = json_object_new_object(); + json_object_string_addf(json_nexthop_ll, "ip", "%pI6", + &attr->mp_nexthop_local); + + if (path->peer->hostname) + json_object_string_add(json_nexthop_ll, + "hostname", + path->peer->hostname); + + json_object_string_add(json_nexthop_ll, "afi", "ipv6"); + json_object_string_add(json_nexthop_ll, "scope", + "link-local"); + + json_object_boolean_true_add(json_nexthop_ll, + "accessible"); + + if (!attr->mp_nexthop_prefer_global) + json_object_boolean_true_add(json_nexthop_ll, + "used"); + else + json_object_boolean_true_add( + json_nexthop_global, "used"); + } else { + vty_out(vty, " (%s) %s\n", + inet_ntop(AF_INET6, &attr->mp_nexthop_local, + buf, INET6_ADDRSTRLEN), + attr->mp_nexthop_prefer_global + ? "(prefer-global)" + : "(used)"); + } + } + /* If we do not have a link-local nexthop then we must flag the + global as "used" */ + else { + if (json_paths) + json_object_boolean_true_add(json_nexthop_global, + "used"); + } + + if (safi == SAFI_EVPN && + bgp_evpn_is_esi_valid(&attr->esi)) { + route_vty_out_detail_es_info(vty, path, attr, json_path); + } + + /* Line 3 display Origin, Med, Locpref, Weight, Tag, valid, + * Int/Ext/Local, Atomic, best */ + if (json_paths) + json_object_string_add(json_path, "origin", + bgp_origin_long_str[attr->origin]); + else + vty_out(vty, " Origin %s", + bgp_origin_long_str[attr->origin]); + + if (attr->flag & ATTR_FLAG_BIT(BGP_ATTR_MULTI_EXIT_DISC)) { + if (json_paths) + json_object_int_add(json_path, "metric", attr->med); + else + vty_out(vty, ", metric %u", attr->med); + } + + if (attr->flag & ATTR_FLAG_BIT(BGP_ATTR_LOCAL_PREF)) { + if (json_paths) + json_object_int_add(json_path, "locPrf", + attr->local_pref); + else + vty_out(vty, ", localpref %u", attr->local_pref); + } + + if (attr->flag & ATTR_FLAG_BIT(BGP_ATTR_AIGP)) { + if (json_paths) + json_object_int_add(json_path, "aigpMetric", + bgp_attr_get_aigp_metric(attr)); + else + vty_out(vty, ", aigp-metric %" PRIu64, + bgp_attr_get_aigp_metric(attr)); + } + + if (attr->weight != 0) { + if (json_paths) + json_object_int_add(json_path, "weight", attr->weight); + else + vty_out(vty, ", weight %u", attr->weight); + } + + if (attr->tag != 0) { + if (json_paths) + json_object_int_add(json_path, "tag", attr->tag); + else + vty_out(vty, ", tag %" ROUTE_TAG_PRI, attr->tag); + } + + if (!CHECK_FLAG(path->flags, BGP_PATH_VALID)) { + if (json_paths) + json_object_boolean_false_add(json_path, "valid"); + else + vty_out(vty, ", invalid"); + } else if (!CHECK_FLAG(path->flags, BGP_PATH_HISTORY)) { + if (json_paths) + json_object_boolean_true_add(json_path, "valid"); + else + vty_out(vty, ", valid"); + } + + if (json_paths) + json_object_int_add(json_path, "version", bn->version); + + if (path->peer != bgp->peer_self) { + if (path->peer->as == path->peer->local_as) { + if (CHECK_FLAG(bgp->config, BGP_CONFIG_CONFEDERATION)) { + if (json_paths) + json_object_string_add( + json_peer, "type", + "confed-internal"); + else + vty_out(vty, ", confed-internal"); + } else { + if (json_paths) + json_object_string_add( + json_peer, "type", "internal"); + else + vty_out(vty, ", internal"); + } + } else { + if (bgp_confederation_peers_check(bgp, + path->peer->as)) { + if (json_paths) + json_object_string_add( + json_peer, "type", + "confed-external"); + else + vty_out(vty, ", confed-external"); + } else { + if (json_paths) + json_object_string_add( + json_peer, "type", "external"); + else + vty_out(vty, ", external"); + } + } + } else if (path->sub_type == BGP_ROUTE_AGGREGATE) { + if (json_paths) { + json_object_boolean_true_add(json_path, "aggregated"); + json_object_boolean_true_add(json_path, "local"); + } else { + vty_out(vty, ", aggregated, local"); + } + } else if (path->type != ZEBRA_ROUTE_BGP) { + if (json_paths) + json_object_boolean_true_add(json_path, "sourced"); + else + vty_out(vty, ", sourced"); + } else { + if (json_paths) { + json_object_boolean_true_add(json_path, "sourced"); + json_object_boolean_true_add(json_path, "local"); + } else { + vty_out(vty, ", sourced, local"); + } + } + + if (attr->flag & ATTR_FLAG_BIT(BGP_ATTR_ATOMIC_AGGREGATE)) { + if (json_paths) + json_object_boolean_true_add(json_path, + "atomicAggregate"); + else + vty_out(vty, ", atomic-aggregate"); + } + + if (attr->flag & ATTR_FLAG_BIT(BGP_ATTR_OTC)) { + if (json_paths) + json_object_int_add(json_path, "otc", attr->otc); + else + vty_out(vty, ", otc %u", attr->otc); + } + + if (CHECK_FLAG(path->flags, BGP_PATH_MULTIPATH) + || (CHECK_FLAG(path->flags, BGP_PATH_SELECTED) + && bgp_path_info_mpath_count(path))) { + if (json_paths) + json_object_boolean_true_add(json_path, "multipath"); + else + vty_out(vty, ", multipath"); + } + + // Mark the bestpath(s) + if (CHECK_FLAG(path->flags, BGP_PATH_DMED_SELECTED)) { + first_as = aspath_get_first_as(attr->aspath); + + if (json_paths) { + if (!json_bestpath) + json_bestpath = json_object_new_object(); + json_object_int_add(json_bestpath, "bestpathFromAs", + first_as); + } else { + if (first_as) + vty_out(vty, ", bestpath-from-AS %u", first_as); + else + vty_out(vty, ", bestpath-from-AS Local"); + } + } + + if (CHECK_FLAG(path->flags, BGP_PATH_SELECTED)) { + if (json_paths) { + if (!json_bestpath) + json_bestpath = json_object_new_object(); + json_object_boolean_true_add(json_bestpath, "overall"); + json_object_string_add( + json_bestpath, "selectionReason", + bgp_path_selection_reason2str(bn->reason)); + } else { + vty_out(vty, ", best"); + vty_out(vty, " (%s)", + bgp_path_selection_reason2str(bn->reason)); + } + } + + if (rpki_curr_state != RPKI_NOT_BEING_USED) { + if (json_paths) + json_object_string_add( + json_path, "rpkiValidationState", + bgp_rpki_validation2str(rpki_curr_state)); + else + vty_out(vty, ", rpki validation-state: %s", + bgp_rpki_validation2str(rpki_curr_state)); + } + + if (json_bestpath) + json_object_object_add(json_path, "bestpath", json_bestpath); + + if (!json_paths) + vty_out(vty, "\n"); + + /* Line 4 display Community */ + if (attr->flag & ATTR_FLAG_BIT(BGP_ATTR_COMMUNITIES)) { + if (json_paths) { + if (!bgp_attr_get_community(attr)->json) + community_str(bgp_attr_get_community(attr), + true, true); + json_object_lock(bgp_attr_get_community(attr)->json); + json_object_object_add( + json_path, "community", + bgp_attr_get_community(attr)->json); + } else { + vty_out(vty, " Community: %s\n", + bgp_attr_get_community(attr)->str); + } + } + + /* Line 5 display Extended-community */ + if (attr->flag & ATTR_FLAG_BIT(BGP_ATTR_EXT_COMMUNITIES)) { + if (json_paths) { + json_ext_community = json_object_new_object(); + json_object_string_add( + json_ext_community, "string", + bgp_attr_get_ecommunity(attr)->str); + json_object_object_add(json_path, "extendedCommunity", + json_ext_community); + } else { + vty_out(vty, " Extended Community: %s\n", + bgp_attr_get_ecommunity(attr)->str); + } + } + + /* Line 6 display Large community */ + if (attr->flag & ATTR_FLAG_BIT(BGP_ATTR_LARGE_COMMUNITIES)) { + if (json_paths) { + if (!bgp_attr_get_lcommunity(attr)->json) + lcommunity_str(bgp_attr_get_lcommunity(attr), + true, true); + json_object_lock(bgp_attr_get_lcommunity(attr)->json); + json_object_object_add( + json_path, "largeCommunity", + bgp_attr_get_lcommunity(attr)->json); + } else { + vty_out(vty, " Large Community: %s\n", + bgp_attr_get_lcommunity(attr)->str); + } + } + + /* Line 7 display Originator, Cluster-id */ + if ((attr->flag & ATTR_FLAG_BIT(BGP_ATTR_ORIGINATOR_ID)) + || (attr->flag & ATTR_FLAG_BIT(BGP_ATTR_CLUSTER_LIST))) { + char buf[BUFSIZ] = {0}; + + if (attr->flag & ATTR_FLAG_BIT(BGP_ATTR_ORIGINATOR_ID)) { + if (json_paths) + json_object_string_addf(json_path, + "originatorId", "%pI4", + &attr->originator_id); + else + vty_out(vty, " Originator: %pI4", + &attr->originator_id); + } + + if (attr->flag & ATTR_FLAG_BIT(BGP_ATTR_CLUSTER_LIST)) { + struct cluster_list *cluster = + bgp_attr_get_cluster(attr); + int i; + + if (json_paths) { + json_cluster_list = json_object_new_object(); + json_cluster_list_list = + json_object_new_array(); + + for (i = 0; i < cluster->length / 4; i++) { + json_string = json_object_new_string( + inet_ntop(AF_INET, + &cluster->list[i], + buf, sizeof(buf))); + json_object_array_add( + json_cluster_list_list, + json_string); + } + + /* + * struct cluster_list does not have + * "str" variable like aspath and community + * do. Add this someday if someone asks + * for it. + * json_object_string_add(json_cluster_list, + * "string", cluster->str); + */ + json_object_object_add(json_cluster_list, + "list", + json_cluster_list_list); + json_object_object_add(json_path, "clusterList", + json_cluster_list); + } else { + vty_out(vty, ", Cluster list: "); + + for (i = 0; i < cluster->length / 4; i++) { + vty_out(vty, "%pI4 ", + &cluster->list[i]); + } + } + } + + if (!json_paths) + vty_out(vty, "\n"); + } + + if (path->extra && path->extra->damp_info) + bgp_damp_info_vty(vty, path, afi, safi, json_path); + + /* Remote Label */ + if (path->extra && bgp_is_valid_label(&path->extra->label[0]) + && (safi != SAFI_EVPN && !is_route_parent_evpn(path))) { + mpls_lse_decode(path->extra->label[0], &label, &ttl, &exp, + &bos); + + if (json_paths) + json_object_int_add(json_path, "remoteLabel", label); + else + vty_out(vty, " Remote label: %d\n", label); + } + + /* Remote SID */ + if ((path->attr->srv6_l3vpn || path->attr->srv6_vpn) && + safi != SAFI_EVPN) { + struct in6_addr *sid_tmp = + path->attr->srv6_l3vpn ? (&path->attr->srv6_l3vpn->sid) + : (&path->attr->srv6_vpn->sid); + if (json_paths) + json_object_string_addf(json_path, "remoteSid", "%pI6", + sid_tmp); + else + vty_out(vty, " Remote SID: %pI6\n", sid_tmp); + } + + /* Label Index */ + if (attr->label_index != BGP_INVALID_LABEL_INDEX) { + if (json_paths) + json_object_int_add(json_path, "labelIndex", + attr->label_index); + else + vty_out(vty, " Label Index: %d\n", + attr->label_index); + } + + /* Line 8 display Addpath IDs */ + if (path->addpath_rx_id + || bgp_addpath_info_has_ids(&path->tx_addpath)) { + if (json_paths) { + json_object_int_add(json_path, "addpathRxId", + path->addpath_rx_id); + + /* Keep backwards compatibility with the old API + * by putting TX All's ID in the old field + */ + json_object_int_add( + json_path, "addpathTxId", + path->tx_addpath + .addpath_tx_id[BGP_ADDPATH_ALL]); + + /* ... but create a specific field for each + * strategy + */ + for (i = 0; i < BGP_ADDPATH_MAX; i++) { + json_object_int_add( + json_path, + bgp_addpath_names(i)->id_json_name, + path->tx_addpath.addpath_tx_id[i]); + } + } else { + vty_out(vty, " AddPath ID: RX %u, ", + path->addpath_rx_id); + + route_vty_out_tx_ids(vty, &path->tx_addpath); + } + } + + /* If we used addpath to TX a non-bestpath we need to display + * "Advertised to" on a path-by-path basis + */ + if (bgp_addpath_is_addpath_used(&bgp->tx_addpath, afi, safi)) { + first = 1; + + for (ALL_LIST_ELEMENTS(bgp->peer, node, nnode, peer)) { + addpath_capable = + bgp_addpath_encode_tx(peer, afi, safi); + has_adj = bgp_adj_out_lookup( + peer, path->net, + bgp_addpath_id_for_peer(peer, afi, safi, + &path->tx_addpath)); + + if ((addpath_capable && has_adj) + || (!addpath_capable && has_adj + && CHECK_FLAG(path->flags, + BGP_PATH_SELECTED))) { + if (json_path && !json_adv_to) + json_adv_to = json_object_new_object(); + + route_vty_out_advertised_to( + vty, peer, &first, + " Advertised to:", json_adv_to); + } + } + + if (json_path) { + if (json_adv_to) { + json_object_object_add( + json_path, "advertisedTo", json_adv_to); + } + } else { + if (!first) { + vty_out(vty, "\n"); + } + } + } + + /* Line 9 display Uptime */ + tbuf = time(NULL) - (monotime(NULL) - path->uptime); + if (json_paths) { + json_last_update = json_object_new_object(); + json_object_int_add(json_last_update, "epoch", tbuf); + json_object_string_add(json_last_update, "string", + ctime_r(&tbuf, timebuf)); + json_object_object_add(json_path, "lastUpdate", + json_last_update); + } else + vty_out(vty, " Last update: %s", ctime_r(&tbuf, timebuf)); + + /* Line 10 display PMSI tunnel attribute, if present */ + if (attr->flag & ATTR_FLAG_BIT(BGP_ATTR_PMSI_TUNNEL)) { + const char *str = lookup_msg(bgp_pmsi_tnltype_str, + bgp_attr_get_pmsi_tnl_type(attr), + PMSI_TNLTYPE_STR_DEFAULT); + + if (json_paths) { + json_pmsi = json_object_new_object(); + json_object_string_add(json_pmsi, "tunnelType", str); + json_object_int_add(json_pmsi, "label", + label2vni(&attr->label)); + json_object_object_add(json_path, "pmsi", json_pmsi); + } else + vty_out(vty, " PMSI Tunnel Type: %s, label: %d\n", + str, label2vni(&attr->label)); + } + + if (path->peer->connection->t_gr_restart && + CHECK_FLAG(path->flags, BGP_PATH_STALE)) { + unsigned long gr_remaining = event_timer_remain_second( + path->peer->connection->t_gr_restart); + + if (json_paths) { + json_object_int_add(json_path, + "gracefulRestartSecondsRemaining", + gr_remaining); + } else + vty_out(vty, + " Time until Graceful Restart stale route deleted: %lu\n", + gr_remaining); + } + + if (path->peer->t_llgr_stale[afi][safi] && + bgp_attr_get_community(attr) && + community_include(bgp_attr_get_community(attr), + COMMUNITY_LLGR_STALE)) { + unsigned long llgr_remaining = event_timer_remain_second( + path->peer->t_llgr_stale[afi][safi]); + + if (json_paths) { + json_object_int_add(json_path, "llgrSecondsRemaining", + llgr_remaining); + } else + vty_out(vty, + " Time until Long-lived stale route deleted: %lu\n", + llgr_remaining); + } + + /* Output some debug about internal state of the dest flags */ + if (json_paths) { + if (CHECK_FLAG(bn->flags, BGP_NODE_PROCESS_SCHEDULED)) + json_object_boolean_true_add(json_path, "processScheduled"); + if (CHECK_FLAG(bn->flags, BGP_NODE_USER_CLEAR)) + json_object_boolean_true_add(json_path, "userCleared"); + if (CHECK_FLAG(bn->flags, BGP_NODE_LABEL_CHANGED)) + json_object_boolean_true_add(json_path, "labelChanged"); + if (CHECK_FLAG(bn->flags, BGP_NODE_REGISTERED_FOR_LABEL)) + json_object_boolean_true_add(json_path, "registeredForLabel"); + if (CHECK_FLAG(bn->flags, BGP_NODE_SELECT_DEFER)) + json_object_boolean_true_add(json_path, "selectDefered"); + if (CHECK_FLAG(bn->flags, BGP_NODE_FIB_INSTALLED)) + json_object_boolean_true_add(json_path, "fibInstalled"); + if (CHECK_FLAG(bn->flags, BGP_NODE_FIB_INSTALL_PENDING)) + json_object_boolean_true_add(json_path, "fibPending"); + + if (json_nexthop_global || json_nexthop_ll) { + json_nexthops = json_object_new_array(); + + if (json_nexthop_global) + json_object_array_add(json_nexthops, + json_nexthop_global); + + if (json_nexthop_ll) + json_object_array_add(json_nexthops, + json_nexthop_ll); + + json_object_object_add(json_path, "nexthops", + json_nexthops); + } + + json_object_object_add(json_path, "peer", json_peer); + json_object_array_add(json_paths, json_path); + } +} + +#define BGP_SHOW_HEADER_CSV "Flags, Network, Next Hop, Metric, LocPrf, Weight, Path" +#define BGP_SHOW_DAMP_HEADER " Network From Reuse Path\n" +#define BGP_SHOW_FLAP_HEADER " Network From Flaps Duration Reuse Path\n" + +static int bgp_show_regexp(struct vty *vty, struct bgp *bgp, const char *regstr, + afi_t afi, safi_t safi, enum bgp_show_type type, + bool use_json); +static int bgp_show_community(struct vty *vty, struct bgp *bgp, + const char *comstr, int exact, afi_t afi, + safi_t safi, uint16_t show_flags); + +static int bgp_show_table(struct vty *vty, struct bgp *bgp, afi_t afi, safi_t safi, + struct bgp_table *table, enum bgp_show_type type, + void *output_arg, const char *rd, int is_last, + unsigned long *output_cum, unsigned long *total_cum, + unsigned long *json_header_depth, uint16_t show_flags, + enum rpki_states rpki_target_state) +{ + struct bgp_path_info *pi; + struct bgp_dest *dest; + bool header = true; + bool json_detail_header = false; + int display; + unsigned long output_count = 0; + unsigned long total_count = 0; + struct prefix *p; + json_object *json_paths = NULL; + int first = 1; + bool use_json = CHECK_FLAG(show_flags, BGP_SHOW_OPT_JSON); + bool wide = CHECK_FLAG(show_flags, BGP_SHOW_OPT_WIDE); + bool all = CHECK_FLAG(show_flags, BGP_SHOW_OPT_AFI_ALL); + bool detail_json = CHECK_FLAG(show_flags, BGP_SHOW_OPT_JSON_DETAIL); + bool detail_routes = CHECK_FLAG(show_flags, BGP_SHOW_OPT_ROUTES_DETAIL); + + if (output_cum && *output_cum != 0) + header = false; + + if (use_json && !*json_header_depth) { + if (all) + *json_header_depth = 1; + else { + vty_out(vty, "{\n"); + *json_header_depth = 2; + } + vty_out(vty, + " \"vrfId\": %d,\n \"vrfName\": \"%s\",\n \"tableVersion\": %" PRId64 + ",\n \"routerId\": \"%pI4\",\n \"defaultLocPrf\": %u,\n" + " \"localAS\": ", + bgp->vrf_id == VRF_UNKNOWN ? -1 : (int)bgp->vrf_id, + bgp->inst_type == BGP_INSTANCE_TYPE_DEFAULT + ? VRF_DEFAULT_NAME + : bgp->name, + table->version, &bgp->router_id, + bgp->default_local_pref); + if ((bgp->asnotation == ASNOTATION_PLAIN) || + ((bgp->asnotation == ASNOTATION_DOT) && + (bgp->as < UINT16_MAX))) + vty_out(vty, "%u", bgp->as); + else { + vty_out(vty, "\""); + vty_out(vty, ASN_FORMAT(bgp->asnotation), &bgp->as); + vty_out(vty, "\""); + } + vty_out(vty, ",\n \"routes\": { "); + if (rd) { + vty_out(vty, " \"routeDistinguishers\" : {"); + ++*json_header_depth; + } + } + + if (use_json && rd) { + vty_out(vty, " \"%s\" : { ", rd); + } + + /* Check for 'json detail', where we need header output once per dest */ + if (use_json && detail_json && type != bgp_show_type_dampend_paths && + type != bgp_show_type_damp_neighbor && + type != bgp_show_type_flap_statistics && + type != bgp_show_type_flap_neighbor) + json_detail_header = true; + + /* Start processing of routes. */ + for (dest = bgp_table_top(table); dest; dest = bgp_route_next(dest)) { + const struct prefix *dest_p = bgp_dest_get_prefix(dest); + enum rpki_states rpki_curr_state = RPKI_NOT_BEING_USED; + bool json_detail_header_used = false; + + pi = bgp_dest_get_bgp_path_info(dest); + if (pi == NULL) + continue; + + display = 0; + if (use_json) + json_paths = json_object_new_array(); + else + json_paths = NULL; + + for (; pi; pi = pi->next) { + struct community *picomm = NULL; + + picomm = bgp_attr_get_community(pi->attr); + + total_count++; + + if (type == bgp_show_type_prefix_version) { + uint32_t version = + strtoul(output_arg, NULL, 10); + if (dest->version < version) + continue; + } + + if (type == bgp_show_type_community_alias) { + char *alias = output_arg; + char **communities; + int num; + bool found = false; + + if (picomm) { + frrstr_split(picomm->str, " ", + &communities, &num); + for (int i = 0; i < num; i++) { + const char *com2alias = + bgp_community2alias( + communities[i]); + if (!found + && strcmp(alias, com2alias) + == 0) + found = true; + XFREE(MTYPE_TMP, + communities[i]); + } + XFREE(MTYPE_TMP, communities); + } + + if (!found && + bgp_attr_get_lcommunity(pi->attr)) { + frrstr_split(bgp_attr_get_lcommunity( + pi->attr) + ->str, + " ", &communities, &num); + for (int i = 0; i < num; i++) { + const char *com2alias = + bgp_community2alias( + communities[i]); + if (!found + && strcmp(alias, com2alias) + == 0) + found = true; + XFREE(MTYPE_TMP, + communities[i]); + } + XFREE(MTYPE_TMP, communities); + } + + if (!found) + continue; + } + + if (type == bgp_show_type_rpki) { + if (dest_p->family == AF_INET + || dest_p->family == AF_INET6) + rpki_curr_state = hook_call( + bgp_rpki_prefix_status, + pi->peer, pi->attr, dest_p); + if (rpki_target_state != RPKI_NOT_BEING_USED + && rpki_curr_state != rpki_target_state) + continue; + } + + if (type == bgp_show_type_flap_statistics + || type == bgp_show_type_flap_neighbor + || type == bgp_show_type_dampend_paths + || type == bgp_show_type_damp_neighbor) { + if (!(pi->extra && pi->extra->damp_info)) + continue; + } + if (type == bgp_show_type_regexp) { + regex_t *regex = output_arg; + + if (bgp_regexec(regex, pi->attr->aspath) + == REG_NOMATCH) + continue; + } + if (type == bgp_show_type_prefix_list) { + struct prefix_list *plist = output_arg; + + if (prefix_list_apply(plist, dest_p) + != PREFIX_PERMIT) + continue; + } + if (type == bgp_show_type_access_list) { + struct access_list *alist = output_arg; + + if (access_list_apply(alist, dest_p) != + FILTER_PERMIT) + continue; + } + if (type == bgp_show_type_filter_list) { + struct as_list *as_list = output_arg; + + if (as_list_apply(as_list, pi->attr->aspath) + != AS_FILTER_PERMIT) + continue; + } + if (type == bgp_show_type_route_map) { + struct route_map *rmap = output_arg; + struct bgp_path_info path; + struct bgp_path_info_extra extra; + struct attr dummy_attr = {}; + route_map_result_t ret; + + dummy_attr = *pi->attr; + + prep_for_rmap_apply(&path, &extra, dest, pi, + pi->peer, &dummy_attr); + + ret = route_map_apply(rmap, dest_p, &path); + bgp_attr_flush(&dummy_attr); + if (ret == RMAP_DENYMATCH) + continue; + } + if (type == bgp_show_type_neighbor + || type == bgp_show_type_flap_neighbor + || type == bgp_show_type_damp_neighbor) { + union sockunion *su = output_arg; + + if (pi->peer == NULL + || pi->peer->su_remote == NULL + || !sockunion_same(pi->peer->su_remote, su)) + continue; + } + if (type == bgp_show_type_cidr_only) { + uint32_t destination; + + destination = ntohl(dest_p->u.prefix4.s_addr); + if (IN_CLASSC(destination) + && dest_p->prefixlen == 24) + continue; + if (IN_CLASSB(destination) + && dest_p->prefixlen == 16) + continue; + if (IN_CLASSA(destination) + && dest_p->prefixlen == 8) + continue; + } + if (type == bgp_show_type_prefix_longer) { + p = output_arg; + if (!prefix_match(p, dest_p)) + continue; + } + if (type == bgp_show_type_community_all) { + if (!picomm) + continue; + } + if (type == bgp_show_type_community) { + struct community *com = output_arg; + + if (!picomm || !community_match(picomm, com)) + continue; + } + if (type == bgp_show_type_community_exact) { + struct community *com = output_arg; + + if (!picomm || !community_cmp(picomm, com)) + continue; + } + if (type == bgp_show_type_community_list) { + struct community_list *list = output_arg; + + if (!community_list_match(picomm, list)) + continue; + } + if (type == bgp_show_type_community_list_exact) { + struct community_list *list = output_arg; + + if (!community_list_exact_match(picomm, list)) + continue; + } + if (type == bgp_show_type_lcommunity) { + struct lcommunity *lcom = output_arg; + + if (!bgp_attr_get_lcommunity(pi->attr) || + !lcommunity_match( + bgp_attr_get_lcommunity(pi->attr), + lcom)) + continue; + } + + if (type == bgp_show_type_lcommunity_exact) { + struct lcommunity *lcom = output_arg; + + if (!bgp_attr_get_lcommunity(pi->attr) || + !lcommunity_cmp( + bgp_attr_get_lcommunity(pi->attr), + lcom)) + continue; + } + if (type == bgp_show_type_lcommunity_list) { + struct community_list *list = output_arg; + + if (!lcommunity_list_match( + bgp_attr_get_lcommunity(pi->attr), + list)) + continue; + } + if (type + == bgp_show_type_lcommunity_list_exact) { + struct community_list *list = output_arg; + + if (!lcommunity_list_exact_match( + bgp_attr_get_lcommunity(pi->attr), + list)) + continue; + } + if (type == bgp_show_type_lcommunity_all) { + if (!bgp_attr_get_lcommunity(pi->attr)) + continue; + } + if (type == bgp_show_type_dampend_paths + || type == bgp_show_type_damp_neighbor) { + if (!CHECK_FLAG(pi->flags, BGP_PATH_DAMPED) + || CHECK_FLAG(pi->flags, BGP_PATH_HISTORY)) + continue; + } + if (type == bgp_show_type_self_originated) { + if (pi->peer != bgp->peer_self) + continue; + } + + if (!use_json && header) { + vty_out(vty, + "BGP table version is %" PRIu64 + ", local router ID is %pI4, vrf id ", + table->version, &bgp->router_id); + if (bgp->vrf_id == VRF_UNKNOWN) + vty_out(vty, "%s", VRFID_NONE_STR); + else + vty_out(vty, "%u", bgp->vrf_id); + vty_out(vty, "\n"); + vty_out(vty, "Default local pref %u, ", + bgp->default_local_pref); + vty_out(vty, "local AS "); + vty_out(vty, ASN_FORMAT(bgp->asnotation), + &bgp->as); + vty_out(vty, "\n"); + if (!detail_routes) { + vty_out(vty, BGP_SHOW_SCODE_HEADER); + vty_out(vty, BGP_SHOW_NCODE_HEADER); + vty_out(vty, BGP_SHOW_OCODE_HEADER); + vty_out(vty, BGP_SHOW_RPKI_HEADER); + } + if (type == bgp_show_type_dampend_paths + || type == bgp_show_type_damp_neighbor) + vty_out(vty, BGP_SHOW_DAMP_HEADER); + else if (type == bgp_show_type_flap_statistics + || type == bgp_show_type_flap_neighbor) + vty_out(vty, BGP_SHOW_FLAP_HEADER); + else if (!detail_routes) + vty_out(vty, (wide ? BGP_SHOW_HEADER_WIDE + : BGP_SHOW_HEADER)); + header = false; + + } + if (rd != NULL && !display && !output_count) { + if (!use_json) + vty_out(vty, + "Route Distinguisher: %s\n", + rd); + } + if (type == bgp_show_type_dampend_paths + || type == bgp_show_type_damp_neighbor) + damp_route_vty_out(vty, dest_p, pi, display, + afi, safi, use_json, + json_paths); + else if (type == bgp_show_type_flap_statistics + || type == bgp_show_type_flap_neighbor) + flap_route_vty_out(vty, dest_p, pi, display, + afi, safi, use_json, + json_paths); + else { + if (detail_routes || detail_json) { + const struct prefix_rd *prd = NULL; + + if (dest->pdest) + prd = bgp_rd_from_dest( + dest->pdest, safi); + + if (!use_json) + route_vty_out_detail_header( + vty, bgp, dest, + bgp_dest_get_prefix( + dest), + prd, table->afi, safi, + NULL, false); + + route_vty_out_detail( + vty, bgp, dest, dest_p, pi, + family2afi(dest_p->family), + safi, RPKI_NOT_BEING_USED, + json_paths); + } else { + route_vty_out(vty, dest_p, pi, display, + safi, json_paths, wide); + } + } + display++; + } + + if (display) { + output_count++; + if (!use_json) + continue; + + /* encode prefix */ + if (dest_p->family == AF_FLOWSPEC) { + char retstr[BGP_FLOWSPEC_STRING_DISPLAY_MAX]; + + + bgp_fs_nlri_get_string( + (unsigned char *) + dest_p->u.prefix_flowspec.ptr, + dest_p->u.prefix_flowspec.prefixlen, + retstr, NLRI_STRING_FORMAT_MIN, NULL, + family2afi(dest_p->u + .prefix_flowspec.family)); + if (first) + vty_out(vty, "\"%s/%d\": ", retstr, + dest_p->u.prefix_flowspec + .prefixlen); + else + vty_out(vty, ",\"%s/%d\": ", retstr, + dest_p->u.prefix_flowspec + .prefixlen); + } else { + if (first) + vty_out(vty, "\"%pFX\": ", dest_p); + else + vty_out(vty, ",\"%pFX\": ", dest_p); + } + + /* This is used for 'json detail' vty keywords. + * + * In plain 'json' the per-prefix header is encoded + * as a standalone dictionary in the first json_paths + * array element: + * "<prefix>": [{header}, {path-1}, {path-N}] + * (which is confusing and borderline broken) + * + * For 'json detail' this changes the value + * of each prefix-key to be a dictionary where each + * header item has its own key, and json_paths is + * tucked under the "paths" key: + * "<prefix>": { + * "<header-key-1>": <header-val-1>, + * "<header-key-N>": <header-val-N>, + * "paths": [{path-1}, {path-N}] + * } + */ + if (json_detail_header && json_paths != NULL) { + const struct prefix_rd *prd; + + /* Start per-prefix dictionary */ + vty_out(vty, "{\n"); + + prd = bgp_rd_from_dest(dest, safi); + + route_vty_out_detail_header( + vty, bgp, dest, + bgp_dest_get_prefix(dest), prd, + table->afi, safi, json_paths, true); + + vty_out(vty, "\"paths\": "); + json_detail_header_used = true; + } + + /* + * We are using no_pretty here because under + * extremely high settings( say lots and lots of + * routes with lots and lots of ways to reach + * that route via different paths ) this can + * save several minutes of output when FRR + * is run on older cpu's or more underperforming + * routers out there + */ + vty_json_no_pretty(vty, json_paths); + + /* End per-prefix dictionary */ + if (json_detail_header_used) + vty_out(vty, "} "); + + json_paths = NULL; + first = 0; + } else + json_object_free(json_paths); + } + + if (output_cum) { + output_count += *output_cum; + *output_cum = output_count; + } + if (total_cum) { + total_count += *total_cum; + *total_cum = total_count; + } + if (use_json) { + if (rd) { + vty_out(vty, " }%s ", (is_last ? "" : ",")); + } + if (is_last) { + unsigned long i; + for (i = 0; i < *json_header_depth; ++i) + vty_out(vty, " } "); + if (!all) + vty_out(vty, "\n"); + } + } else { + if (is_last) { + /* No route is displayed */ + if (output_count == 0) { + if (type == bgp_show_type_normal) + vty_out(vty, + "No BGP prefixes displayed, %ld exist\n", + total_count); + } else + vty_out(vty, + "\nDisplayed %ld routes and %ld total paths\n", + output_count, total_count); + } + } + + return CMD_SUCCESS; +} + +int bgp_show_table_rd(struct vty *vty, struct bgp *bgp, afi_t afi, safi_t safi, + struct bgp_table *table, struct prefix_rd *prd_match, + enum bgp_show_type type, void *output_arg, + uint16_t show_flags) +{ + struct bgp_dest *dest, *next; + unsigned long output_cum = 0; + unsigned long total_cum = 0; + unsigned long json_header_depth = 0; + struct bgp_table *itable; + bool show_msg; + bool use_json = !!CHECK_FLAG(show_flags, BGP_SHOW_OPT_JSON); + + show_msg = (!use_json && type == bgp_show_type_normal); + + for (dest = bgp_table_top(table); dest; dest = next) { + const struct prefix *dest_p = bgp_dest_get_prefix(dest); + + next = bgp_route_next(dest); + if (prd_match && memcmp(dest_p->u.val, prd_match->val, 8) != 0) + continue; + + itable = bgp_dest_get_bgp_table_info(dest); + if (itable != NULL) { + struct prefix_rd prd; + char rd[RD_ADDRSTRLEN]; + + memcpy(&prd, dest_p, sizeof(struct prefix_rd)); + prefix_rd2str(&prd, rd, sizeof(rd), bgp->asnotation); + bgp_show_table(vty, bgp, afi, safi, itable, type, output_arg, + rd, next == NULL, &output_cum, + &total_cum, &json_header_depth, + show_flags, RPKI_NOT_BEING_USED); + if (next == NULL) + show_msg = false; + } + } + if (show_msg) { + if (output_cum == 0) + vty_out(vty, "No BGP prefixes displayed, %ld exist\n", + total_cum); + else + vty_out(vty, + "\nDisplayed %ld routes and %ld total paths\n", + output_cum, total_cum); + } else { + if (use_json && output_cum == 0 && json_header_depth == 0) + vty_out(vty, "{}\n"); + } + return CMD_SUCCESS; +} + +static int bgp_show(struct vty *vty, struct bgp *bgp, afi_t afi, safi_t safi, + enum bgp_show_type type, void *output_arg, + uint16_t show_flags, enum rpki_states rpki_target_state) +{ + struct bgp_table *table; + unsigned long json_header_depth = 0; + bool use_json = CHECK_FLAG(show_flags, BGP_SHOW_OPT_JSON); + + if (bgp == NULL) { + bgp = bgp_get_default(); + } + + if (bgp == NULL) { + if (!use_json) + vty_out(vty, "No BGP process is configured\n"); + else + vty_out(vty, "{}\n"); + return CMD_WARNING; + } + + /* Labeled-unicast routes live in the unicast table. */ + if (safi == SAFI_LABELED_UNICAST) + safi = SAFI_UNICAST; + + table = bgp->rib[afi][safi]; + /* use MPLS and ENCAP specific shows until they are merged */ + if (safi == SAFI_MPLS_VPN) { + return bgp_show_table_rd(vty, bgp, afi, safi, table, NULL, type, + output_arg, show_flags); + } + + if (safi == SAFI_FLOWSPEC && type == bgp_show_type_detail) { + return bgp_show_table_flowspec(vty, bgp, afi, table, type, + output_arg, use_json, + 1, NULL, NULL); + } + + if (safi == SAFI_EVPN) + return bgp_evpn_show_all_routes(vty, bgp, type, use_json, 0); + + return bgp_show_table(vty, bgp, afi, safi, table, type, output_arg, NULL, 1, + NULL, NULL, &json_header_depth, show_flags, + rpki_target_state); +} + +static void bgp_show_all_instances_routes_vty(struct vty *vty, afi_t afi, + safi_t safi, uint16_t show_flags) +{ + struct listnode *node, *nnode; + struct bgp *bgp; + int is_first = 1; + bool route_output = false; + bool use_json = CHECK_FLAG(show_flags, BGP_SHOW_OPT_JSON); + + if (use_json) + vty_out(vty, "{\n"); + + for (ALL_LIST_ELEMENTS(bm->bgp, node, nnode, bgp)) { + route_output = true; + if (use_json) { + if (!is_first) + vty_out(vty, ",\n"); + else + is_first = 0; + + vty_out(vty, "\"%s\":", + (bgp->inst_type == BGP_INSTANCE_TYPE_DEFAULT) + ? VRF_DEFAULT_NAME + : bgp->name); + } else { + vty_out(vty, "\nInstance %s:\n", + (bgp->inst_type == BGP_INSTANCE_TYPE_DEFAULT) + ? VRF_DEFAULT_NAME + : bgp->name); + } + bgp_show(vty, bgp, afi, safi, bgp_show_type_normal, NULL, + show_flags, RPKI_NOT_BEING_USED); + } + + if (use_json) + vty_out(vty, "}\n"); + else if (!route_output) + vty_out(vty, "%% BGP instance not found\n"); +} + +/* Header of detailed BGP route information */ +void route_vty_out_detail_header(struct vty *vty, struct bgp *bgp, + struct bgp_dest *dest, const struct prefix *p, + const struct prefix_rd *prd, afi_t afi, + safi_t safi, json_object *json, + bool incremental_print) +{ + struct bgp_path_info *pi; + struct peer *peer; + struct listnode *node, *nnode; + char buf1[RD_ADDRSTRLEN]; + int count = 0; + int best = 0; + int suppress = 0; + int accept_own = 0; + int route_filter_translated_v4 = 0; + int route_filter_v4 = 0; + int route_filter_translated_v6 = 0; + int route_filter_v6 = 0; + int llgr_stale = 0; + int no_llgr = 0; + int accept_own_nexthop = 0; + int blackhole = 0; + int no_export = 0; + int no_advertise = 0; + int local_as = 0; + int no_peer = 0; + int first = 1; + int has_valid_label = 0; + mpls_label_t label = 0; + json_object *json_adv_to = NULL; + uint32_t ttl = 0; + uint32_t bos = 0; + uint32_t exp = 0; + + mpls_lse_decode(dest->local_label, &label, &ttl, &exp, &bos); + + has_valid_label = bgp_is_valid_label(&label); + + if (safi == SAFI_EVPN) { + if (!json) { + vty_out(vty, "BGP routing table entry for %s%s%pFX\n", + prd ? prefix_rd2str(prd, buf1, sizeof(buf1), + bgp->asnotation) + : "", + prd ? ":" : "", (struct prefix_evpn *)p); + } else { + json_object_string_add( + json, "rd", + prd ? prefix_rd2str(prd, buf1, sizeof(buf1), + bgp->asnotation) + : ""); + bgp_evpn_route2json((struct prefix_evpn *)p, json); + } + } else { + if (!json) { + vty_out(vty, + "BGP routing table entry for %s%s%pFX, version %" PRIu64 + "\n", + (((safi == SAFI_MPLS_VPN || + safi == SAFI_ENCAP) && + prd) + ? prefix_rd2str(prd, buf1, + sizeof(buf1), + bgp->asnotation) + : ""), + safi == SAFI_MPLS_VPN && prd ? ":" : "", p, + dest->version); + + } else { + if (incremental_print) { + vty_out(vty, "\"prefix\": \"%pFX\",\n", p); + vty_out(vty, "\"version\": \"%" PRIu64 "\",\n", + dest->version); + } else { + json_object_string_addf(json, "prefix", "%pFX", + p); + json_object_int_add(json, "version", + dest->version); + } + } + } + + if (has_valid_label) { + if (json) { + if (incremental_print) + vty_out(vty, "\"localLabel\": \"%u\",\n", + label); + else + json_object_int_add(json, "localLabel", label); + } else + vty_out(vty, "Local label: %d\n", label); + } + + if (!json) + if (bgp_labeled_safi(safi) && safi != SAFI_EVPN) + vty_out(vty, "not allocated\n"); + + for (pi = bgp_dest_get_bgp_path_info(dest); pi; pi = pi->next) { + struct community *picomm = NULL; + + picomm = bgp_attr_get_community(pi->attr); + + count++; + if (CHECK_FLAG(pi->flags, BGP_PATH_SELECTED)) { + best = count; + if (bgp_path_suppressed(pi)) + suppress = 1; + + if (!picomm) + continue; + + no_advertise += community_include( + picomm, COMMUNITY_NO_ADVERTISE); + no_export += + community_include(picomm, COMMUNITY_NO_EXPORT); + local_as += + community_include(picomm, COMMUNITY_LOCAL_AS); + accept_own += + community_include(picomm, COMMUNITY_ACCEPT_OWN); + route_filter_translated_v4 += community_include( + picomm, COMMUNITY_ROUTE_FILTER_TRANSLATED_v4); + route_filter_translated_v6 += community_include( + picomm, COMMUNITY_ROUTE_FILTER_TRANSLATED_v6); + route_filter_v4 += community_include( + picomm, COMMUNITY_ROUTE_FILTER_v4); + route_filter_v6 += community_include( + picomm, COMMUNITY_ROUTE_FILTER_v6); + llgr_stale += + community_include(picomm, COMMUNITY_LLGR_STALE); + no_llgr += community_include(picomm, COMMUNITY_NO_LLGR); + accept_own_nexthop += community_include( + picomm, COMMUNITY_ACCEPT_OWN_NEXTHOP); + blackhole += + community_include(picomm, COMMUNITY_BLACKHOLE); + no_peer += community_include(picomm, COMMUNITY_NO_PEER); + } + } + + if (!json) { + vty_out(vty, "Paths: (%d available", count); + if (best) { + vty_out(vty, ", best #%d", best); + if (safi == SAFI_UNICAST) { + if (bgp->inst_type == BGP_INSTANCE_TYPE_DEFAULT) + vty_out(vty, ", table %s", + VRF_DEFAULT_NAME); + else + vty_out(vty, ", vrf %s", + bgp->name); + } + } else + vty_out(vty, ", no best path"); + + if (accept_own) + vty_out(vty, + ", accept own local route exported and imported in different VRF"); + else if (route_filter_translated_v4) + vty_out(vty, + ", mark translated RTs for VPNv4 route filtering"); + else if (route_filter_v4) + vty_out(vty, + ", attach RT as-is for VPNv4 route filtering"); + else if (route_filter_translated_v6) + vty_out(vty, + ", mark translated RTs for VPNv6 route filtering"); + else if (route_filter_v6) + vty_out(vty, + ", attach RT as-is for VPNv6 route filtering"); + else if (llgr_stale) + vty_out(vty, + ", mark routes to be retained for a longer time. Requires support for Long-lived BGP Graceful Restart"); + else if (no_llgr) + vty_out(vty, + ", mark routes to not be treated according to Long-lived BGP Graceful Restart operations"); + else if (accept_own_nexthop) + vty_out(vty, + ", accept local nexthop"); + else if (blackhole) + vty_out(vty, ", inform peer to blackhole prefix"); + else if (no_export) + vty_out(vty, ", not advertised to EBGP peer"); + else if (no_advertise) + vty_out(vty, ", not advertised to any peer"); + else if (local_as) + vty_out(vty, ", not advertised outside local AS"); + else if (no_peer) + vty_out(vty, + ", inform EBGP peer not to advertise to their EBGP peers"); + + if (suppress) + vty_out(vty, + ", Advertisements suppressed by an aggregate."); + vty_out(vty, ")\n"); + } + + /* If we are not using addpath then we can display Advertised to and + * that will + * show what peers we advertised the bestpath to. If we are using + * addpath + * though then we must display Advertised to on a path-by-path basis. */ + if (!bgp_addpath_is_addpath_used(&bgp->tx_addpath, afi, safi)) { + for (ALL_LIST_ELEMENTS(bgp->peer, node, nnode, peer)) { + if (bgp_adj_out_lookup(peer, dest, 0)) { + if (json && !json_adv_to) + json_adv_to = json_object_new_object(); + + route_vty_out_advertised_to( + vty, peer, &first, + " Advertised to non peer-group peers:\n ", + json_adv_to); + } + } + + if (json && json_adv_to) { + if (incremental_print) { + vty_out(vty, "\"advertisedTo\": "); + vty_json(vty, json_adv_to); + vty_out(vty, ","); + } else + json_object_object_add(json, "advertisedTo", + json_adv_to); + } else { + if (!json && first) + vty_out(vty, " Not advertised to any peer"); + vty_out(vty, "\n"); + } + } +} + +static void bgp_show_path_info(const struct prefix_rd *pfx_rd, + struct bgp_dest *bgp_node, struct vty *vty, + struct bgp *bgp, afi_t afi, safi_t safi, + json_object *json, enum bgp_path_type pathtype, + int *display, enum rpki_states rpki_target_state) +{ + struct bgp_path_info *pi; + int header = 1; + json_object *json_header = NULL; + json_object *json_paths = NULL; + const struct prefix *p = bgp_dest_get_prefix(bgp_node); + + for (pi = bgp_dest_get_bgp_path_info(bgp_node); pi; pi = pi->next) { + enum rpki_states rpki_curr_state = RPKI_NOT_BEING_USED; + + if (p->family == AF_INET || p->family == AF_INET6) + rpki_curr_state = hook_call(bgp_rpki_prefix_status, + pi->peer, pi->attr, p); + + if (rpki_target_state != RPKI_NOT_BEING_USED + && rpki_curr_state != rpki_target_state) + continue; + + if (json && !json_paths) { + /* Instantiate json_paths only if path is valid */ + json_paths = json_object_new_array(); + if (pfx_rd) + json_header = json_object_new_object(); + else + json_header = json; + } + + if (header) { + route_vty_out_detail_header( + vty, bgp, bgp_node, + bgp_dest_get_prefix(bgp_node), pfx_rd, AFI_IP, + safi, json_header, false); + header = 0; + } + (*display)++; + + if (pathtype == BGP_PATH_SHOW_ALL + || (pathtype == BGP_PATH_SHOW_BESTPATH + && CHECK_FLAG(pi->flags, BGP_PATH_SELECTED)) + || (pathtype == BGP_PATH_SHOW_MULTIPATH + && (CHECK_FLAG(pi->flags, BGP_PATH_MULTIPATH) + || CHECK_FLAG(pi->flags, BGP_PATH_SELECTED)))) + route_vty_out_detail(vty, bgp, bgp_node, + bgp_dest_get_prefix(bgp_node), pi, + afi, safi, rpki_curr_state, + json_paths); + } + + if (json && json_paths) { + json_object_object_add(json_header, "paths", json_paths); + + if (pfx_rd) + json_object_object_addf( + json, json_header, + BGP_RD_AS_FORMAT(bgp->asnotation), pfx_rd); + } +} + +/* + * Return rd based on safi + */ +const struct prefix_rd *bgp_rd_from_dest(const struct bgp_dest *dest, + safi_t safi) +{ + switch (safi) { + case SAFI_MPLS_VPN: + case SAFI_ENCAP: + case SAFI_EVPN: + return (struct prefix_rd *)(bgp_dest_get_prefix(dest)); + case SAFI_UNSPEC: + case SAFI_UNICAST: + case SAFI_MULTICAST: + case SAFI_LABELED_UNICAST: + case SAFI_FLOWSPEC: + case SAFI_MAX: + return NULL; + } + + assert(!"Reached end of function when we were not expecting it"); +} + +/* Display specified route of BGP table. */ +static int bgp_show_route_in_table(struct vty *vty, struct bgp *bgp, + struct bgp_table *rib, const char *ip_str, + afi_t afi, safi_t safi, + enum rpki_states rpki_target_state, + struct prefix_rd *prd, int prefix_check, + enum bgp_path_type pathtype, bool use_json) +{ + int ret; + int display = 0; + struct prefix match; + struct bgp_dest *dest; + struct bgp_dest *rm; + struct bgp_table *table; + json_object *json = NULL; + json_object *json_paths = NULL; + + /* Check IP address argument. */ + ret = str2prefix(ip_str, &match); + if (!ret) { + vty_out(vty, "address is malformed\n"); + return CMD_WARNING; + } + + match.family = afi2family(afi); + + if (use_json) + json = json_object_new_object(); + + if (safi == SAFI_MPLS_VPN || safi == SAFI_ENCAP) { + for (dest = bgp_table_top(rib); dest; + dest = bgp_route_next(dest)) { + const struct prefix *dest_p = bgp_dest_get_prefix(dest); + + if (prd && memcmp(dest_p->u.val, prd->val, 8) != 0) + continue; + table = bgp_dest_get_bgp_table_info(dest); + if (!table) + continue; + + rm = bgp_node_match(table, &match); + if (rm == NULL) + continue; + + const struct prefix *rm_p = bgp_dest_get_prefix(rm); + if (prefix_check + && rm_p->prefixlen != match.prefixlen) { + bgp_dest_unlock_node(rm); + continue; + } + + bgp_show_path_info((struct prefix_rd *)dest_p, rm, vty, + bgp, afi, safi, json, pathtype, + &display, rpki_target_state); + + bgp_dest_unlock_node(rm); + } + } else if (safi == SAFI_EVPN) { + struct bgp_dest *longest_pfx; + bool is_exact_pfxlen_match = false; + + for (dest = bgp_table_top(rib); dest; + dest = bgp_route_next(dest)) { + const struct prefix *dest_p = bgp_dest_get_prefix(dest); + + if (prd && memcmp(&dest_p->u.val, prd->val, 8) != 0) + continue; + table = bgp_dest_get_bgp_table_info(dest); + if (!table) + continue; + + longest_pfx = NULL; + is_exact_pfxlen_match = false; + /* + * Search through all the prefixes for a match. The + * pfx's are enumerated in ascending order of pfxlens. + * So, the last pfx match is the longest match. Set + * is_exact_pfxlen_match when we get exact pfxlen match + */ + for (rm = bgp_table_top(table); rm; + rm = bgp_route_next(rm)) { + const struct prefix *rm_p = + bgp_dest_get_prefix(rm); + /* + * Get prefixlen of the ip-prefix within type5 + * evpn route + */ + if (evpn_type5_prefix_match(rm_p, &match) + && rm->info) { + longest_pfx = rm; + int type5_pfxlen = + bgp_evpn_get_type5_prefixlen( + rm_p); + if (type5_pfxlen == match.prefixlen) { + is_exact_pfxlen_match = true; + rm = bgp_dest_unlock_node(rm); + + assert(rm); + break; + } + } + } + + if (!longest_pfx) + continue; + + if (prefix_check && !is_exact_pfxlen_match) + continue; + + rm = longest_pfx; + bgp_dest_lock_node(rm); + + bgp_show_path_info((struct prefix_rd *)dest_p, rm, vty, + bgp, afi, safi, json, pathtype, + &display, rpki_target_state); + + bgp_dest_unlock_node(rm); + } + } else if (safi == SAFI_FLOWSPEC) { + if (use_json) + json_paths = json_object_new_array(); + + display = bgp_flowspec_display_match_per_ip(afi, rib, + &match, prefix_check, + vty, + use_json, + json_paths); + if (use_json) { + if (display) + json_object_object_add(json, "paths", + json_paths); + else + json_object_free(json_paths); + } + } else { + dest = bgp_node_match(rib, &match); + if (dest != NULL) { + const struct prefix *dest_p = bgp_dest_get_prefix(dest); + if (!prefix_check + || dest_p->prefixlen == match.prefixlen) { + bgp_show_path_info(NULL, dest, vty, bgp, afi, + safi, json, pathtype, + &display, rpki_target_state); + } + + bgp_dest_unlock_node(dest); + } + } + + if (use_json) { + vty_json(vty, json); + } else { + if (!display) { + vty_out(vty, "%% Network not in table\n"); + return CMD_WARNING; + } + } + + return CMD_SUCCESS; +} + +/* Display specified route of Main RIB */ +static int bgp_show_route(struct vty *vty, struct bgp *bgp, const char *ip_str, + afi_t afi, safi_t safi, struct prefix_rd *prd, + int prefix_check, enum bgp_path_type pathtype, + enum rpki_states rpki_target_state, bool use_json) +{ + if (!bgp) { + bgp = bgp_get_default(); + if (!bgp) { + if (!use_json) + vty_out(vty, "No BGP process is configured\n"); + else + vty_out(vty, "{}\n"); + return CMD_WARNING; + } + } + + /* labeled-unicast routes live in the unicast table */ + if (safi == SAFI_LABELED_UNICAST) + safi = SAFI_UNICAST; + + return bgp_show_route_in_table(vty, bgp, bgp->rib[afi][safi], ip_str, + afi, safi, rpki_target_state, prd, + prefix_check, pathtype, use_json); +} + +static int bgp_show_lcommunity(struct vty *vty, struct bgp *bgp, int argc, + struct cmd_token **argv, bool exact, afi_t afi, + safi_t safi, bool uj) +{ + struct lcommunity *lcom; + struct buffer *b; + int i; + char *str; + int first = 0; + uint16_t show_flags = 0; + int ret; + + if (uj) + SET_FLAG(show_flags, BGP_SHOW_OPT_JSON); + + b = buffer_new(1024); + for (i = 0; i < argc; i++) { + if (first) + buffer_putc(b, ' '); + else { + if (strmatch(argv[i]->text, "AA:BB:CC")) { + first = 1; + buffer_putstr(b, argv[i]->arg); + } + } + } + buffer_putc(b, '\0'); + + str = buffer_getstr(b); + buffer_free(b); + + lcom = lcommunity_str2com(str); + XFREE(MTYPE_TMP, str); + if (!lcom) { + vty_out(vty, "%% Large-community malformed\n"); + return CMD_WARNING; + } + + ret = bgp_show(vty, bgp, afi, safi, + (exact ? bgp_show_type_lcommunity_exact + : bgp_show_type_lcommunity), + lcom, show_flags, RPKI_NOT_BEING_USED); + + lcommunity_free(&lcom); + return ret; +} + +static int bgp_show_lcommunity_list(struct vty *vty, struct bgp *bgp, + const char *lcom, bool exact, afi_t afi, + safi_t safi, bool uj) +{ + struct community_list *list; + uint16_t show_flags = 0; + + if (uj) + SET_FLAG(show_flags, BGP_SHOW_OPT_JSON); + + + list = community_list_lookup(bgp_clist, lcom, 0, + LARGE_COMMUNITY_LIST_MASTER); + if (list == NULL) { + vty_out(vty, "%% %s is not a valid large-community-list name\n", + lcom); + return CMD_WARNING; + } + + return bgp_show(vty, bgp, afi, safi, + (exact ? bgp_show_type_lcommunity_list_exact + : bgp_show_type_lcommunity_list), + list, show_flags, RPKI_NOT_BEING_USED); +} + +DEFUN (show_ip_bgp_large_community_list, + show_ip_bgp_large_community_list_cmd, + "show [ip] bgp [<view|vrf> VIEWVRFNAME] ["BGP_AFI_CMD_STR" ["BGP_SAFI_WITH_LABEL_CMD_STR"]] large-community-list <(1-500)|LCOMMUNITY_LIST_NAME> [exact-match] [json]", + SHOW_STR + IP_STR + BGP_STR + BGP_INSTANCE_HELP_STR + BGP_AFI_HELP_STR + BGP_SAFI_WITH_LABEL_HELP_STR + "Display routes matching the large-community-list\n" + "large-community-list number\n" + "large-community-list name\n" + "Exact match of the large-communities\n" + JSON_STR) +{ + afi_t afi = AFI_IP6; + safi_t safi = SAFI_UNICAST; + int idx = 0; + bool exact_match = 0; + struct bgp *bgp = NULL; + bool uj = use_json(argc, argv); + + if (uj) + argc--; + + bgp_vty_find_and_parse_afi_safi_bgp(vty, argv, argc, &idx, &afi, &safi, + &bgp, uj); + if (!idx) + return CMD_WARNING; + + argv_find(argv, argc, "large-community-list", &idx); + + const char *clist_number_or_name = argv[++idx]->arg; + + if (++idx < argc && strmatch(argv[idx]->text, "exact-match")) + exact_match = 1; + + return bgp_show_lcommunity_list(vty, bgp, clist_number_or_name, + exact_match, afi, safi, uj); +} +DEFUN (show_ip_bgp_large_community, + show_ip_bgp_large_community_cmd, + "show [ip] bgp [<view|vrf> VIEWVRFNAME] ["BGP_AFI_CMD_STR" ["BGP_SAFI_WITH_LABEL_CMD_STR"]] large-community [<AA:BB:CC> [exact-match]] [json]", + SHOW_STR + IP_STR + BGP_STR + BGP_INSTANCE_HELP_STR + BGP_AFI_HELP_STR + BGP_SAFI_WITH_LABEL_HELP_STR + "Display routes matching the large-communities\n" + "List of large-community numbers\n" + "Exact match of the large-communities\n" + JSON_STR) +{ + afi_t afi = AFI_IP6; + safi_t safi = SAFI_UNICAST; + int idx = 0; + bool exact_match = 0; + struct bgp *bgp = NULL; + bool uj = use_json(argc, argv); + uint16_t show_flags = 0; + + if (uj) { + argc--; + SET_FLAG(show_flags, BGP_SHOW_OPT_JSON); + } + + bgp_vty_find_and_parse_afi_safi_bgp(vty, argv, argc, &idx, &afi, &safi, + &bgp, uj); + if (!idx) + return CMD_WARNING; + + if (argv_find(argv, argc, "AA:BB:CC", &idx)) { + if (argv_find(argv, argc, "exact-match", &idx)) { + argc--; + exact_match = 1; + } + return bgp_show_lcommunity(vty, bgp, argc, argv, + exact_match, afi, safi, uj); + } else + return bgp_show(vty, bgp, afi, safi, + bgp_show_type_lcommunity_all, NULL, show_flags, + RPKI_NOT_BEING_USED); +} + +static int bgp_table_stats_single(struct vty *vty, struct bgp *bgp, afi_t afi, + safi_t safi, struct json_object *json_array); +static int bgp_table_stats(struct vty *vty, struct bgp *bgp, afi_t afi, + safi_t safi, struct json_object *json); + + +DEFUN(show_ip_bgp_statistics_all, show_ip_bgp_statistics_all_cmd, + "show [ip] bgp [<view|vrf> VIEWVRFNAME] statistics-all [json]", + SHOW_STR IP_STR BGP_STR BGP_INSTANCE_HELP_STR + "Display number of prefixes for all afi/safi\n" JSON_STR) +{ + bool uj = use_json(argc, argv); + struct bgp *bgp = NULL; + safi_t safi = SAFI_UNICAST; + afi_t afi = AFI_IP6; + int idx = 0; + struct json_object *json_all = NULL; + struct json_object *json_afi_safi = NULL; + + bgp_vty_find_and_parse_afi_safi_bgp(vty, argv, argc, &idx, &afi, &safi, + &bgp, false); + if (!idx) + return CMD_WARNING; + + if (uj) + json_all = json_object_new_object(); + + FOREACH_AFI_SAFI (afi, safi) { + /* + * So limit output to those afi/safi pairs that + * actually have something interesting in them + */ + if (strmatch(get_afi_safi_str(afi, safi, true), + "Unknown")) { + continue; + } + if (uj) { + json_afi_safi = json_object_new_array(); + json_object_object_add( + json_all, + get_afi_safi_str(afi, safi, true), + json_afi_safi); + } else { + json_afi_safi = NULL; + } + + bgp_table_stats(vty, bgp, afi, safi, json_afi_safi); + } + + if (uj) + vty_json(vty, json_all); + + return CMD_SUCCESS; +} + +/* BGP route print out function without JSON */ +DEFUN (show_ip_bgp_l2vpn_evpn_statistics, + show_ip_bgp_l2vpn_evpn_statistics_cmd, + "show [ip] bgp [<view|vrf> VIEWVRFNAME] l2vpn evpn statistics [json]", + SHOW_STR + IP_STR + BGP_STR + BGP_INSTANCE_HELP_STR + L2VPN_HELP_STR + EVPN_HELP_STR + "BGP RIB advertisement statistics\n" + JSON_STR) +{ + afi_t afi = AFI_IP6; + safi_t safi = SAFI_UNICAST; + struct bgp *bgp = NULL; + int idx = 0, ret; + bool uj = use_json(argc, argv); + struct json_object *json_afi_safi = NULL, *json = NULL; + + bgp_vty_find_and_parse_afi_safi_bgp(vty, argv, argc, &idx, &afi, &safi, + &bgp, false); + if (!idx) + return CMD_WARNING; + + if (uj) + json_afi_safi = json_object_new_array(); + else + json_afi_safi = NULL; + + ret = bgp_table_stats(vty, bgp, afi, safi, json_afi_safi); + + if (uj) { + json = json_object_new_object(); + json_object_object_add(json, get_afi_safi_str(afi, safi, true), + json_afi_safi); + vty_json(vty, json); + } + return ret; +} + +/* BGP route print out function without JSON */ +DEFUN(show_ip_bgp_afi_safi_statistics, show_ip_bgp_afi_safi_statistics_cmd, + "show [ip] bgp [<view|vrf> VIEWVRFNAME] [" BGP_AFI_CMD_STR + " [" BGP_SAFI_WITH_LABEL_CMD_STR + "]]\ + statistics [json]", + SHOW_STR IP_STR BGP_STR BGP_INSTANCE_HELP_STR BGP_AFI_HELP_STR + BGP_SAFI_WITH_LABEL_HELP_STR + "BGP RIB advertisement statistics\n" JSON_STR) +{ + afi_t afi = AFI_IP6; + safi_t safi = SAFI_UNICAST; + struct bgp *bgp = NULL; + int idx = 0, ret; + bool uj = use_json(argc, argv); + struct json_object *json_afi_safi = NULL, *json = NULL; + + bgp_vty_find_and_parse_afi_safi_bgp(vty, argv, argc, &idx, &afi, &safi, + &bgp, false); + if (!idx) + return CMD_WARNING; + + if (uj) + json_afi_safi = json_object_new_array(); + else + json_afi_safi = NULL; + + ret = bgp_table_stats(vty, bgp, afi, safi, json_afi_safi); + + if (uj) { + json = json_object_new_object(); + json_object_object_add(json, get_afi_safi_str(afi, safi, true), + json_afi_safi); + vty_json(vty, json); + } + return ret; +} + +DEFPY(show_ip_bgp_dampening_params, show_ip_bgp_dampening_params_cmd, + "show [ip] bgp [<view|vrf> VIEWVRFNAME] [" BGP_AFI_CMD_STR + " [" BGP_SAFI_WITH_LABEL_CMD_STR + "]] [all$all] dampening parameters [json]", + SHOW_STR IP_STR BGP_STR BGP_INSTANCE_HELP_STR BGP_AFI_HELP_STR + BGP_SAFI_WITH_LABEL_HELP_STR + "Display the entries for all address families\n" + "Display detailed information about dampening\n" + "Display detail of configured dampening parameters\n" + JSON_STR) +{ + afi_t afi = AFI_IP6; + safi_t safi = SAFI_UNICAST; + struct bgp *bgp = NULL; + int idx = 0; + uint16_t show_flags = 0; + bool uj = use_json(argc, argv); + + if (uj) { + argc--; + SET_FLAG(show_flags, BGP_SHOW_OPT_JSON); + } + + /* [<ipv4|ipv6> [all]] */ + if (all) { + SET_FLAG(show_flags, BGP_SHOW_OPT_AFI_ALL); + if (argv_find(argv, argc, "ipv4", &idx)) + SET_FLAG(show_flags, BGP_SHOW_OPT_AFI_IP); + + if (argv_find(argv, argc, "ipv6", &idx)) + SET_FLAG(show_flags, BGP_SHOW_OPT_AFI_IP6); + } + + bgp_vty_find_and_parse_afi_safi_bgp(vty, argv, argc, &idx, &afi, &safi, + &bgp, false); + if (!idx) + return CMD_WARNING; + + return bgp_show_dampening_parameters(vty, afi, safi, show_flags); +} + +/* BGP route print out function */ +DEFPY(show_ip_bgp, show_ip_bgp_cmd, + "show [ip] bgp [<view|vrf> VIEWVRFNAME] [" BGP_AFI_CMD_STR + " [" BGP_SAFI_WITH_LABEL_CMD_STR + "]]\ + [all$all]\ + [cidr-only\ + |dampening <flap-statistics|dampened-paths>\ + |community [AA:NN|local-AS|no-advertise|no-export\ + |graceful-shutdown|no-peer|blackhole|llgr-stale|no-llgr\ + |accept-own|accept-own-nexthop|route-filter-v6\ + |route-filter-v4|route-filter-translated-v6\ + |route-filter-translated-v4] [exact-match]\ + |community-list <(1-500)|COMMUNITY_LIST_NAME> [exact-match]\ + |filter-list AS_PATH_FILTER_NAME\ + |prefix-list WORD\ + |access-list ACCESSLIST_NAME\ + |route-map RMAP_NAME\ + |rpki <invalid|valid|notfound>\ + |version (1-4294967295)\ + |alias ALIAS_NAME\ + |A.B.C.D/M longer-prefixes\ + |X:X::X:X/M longer-prefixes\ + |"BGP_SELF_ORIG_CMD_STR"\ + |detail-routes$detail_routes\ + ] [json$uj [detail$detail_json] | wide$wide]", + SHOW_STR IP_STR BGP_STR BGP_INSTANCE_HELP_STR BGP_AFI_HELP_STR + BGP_SAFI_WITH_LABEL_HELP_STR + "Display the entries for all address families\n" + "Display only routes with non-natural netmasks\n" + "Display detailed information about dampening\n" + "Display flap statistics of routes\n" + "Display paths suppressed due to dampening\n" + "Display routes matching the communities\n" COMMUNITY_AANN_STR + "Do not send outside local AS (well-known community)\n" + "Do not advertise to any peer (well-known community)\n" + "Do not export to next AS (well-known community)\n" + "Graceful shutdown (well-known community)\n" + "Do not export to any peer (well-known community)\n" + "Inform EBGP peers to blackhole traffic to prefix (well-known community)\n" + "Staled Long-lived Graceful Restart VPN route (well-known community)\n" + "Removed because Long-lived Graceful Restart was not enabled for VPN route (well-known community)\n" + "Should accept local VPN route if exported and imported into different VRF (well-known community)\n" + "Should accept VPN route with local nexthop (well-known community)\n" + "RT VPNv6 route filtering (well-known community)\n" + "RT VPNv4 route filtering (well-known community)\n" + "RT translated VPNv6 route filtering (well-known community)\n" + "RT translated VPNv4 route filtering (well-known community)\n" + "Exact match of the communities\n" + "Community-list number\n" + "Community-list name\n" + "Display routes matching the community-list\n" + "Exact match of the communities\n" + "Display routes conforming to the filter-list\n" + "Regular expression access list name\n" + "Display routes conforming to the prefix-list\n" + "Prefix-list name\n" + "Display routes conforming to the access-list\n" + "Access-list name\n" + "Display routes matching the route-map\n" + "A route-map to match on\n" + "RPKI route types\n" + "A valid path as determined by rpki\n" + "A invalid path as determined by rpki\n" + "A path that has no rpki data\n" + "Display prefixes with matching version numbers\n" + "Version number and above\n" + "Display prefixes with matching BGP community alias\n" + "BGP community alias\n" + "IPv4 prefix\n" + "Display route and more specific routes\n" + "IPv6 prefix\n" + "Display route and more specific routes\n" + BGP_SELF_ORIG_HELP_STR + "Display detailed version of all routes\n" + JSON_STR + "Display detailed version of JSON output\n" + "Increase table width for longer prefixes\n") +{ + afi_t afi = AFI_IP6; + safi_t safi = SAFI_UNICAST; + enum bgp_show_type sh_type = bgp_show_type_normal; + void *output_arg = NULL; + struct bgp *bgp = NULL; + int idx = 0; + int exact_match = 0; + char *community = NULL; + bool first = true; + uint16_t show_flags = 0; + enum rpki_states rpki_target_state = RPKI_NOT_BEING_USED; + struct prefix p; + + if (uj) { + argc--; + SET_FLAG(show_flags, BGP_SHOW_OPT_JSON); + } + + if (detail_json) + SET_FLAG(show_flags, BGP_SHOW_OPT_JSON_DETAIL); + + if (detail_routes) + SET_FLAG(show_flags, BGP_SHOW_OPT_ROUTES_DETAIL); + + /* [<ipv4|ipv6> [all]] */ + if (all) { + SET_FLAG(show_flags, BGP_SHOW_OPT_AFI_ALL); + + if (argv_find(argv, argc, "ipv4", &idx)) + SET_FLAG(show_flags, BGP_SHOW_OPT_AFI_IP); + + if (argv_find(argv, argc, "ipv6", &idx)) + SET_FLAG(show_flags, BGP_SHOW_OPT_AFI_IP6); + } + + if (wide) + SET_FLAG(show_flags, BGP_SHOW_OPT_WIDE); + + bgp_vty_find_and_parse_afi_safi_bgp(vty, argv, argc, &idx, &afi, &safi, + &bgp, uj); + if (!idx) + return CMD_WARNING; + + if (argv_find(argv, argc, "cidr-only", &idx)) + sh_type = bgp_show_type_cidr_only; + + if (argv_find(argv, argc, "dampening", &idx)) { + if (argv_find(argv, argc, "dampened-paths", &idx)) + sh_type = bgp_show_type_dampend_paths; + else if (argv_find(argv, argc, "flap-statistics", &idx)) + sh_type = bgp_show_type_flap_statistics; + } + + if (argv_find(argv, argc, "community", &idx)) { + char *maybecomm = NULL; + + if (idx + 1 < argc) { + if (argv[idx + 1]->type == VARIABLE_TKN) + maybecomm = argv[idx + 1]->arg; + else + maybecomm = argv[idx + 1]->text; + } + + if (maybecomm && !strmatch(maybecomm, "json") + && !strmatch(maybecomm, "exact-match")) + community = maybecomm; + + if (argv_find(argv, argc, "exact-match", &idx)) + exact_match = 1; + + if (!community) + sh_type = bgp_show_type_community_all; + } + + if (argv_find(argv, argc, "community-list", &idx)) { + const char *clist_number_or_name = argv[++idx]->arg; + struct community_list *list; + + if (argv_find(argv, argc, "exact-match", &idx)) + exact_match = 1; + + list = community_list_lookup(bgp_clist, clist_number_or_name, 0, + COMMUNITY_LIST_MASTER); + if (list == NULL) { + vty_out(vty, "%% %s community-list not found\n", + clist_number_or_name); + return CMD_WARNING; + } + + if (exact_match) + sh_type = bgp_show_type_community_list_exact; + else + sh_type = bgp_show_type_community_list; + output_arg = list; + } + + if (argv_find(argv, argc, "filter-list", &idx)) { + const char *filter = argv[++idx]->arg; + struct as_list *as_list; + + as_list = as_list_lookup(filter); + if (as_list == NULL) { + vty_out(vty, "%% %s AS-path access-list not found\n", + filter); + return CMD_WARNING; + } + + sh_type = bgp_show_type_filter_list; + output_arg = as_list; + } + + if (argv_find(argv, argc, "prefix-list", &idx)) { + const char *prefix_list_str = argv[++idx]->arg; + struct prefix_list *plist; + + plist = prefix_list_lookup(afi, prefix_list_str); + if (plist == NULL) { + vty_out(vty, "%% %s prefix-list not found\n", + prefix_list_str); + return CMD_WARNING; + } + + sh_type = bgp_show_type_prefix_list; + output_arg = plist; + } + + if (argv_find(argv, argc, "access-list", &idx)) { + const char *access_list_str = argv[++idx]->arg; + struct access_list *alist; + + alist = access_list_lookup(afi, access_list_str); + if (!alist) { + vty_out(vty, "%% %s access-list not found\n", + access_list_str); + return CMD_WARNING; + } + + sh_type = bgp_show_type_access_list; + output_arg = alist; + } + + if (argv_find(argv, argc, "route-map", &idx)) { + const char *rmap_str = argv[++idx]->arg; + struct route_map *rmap; + + rmap = route_map_lookup_by_name(rmap_str); + if (!rmap) { + vty_out(vty, "%% %s route-map not found\n", rmap_str); + return CMD_WARNING; + } + + sh_type = bgp_show_type_route_map; + output_arg = rmap; + } + + if (argv_find(argv, argc, "rpki", &idx)) { + sh_type = bgp_show_type_rpki; + if (argv_find(argv, argc, "valid", &idx)) + rpki_target_state = RPKI_VALID; + else if (argv_find(argv, argc, "invalid", &idx)) + rpki_target_state = RPKI_INVALID; + else if (argv_find(argv, argc, "notfound", &idx)) + rpki_target_state = RPKI_NOTFOUND; + } + + /* Display prefixes with matching version numbers */ + if (argv_find(argv, argc, "version", &idx)) { + sh_type = bgp_show_type_prefix_version; + output_arg = argv[idx + 1]->arg; + } + + /* Display prefixes with matching BGP community alias */ + if (argv_find(argv, argc, "alias", &idx)) { + sh_type = bgp_show_type_community_alias; + output_arg = argv[idx + 1]->arg; + } + + /* prefix-longer */ + if (argv_find(argv, argc, "A.B.C.D/M", &idx) + || argv_find(argv, argc, "X:X::X:X/M", &idx)) { + const char *prefix_str = argv[idx]->arg; + + if (!str2prefix(prefix_str, &p)) { + vty_out(vty, "%% Malformed Prefix\n"); + return CMD_WARNING; + } + + sh_type = bgp_show_type_prefix_longer; + output_arg = &p; + } + + /* self originated only */ + if (argv_find(argv, argc, BGP_SELF_ORIG_CMD_STR, &idx)) + sh_type = bgp_show_type_self_originated; + + if (!all) { + /* show bgp: AFI_IP6, show ip bgp: AFI_IP */ + if (community) + return bgp_show_community(vty, bgp, community, + exact_match, afi, safi, + show_flags); + else + return bgp_show(vty, bgp, afi, safi, sh_type, + output_arg, show_flags, + rpki_target_state); + } else { + struct listnode *node; + struct bgp *abgp; + /* show <ip> bgp ipv4 all: AFI_IP, show <ip> bgp ipv6 all: + * AFI_IP6 */ + + if (uj) + vty_out(vty, "{\n"); + + if (CHECK_FLAG(show_flags, BGP_SHOW_OPT_AFI_IP) + || CHECK_FLAG(show_flags, BGP_SHOW_OPT_AFI_IP6)) { + afi = CHECK_FLAG(show_flags, BGP_SHOW_OPT_AFI_IP) + ? AFI_IP + : AFI_IP6; + for (ALL_LIST_ELEMENTS_RO(bm->bgp, node, abgp)) { + FOREACH_SAFI (safi) { + if (!bgp_afi_safi_peer_exists(abgp, afi, + safi)) + continue; + + if (uj) { + if (first) + first = false; + else + vty_out(vty, ",\n"); + vty_out(vty, "\"%s\":{\n", + get_afi_safi_str(afi, + safi, + true)); + } else + vty_out(vty, + "\nFor address family: %s\n", + get_afi_safi_str( + afi, safi, + false)); + + if (community) + bgp_show_community( + vty, abgp, community, + exact_match, afi, safi, + show_flags); + else + bgp_show(vty, abgp, afi, safi, + sh_type, output_arg, + show_flags, + rpki_target_state); + if (uj) + vty_out(vty, "}\n"); + } + } + } else { + /* show <ip> bgp all: for each AFI and SAFI*/ + for (ALL_LIST_ELEMENTS_RO(bm->bgp, node, abgp)) { + FOREACH_AFI_SAFI (afi, safi) { + if (!bgp_afi_safi_peer_exists(abgp, afi, + safi)) + continue; + + if (uj) { + if (first) + first = false; + else + vty_out(vty, ",\n"); + + vty_out(vty, "\"%s\":{\n", + get_afi_safi_str(afi, + safi, + true)); + + /* Adding 'routes' key to make + * the json output format valid + * for evpn + */ + if (safi == SAFI_EVPN) + vty_out(vty, + "\"routes\":"); + + } else + vty_out(vty, + "\nFor address family: %s\n", + get_afi_safi_str( + afi, safi, + false)); + + if (community) + bgp_show_community( + vty, abgp, community, + exact_match, afi, safi, + show_flags); + else + bgp_show(vty, abgp, afi, safi, + sh_type, output_arg, + show_flags, + rpki_target_state); + if (uj) + vty_out(vty, "}\n"); + } + } + } + if (uj) + vty_out(vty, "}\n"); + } + return CMD_SUCCESS; +} + +DEFUN (show_ip_bgp_route, + show_ip_bgp_route_cmd, + "show [ip] bgp [<view|vrf> VIEWVRFNAME] ["BGP_AFI_CMD_STR" ["BGP_SAFI_WITH_LABEL_CMD_STR"]]<A.B.C.D|A.B.C.D/M|X:X::X:X|X:X::X:X/M> [<bestpath|multipath>] [rpki <valid|invalid|notfound>] [json]", + SHOW_STR + IP_STR + BGP_STR + BGP_INSTANCE_HELP_STR + BGP_AFI_HELP_STR + BGP_SAFI_WITH_LABEL_HELP_STR + "Network in the BGP routing table to display\n" + "IPv4 prefix\n" + "Network in the BGP routing table to display\n" + "IPv6 prefix\n" + "Display only the bestpath\n" + "Display only multipaths\n" + "Display only paths that match the specified rpki state\n" + "A valid path as determined by rpki\n" + "A invalid path as determined by rpki\n" + "A path that has no rpki data\n" + JSON_STR) +{ + int prefix_check = 0; + + afi_t afi = AFI_IP6; + safi_t safi = SAFI_UNICAST; + char *prefix = NULL; + struct bgp *bgp = NULL; + enum bgp_path_type path_type; + bool uj = use_json(argc, argv); + + int idx = 0; + + bgp_vty_find_and_parse_afi_safi_bgp(vty, argv, argc, &idx, &afi, &safi, + &bgp, uj); + if (!idx) + return CMD_WARNING; + + if (!bgp) { + vty_out(vty, + "Specified 'all' vrf's but this command currently only works per view/vrf\n"); + return CMD_WARNING; + } + + /* <A.B.C.D|A.B.C.D/M|X:X::X:X|X:X::X:X/M> */ + if (argv_find(argv, argc, "A.B.C.D", &idx) + || argv_find(argv, argc, "X:X::X:X", &idx)) + prefix_check = 0; + else if (argv_find(argv, argc, "A.B.C.D/M", &idx) + || argv_find(argv, argc, "X:X::X:X/M", &idx)) + prefix_check = 1; + + if ((argv[idx]->type == IPV6_TKN || argv[idx]->type == IPV6_PREFIX_TKN) + && afi != AFI_IP6) { + vty_out(vty, + "%% Cannot specify IPv6 address or prefix with IPv4 AFI\n"); + return CMD_WARNING; + } + if ((argv[idx]->type == IPV4_TKN || argv[idx]->type == IPV4_PREFIX_TKN) + && afi != AFI_IP) { + vty_out(vty, + "%% Cannot specify IPv4 address or prefix with IPv6 AFI\n"); + return CMD_WARNING; + } + + prefix = argv[idx]->arg; + + /* [<bestpath|multipath>] */ + if (argv_find(argv, argc, "bestpath", &idx)) + path_type = BGP_PATH_SHOW_BESTPATH; + else if (argv_find(argv, argc, "multipath", &idx)) + path_type = BGP_PATH_SHOW_MULTIPATH; + else + path_type = BGP_PATH_SHOW_ALL; + + return bgp_show_route(vty, bgp, prefix, afi, safi, NULL, prefix_check, + path_type, RPKI_NOT_BEING_USED, uj); +} + +DEFUN (show_ip_bgp_regexp, + show_ip_bgp_regexp_cmd, + "show [ip] bgp [<view|vrf> VIEWVRFNAME] ["BGP_AFI_CMD_STR" ["BGP_SAFI_WITH_LABEL_CMD_STR"]] regexp REGEX [json]", + SHOW_STR + IP_STR + BGP_STR + BGP_INSTANCE_HELP_STR + BGP_AFI_HELP_STR + BGP_SAFI_WITH_LABEL_HELP_STR + "Display routes matching the AS path regular expression\n" + "A regular-expression (1234567890_^|[,{}() ]$*+.?-\\) to match the BGP AS paths\n" + JSON_STR) +{ + afi_t afi = AFI_IP6; + safi_t safi = SAFI_UNICAST; + struct bgp *bgp = NULL; + bool uj = use_json(argc, argv); + char *regstr = NULL; + + int idx = 0; + bgp_vty_find_and_parse_afi_safi_bgp(vty, argv, argc, &idx, &afi, &safi, + &bgp, false); + if (!idx) + return CMD_WARNING; + + // get index of regex + if (argv_find(argv, argc, "REGEX", &idx)) + regstr = argv[idx]->arg; + + assert(regstr); + return bgp_show_regexp(vty, bgp, (const char *)regstr, afi, safi, + bgp_show_type_regexp, uj); +} + +DEFPY (show_ip_bgp_instance_all, + show_ip_bgp_instance_all_cmd, + "show [ip] bgp <view|vrf> all ["BGP_AFI_CMD_STR" ["BGP_SAFI_WITH_LABEL_CMD_STR"]] [json$uj | wide$wide]", + SHOW_STR + IP_STR + BGP_STR + BGP_INSTANCE_ALL_HELP_STR + BGP_AFI_HELP_STR + BGP_SAFI_WITH_LABEL_HELP_STR + JSON_STR + "Increase table width for longer prefixes\n") +{ + afi_t afi = AFI_IP6; + safi_t safi = SAFI_UNICAST; + struct bgp *bgp = NULL; + int idx = 0; + uint16_t show_flags = 0; + + if (uj) { + argc--; + SET_FLAG(show_flags, BGP_SHOW_OPT_JSON); + } + + if (wide) + SET_FLAG(show_flags, BGP_SHOW_OPT_WIDE); + + bgp_vty_find_and_parse_afi_safi_bgp(vty, argv, argc, &idx, &afi, &safi, + &bgp, uj); + if (!idx) + return CMD_WARNING; + + bgp_show_all_instances_routes_vty(vty, afi, safi, show_flags); + return CMD_SUCCESS; +} + +static int bgp_show_regexp(struct vty *vty, struct bgp *bgp, const char *regstr, + afi_t afi, safi_t safi, enum bgp_show_type type, + bool use_json) +{ + regex_t *regex; + int rc; + uint16_t show_flags = 0; + + if (use_json) + SET_FLAG(show_flags, BGP_SHOW_OPT_JSON); + + if (!config_bgp_aspath_validate(regstr)) { + vty_out(vty, "Invalid character in REGEX %s\n", + regstr); + return CMD_WARNING_CONFIG_FAILED; + } + + regex = bgp_regcomp(regstr); + if (!regex) { + vty_out(vty, "Can't compile regexp %s\n", regstr); + return CMD_WARNING; + } + + rc = bgp_show(vty, bgp, afi, safi, type, regex, show_flags, + RPKI_NOT_BEING_USED); + bgp_regex_free(regex); + return rc; +} + +static int bgp_show_community(struct vty *vty, struct bgp *bgp, + const char *comstr, int exact, afi_t afi, + safi_t safi, uint16_t show_flags) +{ + struct community *com; + int ret = 0; + + com = community_str2com(comstr); + if (!com) { + vty_out(vty, "%% Community malformed: %s\n", comstr); + return CMD_WARNING; + } + + ret = bgp_show(vty, bgp, afi, safi, + (exact ? bgp_show_type_community_exact + : bgp_show_type_community), + com, show_flags, RPKI_NOT_BEING_USED); + community_free(&com); + + return ret; +} + +enum bgp_stats { + BGP_STATS_MAXBITLEN = 0, + BGP_STATS_RIB, + BGP_STATS_PREFIXES, + BGP_STATS_TOTPLEN, + BGP_STATS_UNAGGREGATEABLE, + BGP_STATS_MAX_AGGREGATEABLE, + BGP_STATS_AGGREGATES, + BGP_STATS_SPACE, + BGP_STATS_ASPATH_COUNT, + BGP_STATS_ASPATH_MAXHOPS, + BGP_STATS_ASPATH_TOTHOPS, + BGP_STATS_ASPATH_MAXSIZE, + BGP_STATS_ASPATH_TOTSIZE, + BGP_STATS_ASN_HIGHEST, + BGP_STATS_MAX, +}; + +#define TABLE_STATS_IDX_VTY 0 +#define TABLE_STATS_IDX_JSON 1 + +static const char *table_stats_strs[][2] = { + [BGP_STATS_PREFIXES] = {"Total Prefixes", "totalPrefixes"}, + [BGP_STATS_TOTPLEN] = {"Average prefix length", "averagePrefixLength"}, + [BGP_STATS_RIB] = {"Total Advertisements", "totalAdvertisements"}, + [BGP_STATS_UNAGGREGATEABLE] = {"Unaggregateable prefixes", + "unaggregateablePrefixes"}, + [BGP_STATS_MAX_AGGREGATEABLE] = {"Maximum aggregateable prefixes", + "maximumAggregateablePrefixes"}, + [BGP_STATS_AGGREGATES] = {"BGP Aggregate advertisements", + "bgpAggregateAdvertisements"}, + [BGP_STATS_SPACE] = {"Address space advertised", + "addressSpaceAdvertised"}, + [BGP_STATS_ASPATH_COUNT] = {"Advertisements with paths", + "advertisementsWithPaths"}, + [BGP_STATS_ASPATH_MAXHOPS] = {"Longest AS-Path (hops)", + "longestAsPath"}, + [BGP_STATS_ASPATH_MAXSIZE] = {"Largest AS-Path (bytes)", + "largestAsPath"}, + [BGP_STATS_ASPATH_TOTHOPS] = {"Average AS-Path length (hops)", + "averageAsPathLengthHops"}, + [BGP_STATS_ASPATH_TOTSIZE] = {"Average AS-Path size (bytes)", + "averageAsPathSizeBytes"}, + [BGP_STATS_ASN_HIGHEST] = {"Highest public ASN", "highestPublicAsn"}, + [BGP_STATS_MAX] = {NULL, NULL} +}; + +struct bgp_table_stats { + struct bgp_table *table; + unsigned long long counts[BGP_STATS_MAX]; + + unsigned long long + prefix_len_count[MAX(EVPN_ROUTE_PREFIXLEN, IPV6_MAX_BITLEN) + + 1]; + + double total_space; +}; + +static void bgp_table_stats_rn(struct bgp_dest *dest, struct bgp_dest *top, + struct bgp_table_stats *ts, unsigned int space) +{ + struct bgp_dest *pdest = bgp_dest_parent_nolock(dest); + struct bgp_path_info *pi; + const struct prefix *rn_p; + + if (!bgp_dest_has_bgp_path_info_data(dest)) + return; + + rn_p = bgp_dest_get_prefix(dest); + ts->counts[BGP_STATS_PREFIXES]++; + ts->counts[BGP_STATS_TOTPLEN] += rn_p->prefixlen; + + ts->prefix_len_count[rn_p->prefixlen]++; + /* check if the prefix is included by any other announcements */ + while (pdest && !bgp_dest_has_bgp_path_info_data(pdest)) + pdest = bgp_dest_parent_nolock(pdest); + + if (pdest == NULL || pdest == top) { + ts->counts[BGP_STATS_UNAGGREGATEABLE]++; + /* announced address space */ + if (space) + ts->total_space += pow(2.0, space - rn_p->prefixlen); + } else if (bgp_dest_has_bgp_path_info_data(pdest)) + ts->counts[BGP_STATS_MAX_AGGREGATEABLE]++; + + + for (pi = bgp_dest_get_bgp_path_info(dest); pi; pi = pi->next) { + ts->counts[BGP_STATS_RIB]++; + + if (CHECK_FLAG(pi->attr->flag, + ATTR_FLAG_BIT(BGP_ATTR_ATOMIC_AGGREGATE))) + ts->counts[BGP_STATS_AGGREGATES]++; + + /* as-path stats */ + if (pi->attr->aspath) { + unsigned int hops = aspath_count_hops(pi->attr->aspath); + unsigned int size = aspath_size(pi->attr->aspath); + as_t highest = aspath_highest(pi->attr->aspath); + + ts->counts[BGP_STATS_ASPATH_COUNT]++; + + if (hops > ts->counts[BGP_STATS_ASPATH_MAXHOPS]) + ts->counts[BGP_STATS_ASPATH_MAXHOPS] = hops; + + if (size > ts->counts[BGP_STATS_ASPATH_MAXSIZE]) + ts->counts[BGP_STATS_ASPATH_MAXSIZE] = size; + + ts->counts[BGP_STATS_ASPATH_TOTHOPS] += hops; + ts->counts[BGP_STATS_ASPATH_TOTSIZE] += size; + if (highest > ts->counts[BGP_STATS_ASN_HIGHEST]) + ts->counts[BGP_STATS_ASN_HIGHEST] = highest; + } + } +} + +static void bgp_table_stats_walker(struct event *t) +{ + struct bgp_dest *dest, *ndest; + struct bgp_dest *top; + struct bgp_table_stats *ts = EVENT_ARG(t); + unsigned int space = 0; + + if (!(top = bgp_table_top(ts->table))) + return; + + switch (ts->table->afi) { + case AFI_IP: + space = IPV4_MAX_BITLEN; + break; + case AFI_IP6: + space = IPV6_MAX_BITLEN; + break; + case AFI_L2VPN: + space = EVPN_ROUTE_PREFIXLEN; + break; + case AFI_UNSPEC: + case AFI_MAX: + return; + } + + ts->counts[BGP_STATS_MAXBITLEN] = space; + + for (dest = top; dest; dest = bgp_route_next(dest)) { + if (ts->table->safi == SAFI_MPLS_VPN + || ts->table->safi == SAFI_ENCAP + || ts->table->safi == SAFI_EVPN) { + struct bgp_table *table; + + table = bgp_dest_get_bgp_table_info(dest); + if (!table) + continue; + + top = bgp_table_top(table); + for (ndest = bgp_table_top(table); ndest; + ndest = bgp_route_next(ndest)) + bgp_table_stats_rn(ndest, top, ts, space); + } else { + bgp_table_stats_rn(dest, top, ts, space); + } + } +} + +static void bgp_table_stats_all(struct vty *vty, afi_t afi, safi_t safi, + struct json_object *json_array) +{ + struct listnode *node, *nnode; + struct bgp *bgp; + + for (ALL_LIST_ELEMENTS(bm->bgp, node, nnode, bgp)) + bgp_table_stats_single(vty, bgp, afi, safi, json_array); +} + +static int bgp_table_stats_single(struct vty *vty, struct bgp *bgp, afi_t afi, + safi_t safi, struct json_object *json_array) +{ + struct bgp_table_stats ts; + unsigned int i; + int ret = CMD_SUCCESS; + char temp_buf[20]; + struct json_object *json = NULL; + uint32_t bitlen = 0; + struct json_object *json_bitlen; + + if (json_array) + json = json_object_new_object(); + + if (!bgp->rib[afi][safi]) { + char warning_msg[50]; + + snprintf(warning_msg, sizeof(warning_msg), + "%% No RIB exist's for the AFI(%d)/SAFI(%d)", afi, + safi); + + if (!json) + vty_out(vty, "%s\n", warning_msg); + else + json_object_string_add(json, "warning", warning_msg); + + ret = CMD_WARNING; + goto end_table_stats; + } + + if (!json) + vty_out(vty, "BGP %s RIB statistics (%s)\n", + get_afi_safi_str(afi, safi, false), bgp->name_pretty); + else + json_object_string_add(json, "instance", bgp->name_pretty); + + /* labeled-unicast routes live in the unicast table */ + if (safi == SAFI_LABELED_UNICAST) + safi = SAFI_UNICAST; + + memset(&ts, 0, sizeof(ts)); + ts.table = bgp->rib[afi][safi]; + event_execute(bm->master, bgp_table_stats_walker, &ts, 0, NULL); + + for (i = 0; i < BGP_STATS_MAX; i++) { + if ((!json && !table_stats_strs[i][TABLE_STATS_IDX_VTY]) + || (json && !table_stats_strs[i][TABLE_STATS_IDX_JSON])) + continue; + + switch (i) { + case BGP_STATS_ASPATH_TOTHOPS: + case BGP_STATS_ASPATH_TOTSIZE: + if (!json) { + snprintf( + temp_buf, sizeof(temp_buf), "%12.2f", + ts.counts[i] + ? (float)ts.counts[i] + / (float)ts.counts + [BGP_STATS_ASPATH_COUNT] + : 0); + vty_out(vty, "%-30s: %s", + table_stats_strs[i] + [TABLE_STATS_IDX_VTY], + temp_buf); + } else { + json_object_double_add( + json, + table_stats_strs[i] + [TABLE_STATS_IDX_JSON], + ts.counts[i] + ? (double)ts.counts[i] + / (double)ts.counts + [BGP_STATS_ASPATH_COUNT] + : 0); + } + break; + case BGP_STATS_TOTPLEN: + if (!json) { + snprintf( + temp_buf, sizeof(temp_buf), "%12.2f", + ts.counts[i] + ? (float)ts.counts[i] + / (float)ts.counts + [BGP_STATS_PREFIXES] + : 0); + vty_out(vty, "%-30s: %s", + table_stats_strs[i] + [TABLE_STATS_IDX_VTY], + temp_buf); + } else { + json_object_double_add( + json, + table_stats_strs[i] + [TABLE_STATS_IDX_JSON], + ts.counts[i] + ? (double)ts.counts[i] + / (double)ts.counts + [BGP_STATS_PREFIXES] + : 0); + } + break; + case BGP_STATS_SPACE: + if (!json) { + snprintf(temp_buf, sizeof(temp_buf), "%12g", + ts.total_space); + vty_out(vty, "%-30s: %s\n", + table_stats_strs[i] + [TABLE_STATS_IDX_VTY], + temp_buf); + } else { + json_object_double_add( + json, + table_stats_strs[i] + [TABLE_STATS_IDX_JSON], + (double)ts.total_space); + } + if (afi == AFI_IP6) { + if (!json) { + snprintf(temp_buf, sizeof(temp_buf), + "%12g", + ts.total_space + * pow(2.0, -128 + 32)); + vty_out(vty, "%30s: %s\n", + "/32 equivalent %s\n", + temp_buf); + } else { + json_object_double_add( + json, "/32equivalent", + (double)(ts.total_space + * pow(2.0, + -128 + 32))); + } + if (!json) { + snprintf(temp_buf, sizeof(temp_buf), + "%12g", + ts.total_space + * pow(2.0, -128 + 48)); + vty_out(vty, "%30s: %s\n", + "/48 equivalent %s\n", + temp_buf); + } else { + json_object_double_add( + json, "/48equivalent", + (double)(ts.total_space + * pow(2.0, + -128 + 48))); + } + } else { + if (!json) { + snprintf(temp_buf, sizeof(temp_buf), + "%12.2f", + ts.total_space * 100. + * pow(2.0, -32)); + vty_out(vty, "%30s: %s\n", + "% announced ", temp_buf); + } else { + json_object_double_add( + json, "%announced", + (double)(ts.total_space * 100. + * pow(2.0, -32))); + } + if (!json) { + snprintf(temp_buf, sizeof(temp_buf), + "%12.2f", + ts.total_space + * pow(2.0, -32 + 8)); + vty_out(vty, "%30s: %s\n", + "/8 equivalent ", temp_buf); + } else { + json_object_double_add( + json, "/8equivalent", + (double)(ts.total_space + * pow(2.0, -32 + 8))); + } + if (!json) { + snprintf(temp_buf, sizeof(temp_buf), + "%12.2f", + ts.total_space + * pow(2.0, -32 + 24)); + vty_out(vty, "%30s: %s\n", + "/24 equivalent ", temp_buf); + } else { + json_object_double_add( + json, "/24equivalent", + (double)(ts.total_space + * pow(2.0, -32 + 24))); + } + } + break; + default: + if (!json) { + snprintf(temp_buf, sizeof(temp_buf), "%12llu", + ts.counts[i]); + vty_out(vty, "%-30s: %s", + table_stats_strs[i] + [TABLE_STATS_IDX_VTY], + temp_buf); + } else { + json_object_int_add( + json, + table_stats_strs[i] + [TABLE_STATS_IDX_JSON], + ts.counts[i]); + } + } + if (!json) + vty_out(vty, "\n"); + } + + switch (afi) { + case AFI_IP: + bitlen = IPV4_MAX_BITLEN; + break; + case AFI_IP6: + bitlen = IPV6_MAX_BITLEN; + break; + case AFI_L2VPN: + bitlen = EVPN_ROUTE_PREFIXLEN; + break; + case AFI_UNSPEC: + case AFI_MAX: + break; + } + + if (json) { + json_bitlen = json_object_new_array(); + + for (i = 0; i <= bitlen; i++) { + if (!ts.prefix_len_count[i]) + continue; + + struct json_object *ind_bit = json_object_new_object(); + + snprintf(temp_buf, sizeof(temp_buf), "%u", i); + json_object_int_add(ind_bit, temp_buf, + ts.prefix_len_count[i]); + json_object_array_add(json_bitlen, ind_bit); + } + json_object_object_add(json, "prefixLength", json_bitlen); + } + +end_table_stats: + if (json) + json_object_array_add(json_array, json); + return ret; +} + +static int bgp_table_stats(struct vty *vty, struct bgp *bgp, afi_t afi, + safi_t safi, struct json_object *json_array) +{ + if (!bgp) { + bgp_table_stats_all(vty, afi, safi, json_array); + return CMD_SUCCESS; + } + + return bgp_table_stats_single(vty, bgp, afi, safi, json_array); +} + +enum bgp_pcounts { + PCOUNT_ADJ_IN = 0, + PCOUNT_DAMPED, + PCOUNT_REMOVED, + PCOUNT_HISTORY, + PCOUNT_STALE, + PCOUNT_VALID, + PCOUNT_ALL, + PCOUNT_COUNTED, + PCOUNT_BPATH_SELECTED, + PCOUNT_PFCNT, /* the figure we display to users */ + PCOUNT_MAX, +}; + +static const char *const pcount_strs[] = { + [PCOUNT_ADJ_IN] = "Adj-in", + [PCOUNT_DAMPED] = "Damped", + [PCOUNT_REMOVED] = "Removed", + [PCOUNT_HISTORY] = "History", + [PCOUNT_STALE] = "Stale", + [PCOUNT_VALID] = "Valid", + [PCOUNT_ALL] = "All RIB", + [PCOUNT_COUNTED] = "PfxCt counted", + [PCOUNT_BPATH_SELECTED] = "PfxCt Best Selected", + [PCOUNT_PFCNT] = "Useable", + [PCOUNT_MAX] = NULL, +}; + +struct peer_pcounts { + unsigned int count[PCOUNT_MAX]; + const struct peer *peer; + const struct bgp_table *table; + safi_t safi; +}; + +static void bgp_peer_count_proc(struct bgp_dest *rn, struct peer_pcounts *pc) +{ + const struct bgp_adj_in *ain; + const struct bgp_path_info *pi; + const struct peer *peer = pc->peer; + + for (ain = rn->adj_in; ain; ain = ain->next) + if (ain->peer == peer) + pc->count[PCOUNT_ADJ_IN]++; + + for (pi = bgp_dest_get_bgp_path_info(rn); pi; pi = pi->next) { + + if (pi->peer != peer) + continue; + + pc->count[PCOUNT_ALL]++; + + if (CHECK_FLAG(pi->flags, BGP_PATH_DAMPED)) + pc->count[PCOUNT_DAMPED]++; + if (CHECK_FLAG(pi->flags, BGP_PATH_HISTORY)) + pc->count[PCOUNT_HISTORY]++; + if (CHECK_FLAG(pi->flags, BGP_PATH_REMOVED)) + pc->count[PCOUNT_REMOVED]++; + if (CHECK_FLAG(pi->flags, BGP_PATH_STALE)) + pc->count[PCOUNT_STALE]++; + if (CHECK_FLAG(pi->flags, BGP_PATH_VALID)) + pc->count[PCOUNT_VALID]++; + if (!CHECK_FLAG(pi->flags, BGP_PATH_UNUSEABLE)) + pc->count[PCOUNT_PFCNT]++; + if (CHECK_FLAG(pi->flags, BGP_PATH_SELECTED)) + pc->count[PCOUNT_BPATH_SELECTED]++; + + if (CHECK_FLAG(pi->flags, BGP_PATH_COUNTED)) { + pc->count[PCOUNT_COUNTED]++; + if (CHECK_FLAG(pi->flags, BGP_PATH_UNUSEABLE)) + flog_err( + EC_LIB_DEVELOPMENT, + "Attempting to count but flags say it is unusable"); + } else { + if (!CHECK_FLAG(pi->flags, BGP_PATH_UNUSEABLE)) + flog_err( + EC_LIB_DEVELOPMENT, + "Not counted but flags say we should"); + } + } +} + +static void bgp_peer_count_walker(struct event *t) +{ + struct bgp_dest *rn, *rm; + const struct bgp_table *table; + struct peer_pcounts *pc = EVENT_ARG(t); + + if (pc->safi == SAFI_MPLS_VPN || pc->safi == SAFI_ENCAP + || pc->safi == SAFI_EVPN) { + /* Special handling for 2-level routing tables. */ + for (rn = bgp_table_top(pc->table); rn; + rn = bgp_route_next(rn)) { + table = bgp_dest_get_bgp_table_info(rn); + if (table != NULL) + for (rm = bgp_table_top(table); rm; + rm = bgp_route_next(rm)) + bgp_peer_count_proc(rm, pc); + } + } else + for (rn = bgp_table_top(pc->table); rn; rn = bgp_route_next(rn)) + bgp_peer_count_proc(rn, pc); +} + +static int bgp_peer_counts(struct vty *vty, struct peer *peer, afi_t afi, + safi_t safi, bool use_json) +{ + struct peer_pcounts pcounts = {.peer = peer}; + unsigned int i; + json_object *json = NULL; + json_object *json_loop = NULL; + + if (use_json) { + json = json_object_new_object(); + json_loop = json_object_new_object(); + } + + if (!peer || !peer->bgp || !peer->afc[afi][safi] + || !peer->bgp->rib[afi][safi]) { + if (use_json) { + json_object_string_add( + json, "warning", + "No such neighbor or address family"); + vty_out(vty, "%s\n", json_object_to_json_string(json)); + json_object_free(json); + json_object_free(json_loop); + } else + vty_out(vty, "%% No such neighbor or address family\n"); + + return CMD_WARNING; + } + + memset(&pcounts, 0, sizeof(pcounts)); + pcounts.peer = peer; + pcounts.table = peer->bgp->rib[afi][safi]; + pcounts.safi = safi; + + /* in-place call via thread subsystem so as to record execution time + * stats for the thread-walk (i.e. ensure this can't be blamed on + * on just vty_read()). + */ + event_execute(bm->master, bgp_peer_count_walker, &pcounts, 0, NULL); + + if (use_json) { + json_object_string_add(json, "prefixCountsFor", peer->host); + json_object_string_add(json, "multiProtocol", + get_afi_safi_str(afi, safi, true)); + json_object_int_add(json, "pfxCounter", + peer->pcount[afi][safi]); + + for (i = 0; i < PCOUNT_MAX; i++) + json_object_int_add(json_loop, pcount_strs[i], + pcounts.count[i]); + + json_object_object_add(json, "ribTableWalkCounters", json_loop); + + if (pcounts.count[PCOUNT_PFCNT] != peer->pcount[afi][safi]) { + json_object_string_add(json, "pfxctDriftFor", + peer->host); + json_object_string_add( + json, "recommended", + "Please report this bug, with the above command output"); + } + vty_json(vty, json); + } else { + + if (peer->hostname + && CHECK_FLAG(peer->bgp->flags, BGP_FLAG_SHOW_HOSTNAME)) { + vty_out(vty, "Prefix counts for %s/%s, %s\n", + peer->hostname, peer->host, + get_afi_safi_str(afi, safi, false)); + } else { + vty_out(vty, "Prefix counts for %s, %s\n", peer->host, + get_afi_safi_str(afi, safi, false)); + } + + vty_out(vty, "PfxCt: %u\n", peer->pcount[afi][safi]); + vty_out(vty, "\nCounts from RIB table walk:\n\n"); + + for (i = 0; i < PCOUNT_MAX; i++) + vty_out(vty, "%20s: %-10d\n", pcount_strs[i], + pcounts.count[i]); + + if (pcounts.count[PCOUNT_PFCNT] != peer->pcount[afi][safi]) { + vty_out(vty, "%s [pcount] PfxCt drift!\n", peer->host); + vty_out(vty, + "Please report this bug, with the above command output\n"); + } + } + + return CMD_SUCCESS; +} + +DEFUN (show_ip_bgp_instance_neighbor_prefix_counts, + show_ip_bgp_instance_neighbor_prefix_counts_cmd, + "show [ip] bgp [<view|vrf> VIEWVRFNAME] ["BGP_AFI_CMD_STR" ["BGP_SAFI_CMD_STR"]] neighbors <A.B.C.D|X:X::X:X|WORD> prefix-counts [json]", + SHOW_STR + IP_STR + BGP_STR + BGP_INSTANCE_HELP_STR + BGP_AFI_HELP_STR + BGP_SAFI_HELP_STR + "Detailed information on TCP and BGP neighbor connections\n" + "Neighbor to display information about\n" + "Neighbor to display information about\n" + "Neighbor on BGP configured interface\n" + "Display detailed prefix count information\n" + JSON_STR) +{ + afi_t afi = AFI_IP6; + safi_t safi = SAFI_UNICAST; + struct peer *peer; + int idx = 0; + struct bgp *bgp = NULL; + bool uj = use_json(argc, argv); + + if (uj) + argc--; + + bgp_vty_find_and_parse_afi_safi_bgp(vty, argv, argc, &idx, &afi, &safi, + &bgp, uj); + if (!idx) + return CMD_WARNING; + + argv_find(argv, argc, "neighbors", &idx); + peer = peer_lookup_in_view(vty, bgp, argv[idx + 1]->arg, uj); + if (!peer) + return CMD_WARNING; + + return bgp_peer_counts(vty, peer, afi, safi, uj); +} + +#ifdef KEEP_OLD_VPN_COMMANDS +DEFUN (show_ip_bgp_vpn_neighbor_prefix_counts, + show_ip_bgp_vpn_neighbor_prefix_counts_cmd, + "show [ip] bgp <vpnv4|vpnv6> all neighbors <A.B.C.D|X:X::X:X|WORD> prefix-counts [json]", + SHOW_STR + IP_STR + BGP_STR + BGP_VPNVX_HELP_STR + "Display information about all VPNv4 NLRIs\n" + "Detailed information on TCP and BGP neighbor connections\n" + "Neighbor to display information about\n" + "Neighbor to display information about\n" + "Neighbor on BGP configured interface\n" + "Display detailed prefix count information\n" + JSON_STR) +{ + int idx_peer = 6; + struct peer *peer; + bool uj = use_json(argc, argv); + + peer = peer_lookup_in_view(vty, NULL, argv[idx_peer]->arg, uj); + if (!peer) + return CMD_WARNING; + + return bgp_peer_counts(vty, peer, AFI_IP, SAFI_MPLS_VPN, uj); +} + +DEFUN (show_ip_bgp_vpn_all_route_prefix, + show_ip_bgp_vpn_all_route_prefix_cmd, + "show [ip] bgp <vpnv4|vpnv6> all <A.B.C.D|A.B.C.D/M> [json]", + SHOW_STR + IP_STR + BGP_STR + BGP_VPNVX_HELP_STR + "Display information about all VPNv4 NLRIs\n" + "Network in the BGP routing table to display\n" + "Network in the BGP routing table to display\n" + JSON_STR) +{ + int idx = 0; + char *network = NULL; + struct bgp *bgp = bgp_get_default(); + if (!bgp) { + vty_out(vty, "Can't find default instance\n"); + return CMD_WARNING; + } + + if (argv_find(argv, argc, "A.B.C.D", &idx)) + network = argv[idx]->arg; + else if (argv_find(argv, argc, "A.B.C.D/M", &idx)) + network = argv[idx]->arg; + else { + vty_out(vty, "Unable to figure out Network\n"); + return CMD_WARNING; + } + + return bgp_show_route(vty, bgp, network, AFI_IP, SAFI_MPLS_VPN, NULL, 0, + BGP_PATH_SHOW_ALL, RPKI_NOT_BEING_USED, + use_json(argc, argv)); +} +#endif /* KEEP_OLD_VPN_COMMANDS */ + +DEFUN (show_bgp_l2vpn_evpn_route_prefix, + show_bgp_l2vpn_evpn_route_prefix_cmd, + "show bgp l2vpn evpn <A.B.C.D|A.B.C.D/M|X:X::X:X|X:X::X:X/M> [json]", + SHOW_STR + BGP_STR + L2VPN_HELP_STR + EVPN_HELP_STR + "Network in the BGP routing table to display\n" + "Network in the BGP routing table to display\n" + "Network in the BGP routing table to display\n" + "Network in the BGP routing table to display\n" + JSON_STR) +{ + int idx = 0; + char *network = NULL; + int prefix_check = 0; + + if (argv_find(argv, argc, "A.B.C.D", &idx) || + argv_find(argv, argc, "X:X::X:X", &idx)) + network = argv[idx]->arg; + else if (argv_find(argv, argc, "A.B.C.D/M", &idx) || + argv_find(argv, argc, "X:X::X:X/M", &idx)) { + network = argv[idx]->arg; + prefix_check = 1; + } else { + vty_out(vty, "Unable to figure out Network\n"); + return CMD_WARNING; + } + return bgp_show_route(vty, NULL, network, AFI_L2VPN, SAFI_EVPN, NULL, + prefix_check, BGP_PATH_SHOW_ALL, + RPKI_NOT_BEING_USED, use_json(argc, argv)); +} + +static void show_adj_route_header(struct vty *vty, struct peer *peer, + struct bgp_table *table, int *header1, + int *header2, json_object *json, + json_object *json_scode, + json_object *json_ocode, bool wide, + bool detail) +{ + uint64_t version = table ? table->version : 0; + + if (*header1) { + if (json) { + json_object_int_add(json, "bgpTableVersion", version); + json_object_string_addf(json, "bgpLocalRouterId", + "%pI4", &peer->bgp->router_id); + json_object_int_add(json, "defaultLocPrf", + peer->bgp->default_local_pref); + json_object_int_add(json, "localAS", + peer->change_local_as + ? peer->change_local_as + : peer->local_as); + json_object_object_add(json, "bgpStatusCodes", + json_scode); + json_object_object_add(json, "bgpOriginCodes", + json_ocode); + } else { + vty_out(vty, + "BGP table version is %" PRIu64 + ", local router ID is %pI4, vrf id ", + version, &peer->bgp->router_id); + if (peer->bgp->vrf_id == VRF_UNKNOWN) + vty_out(vty, "%s", VRFID_NONE_STR); + else + vty_out(vty, "%u", peer->bgp->vrf_id); + vty_out(vty, "\n"); + vty_out(vty, "Default local pref %u, ", + peer->bgp->default_local_pref); + vty_out(vty, "local AS %u\n", + peer->change_local_as ? peer->change_local_as + : peer->local_as); + if (!detail) { + vty_out(vty, BGP_SHOW_SCODE_HEADER); + vty_out(vty, BGP_SHOW_NCODE_HEADER); + vty_out(vty, BGP_SHOW_OCODE_HEADER); + vty_out(vty, BGP_SHOW_RPKI_HEADER); + } + } + *header1 = 0; + } + if (*header2) { + if (!json && !detail) + vty_out(vty, (wide ? BGP_SHOW_HEADER_WIDE + : BGP_SHOW_HEADER)); + *header2 = 0; + } +} + +static void +show_adj_route(struct vty *vty, struct peer *peer, struct bgp_table *table, + afi_t afi, safi_t safi, enum bgp_show_adj_route_type type, + const char *rmap_name, json_object *json, json_object *json_ar, + json_object *json_scode, json_object *json_ocode, + uint16_t show_flags, int *header1, int *header2, char *rd_str, + const struct prefix *match, unsigned long *output_count, + unsigned long *filtered_count) +{ + struct bgp_adj_in *ain = NULL; + struct bgp_adj_out *adj = NULL; + struct bgp_dest *dest; + struct bgp *bgp; + struct attr attr; + int ret; + struct update_subgroup *subgrp; + struct peer_af *paf = NULL; + bool route_filtered; + bool detail = CHECK_FLAG(show_flags, BGP_SHOW_OPT_ROUTES_DETAIL); + bool use_json = CHECK_FLAG(show_flags, BGP_SHOW_OPT_JSON); + bool wide = CHECK_FLAG(show_flags, BGP_SHOW_OPT_WIDE); + bool show_rd = ((safi == SAFI_MPLS_VPN) || (safi == SAFI_ENCAP) + || (safi == SAFI_EVPN)) + ? true + : false; + int display = 0; + json_object *json_net = NULL; + + bgp = peer->bgp; + + /* If the user supplied a prefix, look for a matching route instead + * of walking the whole table. + */ + if (match) { + dest = bgp_node_match(table, match); + if (!dest) { + if (!use_json) + vty_out(vty, "Network not in table\n"); + return; + } + + const struct prefix *rn_p = bgp_dest_get_prefix(dest); + + if (rn_p->prefixlen != match->prefixlen) { + if (!use_json) + vty_out(vty, "Network not in table\n"); + bgp_dest_unlock_node(dest); + return; + } + + if (type == bgp_show_adj_route_received || + type == bgp_show_adj_route_filtered) { + for (ain = dest->adj_in; ain; ain = ain->next) { + if (ain->peer == peer) { + attr = *ain->attr; + break; + } + } + /* bail out if if adj_out is empty, or + * if the prefix isn't in this peer's + * adj_in + */ + if (!ain || ain->peer != peer) { + if (!use_json) + vty_out(vty, "Network not in table\n"); + bgp_dest_unlock_node(dest); + return; + } + } else if (type == bgp_show_adj_route_advertised) { + bool peer_found = false; + + RB_FOREACH (adj, bgp_adj_out_rb, &dest->adj_out) { + SUBGRP_FOREACH_PEER (adj->subgroup, paf) { + if (paf->peer == peer && adj->attr) { + attr = *adj->attr; + peer_found = true; + break; + } + } + if (peer_found) + break; + } + /* bail out if if adj_out is empty, or + * if the prefix isn't in this peer's + * adj_out + */ + if (!paf || !peer_found) { + if (!use_json) + vty_out(vty, "Network not in table\n"); + bgp_dest_unlock_node(dest); + return; + } + } + + ret = bgp_output_modifier(peer, rn_p, &attr, afi, safi, + rmap_name); + + if (ret != RMAP_DENY) { + show_adj_route_header(vty, peer, table, header1, + header2, json, json_scode, + json_ocode, wide, detail); + + if (use_json) + json_net = json_object_new_object(); + + bgp_show_path_info(NULL /* prefix_rd */, dest, vty, bgp, + afi, safi, json_net, + BGP_PATH_SHOW_ALL, &display, + RPKI_NOT_BEING_USED); + if (use_json) + json_object_object_addf(json_ar, json_net, + "%pFX", rn_p); + (*output_count)++; + } else + (*filtered_count)++; + + bgp_attr_flush(&attr); + bgp_dest_unlock_node(dest); + return; + } + + + subgrp = peer_subgroup(peer, afi, safi); + + if (type == bgp_show_adj_route_advertised && subgrp + && CHECK_FLAG(subgrp->sflags, SUBGRP_STATUS_DEFAULT_ORIGINATE)) { + if (use_json) { + json_object_int_add(json, "bgpTableVersion", + table->version); + json_object_string_addf(json, "bgpLocalRouterId", + "%pI4", &bgp->router_id); + json_object_int_add(json, "defaultLocPrf", + bgp->default_local_pref); + json_object_int_add(json, "localAS", + peer->change_local_as + ? peer->change_local_as + : peer->local_as); + json_object_object_add(json, "bgpStatusCodes", + json_scode); + json_object_object_add(json, "bgpOriginCodes", + json_ocode); + json_object_string_add( + json, "bgpOriginatingDefaultNetwork", + (afi == AFI_IP) ? "0.0.0.0/0" : "::/0"); + } else { + vty_out(vty, + "BGP table version is %" PRIu64 + ", local router ID is %pI4, vrf id ", + table->version, &bgp->router_id); + if (bgp->vrf_id == VRF_UNKNOWN) + vty_out(vty, "%s", VRFID_NONE_STR); + else + vty_out(vty, "%u", bgp->vrf_id); + vty_out(vty, "\n"); + vty_out(vty, "Default local pref %u, ", + bgp->default_local_pref); + vty_out(vty, "local AS %u\n", + peer->change_local_as ? peer->change_local_as + : peer->local_as); + if (!detail) { + vty_out(vty, BGP_SHOW_SCODE_HEADER); + vty_out(vty, BGP_SHOW_NCODE_HEADER); + vty_out(vty, BGP_SHOW_OCODE_HEADER); + vty_out(vty, BGP_SHOW_RPKI_HEADER); + } + + vty_out(vty, "Originating default network %s\n\n", + (afi == AFI_IP) ? "0.0.0.0/0" : "::/0"); + } + (*output_count)++; + *header1 = 0; + } + + for (dest = bgp_table_top(table); dest; dest = bgp_route_next(dest)) { + if (type == bgp_show_adj_route_received + || type == bgp_show_adj_route_filtered) { + for (ain = dest->adj_in; ain; ain = ain->next) { + if (ain->peer != peer) + continue; + show_adj_route_header(vty, peer, table, header1, + header2, json, json_scode, + json_ocode, wide, detail); + + if ((safi == SAFI_MPLS_VPN) + || (safi == SAFI_ENCAP) + || (safi == SAFI_EVPN)) { + if (use_json) + json_object_string_add( + json_ar, "rd", rd_str); + else if (show_rd && rd_str) { + vty_out(vty, + "Route Distinguisher: %s\n", + rd_str); + show_rd = false; + } + } + + attr = *ain->attr; + route_filtered = false; + + /* Filter prefix using distribute list, + * filter list or prefix list + */ + const struct prefix *rn_p = + bgp_dest_get_prefix(dest); + if ((bgp_input_filter(peer, rn_p, &attr, afi, + safi)) + == FILTER_DENY) + route_filtered = true; + + /* Filter prefix using route-map */ + ret = bgp_input_modifier(peer, rn_p, &attr, afi, + safi, rmap_name, NULL, + 0, NULL); + + if (type == bgp_show_adj_route_filtered && + !route_filtered && ret != RMAP_DENY) { + bgp_attr_flush(&attr); + continue; + } + + if (type == bgp_show_adj_route_received + && (route_filtered || ret == RMAP_DENY)) + (*filtered_count)++; + + if (detail) { + if (use_json) + json_net = + json_object_new_object(); + + struct bgp_path_info bpi; + struct bgp_dest buildit = *dest; + struct bgp_dest *pass_in; + + if (route_filtered || + ret == RMAP_DENY) { + bpi.attr = &attr; + bpi.peer = peer; + buildit.info = &bpi; + + pass_in = &buildit; + } else + pass_in = dest; + bgp_show_path_info( + NULL, pass_in, vty, bgp, afi, + safi, json_net, + BGP_PATH_SHOW_ALL, &display, + RPKI_NOT_BEING_USED); + if (use_json) + json_object_object_addf( + json_ar, json_net, + "%pFX", rn_p); + } else + route_vty_out_tmp(vty, dest, rn_p, + &attr, safi, use_json, + json_ar, wide); + bgp_attr_flush(&attr); + (*output_count)++; + } + } else if (type == bgp_show_adj_route_advertised) { + RB_FOREACH (adj, bgp_adj_out_rb, &dest->adj_out) + SUBGRP_FOREACH_PEER (adj->subgroup, paf) { + if (paf->peer != peer || !adj->attr) + continue; + + show_adj_route_header( + vty, peer, table, header1, + header2, json, json_scode, + json_ocode, wide, detail); + + const struct prefix *rn_p = + bgp_dest_get_prefix(dest); + + attr = *adj->attr; + ret = bgp_output_modifier( + peer, rn_p, &attr, afi, safi, + rmap_name); + + if (ret != RMAP_DENY) { + if ((safi == SAFI_MPLS_VPN) + || (safi == SAFI_ENCAP) + || (safi == SAFI_EVPN)) { + if (use_json) + json_object_string_add( + json_ar, + "rd", + rd_str); + else if (show_rd + && rd_str) { + vty_out(vty, + "Route Distinguisher: %s\n", + rd_str); + show_rd = false; + } + } + if (detail) { + if (use_json) + json_net = + json_object_new_object(); + bgp_show_path_info( + NULL /* prefix_rd + */ + , + dest, vty, bgp, + afi, safi, + json_net, + BGP_PATH_SHOW_ALL, + &display, + RPKI_NOT_BEING_USED); + if (use_json) + json_object_object_addf( + json_ar, + json_net, + "%pFX", + rn_p); + } else + route_vty_out_tmp( + vty, dest, rn_p, + &attr, safi, + use_json, + json_ar, wide); + (*output_count)++; + } else { + (*filtered_count)++; + } + + bgp_attr_flush(&attr); + } + } else if (type == bgp_show_adj_route_bestpath) { + struct bgp_path_info *pi; + + show_adj_route_header(vty, peer, table, header1, + header2, json, json_scode, + json_ocode, wide, detail); + + const struct prefix *rn_p = bgp_dest_get_prefix(dest); + + for (pi = bgp_dest_get_bgp_path_info(dest); pi; + pi = pi->next) { + if (pi->peer != peer) + continue; + + if (!CHECK_FLAG(pi->flags, BGP_PATH_SELECTED)) + continue; + + if (detail) { + if (use_json) + json_net = + json_object_new_object(); + bgp_show_path_info( + NULL /* prefix_rd */, dest, vty, + bgp, afi, safi, json_net, + BGP_PATH_SHOW_BESTPATH, + &display, RPKI_NOT_BEING_USED); + if (use_json) + json_object_object_addf( + json_ar, json_net, + "%pFX", rn_p); + } else + route_vty_out_tmp( + vty, dest, rn_p, pi->attr, safi, + use_json, json_ar, wide); + (*output_count)++; + } + } + } +} + +static int peer_adj_routes(struct vty *vty, struct peer *peer, afi_t afi, + safi_t safi, enum bgp_show_adj_route_type type, + const char *rmap_name, const struct prefix *match, + uint16_t show_flags) +{ + struct bgp *bgp; + struct bgp_table *table; + json_object *json = NULL; + json_object *json_scode = NULL; + json_object *json_ocode = NULL; + json_object *json_ar = NULL; + bool use_json = CHECK_FLAG(show_flags, BGP_SHOW_OPT_JSON); + + /* Init BGP headers here so they're only displayed once + * even if 'table' is 2-tier (MPLS_VPN, ENCAP, EVPN). + */ + int header1 = 1; + int header2 = 1; + + /* + * Initialize variables for each RD + * All prefixes under an RD is aggregated within "json_routes" + */ + char rd_str[BUFSIZ] = {0}; + json_object *json_routes = NULL; + + + /* For 2-tier tables, prefix counts need to be + * maintained across multiple runs of show_adj_route() + */ + unsigned long output_count_per_rd; + unsigned long filtered_count_per_rd; + unsigned long output_count = 0; + unsigned long filtered_count = 0; + + if (use_json) { + json = json_object_new_object(); + json_ar = json_object_new_object(); + json_scode = json_object_new_object(); + json_ocode = json_object_new_object(); +#if CONFDATE > 20231208 +CPP_NOTICE("Drop `bgpStatusCodes` from JSON outputs") +#endif + json_object_string_add(json_scode, "suppressed", "s"); + json_object_string_add(json_scode, "damped", "d"); + json_object_string_add(json_scode, "history", "h"); + json_object_string_add(json_scode, "valid", "*"); + json_object_string_add(json_scode, "best", ">"); + json_object_string_add(json_scode, "multipath", "="); + json_object_string_add(json_scode, "internal", "i"); + json_object_string_add(json_scode, "ribFailure", "r"); + json_object_string_add(json_scode, "stale", "S"); + json_object_string_add(json_scode, "removed", "R"); + +#if CONFDATE > 20231208 +CPP_NOTICE("Drop `bgpOriginCodes` from JSON outputs") +#endif + json_object_string_add(json_ocode, "igp", "i"); + json_object_string_add(json_ocode, "egp", "e"); + json_object_string_add(json_ocode, "incomplete", "?"); + } + + if (!peer || !peer->afc[afi][safi]) { + if (use_json) { + json_object_string_add( + json, "warning", + "No such neighbor or address family"); + vty_out(vty, "%s\n", json_object_to_json_string(json)); + json_object_free(json); + json_object_free(json_ar); + json_object_free(json_scode); + json_object_free(json_ocode); + } else + vty_out(vty, "%% No such neighbor or address family\n"); + + return CMD_WARNING; + } + + if ((type == bgp_show_adj_route_received + || type == bgp_show_adj_route_filtered) + && !CHECK_FLAG(peer->af_flags[afi][safi], + PEER_FLAG_SOFT_RECONFIG)) { + if (use_json) { + json_object_string_add( + json, "warning", + "Inbound soft reconfiguration not enabled"); + vty_out(vty, "%s\n", json_object_to_json_string(json)); + json_object_free(json); + json_object_free(json_ar); + json_object_free(json_scode); + json_object_free(json_ocode); + } else + vty_out(vty, + "%% Inbound soft reconfiguration not enabled\n"); + + return CMD_WARNING; + } + + bgp = peer->bgp; + + /* labeled-unicast routes live in the unicast table */ + if (safi == SAFI_LABELED_UNICAST) + table = bgp->rib[afi][SAFI_UNICAST]; + else + table = bgp->rib[afi][safi]; + + if ((safi == SAFI_MPLS_VPN) || (safi == SAFI_ENCAP) + || (safi == SAFI_EVPN)) { + + struct bgp_dest *dest; + + for (dest = bgp_table_top(table); dest; + dest = bgp_route_next(dest)) { + table = bgp_dest_get_bgp_table_info(dest); + if (!table) + continue; + + output_count_per_rd = 0; + filtered_count_per_rd = 0; + + if (use_json) + json_routes = json_object_new_object(); + + const struct prefix_rd *prd; + prd = (const struct prefix_rd *)bgp_dest_get_prefix( + dest); + + prefix_rd2str(prd, rd_str, sizeof(rd_str), + bgp->asnotation); + + show_adj_route( + vty, peer, table, afi, safi, type, rmap_name, + json, json_routes, json_scode, json_ocode, + show_flags, &header1, &header2, rd_str, match, + &output_count_per_rd, &filtered_count_per_rd); + + /* Don't include an empty RD in the output! */ + if (json_routes && (output_count_per_rd > 0)) + json_object_object_add(json_ar, rd_str, + json_routes); + + output_count += output_count_per_rd; + filtered_count += filtered_count_per_rd; + } + } else + show_adj_route(vty, peer, table, afi, safi, type, rmap_name, + json, json_ar, json_scode, json_ocode, + show_flags, &header1, &header2, rd_str, match, + &output_count, &filtered_count); + + if (use_json) { + if (type == bgp_show_adj_route_advertised) + json_object_object_add(json, "advertisedRoutes", + json_ar); + else + json_object_object_add(json, "receivedRoutes", json_ar); + json_object_int_add(json, "totalPrefixCounter", output_count); + json_object_int_add(json, "filteredPrefixCounter", + filtered_count); + + /* + * These fields only give up ownership to `json` when `header1` + * is used (set to zero). See code in `show_adj_route` and + * `show_adj_route_header`. + */ + if (header1 == 1) { + json_object_free(json_scode); + json_object_free(json_ocode); + } + + /* + * This is an extremely expensive operation at scale + * and non-pretty reduces memory footprint significantly. + */ + vty_json_no_pretty(vty, json); + } else if (output_count > 0) { + if (!match && filtered_count > 0) + vty_out(vty, + "\nTotal number of prefixes %ld (%ld filtered)\n", + output_count, filtered_count); + else + vty_out(vty, "\nTotal number of prefixes %ld\n", + output_count); + } + + return CMD_SUCCESS; +} + +DEFPY (show_ip_bgp_instance_neighbor_bestpath_route, + show_ip_bgp_instance_neighbor_bestpath_route_cmd, + "show [ip] bgp [<view|vrf> VIEWVRFNAME] [" BGP_AFI_CMD_STR " [" BGP_SAFI_WITH_LABEL_CMD_STR "]] neighbors <A.B.C.D|X:X::X:X|WORD> bestpath-routes [detail$detail] [json$uj | wide$wide]", + SHOW_STR + IP_STR + BGP_STR + BGP_INSTANCE_HELP_STR + BGP_AFI_HELP_STR + BGP_SAFI_WITH_LABEL_HELP_STR + "Detailed information on TCP and BGP neighbor connections\n" + "Neighbor to display information about\n" + "Neighbor to display information about\n" + "Neighbor on BGP configured interface\n" + "Display the routes selected by best path\n" + "Display detailed version of routes\n" + JSON_STR + "Increase table width for longer prefixes\n") +{ + afi_t afi = AFI_IP6; + safi_t safi = SAFI_UNICAST; + char *rmap_name = NULL; + char *peerstr = NULL; + struct bgp *bgp = NULL; + struct peer *peer; + enum bgp_show_adj_route_type type = bgp_show_adj_route_bestpath; + int idx = 0; + uint16_t show_flags = 0; + + if (detail) + SET_FLAG(show_flags, BGP_SHOW_OPT_ROUTES_DETAIL); + + if (uj) + SET_FLAG(show_flags, BGP_SHOW_OPT_JSON); + + if (wide) + SET_FLAG(show_flags, BGP_SHOW_OPT_WIDE); + + bgp_vty_find_and_parse_afi_safi_bgp(vty, argv, argc, &idx, &afi, &safi, + &bgp, uj); + + if (!idx) + return CMD_WARNING; + + argv_find(argv, argc, "neighbors", &idx); + peerstr = argv[++idx]->arg; + + peer = peer_lookup_in_view(vty, bgp, peerstr, uj); + if (!peer) + return CMD_WARNING; + + return peer_adj_routes(vty, peer, afi, safi, type, rmap_name, NULL, + show_flags); +} + +DEFPY(show_ip_bgp_instance_neighbor_advertised_route, + show_ip_bgp_instance_neighbor_advertised_route_cmd, + "show [ip] bgp [<view|vrf> VIEWVRFNAME] [" BGP_AFI_CMD_STR " [" BGP_SAFI_WITH_LABEL_CMD_STR "]] [all$all] neighbors <A.B.C.D|X:X::X:X|WORD> <advertised-routes|received-routes|filtered-routes> [route-map RMAP_NAME$route_map] [<A.B.C.D/M|X:X::X:X/M>$prefix | detail$detail] [json$uj | wide$wide]", + SHOW_STR + IP_STR + BGP_STR + BGP_INSTANCE_HELP_STR + BGP_AFI_HELP_STR + BGP_SAFI_WITH_LABEL_HELP_STR + "Display the entries for all address families\n" + "Detailed information on TCP and BGP neighbor connections\n" + "Neighbor to display information about\n" + "Neighbor to display information about\n" + "Neighbor on BGP configured interface\n" + "Display the routes advertised to a BGP neighbor\n" + "Display the received routes from neighbor\n" + "Display the filtered routes received from neighbor\n" + "Route-map to modify the attributes\n" + "Name of the route map\n" + "IPv4 prefix\n" + "IPv6 prefix\n" + "Display detailed version of routes\n" + JSON_STR + "Increase table width for longer prefixes\n") +{ + afi_t afi = AFI_IP6; + safi_t safi = SAFI_UNICAST; + char *peerstr = NULL; + struct bgp *bgp = NULL; + struct peer *peer; + enum bgp_show_adj_route_type type = bgp_show_adj_route_advertised; + int idx = 0; + bool first = true; + uint16_t show_flags = 0; + struct listnode *node; + struct bgp *abgp; + + if (detail || prefix_str) + SET_FLAG(show_flags, BGP_SHOW_OPT_ROUTES_DETAIL); + + if (uj) { + argc--; + SET_FLAG(show_flags, BGP_SHOW_OPT_JSON); + } + + if (all) { + SET_FLAG(show_flags, BGP_SHOW_OPT_AFI_ALL); + if (argv_find(argv, argc, "ipv4", &idx)) + SET_FLAG(show_flags, BGP_SHOW_OPT_AFI_IP); + + if (argv_find(argv, argc, "ipv6", &idx)) + SET_FLAG(show_flags, BGP_SHOW_OPT_AFI_IP6); + } + + if (wide) + SET_FLAG(show_flags, BGP_SHOW_OPT_WIDE); + + bgp_vty_find_and_parse_afi_safi_bgp(vty, argv, argc, &idx, &afi, &safi, + &bgp, uj); + if (!idx) + return CMD_WARNING; + + /* neighbors <A.B.C.D|X:X::X:X|WORD> */ + argv_find(argv, argc, "neighbors", &idx); + peerstr = argv[++idx]->arg; + + peer = peer_lookup_in_view(vty, bgp, peerstr, uj); + if (!peer) + return CMD_WARNING; + + if (argv_find(argv, argc, "advertised-routes", &idx)) + type = bgp_show_adj_route_advertised; + else if (argv_find(argv, argc, "received-routes", &idx)) + type = bgp_show_adj_route_received; + else if (argv_find(argv, argc, "filtered-routes", &idx)) + type = bgp_show_adj_route_filtered; + + if (!all) + return peer_adj_routes(vty, peer, afi, safi, type, route_map, + prefix_str ? prefix : NULL, show_flags); + if (uj) + vty_out(vty, "{\n"); + + if (CHECK_FLAG(show_flags, BGP_SHOW_OPT_AFI_IP) + || CHECK_FLAG(show_flags, BGP_SHOW_OPT_AFI_IP6)) { + afi = CHECK_FLAG(show_flags, BGP_SHOW_OPT_AFI_IP) ? AFI_IP + : AFI_IP6; + for (ALL_LIST_ELEMENTS_RO(bm->bgp, node, abgp)) { + FOREACH_SAFI (safi) { + if (!bgp_afi_safi_peer_exists(abgp, afi, safi)) + continue; + + if (uj) { + if (first) + first = false; + else + vty_out(vty, ",\n"); + vty_out(vty, "\"%s\":", + get_afi_safi_str(afi, safi, + true)); + } else + vty_out(vty, + "\nFor address family: %s\n", + get_afi_safi_str(afi, safi, + false)); + + peer_adj_routes(vty, peer, afi, safi, type, + route_map, prefix, show_flags); + } + } + } else { + for (ALL_LIST_ELEMENTS_RO(bm->bgp, node, abgp)) { + FOREACH_AFI_SAFI (afi, safi) { + if (!bgp_afi_safi_peer_exists(abgp, afi, safi)) + continue; + + if (uj) { + if (first) + first = false; + else + vty_out(vty, ",\n"); + vty_out(vty, "\"%s\":", + get_afi_safi_str(afi, safi, + true)); + } else + vty_out(vty, + "\nFor address family: %s\n", + get_afi_safi_str(afi, safi, + false)); + + peer_adj_routes(vty, peer, afi, safi, type, + route_map, prefix, show_flags); + } + } + } + if (uj) + vty_out(vty, "}\n"); + + return CMD_SUCCESS; +} + +DEFUN (show_ip_bgp_neighbor_received_prefix_filter, + show_ip_bgp_neighbor_received_prefix_filter_cmd, + "show [ip] bgp [<view|vrf> VIEWVRFNAME] [<ipv4|ipv6> [unicast]] neighbors <A.B.C.D|X:X::X:X|WORD> received prefix-filter [json]", + SHOW_STR + IP_STR + BGP_STR + BGP_INSTANCE_HELP_STR + BGP_AF_STR + BGP_AF_STR + BGP_AF_MODIFIER_STR + "Detailed information on TCP and BGP neighbor connections\n" + "Neighbor to display information about\n" + "Neighbor to display information about\n" + "Neighbor on BGP configured interface\n" + "Display information received from a BGP neighbor\n" + "Display the prefixlist filter\n" + JSON_STR) +{ + afi_t afi = AFI_IP6; + safi_t safi = SAFI_UNICAST; + char *peerstr = NULL; + char name[BUFSIZ]; + struct peer *peer; + int count; + int idx = 0; + struct bgp *bgp = NULL; + bool uj = use_json(argc, argv); + + if (uj) + argc--; + + bgp_vty_find_and_parse_afi_safi_bgp(vty, argv, argc, &idx, &afi, &safi, + &bgp, uj); + if (!idx) + return CMD_WARNING; + + /* neighbors <A.B.C.D|X:X::X:X|WORD> */ + argv_find(argv, argc, "neighbors", &idx); + peerstr = argv[++idx]->arg; + + peer = peer_lookup_in_view(vty, bgp, peerstr, uj); + if (!peer) + return CMD_WARNING; + + snprintf(name, sizeof(name), "%s.%d.%d", peer->host, afi, safi); + count = prefix_bgp_show_prefix_list(NULL, afi, name, uj); + if (count) { + if (!uj) + vty_out(vty, "Address Family: %s\n", + get_afi_safi_str(afi, safi, false)); + prefix_bgp_show_prefix_list(vty, afi, name, uj); + } else { + if (uj) + vty_out(vty, "{}\n"); + else + vty_out(vty, "No functional output\n"); + } + + return CMD_SUCCESS; +} + +static int bgp_show_neighbor_route(struct vty *vty, struct peer *peer, + afi_t afi, safi_t safi, + enum bgp_show_type type, bool use_json) +{ + uint16_t show_flags = 0; + + if (use_json) + SET_FLAG(show_flags, BGP_SHOW_OPT_JSON); + + if (!peer || !peer->afc[afi][safi]) { + if (use_json) { + json_object *json_no = NULL; + json_no = json_object_new_object(); + json_object_string_add( + json_no, "warning", + "No such neighbor or address family"); + vty_out(vty, "%s\n", + json_object_to_json_string(json_no)); + json_object_free(json_no); + } else + vty_out(vty, "%% No such neighbor or address family\n"); + return CMD_WARNING; + } + + /* labeled-unicast routes live in the unicast table */ + if (safi == SAFI_LABELED_UNICAST) + safi = SAFI_UNICAST; + + return bgp_show(vty, peer->bgp, afi, safi, type, &peer->connection->su, + show_flags, RPKI_NOT_BEING_USED); +} + +/* + * Used for "detailed" output for cmds like show bgp <afi> <safi> (or) + * show bgp <vrf> (or) show bgp <vrf> <afi> <safi> + */ +DEFPY(show_ip_bgp_vrf_afi_safi_routes_detailed, + show_ip_bgp_vrf_afi_safi_routes_detailed_cmd, + "show [ip] bgp [<view|vrf> VIEWVRFNAME$vrf_name] ["BGP_AFI_CMD_STR" ["BGP_SAFI_WITH_LABEL_CMD_STR"]] detail [json$uj]", + SHOW_STR + IP_STR + BGP_STR + BGP_INSTANCE_HELP_STR + BGP_AFI_HELP_STR + BGP_SAFI_WITH_LABEL_HELP_STR + "Detailed information\n" + JSON_STR) +{ + afi_t afi = AFI_IP6; + safi_t safi = SAFI_UNICAST; + struct bgp *bgp = NULL; + int idx = 0; + uint16_t show_flags = BGP_SHOW_OPT_ROUTES_DETAIL; + + if (uj) + SET_FLAG(show_flags, BGP_SHOW_OPT_JSON); + + bgp_vty_find_and_parse_afi_safi_bgp(vty, argv, argc, &idx, &afi, &safi, + &bgp, uj); + if (!idx) + return CMD_WARNING; + /* 'vrf all' case to iterate all vrfs & show output per vrf instance */ + if (vrf_name && strmatch(vrf_name, "all")) { + bgp_show_all_instances_routes_vty(vty, afi, safi, show_flags); + return CMD_SUCCESS; + } + + /* All other cases except vrf all */ + return bgp_show(vty, bgp, afi, safi, bgp_show_type_detail, NULL, + show_flags, RPKI_NOT_BEING_USED); +} + +DEFUN (show_ip_bgp_neighbor_routes, + show_ip_bgp_neighbor_routes_cmd, + "show [ip] bgp [<view|vrf> VIEWVRFNAME] ["BGP_AFI_CMD_STR" ["BGP_SAFI_WITH_LABEL_CMD_STR"]] neighbors <A.B.C.D|X:X::X:X|WORD> <flap-statistics|dampened-routes|routes> [json]", + SHOW_STR + IP_STR + BGP_STR + BGP_INSTANCE_HELP_STR + BGP_AFI_HELP_STR + BGP_SAFI_WITH_LABEL_HELP_STR + "Detailed information on TCP and BGP neighbor connections\n" + "Neighbor to display information about\n" + "Neighbor to display information about\n" + "Neighbor on BGP configured interface\n" + "Display flap statistics of the routes learned from neighbor\n" + "Display the dampened routes received from neighbor\n" + "Display routes learned from neighbor\n" + JSON_STR) +{ + char *peerstr = NULL; + struct bgp *bgp = NULL; + afi_t afi = AFI_IP6; + safi_t safi = SAFI_UNICAST; + struct peer *peer; + enum bgp_show_type sh_type = bgp_show_type_neighbor; + int idx = 0; + bool uj = use_json(argc, argv); + + if (uj) + argc--; + + bgp_vty_find_and_parse_afi_safi_bgp(vty, argv, argc, &idx, &afi, &safi, + &bgp, uj); + if (!idx) + return CMD_WARNING; + + /* neighbors <A.B.C.D|X:X::X:X|WORD> */ + argv_find(argv, argc, "neighbors", &idx); + peerstr = argv[++idx]->arg; + + peer = peer_lookup_in_view(vty, bgp, peerstr, uj); + if (!peer) + return CMD_WARNING; + + if (argv_find(argv, argc, "flap-statistics", &idx)) + sh_type = bgp_show_type_flap_neighbor; + else if (argv_find(argv, argc, "dampened-routes", &idx)) + sh_type = bgp_show_type_damp_neighbor; + else if (argv_find(argv, argc, "routes", &idx)) + sh_type = bgp_show_type_neighbor; + + return bgp_show_neighbor_route(vty, peer, afi, safi, sh_type, uj); +} + +struct bgp_table *bgp_distance_table[AFI_MAX][SAFI_MAX]; + +struct bgp_distance { + /* Distance value for the IP source prefix. */ + uint8_t distance; + + /* Name of the access-list to be matched. */ + char *access_list; +}; + +DEFUN (show_bgp_afi_vpn_rd_route, + show_bgp_afi_vpn_rd_route_cmd, + "show bgp "BGP_AFI_CMD_STR" vpn rd <ASN:NN_OR_IP-ADDRESS:NN|all> <A.B.C.D/M|X:X::X:X/M> [json]", + SHOW_STR + BGP_STR + BGP_AFI_HELP_STR + BGP_AF_MODIFIER_STR + "Display information for a route distinguisher\n" + "Route Distinguisher\n" + "All Route Distinguishers\n" + "Network in the BGP routing table to display\n" + "Network in the BGP routing table to display\n" + JSON_STR) +{ + int ret; + struct prefix_rd prd; + afi_t afi = AFI_MAX; + int idx = 0; + + if (!argv_find_and_parse_afi(argv, argc, &idx, &afi)) { + vty_out(vty, "%% Malformed Address Family\n"); + return CMD_WARNING; + } + + if (!strcmp(argv[5]->arg, "all")) + return bgp_show_route(vty, NULL, argv[6]->arg, afi, + SAFI_MPLS_VPN, NULL, 0, BGP_PATH_SHOW_ALL, + RPKI_NOT_BEING_USED, + use_json(argc, argv)); + + ret = str2prefix_rd(argv[5]->arg, &prd); + if (!ret) { + vty_out(vty, "%% Malformed Route Distinguisher\n"); + return CMD_WARNING; + } + + return bgp_show_route(vty, NULL, argv[6]->arg, afi, SAFI_MPLS_VPN, &prd, + 0, BGP_PATH_SHOW_ALL, RPKI_NOT_BEING_USED, + use_json(argc, argv)); +} + +static struct bgp_distance *bgp_distance_new(void) +{ + return XCALLOC(MTYPE_BGP_DISTANCE, sizeof(struct bgp_distance)); +} + +static void bgp_distance_free(struct bgp_distance *bdistance) +{ + XFREE(MTYPE_BGP_DISTANCE, bdistance); +} + +static int bgp_distance_set(struct vty *vty, const char *distance_str, + const char *ip_str, const char *access_list_str) +{ + int ret; + afi_t afi; + safi_t safi; + struct prefix p; + uint8_t distance; + struct bgp_dest *dest; + struct bgp_distance *bdistance; + + afi = bgp_node_afi(vty); + safi = bgp_node_safi(vty); + + ret = str2prefix(ip_str, &p); + if (ret == 0) { + vty_out(vty, "Malformed prefix\n"); + return CMD_WARNING_CONFIG_FAILED; + } + + distance = atoi(distance_str); + + /* Get BGP distance node. */ + dest = bgp_node_get(bgp_distance_table[afi][safi], &p); + bdistance = bgp_dest_get_bgp_distance_info(dest); + if (bdistance) + bgp_dest_unlock_node(dest); + else { + bdistance = bgp_distance_new(); + bgp_dest_set_bgp_distance_info(dest, bdistance); + } + + /* Set distance value. */ + bdistance->distance = distance; + + /* Reset access-list configuration. */ + XFREE(MTYPE_AS_LIST, bdistance->access_list); + if (access_list_str) + bdistance->access_list = + XSTRDUP(MTYPE_AS_LIST, access_list_str); + + return CMD_SUCCESS; +} + +static int bgp_distance_unset(struct vty *vty, const char *distance_str, + const char *ip_str, const char *access_list_str) +{ + int ret; + afi_t afi; + safi_t safi; + struct prefix p; + int distance; + struct bgp_dest *dest; + struct bgp_distance *bdistance; + + afi = bgp_node_afi(vty); + safi = bgp_node_safi(vty); + + ret = str2prefix(ip_str, &p); + if (ret == 0) { + vty_out(vty, "Malformed prefix\n"); + return CMD_WARNING_CONFIG_FAILED; + } + + dest = bgp_node_lookup(bgp_distance_table[afi][safi], &p); + if (!dest) { + vty_out(vty, "Can't find specified prefix\n"); + return CMD_WARNING_CONFIG_FAILED; + } + + bdistance = bgp_dest_get_bgp_distance_info(dest); + distance = atoi(distance_str); + + if (bdistance->distance != distance) { + vty_out(vty, "Distance does not match configured\n"); + bgp_dest_unlock_node(dest); + return CMD_WARNING_CONFIG_FAILED; + } + + XFREE(MTYPE_AS_LIST, bdistance->access_list); + bgp_distance_free(bdistance); + + bgp_dest_set_bgp_path_info(dest, NULL); + dest = bgp_dest_unlock_node(dest); + assert(dest); + bgp_dest_unlock_node(dest); + + return CMD_SUCCESS; +} + +/* Apply BGP information to distance method. */ +uint8_t bgp_distance_apply(const struct prefix *p, struct bgp_path_info *pinfo, + afi_t afi, safi_t safi, struct bgp *bgp) +{ + struct bgp_dest *dest; + struct prefix q = {0}; + struct peer *peer; + struct bgp_distance *bdistance; + struct access_list *alist; + struct bgp_static *bgp_static; + struct bgp_path_info *bpi_ultimate; + + if (!bgp) + return 0; + + peer = pinfo->peer; + + if (pinfo->attr->distance) + return pinfo->attr->distance; + + /* get peer origin to calculate appropriate distance */ + if (pinfo->sub_type == BGP_ROUTE_IMPORTED) { + bpi_ultimate = bgp_get_imported_bpi_ultimate(pinfo); + peer = bpi_ultimate->peer; + } + + /* Check source address. + * Note: for aggregate route, peer can have unspec af type. + */ + if (pinfo->sub_type != BGP_ROUTE_AGGREGATE && + !sockunion2hostprefix(&peer->connection->su, &q)) + return 0; + + dest = bgp_node_match(bgp_distance_table[afi][safi], &q); + if (dest) { + bdistance = bgp_dest_get_bgp_distance_info(dest); + bgp_dest_unlock_node(dest); + + if (bdistance->access_list) { + alist = access_list_lookup(afi, bdistance->access_list); + if (alist + && access_list_apply(alist, p) == FILTER_PERMIT) + return bdistance->distance; + } else + return bdistance->distance; + } + + /* Backdoor check. */ + dest = bgp_node_lookup(bgp->route[afi][safi], p); + if (dest) { + bgp_static = bgp_dest_get_bgp_static_info(dest); + bgp_dest_unlock_node(dest); + + if (bgp_static->backdoor) { + if (bgp->distance_local[afi][safi]) + return bgp->distance_local[afi][safi]; + else + return ZEBRA_IBGP_DISTANCE_DEFAULT; + } + } + + if (peer->sort == BGP_PEER_EBGP) { + if (bgp->distance_ebgp[afi][safi]) + return bgp->distance_ebgp[afi][safi]; + return ZEBRA_EBGP_DISTANCE_DEFAULT; + } else if (peer->sort == BGP_PEER_IBGP) { + if (bgp->distance_ibgp[afi][safi]) + return bgp->distance_ibgp[afi][safi]; + return ZEBRA_IBGP_DISTANCE_DEFAULT; + } else { + if (bgp->distance_local[afi][safi]) + return bgp->distance_local[afi][safi]; + return ZEBRA_IBGP_DISTANCE_DEFAULT; + } +} + +/* If we enter `distance bgp (1-255) (1-255) (1-255)`, + * we should tell ZEBRA update the routes for a specific + * AFI/SAFI to reflect changes in RIB. + */ +static void bgp_announce_routes_distance_update(struct bgp *bgp, + afi_t update_afi, + safi_t update_safi) +{ + afi_t afi; + safi_t safi; + + FOREACH_AFI_SAFI (afi, safi) { + if (!bgp_fibupd_safi(safi)) + continue; + + if (afi != update_afi && safi != update_safi) + continue; + + if (BGP_DEBUG(zebra, ZEBRA)) + zlog_debug( + "%s: Announcing routes due to distance change afi/safi (%d/%d)", + __func__, afi, safi); + bgp_zebra_announce_table(bgp, afi, safi); + } +} + +DEFUN (bgp_distance, + bgp_distance_cmd, + "distance bgp (1-255) (1-255) (1-255)", + "Define an administrative distance\n" + "BGP distance\n" + "Distance for routes external to the AS\n" + "Distance for routes internal to the AS\n" + "Distance for local routes\n") +{ + VTY_DECLVAR_CONTEXT(bgp, bgp); + int idx_number = 2; + int idx_number_2 = 3; + int idx_number_3 = 4; + int distance_ebgp = atoi(argv[idx_number]->arg); + int distance_ibgp = atoi(argv[idx_number_2]->arg); + int distance_local = atoi(argv[idx_number_3]->arg); + afi_t afi; + safi_t safi; + + afi = bgp_node_afi(vty); + safi = bgp_node_safi(vty); + + if (bgp->distance_ebgp[afi][safi] != distance_ebgp + || bgp->distance_ibgp[afi][safi] != distance_ibgp + || bgp->distance_local[afi][safi] != distance_local) { + bgp->distance_ebgp[afi][safi] = distance_ebgp; + bgp->distance_ibgp[afi][safi] = distance_ibgp; + bgp->distance_local[afi][safi] = distance_local; + bgp_announce_routes_distance_update(bgp, afi, safi); + } + return CMD_SUCCESS; +} + +DEFUN (no_bgp_distance, + no_bgp_distance_cmd, + "no distance bgp [(1-255) (1-255) (1-255)]", + NO_STR + "Define an administrative distance\n" + "BGP distance\n" + "Distance for routes external to the AS\n" + "Distance for routes internal to the AS\n" + "Distance for local routes\n") +{ + VTY_DECLVAR_CONTEXT(bgp, bgp); + afi_t afi; + safi_t safi; + + afi = bgp_node_afi(vty); + safi = bgp_node_safi(vty); + + if (bgp->distance_ebgp[afi][safi] != 0 + || bgp->distance_ibgp[afi][safi] != 0 + || bgp->distance_local[afi][safi] != 0) { + bgp->distance_ebgp[afi][safi] = 0; + bgp->distance_ibgp[afi][safi] = 0; + bgp->distance_local[afi][safi] = 0; + bgp_announce_routes_distance_update(bgp, afi, safi); + } + return CMD_SUCCESS; +} + + +DEFUN (bgp_distance_source, + bgp_distance_source_cmd, + "distance (1-255) A.B.C.D/M", + "Define an administrative distance\n" + "Administrative distance\n" + "IP source prefix\n") +{ + int idx_number = 1; + int idx_ipv4_prefixlen = 2; + bgp_distance_set(vty, argv[idx_number]->arg, + argv[idx_ipv4_prefixlen]->arg, NULL); + return CMD_SUCCESS; +} + +DEFUN (no_bgp_distance_source, + no_bgp_distance_source_cmd, + "no distance (1-255) A.B.C.D/M", + NO_STR + "Define an administrative distance\n" + "Administrative distance\n" + "IP source prefix\n") +{ + int idx_number = 2; + int idx_ipv4_prefixlen = 3; + bgp_distance_unset(vty, argv[idx_number]->arg, + argv[idx_ipv4_prefixlen]->arg, NULL); + return CMD_SUCCESS; +} + +DEFUN (bgp_distance_source_access_list, + bgp_distance_source_access_list_cmd, + "distance (1-255) A.B.C.D/M WORD", + "Define an administrative distance\n" + "Administrative distance\n" + "IP source prefix\n" + "Access list name\n") +{ + int idx_number = 1; + int idx_ipv4_prefixlen = 2; + int idx_word = 3; + bgp_distance_set(vty, argv[idx_number]->arg, + argv[idx_ipv4_prefixlen]->arg, argv[idx_word]->arg); + return CMD_SUCCESS; +} + +DEFUN (no_bgp_distance_source_access_list, + no_bgp_distance_source_access_list_cmd, + "no distance (1-255) A.B.C.D/M WORD", + NO_STR + "Define an administrative distance\n" + "Administrative distance\n" + "IP source prefix\n" + "Access list name\n") +{ + int idx_number = 2; + int idx_ipv4_prefixlen = 3; + int idx_word = 4; + bgp_distance_unset(vty, argv[idx_number]->arg, + argv[idx_ipv4_prefixlen]->arg, argv[idx_word]->arg); + return CMD_SUCCESS; +} + +DEFUN (ipv6_bgp_distance_source, + ipv6_bgp_distance_source_cmd, + "distance (1-255) X:X::X:X/M", + "Define an administrative distance\n" + "Administrative distance\n" + "IP source prefix\n") +{ + bgp_distance_set(vty, argv[1]->arg, argv[2]->arg, NULL); + return CMD_SUCCESS; +} + +DEFUN (no_ipv6_bgp_distance_source, + no_ipv6_bgp_distance_source_cmd, + "no distance (1-255) X:X::X:X/M", + NO_STR + "Define an administrative distance\n" + "Administrative distance\n" + "IP source prefix\n") +{ + bgp_distance_unset(vty, argv[2]->arg, argv[3]->arg, NULL); + return CMD_SUCCESS; +} + +DEFUN (ipv6_bgp_distance_source_access_list, + ipv6_bgp_distance_source_access_list_cmd, + "distance (1-255) X:X::X:X/M WORD", + "Define an administrative distance\n" + "Administrative distance\n" + "IP source prefix\n" + "Access list name\n") +{ + bgp_distance_set(vty, argv[1]->arg, argv[2]->arg, argv[3]->arg); + return CMD_SUCCESS; +} + +DEFUN (no_ipv6_bgp_distance_source_access_list, + no_ipv6_bgp_distance_source_access_list_cmd, + "no distance (1-255) X:X::X:X/M WORD", + NO_STR + "Define an administrative distance\n" + "Administrative distance\n" + "IP source prefix\n" + "Access list name\n") +{ + bgp_distance_unset(vty, argv[2]->arg, argv[3]->arg, argv[4]->arg); + return CMD_SUCCESS; +} + +DEFUN (bgp_damp_set, + bgp_damp_set_cmd, + "bgp dampening [(1-45) [(1-20000) (1-50000) (1-255)]]", + "BGP Specific commands\n" + "Enable route-flap dampening\n" + "Half-life time for the penalty\n" + "Value to start reusing a route\n" + "Value to start suppressing a route\n" + "Maximum duration to suppress a stable route\n") +{ + VTY_DECLVAR_CONTEXT(bgp, bgp); + int idx_half_life = 2; + int idx_reuse = 3; + int idx_suppress = 4; + int idx_max_suppress = 5; + int half = DEFAULT_HALF_LIFE * 60; + int reuse = DEFAULT_REUSE; + int suppress = DEFAULT_SUPPRESS; + int max = 4 * half; + + if (argc == 6) { + half = atoi(argv[idx_half_life]->arg) * 60; + reuse = atoi(argv[idx_reuse]->arg); + suppress = atoi(argv[idx_suppress]->arg); + max = atoi(argv[idx_max_suppress]->arg) * 60; + } else if (argc == 3) { + half = atoi(argv[idx_half_life]->arg) * 60; + max = 4 * half; + } + + /* + * These can't be 0 but our SA doesn't understand the + * way our cli is constructed + */ + assert(reuse); + assert(half); + if (suppress < reuse) { + vty_out(vty, + "Suppress value cannot be less than reuse value \n"); + return 0; + } + + return bgp_damp_enable(bgp, bgp_node_afi(vty), bgp_node_safi(vty), half, + reuse, suppress, max); +} + +DEFUN (bgp_damp_unset, + bgp_damp_unset_cmd, + "no bgp dampening [(1-45) [(1-20000) (1-50000) (1-255)]]", + NO_STR + "BGP Specific commands\n" + "Enable route-flap dampening\n" + "Half-life time for the penalty\n" + "Value to start reusing a route\n" + "Value to start suppressing a route\n" + "Maximum duration to suppress a stable route\n") +{ + VTY_DECLVAR_CONTEXT(bgp, bgp); + return bgp_damp_disable(bgp, bgp_node_afi(vty), bgp_node_safi(vty)); +} + +/* Display specified route of BGP table. */ +static int bgp_clear_damp_route(struct vty *vty, const char *view_name, + const char *ip_str, afi_t afi, safi_t safi, + struct prefix_rd *prd, int prefix_check) +{ + int ret; + struct prefix match; + struct bgp_dest *dest; + struct bgp_dest *rm; + struct bgp_path_info *pi; + struct bgp_path_info *pi_temp; + struct bgp *bgp; + struct bgp_table *table; + + /* BGP structure lookup. */ + if (view_name) { + bgp = bgp_lookup_by_name(view_name); + if (bgp == NULL) { + vty_out(vty, "%% Can't find BGP instance %s\n", + view_name); + return CMD_WARNING; + } + } else { + bgp = bgp_get_default(); + if (bgp == NULL) { + vty_out(vty, "%% No BGP process is configured\n"); + return CMD_WARNING; + } + } + + /* Check IP address argument. */ + ret = str2prefix(ip_str, &match); + if (!ret) { + vty_out(vty, "%% address is malformed\n"); + return CMD_WARNING; + } + + match.family = afi2family(afi); + + if ((safi == SAFI_MPLS_VPN) || (safi == SAFI_ENCAP) + || (safi == SAFI_EVPN)) { + for (dest = bgp_table_top(bgp->rib[AFI_IP][safi]); dest; + dest = bgp_route_next(dest)) { + const struct prefix *dest_p = bgp_dest_get_prefix(dest); + + if (prd && memcmp(dest_p->u.val, prd->val, 8) != 0) + continue; + table = bgp_dest_get_bgp_table_info(dest); + if (!table) + continue; + rm = bgp_node_match(table, &match); + if (rm == NULL) + continue; + + const struct prefix *rm_p = bgp_dest_get_prefix(dest); + + if (!prefix_check + || rm_p->prefixlen == match.prefixlen) { + pi = bgp_dest_get_bgp_path_info(rm); + while (pi) { + if (pi->extra && pi->extra->damp_info) { + pi_temp = pi->next; + bgp_damp_info_free( + pi->extra->damp_info, + 1, afi, safi); + pi = pi_temp; + } else + pi = pi->next; + } + } + + bgp_dest_unlock_node(rm); + } + } else { + dest = bgp_node_match(bgp->rib[afi][safi], &match); + if (dest != NULL) { + const struct prefix *dest_p = bgp_dest_get_prefix(dest); + + if (!prefix_check + || dest_p->prefixlen == match.prefixlen) { + pi = bgp_dest_get_bgp_path_info(dest); + while (pi) { + if (pi->extra && pi->extra->damp_info) { + pi_temp = pi->next; + bgp_damp_info_free( + pi->extra->damp_info, + 1, afi, safi); + pi = pi_temp; + } else + pi = pi->next; + } + } + + bgp_dest_unlock_node(dest); + } + } + + return CMD_SUCCESS; +} + +DEFUN (clear_ip_bgp_dampening, + clear_ip_bgp_dampening_cmd, + "clear ip bgp dampening", + CLEAR_STR + IP_STR + BGP_STR + "Clear route flap dampening information\n") +{ + bgp_damp_info_clean(AFI_IP, SAFI_UNICAST); + return CMD_SUCCESS; +} + +DEFUN (clear_ip_bgp_dampening_prefix, + clear_ip_bgp_dampening_prefix_cmd, + "clear ip bgp dampening A.B.C.D/M", + CLEAR_STR + IP_STR + BGP_STR + "Clear route flap dampening information\n" + "IPv4 prefix\n") +{ + int idx_ipv4_prefixlen = 4; + return bgp_clear_damp_route(vty, NULL, argv[idx_ipv4_prefixlen]->arg, + AFI_IP, SAFI_UNICAST, NULL, 1); +} + +DEFUN (clear_ip_bgp_dampening_address, + clear_ip_bgp_dampening_address_cmd, + "clear ip bgp dampening A.B.C.D", + CLEAR_STR + IP_STR + BGP_STR + "Clear route flap dampening information\n" + "Network to clear damping information\n") +{ + int idx_ipv4 = 4; + return bgp_clear_damp_route(vty, NULL, argv[idx_ipv4]->arg, AFI_IP, + SAFI_UNICAST, NULL, 0); +} + +DEFUN (clear_ip_bgp_dampening_address_mask, + clear_ip_bgp_dampening_address_mask_cmd, + "clear ip bgp dampening A.B.C.D A.B.C.D", + CLEAR_STR + IP_STR + BGP_STR + "Clear route flap dampening information\n" + "Network to clear damping information\n" + "Network mask\n") +{ + int idx_ipv4 = 4; + int idx_ipv4_2 = 5; + int ret; + char prefix_str[BUFSIZ]; + + ret = netmask_str2prefix_str(argv[idx_ipv4]->arg, argv[idx_ipv4_2]->arg, + prefix_str, sizeof(prefix_str)); + if (!ret) { + vty_out(vty, "%% Inconsistent address and mask\n"); + return CMD_WARNING; + } + + return bgp_clear_damp_route(vty, NULL, prefix_str, AFI_IP, SAFI_UNICAST, + NULL, 0); +} + +static void show_bgp_peerhash_entry(struct hash_bucket *bucket, void *arg) +{ + struct vty *vty = arg; + struct peer *peer = bucket->data; + + vty_out(vty, "\tPeer: %s %pSU\n", peer->host, &peer->connection->su); +} + +DEFUN (show_bgp_listeners, + show_bgp_listeners_cmd, + "show bgp listeners", + SHOW_STR + BGP_STR + "Display Listen Sockets and who created them\n") +{ + bgp_dump_listener_info(vty); + + return CMD_SUCCESS; +} + +DEFUN (show_bgp_peerhash, + show_bgp_peerhash_cmd, + "show bgp peerhash", + SHOW_STR + BGP_STR + "Display information about the BGP peerhash\n") +{ + struct list *instances = bm->bgp; + struct listnode *node; + struct bgp *bgp; + + for (ALL_LIST_ELEMENTS_RO(instances, node, bgp)) { + vty_out(vty, "BGP: %s\n", bgp->name_pretty); + hash_iterate(bgp->peerhash, show_bgp_peerhash_entry, + vty); + } + + return CMD_SUCCESS; +} + +/* also used for encap safi */ +static void bgp_config_write_network_vpn(struct vty *vty, struct bgp *bgp, + afi_t afi, safi_t safi) +{ + struct bgp_dest *pdest; + struct bgp_dest *dest; + struct bgp_table *table; + const struct prefix *p; + struct bgp_static *bgp_static; + mpls_label_t label; + + /* Network configuration. */ + for (pdest = bgp_table_top(bgp->route[afi][safi]); pdest; + pdest = bgp_route_next(pdest)) { + table = bgp_dest_get_bgp_table_info(pdest); + if (!table) + continue; + + for (dest = bgp_table_top(table); dest; + dest = bgp_route_next(dest)) { + bgp_static = bgp_dest_get_bgp_static_info(dest); + if (bgp_static == NULL) + continue; + + p = bgp_dest_get_prefix(dest); + + /* "network" configuration display. */ + label = decode_label(&bgp_static->label); + + vty_out(vty, " network %pFX rd %s", p, + bgp_static->prd_pretty); + if (safi == SAFI_MPLS_VPN) + vty_out(vty, " label %u", label); + + if (bgp_static->rmap.name) + vty_out(vty, " route-map %s", + bgp_static->rmap.name); + + if (bgp_static->backdoor) + vty_out(vty, " backdoor"); + + vty_out(vty, "\n"); + } + } +} + +static void bgp_config_write_network_evpn(struct vty *vty, struct bgp *bgp, + afi_t afi, safi_t safi) +{ + struct bgp_dest *pdest; + struct bgp_dest *dest; + struct bgp_table *table; + const struct prefix *p; + struct bgp_static *bgp_static; + char buf[PREFIX_STRLEN * 2]; + char buf2[SU_ADDRSTRLEN]; + char esi_buf[ESI_STR_LEN]; + + /* Network configuration. */ + for (pdest = bgp_table_top(bgp->route[afi][safi]); pdest; + pdest = bgp_route_next(pdest)) { + table = bgp_dest_get_bgp_table_info(pdest); + if (!table) + continue; + + for (dest = bgp_table_top(table); dest; + dest = bgp_route_next(dest)) { + bgp_static = bgp_dest_get_bgp_static_info(dest); + if (bgp_static == NULL) + continue; + + char *macrouter = NULL; + + if (bgp_static->router_mac) + macrouter = prefix_mac2str( + bgp_static->router_mac, NULL, 0); + if (bgp_static->eth_s_id) + esi_to_str(bgp_static->eth_s_id, + esi_buf, sizeof(esi_buf)); + p = bgp_dest_get_prefix(dest); + + /* "network" configuration display. */ + if (p->u.prefix_evpn.route_type == 5) { + char local_buf[PREFIX_STRLEN]; + + uint8_t family = is_evpn_prefix_ipaddr_v4(( + struct prefix_evpn *)p) + ? AF_INET + : AF_INET6; + inet_ntop(family, + &p->u.prefix_evpn.prefix_addr.ip.ip + .addr, + local_buf, sizeof(local_buf)); + snprintf(buf, sizeof(buf), "%s/%u", local_buf, + p->u.prefix_evpn.prefix_addr + .ip_prefix_length); + } else { + prefix2str(p, buf, sizeof(buf)); + } + + if (bgp_static->gatewayIp.family == AF_INET + || bgp_static->gatewayIp.family == AF_INET6) + inet_ntop(bgp_static->gatewayIp.family, + &bgp_static->gatewayIp.u.prefix, buf2, + sizeof(buf2)); + vty_out(vty, + " network %s rd %s ethtag %u label %u esi %s gwip %s routermac %s\n", + buf, bgp_static->prd_pretty, + p->u.prefix_evpn.prefix_addr.eth_tag, + decode_label(&bgp_static->label), esi_buf, buf2, + macrouter); + + XFREE(MTYPE_TMP, macrouter); + } + } +} + +/* Configuration of static route announcement and aggregate + information. */ +void bgp_config_write_network(struct vty *vty, struct bgp *bgp, afi_t afi, + safi_t safi) +{ + struct bgp_dest *dest; + const struct prefix *p; + struct bgp_static *bgp_static; + struct bgp_aggregate *bgp_aggregate; + + if ((safi == SAFI_MPLS_VPN) || (safi == SAFI_ENCAP)) { + bgp_config_write_network_vpn(vty, bgp, afi, safi); + return; + } + + if (afi == AFI_L2VPN && safi == SAFI_EVPN) { + bgp_config_write_network_evpn(vty, bgp, afi, safi); + return; + } + + /* Network configuration. */ + for (dest = bgp_table_top(bgp->route[afi][safi]); dest; + dest = bgp_route_next(dest)) { + bgp_static = bgp_dest_get_bgp_static_info(dest); + if (bgp_static == NULL) + continue; + + p = bgp_dest_get_prefix(dest); + + vty_out(vty, " network %pFX", p); + + if (bgp_static->label_index != BGP_INVALID_LABEL_INDEX) + vty_out(vty, " label-index %u", + bgp_static->label_index); + + if (bgp_static->rmap.name) + vty_out(vty, " route-map %s", bgp_static->rmap.name); + + if (bgp_static->backdoor) + vty_out(vty, " backdoor"); + + vty_out(vty, "\n"); + } + + /* Aggregate-address configuration. */ + for (dest = bgp_table_top(bgp->aggregate[afi][safi]); dest; + dest = bgp_route_next(dest)) { + bgp_aggregate = bgp_dest_get_bgp_aggregate_info(dest); + if (bgp_aggregate == NULL) + continue; + + p = bgp_dest_get_prefix(dest); + + vty_out(vty, " aggregate-address %pFX", p); + + if (bgp_aggregate->as_set) + vty_out(vty, " as-set"); + + if (bgp_aggregate->summary_only) + vty_out(vty, " summary-only"); + + if (bgp_aggregate->rmap.name) + vty_out(vty, " route-map %s", bgp_aggregate->rmap.name); + + if (bgp_aggregate->origin != BGP_ORIGIN_UNSPECIFIED) + vty_out(vty, " origin %s", + bgp_origin2str(bgp_aggregate->origin)); + + if (bgp_aggregate->match_med) + vty_out(vty, " matching-MED-only"); + + if (bgp_aggregate->suppress_map_name) + vty_out(vty, " suppress-map %s", + bgp_aggregate->suppress_map_name); + + vty_out(vty, "\n"); + } +} + +void bgp_config_write_distance(struct vty *vty, struct bgp *bgp, afi_t afi, + safi_t safi) +{ + struct bgp_dest *dest; + struct bgp_distance *bdistance; + + /* Distance configuration. */ + if (bgp->distance_ebgp[afi][safi] && bgp->distance_ibgp[afi][safi] + && bgp->distance_local[afi][safi] + && (bgp->distance_ebgp[afi][safi] != ZEBRA_EBGP_DISTANCE_DEFAULT + || bgp->distance_ibgp[afi][safi] != ZEBRA_IBGP_DISTANCE_DEFAULT + || bgp->distance_local[afi][safi] + != ZEBRA_IBGP_DISTANCE_DEFAULT)) { + vty_out(vty, " distance bgp %d %d %d\n", + bgp->distance_ebgp[afi][safi], + bgp->distance_ibgp[afi][safi], + bgp->distance_local[afi][safi]); + } + + for (dest = bgp_table_top(bgp_distance_table[afi][safi]); dest; + dest = bgp_route_next(dest)) { + bdistance = bgp_dest_get_bgp_distance_info(dest); + if (bdistance != NULL) + vty_out(vty, " distance %d %pBD %s\n", + bdistance->distance, dest, + bdistance->access_list ? bdistance->access_list + : ""); + } +} + +/* Allocate routing table structure and install commands. */ +void bgp_route_init(void) +{ + afi_t afi; + safi_t safi; + + /* Init BGP distance table. */ + FOREACH_AFI_SAFI (afi, safi) + bgp_distance_table[afi][safi] = bgp_table_init(NULL, afi, safi); + + /* IPv4 BGP commands. */ + install_element(BGP_NODE, &bgp_table_map_cmd); + install_element(BGP_NODE, &bgp_network_cmd); + install_element(BGP_NODE, &no_bgp_table_map_cmd); + + install_element(BGP_NODE, &aggregate_addressv4_cmd); + + /* IPv4 unicast configuration. */ + install_element(BGP_IPV4_NODE, &bgp_table_map_cmd); + install_element(BGP_IPV4_NODE, &bgp_network_cmd); + install_element(BGP_IPV4_NODE, &no_bgp_table_map_cmd); + + install_element(BGP_IPV4_NODE, &aggregate_addressv4_cmd); + + /* IPv4 multicast configuration. */ + install_element(BGP_IPV4M_NODE, &bgp_table_map_cmd); + install_element(BGP_IPV4M_NODE, &bgp_network_cmd); + install_element(BGP_IPV4M_NODE, &no_bgp_table_map_cmd); + install_element(BGP_IPV4M_NODE, &aggregate_addressv4_cmd); + + /* IPv4 labeled-unicast configuration. */ + install_element(BGP_IPV4L_NODE, &bgp_network_cmd); + install_element(BGP_IPV4L_NODE, &aggregate_addressv4_cmd); + + install_element(VIEW_NODE, &show_ip_bgp_instance_all_cmd); + install_element(VIEW_NODE, &show_ip_bgp_afi_safi_statistics_cmd); + install_element(VIEW_NODE, &show_ip_bgp_l2vpn_evpn_statistics_cmd); + install_element(VIEW_NODE, &show_ip_bgp_dampening_params_cmd); + install_element(VIEW_NODE, &show_ip_bgp_cmd); + install_element(VIEW_NODE, &show_ip_bgp_route_cmd); + install_element(VIEW_NODE, &show_ip_bgp_regexp_cmd); + install_element(VIEW_NODE, &show_ip_bgp_statistics_all_cmd); + + install_element(VIEW_NODE, + &show_ip_bgp_instance_neighbor_advertised_route_cmd); + install_element(VIEW_NODE, + &show_ip_bgp_instance_neighbor_bestpath_route_cmd); + install_element(VIEW_NODE, &show_ip_bgp_neighbor_routes_cmd); + install_element(VIEW_NODE, + &show_ip_bgp_neighbor_received_prefix_filter_cmd); +#ifdef KEEP_OLD_VPN_COMMANDS + install_element(VIEW_NODE, &show_ip_bgp_vpn_all_route_prefix_cmd); +#endif /* KEEP_OLD_VPN_COMMANDS */ + install_element(VIEW_NODE, &show_bgp_afi_vpn_rd_route_cmd); + install_element(VIEW_NODE, + &show_bgp_l2vpn_evpn_route_prefix_cmd); + + /* BGP dampening clear commands */ + install_element(ENABLE_NODE, &clear_ip_bgp_dampening_cmd); + install_element(ENABLE_NODE, &clear_ip_bgp_dampening_prefix_cmd); + + install_element(ENABLE_NODE, &clear_ip_bgp_dampening_address_cmd); + install_element(ENABLE_NODE, &clear_ip_bgp_dampening_address_mask_cmd); + + /* prefix count */ + install_element(ENABLE_NODE, + &show_ip_bgp_instance_neighbor_prefix_counts_cmd); +#ifdef KEEP_OLD_VPN_COMMANDS + install_element(ENABLE_NODE, + &show_ip_bgp_vpn_neighbor_prefix_counts_cmd); +#endif /* KEEP_OLD_VPN_COMMANDS */ + + /* New config IPv6 BGP commands. */ + install_element(BGP_IPV6_NODE, &bgp_table_map_cmd); + install_element(BGP_IPV6_NODE, &ipv6_bgp_network_cmd); + install_element(BGP_IPV6_NODE, &no_bgp_table_map_cmd); + + install_element(BGP_IPV6_NODE, &aggregate_addressv6_cmd); + + install_element(BGP_IPV6M_NODE, &ipv6_bgp_network_cmd); + + /* IPv6 labeled unicast address family. */ + install_element(BGP_IPV6L_NODE, &ipv6_bgp_network_cmd); + install_element(BGP_IPV6L_NODE, &aggregate_addressv6_cmd); + + install_element(BGP_NODE, &bgp_distance_cmd); + install_element(BGP_NODE, &no_bgp_distance_cmd); + install_element(BGP_NODE, &bgp_distance_source_cmd); + install_element(BGP_NODE, &no_bgp_distance_source_cmd); + install_element(BGP_NODE, &bgp_distance_source_access_list_cmd); + install_element(BGP_NODE, &no_bgp_distance_source_access_list_cmd); + install_element(BGP_IPV4_NODE, &bgp_distance_cmd); + install_element(BGP_IPV4_NODE, &no_bgp_distance_cmd); + install_element(BGP_IPV4_NODE, &bgp_distance_source_cmd); + install_element(BGP_IPV4_NODE, &no_bgp_distance_source_cmd); + install_element(BGP_IPV4_NODE, &bgp_distance_source_access_list_cmd); + install_element(BGP_IPV4_NODE, &no_bgp_distance_source_access_list_cmd); + install_element(BGP_IPV4M_NODE, &bgp_distance_cmd); + install_element(BGP_IPV4M_NODE, &no_bgp_distance_cmd); + install_element(BGP_IPV4M_NODE, &bgp_distance_source_cmd); + install_element(BGP_IPV4M_NODE, &no_bgp_distance_source_cmd); + install_element(BGP_IPV4M_NODE, &bgp_distance_source_access_list_cmd); + install_element(BGP_IPV4M_NODE, + &no_bgp_distance_source_access_list_cmd); + install_element(BGP_IPV6_NODE, &bgp_distance_cmd); + install_element(BGP_IPV6_NODE, &no_bgp_distance_cmd); + install_element(BGP_IPV6_NODE, &ipv6_bgp_distance_source_cmd); + install_element(BGP_IPV6_NODE, &no_ipv6_bgp_distance_source_cmd); + install_element(BGP_IPV6_NODE, + &ipv6_bgp_distance_source_access_list_cmd); + install_element(BGP_IPV6_NODE, + &no_ipv6_bgp_distance_source_access_list_cmd); + install_element(BGP_IPV6M_NODE, &bgp_distance_cmd); + install_element(BGP_IPV6M_NODE, &no_bgp_distance_cmd); + install_element(BGP_IPV6M_NODE, &ipv6_bgp_distance_source_cmd); + install_element(BGP_IPV6M_NODE, &no_ipv6_bgp_distance_source_cmd); + install_element(BGP_IPV6M_NODE, + &ipv6_bgp_distance_source_access_list_cmd); + install_element(BGP_IPV6M_NODE, + &no_ipv6_bgp_distance_source_access_list_cmd); + + /* BGP dampening */ + install_element(BGP_NODE, &bgp_damp_set_cmd); + install_element(BGP_NODE, &bgp_damp_unset_cmd); + install_element(BGP_IPV4_NODE, &bgp_damp_set_cmd); + install_element(BGP_IPV4_NODE, &bgp_damp_unset_cmd); + install_element(BGP_IPV4M_NODE, &bgp_damp_set_cmd); + install_element(BGP_IPV4M_NODE, &bgp_damp_unset_cmd); + install_element(BGP_IPV4L_NODE, &bgp_damp_set_cmd); + install_element(BGP_IPV4L_NODE, &bgp_damp_unset_cmd); + install_element(BGP_IPV6_NODE, &bgp_damp_set_cmd); + install_element(BGP_IPV6_NODE, &bgp_damp_unset_cmd); + install_element(BGP_IPV6M_NODE, &bgp_damp_set_cmd); + install_element(BGP_IPV6M_NODE, &bgp_damp_unset_cmd); + install_element(BGP_IPV6L_NODE, &bgp_damp_set_cmd); + install_element(BGP_IPV6L_NODE, &bgp_damp_unset_cmd); + + /* Large Communities */ + install_element(VIEW_NODE, &show_ip_bgp_large_community_list_cmd); + install_element(VIEW_NODE, &show_ip_bgp_large_community_cmd); + + /* show bgp vrf <afi> <safi> detailed */ + install_element(VIEW_NODE, + &show_ip_bgp_vrf_afi_safi_routes_detailed_cmd); + + install_element(VIEW_NODE, &show_bgp_listeners_cmd); + install_element(VIEW_NODE, &show_bgp_peerhash_cmd); +} + +void bgp_route_finish(void) +{ + afi_t afi; + safi_t safi; + + FOREACH_AFI_SAFI (afi, safi) { + bgp_table_unlock(bgp_distance_table[afi][safi]); + bgp_distance_table[afi][safi] = NULL; + } +} |