diff options
| -rw-r--r-- | bgpd/bgp_addpath.h | 6 | ||||
| -rw-r--r-- | bgpd/bgp_fsm.c | 2 | ||||
| -rw-r--r-- | bgpd/bgp_open.c | 88 | ||||
| -rw-r--r-- | bgpd/bgp_open.h | 2 | ||||
| -rw-r--r-- | bgpd/bgp_packet.c | 128 | ||||
| -rw-r--r-- | bgpd/bgp_updgrp.c | 7 | ||||
| -rw-r--r-- | bgpd/bgp_updgrp_adv.c | 34 | ||||
| -rw-r--r-- | bgpd/bgp_vty.c | 203 | ||||
| -rw-r--r-- | bgpd/bgpd.c | 5 | ||||
| -rw-r--r-- | bgpd/bgpd.h | 13 | ||||
| -rw-r--r-- | doc/user/bgp.rst | 11 | ||||
| -rw-r--r-- | tests/topotests/bgp_addpath_paths_limit/__init__.py | 0 | ||||
| -rw-r--r-- | tests/topotests/bgp_addpath_paths_limit/r1/frr.conf | 13 | ||||
| -rw-r--r-- | tests/topotests/bgp_addpath_paths_limit/r2/frr.conf | 37 | ||||
| -rw-r--r-- | tests/topotests/bgp_addpath_paths_limit/r3/frr.conf | 16 | ||||
| -rw-r--r-- | tests/topotests/bgp_addpath_paths_limit/r4/frr.conf | 16 | ||||
| -rw-r--r-- | tests/topotests/bgp_addpath_paths_limit/r5/frr.conf | 16 | ||||
| -rw-r--r-- | tests/topotests/bgp_addpath_paths_limit/r6/frr.conf | 16 | ||||
| -rw-r--r-- | tests/topotests/bgp_addpath_paths_limit/r7/frr.conf | 13 | ||||
| -rw-r--r-- | tests/topotests/bgp_addpath_paths_limit/test_bgp_addpath_paths_limit.py | 128 | 
20 files changed, 748 insertions, 6 deletions
diff --git a/bgpd/bgp_addpath.h b/bgpd/bgp_addpath.h index d562000e30..b19e63c946 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 234dbb0715..657c7e22d7 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 43a59e2448..aa1d81362b 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 34f4b7619e..a01e49ceba 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 9d484d901a..da352a8441 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; @@ -1462,6 +1462,49 @@ void bgp_capability_send(struct peer *peer, afi_t afi, safi_t safi,  				   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. */  		bgp_map_afi_safi_int2iana(afi, safi, &pkt_afi, &pkt_safi); @@ -3170,6 +3213,85 @@ ignore:  	}  } +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 d13515af6f..c522865eba 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 7ecebe3020..cc039e3e11 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 31524e2221..efc71bc8a7 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 <A.B.C.D|X:X::X:X|WORD>$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 <A.B.C.D|X:X::X:X|WORD>$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 8fc52652a2..e7712f0f3e 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 0f69095323..cf333e07cc 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 53dc551ca3..b10055f8c4 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 <A.B.C.D|X:X::X:X|WORD> 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 0000000000..e69de29bb2 --- /dev/null +++ b/tests/topotests/bgp_addpath_paths_limit/__init__.py 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 0000000000..65beb7f286 --- /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 0000000000..796b4d0ba7 --- /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 0000000000..4d834d3113 --- /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 0000000000..01e0aa99d3 --- /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 0000000000..02bb847987 --- /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 0000000000..39fdbcce32 --- /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 0000000000..8c44566b2f --- /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 0000000000..b7a1cfbb27 --- /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 <donatas@opensourcerouting.org> +# + +""" +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))  | 
