diff --git a/bgpd/bgp_addpath.h b/bgpd/bgp_addpath.h index d562000e3000..b19e63c946ac 100644 --- a/bgpd/bgp_addpath.h +++ b/bgpd/bgp_addpath.h @@ -21,6 +21,12 @@ struct bgp_addpath_capability { uint8_t flags; }; +struct bgp_paths_limit_capability { + uint16_t afi; + uint8_t safi; + uint16_t paths_limit; +}; + #define BGP_ADDPATH_TX_ID_FOR_DEFAULT_ORIGINATE 1 void bgp_addpath_init_bgp_data(struct bgp_addpath_bgp_data *d); diff --git a/bgpd/bgp_fsm.c b/bgpd/bgp_fsm.c index 234dbb0715a5..657c7e22d7e4 100644 --- a/bgpd/bgp_fsm.c +++ b/bgpd/bgp_fsm.c @@ -260,6 +260,8 @@ static struct peer *peer_xfer_conn(struct peer *from_peer) peer->afc_recv[afi][safi] = from_peer->afc_recv[afi][safi]; peer->orf_plist[afi][safi] = from_peer->orf_plist[afi][safi]; peer->llgr[afi][safi] = from_peer->llgr[afi][safi]; + peer->addpath_paths_limit[afi][safi] = + from_peer->addpath_paths_limit[afi][safi]; } if (bgp_getsockname(peer) < 0) { diff --git a/bgpd/bgp_open.c b/bgpd/bgp_open.c index 43a59e24489c..aa1d81362bd0 100644 --- a/bgpd/bgp_open.c +++ b/bgpd/bgp_open.c @@ -42,6 +42,7 @@ const struct message capcode_str[] = { { CAPABILITY_CODE_LLGR, "Long-lived BGP Graceful Restart" }, { CAPABILITY_CODE_ROLE, "Role" }, { CAPABILITY_CODE_SOFT_VERSION, "Software Version" }, + { CAPABILITY_CODE_PATHS_LIMIT, "Paths-Limit" }, { 0 } }; @@ -61,6 +62,7 @@ static const size_t cap_minsizes[] = { [CAPABILITY_CODE_LLGR] = CAPABILITY_CODE_LLGR_LEN, [CAPABILITY_CODE_ROLE] = CAPABILITY_CODE_ROLE_LEN, [CAPABILITY_CODE_SOFT_VERSION] = CAPABILITY_CODE_SOFT_VERSION_LEN, + [CAPABILITY_CODE_PATHS_LIMIT] = CAPABILITY_CODE_PATHS_LIMIT_LEN, }; /* value the capability must be a multiple of. @@ -83,6 +85,7 @@ static const size_t cap_modsizes[] = { [CAPABILITY_CODE_LLGR] = 1, [CAPABILITY_CODE_ROLE] = 1, [CAPABILITY_CODE_SOFT_VERSION] = 1, + [CAPABILITY_CODE_PATHS_LIMIT] = 5, }; /* BGP-4 Multiprotocol Extentions lead us to the complex world. We can @@ -739,6 +742,62 @@ static int bgp_capability_addpath(struct peer *peer, return 0; } +static int bgp_capability_paths_limit(struct peer *peer, + struct capability_header *hdr) +{ + struct stream *s = BGP_INPUT(peer); + size_t end = stream_get_getp(s) + hdr->length; + + if (hdr->length % CAPABILITY_CODE_PATHS_LIMIT_LEN) { + flog_warn(EC_BGP_CAPABILITY_INVALID_LENGTH, + "Paths-Limit: Received invalid length %d, non-multiple of %d", + hdr->length, CAPABILITY_CODE_PATHS_LIMIT_LEN); + return -1; + } + + if (!CHECK_FLAG(peer->cap, PEER_CAP_ADDPATH_RCV)) { + flog_warn(EC_BGP_CAPABILITY_INVALID_DATA, + "Paths-Limit: Received Paths-Limit capability without Add-Path capability"); + return -1; + } + + SET_FLAG(peer->cap, PEER_CAP_PATHS_LIMIT_RCV); + + while (stream_get_getp(s) + CAPABILITY_CODE_PATHS_LIMIT_LEN <= end) { + afi_t afi; + safi_t safi; + iana_afi_t pkt_afi = stream_getw(s); + iana_safi_t pkt_safi = stream_getc(s); + uint16_t paths_limit = stream_getw(s); + + if (bgp_debug_neighbor_events(peer)) + zlog_debug("%s OPEN has %s capability for afi/safi: %s/%s limit: %u", + peer->host, + lookup_msg(capcode_str, hdr->code, NULL), + iana_afi2str(pkt_afi), + iana_safi2str(pkt_safi), paths_limit); + + if (bgp_map_afi_safi_iana2int(pkt_afi, pkt_safi, &afi, &safi)) { + if (bgp_debug_neighbor_events(peer)) + zlog_debug("%s Addr-family %s/%s(afi/safi) not supported. Ignore the Paths-Limit capability for this AFI/SAFI", + peer->host, iana_afi2str(pkt_afi), + iana_safi2str(pkt_safi)); + continue; + } else if (!peer->afc[afi][safi]) { + if (bgp_debug_neighbor_events(peer)) + zlog_debug("%s Addr-family %s/%s(afi/safi) not enabled. Ignore the Paths-Limit capability for this AFI/SAFI", + peer->host, iana_afi2str(pkt_afi), + iana_safi2str(pkt_safi)); + continue; + } + + SET_FLAG(peer->af_cap[afi][safi], PEER_CAP_PATHS_LIMIT_AF_RCV); + peer->addpath_paths_limit[afi][safi].receive = paths_limit; + } + + return 0; +} + static int bgp_capability_enhe(struct peer *peer, struct capability_header *hdr) { struct stream *s = BGP_INPUT(peer); @@ -1012,6 +1071,7 @@ static int bgp_capability_parse(struct peer *peer, size_t length, case CAPABILITY_CODE_EXT_MESSAGE: case CAPABILITY_CODE_ROLE: case CAPABILITY_CODE_SOFT_VERSION: + case CAPABILITY_CODE_PATHS_LIMIT: /* Check length. */ if (caphdr.length < cap_minsizes[caphdr.code]) { zlog_info( @@ -1113,6 +1173,9 @@ static int bgp_capability_parse(struct peer *peer, size_t length, case CAPABILITY_CODE_SOFT_VERSION: ret = bgp_capability_software_version(peer, &caphdr); break; + case CAPABILITY_CODE_PATHS_LIMIT: + ret = bgp_capability_paths_limit(peer, &caphdr); + break; default: if (caphdr.code > 128) { /* We don't send Notification for unknown vendor @@ -1874,6 +1937,31 @@ uint16_t bgp_open_capability(struct stream *s, struct peer *peer, } } + /* Paths-Limit capability */ + SET_FLAG(peer->cap, PEER_CAP_PATHS_LIMIT_ADV); + stream_putc(s, BGP_OPEN_OPT_CAP); + ext_opt_params ? stream_putw(s, (CAPABILITY_CODE_PATHS_LIMIT_LEN * + afi_safi_count) + + 2) + : stream_putc(s, (CAPABILITY_CODE_PATHS_LIMIT_LEN * + afi_safi_count) + + 2); + stream_putc(s, CAPABILITY_CODE_PATHS_LIMIT); + stream_putc(s, CAPABILITY_CODE_PATHS_LIMIT_LEN * afi_safi_count); + + FOREACH_AFI_SAFI (afi, safi) { + if (!peer->afc[afi][safi]) + continue; + + bgp_map_afi_safi_int2iana(afi, safi, &pkt_afi, &pkt_safi); + + stream_putw(s, pkt_afi); + stream_putc(s, pkt_safi); + stream_putw(s, peer->addpath_paths_limit[afi][safi].send); + + SET_FLAG(peer->af_cap[afi][safi], PEER_CAP_PATHS_LIMIT_AF_ADV); + } + /* ORF capability. */ FOREACH_AFI_SAFI (afi, safi) { if (CHECK_FLAG(peer->af_flags[afi][safi], diff --git a/bgpd/bgp_open.h b/bgpd/bgp_open.h index 34f4b7619edc..a01e49ceba51 100644 --- a/bgpd/bgp_open.h +++ b/bgpd/bgp_open.h @@ -53,6 +53,7 @@ struct graceful_restart_af { #define CAPABILITY_CODE_ENHE 5 /* Extended Next Hop Encoding */ #define CAPABILITY_CODE_EXT_MESSAGE 6 /* Extended Message Support */ #define CAPABILITY_CODE_ROLE 9 /* Role Capability */ +#define CAPABILITY_CODE_PATHS_LIMIT 76 /* Paths Limit Capability */ /* Capability Length */ #define CAPABILITY_CODE_MP_LEN 4 @@ -61,6 +62,7 @@ struct graceful_restart_af { #define CAPABILITY_CODE_RESTART_LEN 2 /* Receiving only case */ #define CAPABILITY_CODE_AS4_LEN 4 #define CAPABILITY_CODE_ADDPATH_LEN 4 +#define CAPABILITY_CODE_PATHS_LIMIT_LEN 5 #define CAPABILITY_CODE_ENHE_LEN 6 /* NRLI AFI = 2, SAFI = 2, Nexthop AFI = 2 */ #define CAPABILITY_CODE_MIN_FQDN_LEN 2 #define CAPABILITY_CODE_ENHANCED_LEN 0 diff --git a/bgpd/bgp_packet.c b/bgpd/bgp_packet.c index 9d484d901ac1..da352a844127 100644 --- a/bgpd/bgp_packet.c +++ b/bgpd/bgp_packet.c @@ -1224,7 +1224,7 @@ void bgp_capability_send(struct peer *peer, afi_t afi, safi_t safi, if (!peer_established(peer->connection)) return; - if (!CHECK_FLAG(peer->cap, PEER_CAP_DYNAMIC_RCV) && + if (!CHECK_FLAG(peer->cap, PEER_CAP_DYNAMIC_RCV) || !CHECK_FLAG(peer->cap, PEER_CAP_DYNAMIC_ADV)) return; @@ -1461,6 +1461,49 @@ void bgp_capability_send(struct peer *peer, afi_t afi, safi_t safi, capability, iana_afi2str(pkt_afi), iana_safi2str(pkt_safi)); + break; + case CAPABILITY_CODE_PATHS_LIMIT: + SET_FLAG(peer->cap, PEER_CAP_PATHS_LIMIT_ADV); + + FOREACH_AFI_SAFI (afi, safi) { + if (!peer->afc[afi][safi]) + continue; + + addpath_afi_safi_count++; + } + + stream_putc(s, action); + stream_putc(s, CAPABILITY_CODE_PATHS_LIMIT); + stream_putc(s, CAPABILITY_CODE_PATHS_LIMIT_LEN * + addpath_afi_safi_count); + + FOREACH_AFI_SAFI (afi, safi) { + if (!peer->afc[afi][safi]) + continue; + + bgp_map_afi_safi_int2iana(afi, safi, &pkt_afi, + &pkt_safi); + + stream_putw(s, pkt_afi); + stream_putc(s, pkt_safi); + stream_putw(s, + peer->addpath_paths_limit[afi][safi].send); + + SET_FLAG(peer->af_cap[afi][safi], + PEER_CAP_PATHS_LIMIT_AF_ADV); + + if (bgp_debug_neighbor_events(peer)) + zlog_debug("%pBP sending CAPABILITY has %s %s for afi/safi: %s/%s, limit: %u", + peer, + action == CAPABILITY_ACTION_SET + ? "Advertising" + : "Removing", + capability, iana_afi2str(pkt_afi), + iana_safi2str(pkt_safi), + peer->addpath_paths_limit[afi][safi] + .send); + } + break; case CAPABILITY_CODE_ORF: /* Convert AFI, SAFI to values for packet. */ @@ -3170,6 +3213,85 @@ static void bgp_dynamic_capability_addpath(uint8_t *pnt, int action, } } +static void bgp_dynamic_capability_paths_limit(uint8_t *pnt, int action, + struct capability_header *hdr, + struct peer *peer) +{ + uint8_t *data = pnt + 3; + uint8_t *end = data + hdr->length; + size_t len = end - data; + afi_t afi; + safi_t safi; + + if (action == CAPABILITY_ACTION_SET) { + if (len % CAPABILITY_CODE_PATHS_LIMIT_LEN) { + flog_warn(EC_BGP_CAPABILITY_INVALID_LENGTH, + "Paths-Limit: Received invalid length %zu, non-multiple of %d", + len, CAPABILITY_CODE_PATHS_LIMIT_LEN); + return; + } + + if (!CHECK_FLAG(peer->cap, PEER_CAP_ADDPATH_RCV)) { + flog_warn(EC_BGP_CAPABILITY_INVALID_DATA, + "Paths-Limit: Received Paths-Limit capability without Add-Path capability"); + goto ignore; + } + + SET_FLAG(peer->cap, PEER_CAP_PATHS_LIMIT_RCV); + + while (data + CAPABILITY_CODE_PATHS_LIMIT_LEN <= end) { + afi_t afi; + safi_t safi; + iana_afi_t pkt_afi; + iana_safi_t pkt_safi; + struct bgp_paths_limit_capability bpl = {}; + + memcpy(&bpl, data, sizeof(bpl)); + pkt_afi = ntohs(bpl.afi); + pkt_safi = safi_int2iana(bpl.safi); + + if (bgp_debug_neighbor_events(peer)) + zlog_debug("%s OPEN has %s capability for afi/safi: %s/%s limit: %u", + peer->host, + lookup_msg(capcode_str, hdr->code, + NULL), + iana_afi2str(pkt_afi), + iana_safi2str(pkt_safi), + bpl.paths_limit); + + if (bgp_map_afi_safi_iana2int(pkt_afi, pkt_safi, &afi, + &safi)) { + if (bgp_debug_neighbor_events(peer)) + zlog_debug("%s Addr-family %s/%s(afi/safi) not supported. Ignore the Paths-Limit capability for this AFI/SAFI", + peer->host, + iana_afi2str(pkt_afi), + iana_safi2str(pkt_safi)); + goto ignore; + } else if (!peer->afc[afi][safi]) { + if (bgp_debug_neighbor_events(peer)) + zlog_debug("%s Addr-family %s/%s(afi/safi) not enabled. Ignore the Paths-Limit capability for this AFI/SAFI", + peer->host, + iana_afi2str(pkt_afi), + iana_safi2str(pkt_safi)); + goto ignore; + } + + SET_FLAG(peer->af_cap[afi][safi], + PEER_CAP_PATHS_LIMIT_AF_RCV); + peer->addpath_paths_limit[afi][safi].receive = + bpl.paths_limit; +ignore: + data += CAPABILITY_CODE_PATHS_LIMIT_LEN; + } + } else { + FOREACH_AFI_SAFI (afi, safi) + UNSET_FLAG(peer->af_cap[afi][safi], + PEER_CAP_PATHS_LIMIT_AF_RCV); + + UNSET_FLAG(peer->cap, PEER_CAP_PATHS_LIMIT_RCV); + } +} + static void bgp_dynamic_capability_orf(uint8_t *pnt, int action, struct capability_header *hdr, struct peer *peer) @@ -3723,6 +3845,10 @@ static int bgp_capability_msg_parse(struct peer *peer, uint8_t *pnt, case CAPABILITY_CODE_ADDPATH: bgp_dynamic_capability_addpath(pnt, action, hdr, peer); break; + case CAPABILITY_CODE_PATHS_LIMIT: + bgp_dynamic_capability_paths_limit(pnt, action, hdr, + peer); + break; case CAPABILITY_CODE_ORF: bgp_dynamic_capability_orf(pnt, action, hdr, peer); break; diff --git a/bgpd/bgp_updgrp.c b/bgpd/bgp_updgrp.c index d13515af6f4b..c522865ebabb 100644 --- a/bgpd/bgp_updgrp.c +++ b/bgpd/bgp_updgrp.c @@ -145,6 +145,8 @@ static void conf_copy(struct peer *dst, struct peer *src, afi_t afi, dst->addpath_type[afi][safi] = src->addpath_type[afi][safi]; dst->addpath_best_selected[afi][safi] = src->addpath_best_selected[afi][safi]; + dst->addpath_paths_limit[afi][safi] = + src->addpath_paths_limit[afi][safi]; dst->local_as = src->local_as; dst->change_local_as = src->change_local_as; dst->shared_network = src->shared_network; @@ -348,6 +350,8 @@ static unsigned int updgrp_hash_key_make(const void *p) key = jhash_1word((flags & PEER_UPDGRP_AF_FLAGS), key); key = jhash_1word((uint32_t)peer->addpath_type[afi][safi], key); key = jhash_1word(peer->addpath_best_selected[afi][safi], key); + key = jhash_1word(peer->addpath_paths_limit[afi][safi].receive, key); + key = jhash_1word(peer->addpath_paths_limit[afi][safi].send, key); key = jhash_1word((peer->cap & PEER_UPDGRP_CAP_FLAGS), key); key = jhash_1word((peer->af_cap[afi][safi] & PEER_UPDGRP_AF_CAP_FLAGS), key); @@ -461,6 +465,9 @@ static unsigned int updgrp_hash_key_make(const void *p) PEER_UPDGRP_AF_CAP_FLAGS), peer->v_routeadv, peer->change_local_as, peer->as_path_loop_detection); + zlog_debug("%pBP Update Group Hash: addpath paths-limit: (send %u, receive %u)", + peer, peer->addpath_paths_limit[afi][safi].send, + peer->addpath_paths_limit[afi][safi].receive); zlog_debug( "%pBP Update Group Hash: max packet size: %u pmax_out: %u Peer Group: %s rmap out: %s", peer, peer->max_packet_size, peer->pmax_out[afi][safi], diff --git a/bgpd/bgp_updgrp_adv.c b/bgpd/bgp_updgrp_adv.c index 7ecebe3020cf..cc039e3e11f5 100644 --- a/bgpd/bgp_updgrp_adv.c +++ b/bgpd/bgp_updgrp_adv.c @@ -97,13 +97,19 @@ subgrp_announce_addpath_best_selected(struct bgp_dest *dest, enum bgp_path_selection_reason reason; char pfx_buf[PREFIX2STR_BUFFER] = {}; int paths_eq = 0; - int best_path_count = 0; struct list *list = list_new(); struct bgp_path_info *pi = NULL; + uint16_t paths_count = 0; + uint16_t paths_limit = peer->addpath_paths_limit[afi][safi].receive; if (peer->addpath_type[afi][safi] == BGP_ADDPATH_BEST_SELECTED) { - while (best_path_count++ < - peer->addpath_best_selected[afi][safi]) { + paths_limit = + paths_limit + ? MIN(paths_limit, + peer->addpath_best_selected[afi][safi]) + : peer->addpath_best_selected[afi][safi]; + + while (paths_count++ < paths_limit) { struct bgp_path_info *exist = NULL; for (pi = bgp_dest_get_bgp_path_info(dest); pi; @@ -139,8 +145,26 @@ subgrp_announce_addpath_best_selected(struct bgp_dest *dest, subgroup_process_announce_selected( subgrp, NULL, dest, afi, safi, id); } else { - subgroup_process_announce_selected(subgrp, pi, dest, - afi, safi, id); + /* No Paths-Limit involved */ + if (!paths_limit) { + subgroup_process_announce_selected(subgrp, pi, + dest, afi, + safi, id); + continue; + } + + /* If we have Paths-Limit capability, we MUST + * not send more than the number of paths expected + * by the peer. + */ + if (paths_count++ < paths_limit) + subgroup_process_announce_selected(subgrp, pi, + dest, afi, + safi, id); + else + subgroup_process_announce_selected(subgrp, NULL, + dest, afi, + safi, id); } } diff --git a/bgpd/bgp_vty.c b/bgpd/bgp_vty.c index 31524e2221b9..efc71bc8a77b 100644 --- a/bgpd/bgp_vty.c +++ b/bgpd/bgp_vty.c @@ -9198,6 +9198,63 @@ DEFPY( return CMD_SUCCESS; } +DEFPY (neighbor_addpath_paths_limit, + neighbor_addpath_paths_limit_cmd, + "neighbor $neighbor addpath-rx-paths-limit (1-65535)$paths_limit", + NEIGHBOR_STR + NEIGHBOR_ADDR_STR2 + "Paths Limit for Addpath to receive from the peer\n" + "Maximum number of paths\n") +{ + struct peer *peer; + afi_t afi = bgp_node_afi(vty); + safi_t safi = bgp_node_safi(vty); + int ret; + + peer = peer_and_group_lookup_vty(vty, neighbor); + if (!peer) + return CMD_WARNING_CONFIG_FAILED; + + ret = peer_af_flag_set_vty(vty, neighbor, afi, safi, + PEER_FLAG_ADDPATH_RX_PATHS_LIMIT); + + peer->addpath_paths_limit[afi][safi].send = paths_limit; + + bgp_capability_send(peer, afi, safi, CAPABILITY_CODE_PATHS_LIMIT, + CAPABILITY_ACTION_SET); + + return ret; +} + +DEFPY (no_neighbor_addpath_paths_limit, + no_neighbor_addpath_paths_limit_cmd, + "no neighbor $neighbor addpath-rx-paths-limit [(1-65535)]", + NO_STR + NEIGHBOR_STR + NEIGHBOR_ADDR_STR2 + "Paths Limit for Addpath to receive from the peer\n" + "Maximum number of paths\n") +{ + struct peer *peer; + afi_t afi = bgp_node_afi(vty); + safi_t safi = bgp_node_safi(vty); + int ret; + + peer = peer_and_group_lookup_vty(vty, neighbor); + if (!peer) + return CMD_WARNING_CONFIG_FAILED; + + ret = peer_af_flag_unset_vty(vty, neighbor, afi, safi, + PEER_FLAG_ADDPATH_RX_PATHS_LIMIT); + + peer->addpath_paths_limit[afi][safi].send = 0; + + bgp_capability_send(peer, afi, safi, CAPABILITY_CODE_PATHS_LIMIT, + CAPABILITY_ACTION_UNSET); + + return ret; +} + DEFPY( no_neighbor_aspath_loop_detection, no_neighbor_aspath_loop_detection_cmd, @@ -14146,6 +14203,86 @@ static void bgp_show_peer(struct vty *vty, struct peer *p, bool use_json, json_add); } + /* Paths-Limit */ + if (CHECK_FLAG(p->cap, PEER_CAP_PATHS_LIMIT_RCV) || + CHECK_FLAG(p->cap, PEER_CAP_PATHS_LIMIT_ADV)) { + json_object *json_add = NULL; + const char *print_store; + + json_add = json_object_new_object(); + + FOREACH_AFI_SAFI (afi, safi) { + json_object *json_sub = NULL; + + json_sub = json_object_new_object(); + print_store = get_afi_safi_str(afi, safi, + true); + + if (CHECK_FLAG(p->af_cap[afi][safi], + PEER_CAP_PATHS_LIMIT_AF_ADV) || + CHECK_FLAG(p->af_cap[afi][safi], + PEER_CAP_PATHS_LIMIT_AF_RCV)) { + if (CHECK_FLAG(p->af_cap[afi][safi], + PEER_CAP_PATHS_LIMIT_AF_ADV) && + CHECK_FLAG(p->af_cap[afi][safi], + PEER_CAP_PATHS_LIMIT_AF_RCV)) { + json_object_boolean_true_add( + json_sub, + "advertisedAndReceived"); + json_object_int_add( + json_sub, + "advertisedPathsLimit", + p->addpath_paths_limit + [afi][safi] + .send); + json_object_int_add( + json_sub, + "receivedPathsLimit", + p->addpath_paths_limit + [afi][safi] + .receive); + } else if (CHECK_FLAG(p->af_cap[afi] + [safi], + PEER_CAP_PATHS_LIMIT_AF_ADV)) { + json_object_boolean_true_add( + json_sub, + "advertised"); + json_object_int_add( + json_sub, + "advertisedPathsLimit", + p->addpath_paths_limit + [afi][safi] + .send); + } else if (CHECK_FLAG(p->af_cap[afi] + [safi], + PEER_CAP_PATHS_LIMIT_AF_RCV)) { + json_object_boolean_true_add( + json_sub, + "received"); + json_object_int_add( + json_sub, + "receivedPathsLimit", + p->addpath_paths_limit + [afi][safi] + .receive); + } + } + + if (CHECK_FLAG(p->af_cap[afi][safi], + PEER_CAP_PATHS_LIMIT_AF_ADV) || + CHECK_FLAG(p->af_cap[afi][safi], + PEER_CAP_PATHS_LIMIT_AF_RCV)) + json_object_object_add(json_add, + print_store, + json_sub); + else + json_object_free(json_sub); + } + + json_object_object_add(json_cap, "pathsLimit", + json_add); + } + /* Dynamic */ if (CHECK_FLAG(p->cap, PEER_CAP_DYNAMIC_RCV) || CHECK_FLAG(p->cap, PEER_CAP_DYNAMIC_ADV)) { @@ -14599,6 +14736,47 @@ static void bgp_show_peer(struct vty *vty, struct peer *p, bool use_json, } } + /* Paths-Limit */ + if (CHECK_FLAG(p->cap, PEER_CAP_PATHS_LIMIT_RCV) || + CHECK_FLAG(p->cap, PEER_CAP_PATHS_LIMIT_ADV)) { + vty_out(vty, " Paths-Limit:\n"); + + FOREACH_AFI_SAFI (afi, safi) { + if (CHECK_FLAG(p->af_cap[afi][safi], + PEER_CAP_PATHS_LIMIT_AF_ADV) || + CHECK_FLAG(p->af_cap[afi][safi], + PEER_CAP_PATHS_LIMIT_AF_RCV)) { + vty_out(vty, " %s: ", + get_afi_safi_str(afi, + safi, + false)); + + if (CHECK_FLAG(p->af_cap[afi][safi], + PEER_CAP_PATHS_LIMIT_AF_ADV)) + vty_out(vty, + "advertised (%u)", + p->addpath_paths_limit + [afi][safi] + .send); + + if (CHECK_FLAG(p->af_cap[afi][safi], + PEER_CAP_PATHS_LIMIT_AF_RCV)) + vty_out(vty, + "%sreceived (%u)", + CHECK_FLAG(p->af_cap[afi] + [safi], + PEER_CAP_PATHS_LIMIT_AF_ADV) + ? " and " + : "", + p->addpath_paths_limit + [afi][safi] + .receive); + + vty_out(vty, "\n"); + } + } + } + /* Dynamic */ if (CHECK_FLAG(p->cap, PEER_CAP_DYNAMIC_RCV) || CHECK_FLAG(p->cap, PEER_CAP_DYNAMIC_ADV)) { @@ -18383,6 +18561,11 @@ static void bgp_config_write_peer_af(struct vty *vty, struct bgp *bgp, if (CHECK_FLAG(peer->af_flags[afi][safi], PEER_FLAG_DISABLE_ADDPATH_RX)) vty_out(vty, " neighbor %s disable-addpath-rx\n", addr); + if (CHECK_FLAG(peer->af_flags[afi][safi], + PEER_FLAG_ADDPATH_RX_PATHS_LIMIT)) + vty_out(vty, " neighbor %s addpath-rx-paths-limit %u\n", addr, + peer->addpath_paths_limit[afi][safi].send); + /* ORF capability. */ if (peergroup_af_flag_check(peer, afi, safi, PEER_FLAG_ORF_PREFIX_SM) || peergroup_af_flag_check(peer, afi, safi, @@ -20544,6 +20727,26 @@ void bgp_vty_init(void) install_element(BGP_VPNV6_NODE, &no_neighbor_addpath_tx_bestpath_per_as_cmd); + /* "neighbor addpath-rx-paths-limit" commands.*/ + install_element(BGP_NODE, &neighbor_addpath_paths_limit_cmd); + install_element(BGP_NODE, &no_neighbor_addpath_paths_limit_cmd); + install_element(BGP_IPV4_NODE, &neighbor_addpath_paths_limit_cmd); + install_element(BGP_IPV4_NODE, &no_neighbor_addpath_paths_limit_cmd); + install_element(BGP_IPV4M_NODE, &neighbor_addpath_paths_limit_cmd); + install_element(BGP_IPV4M_NODE, &no_neighbor_addpath_paths_limit_cmd); + install_element(BGP_IPV4L_NODE, &neighbor_addpath_paths_limit_cmd); + install_element(BGP_IPV4L_NODE, &no_neighbor_addpath_paths_limit_cmd); + install_element(BGP_IPV6_NODE, &neighbor_addpath_paths_limit_cmd); + install_element(BGP_IPV6_NODE, &no_neighbor_addpath_paths_limit_cmd); + install_element(BGP_IPV6M_NODE, &neighbor_addpath_paths_limit_cmd); + install_element(BGP_IPV6M_NODE, &no_neighbor_addpath_paths_limit_cmd); + install_element(BGP_IPV6L_NODE, &neighbor_addpath_paths_limit_cmd); + install_element(BGP_IPV6L_NODE, &no_neighbor_addpath_paths_limit_cmd); + install_element(BGP_VPNV4_NODE, &neighbor_addpath_paths_limit_cmd); + install_element(BGP_VPNV4_NODE, &no_neighbor_addpath_paths_limit_cmd); + install_element(BGP_VPNV6_NODE, &neighbor_addpath_paths_limit_cmd); + install_element(BGP_VPNV6_NODE, &no_neighbor_addpath_paths_limit_cmd); + /* "neighbor sender-as-path-loop-detection" commands. */ install_element(BGP_NODE, &neighbor_aspath_loop_detection_cmd); install_element(BGP_NODE, &no_neighbor_aspath_loop_detection_cmd); diff --git a/bgpd/bgpd.c b/bgpd/bgpd.c index 8fc52652a238..e7712f0f3e0c 100644 --- a/bgpd/bgpd.c +++ b/bgpd/bgpd.c @@ -1527,6 +1527,8 @@ struct peer *peer_new(struct bgp *bgp) PEER_FLAG_SEND_LARGE_COMMUNITY); peer->addpath_type[afi][safi] = BGP_ADDPATH_NONE; peer->addpath_best_selected[afi][safi] = 0; + peer->addpath_paths_limit[afi][safi].receive = 0; + peer->addpath_paths_limit[afi][safi].send = 0; peer->soo[afi][safi] = NULL; } @@ -1620,6 +1622,8 @@ void peer_xfer_config(struct peer *peer_dst, struct peer *peer_src) peer_dst->weight[afi][safi] = peer_src->weight[afi][safi]; peer_dst->addpath_type[afi][safi] = peer_src->addpath_type[afi][safi]; + peer_dst->addpath_paths_limit[afi][safi] = + peer_src->addpath_paths_limit[afi][safi]; } for (afidx = BGP_AF_START; afidx < BGP_AF_MAX; afidx++) { @@ -4614,6 +4618,7 @@ static const struct peer_flag_action peer_af_flag_action_list[] = { {PEER_FLAG_SOO, 0, peer_change_reset}, {PEER_FLAG_ACCEPT_OWN, 0, peer_change_reset}, {PEER_FLAG_SEND_EXT_COMMUNITY_RPKI, 1, peer_change_reset_out}, + {PEER_FLAG_ADDPATH_RX_PATHS_LIMIT, 0, peer_change_none}, {0, 0, 0}}; /* Proper action set. */ diff --git a/bgpd/bgpd.h b/bgpd/bgpd.h index 0f69095323b1..cf333e07cc62 100644 --- a/bgpd/bgpd.h +++ b/bgpd/bgpd.h @@ -1140,6 +1140,11 @@ struct llgr_info { uint8_t flags; }; +struct addpath_paths_limit { + uint16_t send; + uint16_t receive; +}; + struct peer_connection { struct peer *peer; @@ -1333,6 +1338,8 @@ struct peer { #define PEER_CAP_ROLE_RCV (1ULL << 26) /* role received */ #define PEER_CAP_SOFT_VERSION_ADV (1ULL << 27) #define PEER_CAP_SOFT_VERSION_RCV (1ULL << 28) +#define PEER_CAP_PATHS_LIMIT_ADV (1U << 29) +#define PEER_CAP_PATHS_LIMIT_RCV (1U << 30) /* Capability flags (reset in bgp_stop) */ uint32_t af_cap[AFI_MAX][SAFI_MAX]; @@ -1351,6 +1358,8 @@ struct peer { #define PEER_CAP_ENHE_AF_NEGO (1U << 14) /* Extended nexthop afi/safi negotiated */ #define PEER_CAP_LLGR_AF_ADV (1U << 15) #define PEER_CAP_LLGR_AF_RCV (1U << 16) +#define PEER_CAP_PATHS_LIMIT_AF_ADV (1U << 17) +#define PEER_CAP_PATHS_LIMIT_AF_RCV (1U << 18) /* Global configuration flags. */ /* @@ -1528,6 +1537,7 @@ struct peer { #define PEER_FLAG_DISABLE_ADDPATH_RX (1ULL << 27) #define PEER_FLAG_SOO (1ULL << 28) #define PEER_FLAG_SEND_EXT_COMMUNITY_RPKI (1ULL << 29) +#define PEER_FLAG_ADDPATH_RX_PATHS_LIMIT (1ULL << 30) #define PEER_FLAG_ACCEPT_OWN (1ULL << 63) enum bgp_addpath_strat addpath_type[AFI_MAX][SAFI_MAX]; @@ -1845,6 +1855,9 @@ struct peer { /* Add-Path Best selected paths number to advertise */ uint8_t addpath_best_selected[AFI_MAX][SAFI_MAX]; + /* Add-Path Paths-Limit */ + struct addpath_paths_limit addpath_paths_limit[AFI_MAX][SAFI_MAX]; + QOBJ_FIELDS; }; DECLARE_QOBJ_TYPE(peer); diff --git a/doc/user/bgp.rst b/doc/user/bgp.rst index 53dc551ca353..b10055f8c4e3 100644 --- a/doc/user/bgp.rst +++ b/doc/user/bgp.rst @@ -1780,6 +1780,17 @@ Configuring Peers Do not accept additional paths from this neighbor. +.. clicmd:: neighbor addpath-rx-paths-limit (1-65535) + + Limit the maximum number of paths a BGP speaker can receive from a peer, optimizing + the transmission of BGP routes by selectively relaying pertinent routes instead of + the entire set. + + If this command is configured, the sender will only send the number of paths specified + in PATHS-LIMIT capability. + + To exchange this limit, both peers must support the PATHS-LIMIT capability. + .. clicmd:: neighbor PEER ttl-security hops NUMBER This command enforces Generalized TTL Security Mechanism (GTSM), as diff --git a/tests/topotests/bgp_addpath_paths_limit/__init__.py b/tests/topotests/bgp_addpath_paths_limit/__init__.py new file mode 100644 index 000000000000..e69de29bb2d1 diff --git a/tests/topotests/bgp_addpath_paths_limit/r1/frr.conf b/tests/topotests/bgp_addpath_paths_limit/r1/frr.conf new file mode 100644 index 000000000000..65beb7f28686 --- /dev/null +++ b/tests/topotests/bgp_addpath_paths_limit/r1/frr.conf @@ -0,0 +1,13 @@ +! +int r1-eth0 + ip address 192.168.1.1/24 +! +router bgp 65001 + timers bgp 3 10 + no bgp ebgp-requires-policy + neighbor 192.168.1.2 remote-as external + neighbor 192.168.1.2 timers connect 5 + address-family ipv4 unicast + neighbor 192.168.1.2 addpath-rx-paths-limit 2 + exit-address-family +! diff --git a/tests/topotests/bgp_addpath_paths_limit/r2/frr.conf b/tests/topotests/bgp_addpath_paths_limit/r2/frr.conf new file mode 100644 index 000000000000..796b4d0ba7fd --- /dev/null +++ b/tests/topotests/bgp_addpath_paths_limit/r2/frr.conf @@ -0,0 +1,37 @@ +! +int r2-eth0 + ip address 192.168.1.2/24 +! +int r2-eth1 + ip address 192.168.2.2/24 +! +int r2-eth2 + ip address 192.168.7.2/24 +! +router bgp 65002 + timers bgp 3 10 + no bgp ebgp-requires-policy + neighbor 192.168.1.1 remote-as external + neighbor 192.168.7.7 remote-as external + neighbor 192.168.7.7 timers connect 5 + neighbor 192.168.2.3 remote-as external + neighbor 192.168.2.3 timers connect 5 + neighbor 192.168.2.3 weight 3 + neighbor 192.168.2.4 remote-as external + neighbor 192.168.2.4 timers connect 5 + neighbor 192.168.2.4 weight 4 + neighbor 192.168.2.5 remote-as external + neighbor 192.168.2.5 timers connect 5 + neighbor 192.168.2.5 weight 5 + neighbor 192.168.2.6 remote-as external + neighbor 192.168.2.6 timers connect 5 + neighbor 192.168.2.6 weight 6 + address-family ipv4 unicast + neighbor 192.168.1.1 addpath-tx-all-paths + neighbor 192.168.1.1 prefix-list announce out + neighbor 192.168.7.7 addpath-tx-all-paths + neighbor 192.168.7.7 prefix-list announce out + exit-address-family +! +ip prefix-list announce seq 5 permit 172.16.16.254/32 +! diff --git a/tests/topotests/bgp_addpath_paths_limit/r3/frr.conf b/tests/topotests/bgp_addpath_paths_limit/r3/frr.conf new file mode 100644 index 000000000000..4d834d3113bf --- /dev/null +++ b/tests/topotests/bgp_addpath_paths_limit/r3/frr.conf @@ -0,0 +1,16 @@ +! +int lo + ip address 172.16.16.254/32 +! +int r3-eth0 + ip address 192.168.2.3/24 +! +router bgp 65003 + timers bgp 3 10 + no bgp ebgp-requires-policy + neighbor 192.168.2.2 remote-as external + neighbor 192.168.2.2 timers connect 5 + address-family ipv4 unicast + redistribute connected + exit-address-family +! diff --git a/tests/topotests/bgp_addpath_paths_limit/r4/frr.conf b/tests/topotests/bgp_addpath_paths_limit/r4/frr.conf new file mode 100644 index 000000000000..01e0aa99d36f --- /dev/null +++ b/tests/topotests/bgp_addpath_paths_limit/r4/frr.conf @@ -0,0 +1,16 @@ +! +int lo + ip address 172.16.16.254/32 +! +int r4-eth0 + ip address 192.168.2.4/24 +! +router bgp 65004 + timers bgp 3 10 + no bgp ebgp-requires-policy + neighbor 192.168.2.2 remote-as external + neighbor 192.168.2.2 timers connect 5 + address-family ipv4 unicast + redistribute connected + exit-address-family +! diff --git a/tests/topotests/bgp_addpath_paths_limit/r5/frr.conf b/tests/topotests/bgp_addpath_paths_limit/r5/frr.conf new file mode 100644 index 000000000000..02bb84798781 --- /dev/null +++ b/tests/topotests/bgp_addpath_paths_limit/r5/frr.conf @@ -0,0 +1,16 @@ +! +int lo + ip address 172.16.16.254/32 +! +int r5-eth0 + ip address 192.168.2.5/24 +! +router bgp 65005 + timers bgp 3 10 + no bgp ebgp-requires-policy + neighbor 192.168.2.2 remote-as external + neighbor 192.168.2.2 timers connect 5 + address-family ipv4 unicast + redistribute connected + exit-address-family +! diff --git a/tests/topotests/bgp_addpath_paths_limit/r6/frr.conf b/tests/topotests/bgp_addpath_paths_limit/r6/frr.conf new file mode 100644 index 000000000000..39fdbcce3256 --- /dev/null +++ b/tests/topotests/bgp_addpath_paths_limit/r6/frr.conf @@ -0,0 +1,16 @@ +! +int lo + ip address 172.16.16.254/32 +! +int r6-eth0 + ip address 192.168.2.6/24 +! +router bgp 65006 + timers bgp 3 10 + no bgp ebgp-requires-policy + neighbor 192.168.2.2 remote-as external + neighbor 192.168.2.2 timers connect 5 + address-family ipv4 unicast + redistribute connected + exit-address-family +! diff --git a/tests/topotests/bgp_addpath_paths_limit/r7/frr.conf b/tests/topotests/bgp_addpath_paths_limit/r7/frr.conf new file mode 100644 index 000000000000..8c44566b2f70 --- /dev/null +++ b/tests/topotests/bgp_addpath_paths_limit/r7/frr.conf @@ -0,0 +1,13 @@ +! +int r7-eth0 + ip address 192.168.7.7/24 +! +router bgp 65007 + timers bgp 3 10 + no bgp ebgp-requires-policy + neighbor 192.168.7.2 remote-as external + neighbor 192.168.7.2 timers connect 5 + address-family ipv4 unicast + neighbor 192.168.7.2 addpath-rx-paths-limit 3 + exit-address-family +! diff --git a/tests/topotests/bgp_addpath_paths_limit/test_bgp_addpath_paths_limit.py b/tests/topotests/bgp_addpath_paths_limit/test_bgp_addpath_paths_limit.py new file mode 100644 index 000000000000..b7a1cfbb2774 --- /dev/null +++ b/tests/topotests/bgp_addpath_paths_limit/test_bgp_addpath_paths_limit.py @@ -0,0 +1,128 @@ +#!/usr/bin/env python +# SPDX-License-Identifier: ISC + +# Copyright (c) 2024 by +# Donatas Abraitis +# + +""" +Test if Paths-Limit capability works as expected. +""" + +import os +import sys +import json +import pytest +import functools + +CWD = os.path.dirname(os.path.realpath(__file__)) +sys.path.append(os.path.join(CWD, "../")) + +# pylint: disable=C0413 +from lib import topotest +from lib.topogen import Topogen, TopoRouter, get_topogen + +pytestmark = [pytest.mark.bgpd] + + +def build_topo(tgen): + for routern in range(1, 8): + tgen.add_router("r{}".format(routern)) + + switch = tgen.add_switch("s1") + switch.add_link(tgen.gears["r1"]) + switch.add_link(tgen.gears["r2"]) + + switch = tgen.add_switch("s2") + switch.add_link(tgen.gears["r2"]) + switch.add_link(tgen.gears["r3"]) + switch.add_link(tgen.gears["r4"]) + switch.add_link(tgen.gears["r5"]) + switch.add_link(tgen.gears["r6"]) + + switch = tgen.add_switch("s3") + switch.add_link(tgen.gears["r7"]) + switch.add_link(tgen.gears["r2"]) + + +def setup_module(mod): + tgen = Topogen(build_topo, mod.__name__) + tgen.start_topology() + + router_list = tgen.routers() + + for _, (rname, router) in enumerate(router_list.items(), 1): + router.load_frr_config(os.path.join(CWD, "{}/frr.conf".format(rname))) + + tgen.start_router() + + +def teardown_module(mod): + tgen = get_topogen() + tgen.stop_topology() + + +def test_bgp_addpath_paths_limit(): + tgen = get_topogen() + + if tgen.routers_have_failure(): + pytest.skip(tgen.errors) + + r1 = tgen.gears["r1"] + r2 = tgen.gears["r2"] + r7 = tgen.gears["r7"] + + def _bgp_converge(): + output = json.loads(r2.vtysh_cmd("show bgp neighbor json")) + expected = { + "192.168.7.7": { + "neighborCapabilities": { + "pathsLimit": { + "ipv4Unicast": { + "advertisedAndReceived": True, + "advertisedPathsLimit": 0, + "receivedPathsLimit": 3, + } + } + } + }, + "192.168.1.1": { + "neighborCapabilities": { + "pathsLimit": { + "ipv4Unicast": { + "advertisedAndReceived": True, + "advertisedPathsLimit": 0, + "receivedPathsLimit": 2, + } + } + } + }, + } + return topotest.json_cmp(output, expected) + + test_func = functools.partial(_bgp_converge) + _, result = topotest.run_and_expect(test_func, None, count=30, wait=1) + assert result is None, "Can't converge initially" + + def _bgp_check_received_routes(router, expected): + output = json.loads( + router.vtysh_cmd("show bgp ipv4 unicast 172.16.16.254/32 json") + ) + + if "paths" not in output: + return "No paths received" + + return topotest.json_cmp(len(output["paths"]), expected) + + test_func = functools.partial(_bgp_check_received_routes, r1, 2) + _, result = topotest.run_and_expect(test_func, None, count=30, wait=1) + assert result is None, "Received routes count is not as expected" + + test_func = functools.partial(_bgp_check_received_routes, r7, 3) + _, result = topotest.run_and_expect(test_func, None, count=30, wait=1) + assert result is None, "Received routes count is not as expected" + + +if __name__ == "__main__": + args = ["-s"] + sys.argv[1:] + sys.exit(pytest.main(args))