diff options
| author | Donatas Abraitis <donatas@opensourcerouting.org> | 2025-02-28 10:08:27 +0200 | 
|---|---|---|
| committer | GitHub <noreply@github.com> | 2025-02-28 10:08:27 +0200 | 
| commit | d49561e32a8ea870a4597e56591cd4ca44abaa7c (patch) | |
| tree | b01db3228135f9af3b14a573b3ec8808f987e736 | |
| parent | e27631e10ae0f45ccada53e556c6c1331dbadd19 (diff) | |
| parent | 2b71dc2d91976f947fe603fc8ec8f7d435fcac50 (diff) | |
Merge pull request #18159 from pguibert6WIND/bgp_ecommlist_count
Bgp ecommlist count
| -rw-r--r-- | bgpd/bgp_clist.c | 67 | ||||
| -rw-r--r-- | bgpd/bgp_clist.h | 2 | ||||
| -rw-r--r-- | bgpd/bgp_ecommunity.c | 31 | ||||
| -rw-r--r-- | bgpd/bgp_ecommunity.h | 4 | ||||
| -rw-r--r-- | bgpd/bgp_routemap.c | 145 | ||||
| -rw-r--r-- | bgpd/bgp_routemap_nb.c | 7 | ||||
| -rw-r--r-- | bgpd/bgp_routemap_nb.h | 4 | ||||
| -rw-r--r-- | bgpd/bgp_routemap_nb_config.c | 51 | ||||
| -rw-r--r-- | doc/user/bgp.rst | 17 | ||||
| -rw-r--r-- | lib/routemap.h | 1 | ||||
| -rw-r--r-- | lib/routemap_cli.c | 14 | ||||
| -rw-r--r-- | tests/topotests/bgp_ecomm_list_match/r1/frr.conf | 51 | ||||
| -rw-r--r-- | tests/topotests/bgp_ecomm_list_match/r2/frr.conf | 32 | ||||
| -rw-r--r-- | tests/topotests/bgp_ecomm_list_match/r3/frr.conf | 26 | ||||
| -rw-r--r-- | tests/topotests/bgp_ecomm_list_match/test_bgp_ecomm_list_match.py | 198 | ||||
| -rw-r--r-- | yang/frr-bgp-route-map.yang | 19 | 
16 files changed, 655 insertions, 14 deletions
diff --git a/bgpd/bgp_clist.c b/bgpd/bgp_clist.c index ca9c428b47..335bccf34f 100644 --- a/bgpd/bgp_clist.c +++ b/bgpd/bgp_clist.c @@ -560,6 +560,11 @@ static bool community_regexp_match(struct community *com, regex_t *reg)  	return rv == 0;  } +static char *ecommunity_str_get(struct ecommunity *ecom, int i) +{ +	return ecommunity_ecom2str_one(ecom, ECOMMUNITY_FORMAT_DISPLAY, i); +} +  static char *lcommunity_str_get(struct lcommunity *lcom, int i)  {  	struct lcommunity_val lcomval; @@ -611,6 +616,29 @@ static bool lcommunity_regexp_include(regex_t *reg, struct lcommunity *lcom,  	return false;  } +/* Internal function to perform regular expression match for a single ecommunity. */ +static bool ecommunity_regexp_include(regex_t *reg, struct ecommunity *ecom, int i) +{ +	char *str; + +	/* When there is no communities attribute it is treated as empty string. +	 */ +	if (ecom == NULL || ecom->size == 0) +		str = XSTRDUP(MTYPE_ECOMMUNITY_STR, ""); +	else +		str = ecommunity_str_get(ecom, i); + +	/* Regular expression match.  */ +	if (regexec(reg, str, 0, NULL, 0) == 0) { +		XFREE(MTYPE_ECOMMUNITY_STR, str); +		return true; +	} + +	XFREE(MTYPE_ECOMMUNITY_STR, str); +	/* No match.  */ +	return false; +} +  static bool lcommunity_regexp_match(struct lcommunity *com, regex_t *reg)  {  	const char *str; @@ -697,6 +725,24 @@ bool lcommunity_list_match(struct lcommunity *lcom, struct community_list *list)  	return false;  } +/* Perform exact matching. In case of expanded extended-community-list, do + * same thing as ecommunity_list_match(). + */ +bool ecommunity_list_exact_match(struct ecommunity *ecom, struct community_list *list) +{ +	struct community_entry *entry; + +	for (entry = list->head; entry; entry = entry->next) { +		if (entry->style == EXTCOMMUNITY_LIST_STANDARD) { +			if (ecommunity_cmp(ecom, entry->u.lcom)) +				return entry->direct == COMMUNITY_PERMIT; +		} else if (entry->style == EXTCOMMUNITY_LIST_EXPANDED) { +			if (ecommunity_regexp_match(ecom, entry->reg)) +				return entry->direct == COMMUNITY_PERMIT; +		} +	} +	return false; +}  /* Perform exact matching.  In case of expanded large-community-list, do   * same thing as lcommunity_list_match(). @@ -981,6 +1027,27 @@ bool lcommunity_list_any_match(struct lcommunity *lcom,  	return false;  } +bool ecommunity_list_any_match(struct ecommunity *ecom, struct community_list *list) +{ +	struct community_entry *entry; +	uint8_t *ptr; +	uint32_t i; + +	for (i = 0; i < ecom->size; i++) { +		ptr = ecom->val + (i * ecom->unit_size); + +		for (entry = list->head; entry; entry = entry->next) { +			if ((entry->style == EXTCOMMUNITY_LIST_STANDARD) && +			    ecommunity_include_one(entry->u.ecom, ptr)) +				return entry->direct == COMMUNITY_PERMIT; +			if ((entry->style == EXTCOMMUNITY_LIST_EXPANDED) && +			    ecommunity_regexp_include(entry->reg, ecom, i)) +				return entry->direct == COMMUNITY_PERMIT; +		} +	} +	return false; +} +  /* Delete all permitted large communities in the list from com.  */  struct lcommunity *lcommunity_list_match_delete(struct lcommunity *lcom,  						struct community_list *list) diff --git a/bgpd/bgp_clist.h b/bgpd/bgp_clist.h index 7f35a6d53e..736c3d1195 100644 --- a/bgpd/bgp_clist.h +++ b/bgpd/bgp_clist.h @@ -158,6 +158,7 @@ extern bool lcommunity_list_match(struct lcommunity *lcom,  				  struct community_list *list);  extern bool community_list_exact_match(struct community *com,  				       struct community_list *list); +extern bool ecommunity_list_exact_match(struct ecommunity *com, struct community_list *list);  extern bool lcommunity_list_exact_match(struct lcommunity *lcom,  					struct community_list *list);  extern bool community_list_any_match(struct community *com, @@ -166,6 +167,7 @@ extern struct community *  community_list_match_delete(struct community *com, struct community_list *list);  extern bool lcommunity_list_any_match(struct lcommunity *lcom,  				      struct community_list *list); +extern bool ecommunity_list_any_match(struct ecommunity *ecom, struct community_list *list);  extern struct lcommunity *  lcommunity_list_match_delete(struct lcommunity *lcom,  			     struct community_list *list); diff --git a/bgpd/bgp_ecommunity.c b/bgpd/bgp_ecommunity.c index 2c6ae65f85..dcdf2ba25d 100644 --- a/bgpd/bgp_ecommunity.c +++ b/bgpd/bgp_ecommunity.c @@ -1145,8 +1145,10 @@ bool ecommunity_has_route_target(struct ecommunity *ecom)   *   * Filter is added to display only ECOMMUNITY_ROUTE_TARGET in some cases.   * 0 value displays all. + * Index is a unsigned integer value, and stands for the extended community list entry + * to display when value is not -1.   */ -char *ecommunity_ecom2str(struct ecommunity *ecom, int format, int filter) +static char *_ecommunity_ecom2str(struct ecommunity *ecom, int format, int filter, int index)  {  	uint32_t i;  	uint8_t *pnt; @@ -1168,8 +1170,10 @@ char *ecommunity_ecom2str(struct ecommunity *ecom, int format, int filter)  		bool unk_ecom = false;  		memset(encbuf, 0x00, sizeof(encbuf)); +		if (index != -1 && (uint32_t)index != i) +			continue;  		/* Space between each value.  */ -		if (i > 0) +		if (index == -1 && i > 0)  			strlcat(str_buf, " ", str_size);  		/* Retrieve value field */ @@ -1496,6 +1500,29 @@ unknown:  	return str_buf;  } +char *ecommunity_ecom2str(struct ecommunity *ecom, int format, int filter) +{ +	return _ecommunity_ecom2str(ecom, format, filter, -1); +} + +char *ecommunity_ecom2str_one(struct ecommunity *ecom, int format, int number) +{ +	return _ecommunity_ecom2str(ecom, format, 0, number); +} + +bool ecommunity_include_one(struct ecommunity *ecom, uint8_t *ptr) +{ +	uint32_t i; +	uint8_t *ecom_ptr; + +	for (i = 0; i < ecom->size; i++) { +		ecom_ptr = ecom->val + (i * ecom->unit_size); +		if (memcmp(ptr, ecom_ptr, ecom->unit_size) == 0) +			return true; +	} +	return false; +} +  bool ecommunity_include(struct ecommunity *e1, struct ecommunity *e2)  {  	uint32_t i, j; diff --git a/bgpd/bgp_ecommunity.h b/bgpd/bgp_ecommunity.h index 0e68b15807..d3708d53d4 100644 --- a/bgpd/bgp_ecommunity.h +++ b/bgpd/bgp_ecommunity.h @@ -379,9 +379,11 @@ extern unsigned int ecommunity_hash_make(const void *);  extern struct ecommunity *ecommunity_str2com(const char *, int, int);  extern struct ecommunity *ecommunity_str2com_ipv6(const char *str, int type,  						  int keyword_included); -extern char *ecommunity_ecom2str(struct ecommunity *, int, int); +extern char *ecommunity_ecom2str(struct ecommunity *ecom, int format, int filter); +extern char *ecommunity_ecom2str_one(struct ecommunity *ecom, int format, int number);  extern bool ecommunity_has_route_target(struct ecommunity *ecom);  extern void ecommunity_strfree(char **s); +extern bool ecommunity_include_one(struct ecommunity *ecom, uint8_t *ptr);  extern bool ecommunity_include(struct ecommunity *e1, struct ecommunity *e2);  extern bool ecommunity_match(const struct ecommunity *,  			     const struct ecommunity *); diff --git a/bgpd/bgp_routemap.c b/bgpd/bgp_routemap.c index fa8701dc50..f3192a4734 100644 --- a/bgpd/bgp_routemap.c +++ b/bgpd/bgp_routemap.c @@ -1358,6 +1358,65 @@ static const struct route_map_rule_cmd route_match_community_limit_cmd = {  	route_match_community_limit_compile, route_match_community_limit_free  }; +/* `match extcommunity-limit' */ + +/* Match function should return : + * - RMAP_MATCH if the bgp update extcommunity list count + * is less or equal to the configured limit. + * - RMAP_NOMATCH if the extcommunity list count is greater than the + * configured limit. + */ +static enum route_map_cmd_result_t +route_match_extcommunity_limit(void *rule, const struct prefix *prefix, void *object) +{ +	struct bgp_path_info *path = NULL; +	struct ecommunity *piextcomm = NULL, *pi6extcomm = NULL; +	uint16_t count = 0; +	uint16_t *limit_rule = rule; + +	path = (struct bgp_path_info *)object; + +	piextcomm = bgp_attr_get_ecommunity(path->attr); +	if (piextcomm) +		count = piextcomm->size; + +	pi6extcomm = bgp_attr_get_ipv6_ecommunity(path->attr); +	if (pi6extcomm) +		count += pi6extcomm->size; + +	if (count <= *limit_rule) +		return RMAP_MATCH; + +	return RMAP_NOMATCH; +} + +/* Route map `extcommunity-limit' match statement. */ +static void *route_match_extcommunity_limit_compile(const char *arg) +{ +	uint16_t *limit = NULL; +	char *end = NULL; + +	limit = XMALLOC(MTYPE_ROUTE_MAP_COMPILED, sizeof(uint16_t)); +	*limit = strtoul(arg, &end, 10); +	if (*end != '\0') { +		XFREE(MTYPE_ROUTE_MAP_COMPILED, limit); +		return NULL; +	} +	return limit; +} + +/* Free route map's compiled `community-limit' value. */ +static void route_match_extcommunity_limit_free(void *rule) +{ +	XFREE(MTYPE_ROUTE_MAP_COMPILED, rule); +} + +/* Route map commands for community limit matching. */ +static const struct route_map_rule_cmd route_match_extcommunity_limit_cmd = { +	"extcommunity-limit", route_match_extcommunity_limit, +	route_match_extcommunity_limit_compile, route_match_extcommunity_limit_free +}; +  static enum route_map_cmd_result_t  route_set_evpn_gateway_ip(void *rule, const struct prefix *prefix, void *object)  { @@ -1853,8 +1912,18 @@ route_match_ecommunity(void *rule, const struct prefix *prefix, void *object)  	if (!list)  		return RMAP_NOMATCH; -	if (ecommunity_list_match(bgp_attr_get_ecommunity(path->attr), list)) -		return RMAP_MATCH; +	if (rcom->exact) { +		if (ecommunity_list_exact_match(bgp_attr_get_ecommunity(path->attr), list)) +			return RMAP_MATCH; +	} else if (rcom->any) { +		if (!bgp_attr_get_ecommunity(path->attr)) +			return RMAP_OKAY; +		if (ecommunity_list_any_match(bgp_attr_get_ecommunity(path->attr), list)) +			return RMAP_MATCH; +	} else { +		if (ecommunity_list_match(bgp_attr_get_ecommunity(path->attr), list)) +			return RMAP_MATCH; +	}  	return RMAP_NOMATCH;  } @@ -1863,11 +1932,28 @@ route_match_ecommunity(void *rule, const struct prefix *prefix, void *object)  static void *route_match_ecommunity_compile(const char *arg)  {  	struct rmap_community *rcom; +	int len; +	char *p;  	rcom = XCALLOC(MTYPE_ROUTE_MAP_COMPILED, sizeof(struct rmap_community)); -	rcom->name = XSTRDUP(MTYPE_ROUTE_MAP_COMPILED, arg); -	rcom->name_hash = bgp_clist_hash_key(rcom->name); +	p = strchr(arg, ' '); +	if (p) { +		len = p - arg; +		rcom->name = XCALLOC(MTYPE_ROUTE_MAP_COMPILED, len + 1); +		memcpy(rcom->name, arg, len); +		p++; +		if (*p == 'e') +			rcom->exact = true; +		else +			rcom->any = true; +	} else { +		rcom->name = XSTRDUP(MTYPE_ROUTE_MAP_COMPILED, arg); +		rcom->exact = false; +		rcom->any = false; +	} + +	rcom->name_hash = bgp_clist_hash_key(rcom->name);  	return rcom;  } @@ -5861,16 +5947,19 @@ DEFUN_YANG(  DEFPY_YANG (match_ecommunity,  	    match_ecommunity_cmd, -            "match extcommunity <(1-99)|(100-500)|EXTCOMMUNITY_LIST_NAME>", +            "match extcommunity <(1-99)|(100-500)|EXTCOMMUNITY_LIST_NAME> [<exact-match$exact|any$any>]",  	    MATCH_STR  	    "Match BGP/VPN extended community list\n"  	    "Extended community-list number (standard)\n"  	    "Extended community-list number (expanded)\n" -	    "Extended community-list name\n") +	    "Extended community-list name\n" +	    "Do exact matching of communities\n" +	    "Do matching of any community\n")  {  	const char *xpath =  		"./match-condition[condition='frr-bgp-route-map:match-extcommunity']";  	char xpath_value[XPATH_MAXLEN]; +	char xpath_match[XPATH_MAXLEN];  	int idx_comm_list = 2;  	nb_cli_enqueue_change(vty, xpath, NB_OP_CREATE, NULL); @@ -5881,19 +5970,57 @@ DEFPY_YANG (match_ecommunity,  		xpath);  	nb_cli_enqueue_change(vty, xpath_value, NB_OP_MODIFY, argv[idx_comm_list]->arg); +	snprintf(xpath_match, sizeof(xpath_match), +		 "%s/rmap-match-condition/frr-bgp-route-map:comm-list/comm-list-name-exact-match", +		 xpath); +	if (exact) +		nb_cli_enqueue_change(vty, xpath_match, NB_OP_MODIFY, "true"); +	else +		nb_cli_enqueue_change(vty, xpath_match, NB_OP_MODIFY, "false"); + +	snprintf(xpath_match, sizeof(xpath_match), +		 "%s/rmap-match-condition/frr-bgp-route-map:comm-list/comm-list-name-any", xpath); +	if (any) +		nb_cli_enqueue_change(vty, xpath_match, NB_OP_MODIFY, "true"); +	else +		nb_cli_enqueue_change(vty, xpath_match, NB_OP_MODIFY, "false"); + +	return nb_cli_apply_changes(vty, NULL); +} + + +DEFPY_YANG( +	match_extcommunity_limit, match_extcommunity_limit_cmd, +	"[no$no] match extcommunity-limit ![(0-65535)$limit]", +	NO_STR +	MATCH_STR +	"Match BGP extended community limit\n" +	"Extended community limit number\n") +{ +	const char *xpath = +		"./match-condition[condition='frr-bgp-route-map:match-extcommunity-limit']"; +	char xpath_value[XPATH_MAXLEN]; + +	nb_cli_enqueue_change(vty, xpath, no ? NB_OP_DESTROY : NB_OP_CREATE, NULL); +	snprintf(xpath_value, sizeof(xpath_value), +		 "%s/rmap-match-condition/frr-bgp-route-map:extcommunity-limit", xpath); + +	nb_cli_enqueue_change(vty, xpath_value, no ? NB_OP_DESTROY : NB_OP_MODIFY, limit_str);  	return nb_cli_apply_changes(vty, NULL);  }  DEFUN_YANG (no_match_ecommunity,  	    no_match_ecommunity_cmd, -	    "no match extcommunity [<(1-99)|(100-500)|EXTCOMMUNITY_LIST_NAME>]", +	    "no match extcommunity [<(1-99)|(100-500)|EXTCOMMUNITY_LIST_NAME> [<exact-match$exact|any$any>]]",  	    NO_STR  	    MATCH_STR  	    "Match BGP/VPN extended community list\n"  	    "Extended community-list number (standard)\n"  	    "Extended community-list number (expanded)\n" -	    "Extended community-list name\n") +	    "Extended community-list name\n" +	    "Do exact matching of communities\n" +	    "Do matching of any community\n")  {  	const char *xpath =  		"./match-condition[condition='frr-bgp-route-map:match-extcommunity']"; @@ -7980,6 +8107,7 @@ void bgp_route_map_init(void)  	route_map_install_match(&route_match_evpn_route_type_cmd);  	route_map_install_match(&route_match_evpn_rd_cmd);  	route_map_install_match(&route_match_community_limit_cmd); +	route_map_install_match(&route_match_extcommunity_limit_cmd);  	route_map_install_match(&route_match_evpn_default_route_cmd);  	route_map_install_match(&route_match_vrl_source_vrf_cmd); @@ -8053,6 +8181,7 @@ void bgp_route_map_init(void)  	install_element(RMAP_NODE, &match_community_cmd);  	install_element(RMAP_NODE, &no_match_community_cmd);  	install_element(RMAP_NODE, &match_community_limit_cmd); +	install_element(RMAP_NODE, &match_extcommunity_limit_cmd);  	install_element(RMAP_NODE, &match_lcommunity_cmd);  	install_element(RMAP_NODE, &no_match_lcommunity_cmd);  	install_element(RMAP_NODE, &match_ecommunity_cmd); diff --git a/bgpd/bgp_routemap_nb.c b/bgpd/bgp_routemap_nb.c index 4645593441..9372647f3d 100644 --- a/bgpd/bgp_routemap_nb.c +++ b/bgpd/bgp_routemap_nb.c @@ -222,6 +222,13 @@ const struct frr_yang_module_info frr_bgp_route_map_info = {  			}  		},  		{ +			.xpath = "/frr-route-map:lib/route-map/entry/match-condition/rmap-match-condition/frr-bgp-route-map:extcommunity-limit", +			.cbs = { +				.modify = lib_route_map_entry_match_condition_rmap_match_condition_extcommunity_limit_modify, +				.destroy = lib_route_map_entry_match_condition_rmap_match_condition_extcommunity_limit_destroy, +			} +		}, +		{  			.xpath = "/frr-route-map:lib/route-map/entry/set-action/rmap-set-action/frr-bgp-route-map:extcommunity-rt",  			.cbs = {  				.modify = lib_route_map_entry_set_action_rmap_set_action_extcommunity_rt_modify, diff --git a/bgpd/bgp_routemap_nb.h b/bgpd/bgp_routemap_nb.h index 45689242a0..f04429078f 100644 --- a/bgpd/bgp_routemap_nb.h +++ b/bgpd/bgp_routemap_nb.h @@ -76,6 +76,10 @@ int lib_route_map_entry_match_condition_rmap_match_condition_community_limit_mod  	struct nb_cb_modify_args *args);  int lib_route_map_entry_match_condition_rmap_match_condition_community_limit_destroy(  	struct nb_cb_destroy_args *args); +int lib_route_map_entry_match_condition_rmap_match_condition_extcommunity_limit_modify( +	struct nb_cb_modify_args *args); +int lib_route_map_entry_match_condition_rmap_match_condition_extcommunity_limit_destroy( +	struct nb_cb_destroy_args *args);  int lib_route_map_entry_match_condition_rmap_match_condition_comm_list_create(  	struct nb_cb_create_args *args);  int lib_route_map_entry_match_condition_rmap_match_condition_comm_list_destroy( diff --git a/bgpd/bgp_routemap_nb_config.c b/bgpd/bgp_routemap_nb_config.c index 223c416dc5..5f5274c5e1 100644 --- a/bgpd/bgp_routemap_nb_config.c +++ b/bgpd/bgp_routemap_nb_config.c @@ -1667,6 +1667,57 @@ int lib_route_map_entry_set_action_rmap_set_action_distance_destroy(  }  /* + * XPath: /frr-route-map:lib/route-map/entry/match-condition/rmap-match-condition/frr-bgp-route-map:extcommunity-limit + */ +int lib_route_map_entry_match_condition_rmap_match_condition_extcommunity_limit_modify( +	struct nb_cb_modify_args *args) +{ +	struct routemap_hook_context *rhc; +	const char *limit; +	enum rmap_compile_rets ret; + +	switch (args->event) { +	case NB_EV_VALIDATE: +	case NB_EV_PREPARE: +	case NB_EV_ABORT: +		break; +	case NB_EV_APPLY: +		/* Add configuration. */ +		rhc = nb_running_get_entry(args->dnode, NULL, true); +		limit = yang_dnode_get_string(args->dnode, NULL); + +		rhc->rhc_mhook = bgp_route_match_delete; +		rhc->rhc_rule = "extcommunity-limit"; +		rhc->rhc_event = RMAP_EVENT_MATCH_DELETED; + +		ret = bgp_route_match_add(rhc->rhc_rmi, "extcommunity-limit", limit, +					  RMAP_EVENT_MATCH_ADDED, args->errmsg, args->errmsg_len); + +		if (ret != RMAP_COMPILE_SUCCESS) { +			rhc->rhc_mhook = NULL; +			return NB_ERR_INCONSISTENCY; +		} +	} + +	return NB_OK; +} + +int lib_route_map_entry_match_condition_rmap_match_condition_extcommunity_limit_destroy( +	struct nb_cb_destroy_args *args) +{ +	switch (args->event) { +	case NB_EV_VALIDATE: +	case NB_EV_PREPARE: +	case NB_EV_ABORT: +		break; +	case NB_EV_APPLY: +		return lib_route_map_entry_match_destroy(args); +	} + +	return NB_OK; +} + +/*   * XPath:   * /frr-route-map:lib/route-map/entry/set-action/rmap-set-action/frr-bgp-route-map:extcommunity-rt   */ diff --git a/doc/user/bgp.rst b/doc/user/bgp.rst index cba539708a..ca676cd7ba 100644 --- a/doc/user/bgp.rst +++ b/doc/user/bgp.rst @@ -2918,7 +2918,22 @@ Extended Community Lists  BGP Extended Communities in Route Map  """"""""""""""""""""""""""""""""""""" -.. clicmd:: match extcommunity WORD +.. clicmd:: match extcommunity WORD [exact-match|any] + +   This command perform match to BGP updates using extended community list WORD. +   When the one of BGP extended communities value match to the one of the extended +   communities value in community list, it is match. When ``exact-match`` keyword +   is specified, match happens only when BGP updates have completely same extended +   communities value specified in the extended community list. When ``any`` keyword +   is set, match happens when any of the extended communities of the BGP updates +   matches an extended community of the specified list. + + .. clicmd:: match extcommunity-limit (0-65535) + +   This command matches BGP updates that use extended community list and IPv6 +   extended community list, and with an extended community list count less or +   equal than the defined limit. Setting extended community-limit to 0 will +   only match BGP updates with no extended community.  .. clicmd:: set extcommunity none diff --git a/lib/routemap.h b/lib/routemap.h index 1c02348313..0e41a7a856 100644 --- a/lib/routemap.h +++ b/lib/routemap.h @@ -317,6 +317,7 @@ DECLARE_QOBJ_TYPE(route_map);  	(strmatch(C, "frr-bgp-route-map:match-large-community"))  #define IS_MATCH_EXTCOMMUNITY(C)                                               \  	(strmatch(C, "frr-bgp-route-map:match-extcommunity")) +#define IS_MATCH_EXTCOMMUNITY_LIMIT(C) (strmatch(C, "frr-bgp-route-map:match-extcommunity-limit"))  #define IS_MATCH_IPV4_NH(C)                                                    \  	(strmatch(C, "frr-bgp-route-map:ipv4-nexthop"))  #define IS_MATCH_IPV6_NH(C)                                                    \ diff --git a/lib/routemap_cli.c b/lib/routemap_cli.c index eb01709707..f045bc7e4c 100644 --- a/lib/routemap_cli.c +++ b/lib/routemap_cli.c @@ -715,6 +715,10 @@ void route_map_condition_show(struct vty *vty, const struct lyd_node *dnode,  			yang_dnode_get_string(  				dnode,  				"./rmap-match-condition/frr-bgp-route-map:rpki")); +	} else if (IS_MATCH_EXTCOMMUNITY_LIMIT(condition)) { +		vty_out(vty, " match extcommunity-limit %s\n", +			yang_dnode_get_string(dnode, +					      "./rmap-match-condition/frr-bgp-route-map:extcommunity-limit"));  	} else if (IS_MATCH_RPKI_EXTCOMMUNITY(condition)) {  		vty_out(vty, " match rpki-extcommunity %s\n",  			yang_dnode_get_string( @@ -843,10 +847,18 @@ void route_map_condition_show(struct vty *vty, const struct lyd_node *dnode,  			vty_out(vty, " any");  		vty_out(vty, "\n");  	} else if (IS_MATCH_EXTCOMMUNITY(condition)) { -		vty_out(vty, " match extcommunity %s\n", +		vty_out(vty, " match extcommunity %s",  			yang_dnode_get_string(  				dnode,  				"./rmap-match-condition/frr-bgp-route-map:comm-list/comm-list-name")); +		if (yang_dnode_get_bool( +			    dnode, +			    "./rmap-match-condition/frr-bgp-route-map:comm-list/comm-list-name-exact-match")) +			vty_out(vty, " exact-match"); +		if (yang_dnode_get_bool(dnode, +					"./rmap-match-condition/frr-bgp-route-map:comm-list/comm-list-name-any")) +			vty_out(vty, " any"); +		vty_out(vty, "\n");  	} else if (IS_MATCH_IPV4_NH(condition)) {  		vty_out(vty, " match ip next-hop address %s\n",  			yang_dnode_get_string( diff --git a/tests/topotests/bgp_ecomm_list_match/r1/frr.conf b/tests/topotests/bgp_ecomm_list_match/r1/frr.conf new file mode 100644 index 0000000000..845f64ff95 --- /dev/null +++ b/tests/topotests/bgp_ecomm_list_match/r1/frr.conf @@ -0,0 +1,51 @@ +! +interface lo + ip address 172.16.255.1/32 + ip address 172.16.255.2/32 + ip address 172.16.255.3/32 + ip address 172.16.255.4/32 + ip address 172.16.255.5/32 + ip address 172.16.255.6/32 +! +interface r1-eth0 + ip address 192.168.0.1/24 +! +ip forwarding +! +router bgp 65001 + no bgp ebgp-requires-policy + neighbor 192.168.0.2 remote-as external + neighbor 192.168.0.2 timers 1 3 + neighbor 192.168.0.2 timers connect 1 + address-family ipv4 +  redistribute connected +  neighbor 192.168.0.2 route-map r2 out + exit-address-family +! +ip prefix-list p1 seq 5 permit 172.16.255.1/32 +ip prefix-list p3 seq 5 permit 172.16.255.3/32 +ip prefix-list p4 seq 5 permit 172.16.255.4/32 +ip prefix-list p5 seq 5 permit 172.16.255.5/32 +ip prefix-list p6 seq 5 permit 172.16.255.6/32 +! +route-map r2 permit 10 + match ip address prefix-list p1 + set extcommunity rt 65001:1 65001:2 +route-map r2 permit 20 + match ip address prefix-list p3 + set extcommunity rt 65001:3 +route-map r2 permit 30 + match ip address prefix-list p4 + set extcommunity rt 65001:10 65001:12 65001:13 +exit +route-map r2 permit 40 + match ip address prefix-list p5 + set extcommunity rt 65001:13 65001:14 +exit +route-map r2 permit 50 + match ip address prefix-list p6 + set extcommunity rt 65001:16 65001:17 65001:18 65001:19 +exit +route-map r2 permit 60 +exit +! diff --git a/tests/topotests/bgp_ecomm_list_match/r2/frr.conf b/tests/topotests/bgp_ecomm_list_match/r2/frr.conf new file mode 100644 index 0000000000..ed05410bd3 --- /dev/null +++ b/tests/topotests/bgp_ecomm_list_match/r2/frr.conf @@ -0,0 +1,32 @@ +! +interface r2-eth0 + ip address 192.168.0.2/24 +! +interface r2-eth1 + ip address 192.168.1.2/24 +! +ip forwarding +! +!debug bgp updates +! +router bgp 65002 + no bgp ebgp-requires-policy + neighbor 192.168.0.1 remote-as external + neighbor 192.168.0.1 timers 1 3 + neighbor 192.168.0.1 timers connect 1 + neighbor 192.168.1.3 remote-as external + neighbor 192.168.1.3 timers 1 3 + neighbor 192.168.1.3 timers connect 1 + address-family ipv4 +  neighbor 192.168.0.1 route-map r1 in +  neighbor 192.168.0.1 soft-reconfiguration inbound + exit-address-family +! +bgp extcommunity-list 1 seq 5 permit rt 65001:1 rt 65001:2 +bgp extcommunity-list 1 seq 10 permit rt 65001:3 +! +route-map r1 deny 10 + match extcommunity 1 +route-map r1 permit 20 +exit +! diff --git a/tests/topotests/bgp_ecomm_list_match/r3/frr.conf b/tests/topotests/bgp_ecomm_list_match/r3/frr.conf new file mode 100644 index 0000000000..684afe068c --- /dev/null +++ b/tests/topotests/bgp_ecomm_list_match/r3/frr.conf @@ -0,0 +1,26 @@ +! +interface r3-eth0 + ip address 192.168.1.3/24 +! +ip forwarding +! +!debug bgp updates +! +router bgp 65003 + no bgp ebgp-requires-policy + neighbor 192.168.1.2 remote-as external + neighbor 192.168.1.2 timers 1 3 + neighbor 192.168.1.2 timers connect 1 + address-family ipv4 +  neighbor 192.168.1.2 route-map r1 in +  neighbor 192.168.1.2 soft-reconfiguration inbound + exit-address-family +! +bgp extcommunity-list 2 seq 10 permit rt 65001:12 +! +route-map r1 deny 10 + match extcommunity 2 any +exit +route-map r1 permit 20 +exit +! diff --git a/tests/topotests/bgp_ecomm_list_match/test_bgp_ecomm_list_match.py b/tests/topotests/bgp_ecomm_list_match/test_bgp_ecomm_list_match.py new file mode 100644 index 0000000000..670a600a8c --- /dev/null +++ b/tests/topotests/bgp_ecomm_list_match/test_bgp_ecomm_list_match.py @@ -0,0 +1,198 @@ +#!/usr/bin/env python +# SPDX-License-Identifier: ISC + +# +# Copyright (c) 2025 by 6WIND +# + +""" +Check if BGP extcommunity-list works as OR if multiple community entries specified, +like: + +bgp extcommunity-list 1 seq 5 permit rt 65001:1 rt 65002:2 +bgp community-list 1 seq 10 permit rt 65001:3 +! +route-map test deny 10 + match extcommunity 1 +route-map test permit 20 + +Here, we should deny routes in/out if the path has: +(ty 65001:1 AND rt 65001:2) OR rt 65001:3. +""" + +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, logger +from lib.common_config import step + +pytestmark = [pytest.mark.bgpd] + + +def build_topo(tgen): +    for routern in range(1, 4): +        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["r3"]) +    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 tgen.routers().items(): +        logger.info("Loading router %s" % rname) +        router.load_frr_config(os.path.join(CWD, "{}/frr.conf".format(rname))) + +    # Initialize all routers. +    tgen.start_router() + + +def teardown_module(mod): +    tgen = get_topogen() +    tgen.stop_topology() + + +def test_bgp_extcomm_list_match(): +    tgen = get_topogen() + +    if tgen.routers_have_failure(): +        pytest.skip(tgen.errors) + +    router = tgen.gears["r2"] + +    def _bgp_converge(): +        output = json.loads( +            router.vtysh_cmd( +                "show bgp ipv4 unicast neighbors 192.168.0.1 filtered-routes json" +            ) +        ) +        expected = { +            "receivedRoutes": { +                "172.16.255.1/32": { +                    "path": "65001", +                }, +                "172.16.255.3/32": { +                    "path": "65001", +                }, +            } +        } +        return topotest.json_cmp(output, expected) + +    step("Initial BGP converge between R1 and R2") +    test_func = functools.partial(_bgp_converge) +    _, result = topotest.run_and_expect(test_func, None, count=60, wait=0.5) +    assert result is None, "Failed to filter BGP UPDATES with community-list on R2" + + +def test_bgp_extcomm_list_match_any(): +    tgen = get_topogen() + +    if tgen.routers_have_failure(): +        pytest.skip(tgen.errors) + +    router = tgen.gears["r3"] + +    def _bgp_converge(): +        output = json.loads( +            router.vtysh_cmd( +                "show bgp ipv4 unicast neighbors 192.168.1.2 filtered-routes json" +            ) +        ) +        expected = { +            "receivedRoutes": { +                "172.16.255.4/32": { +                    "path": "65002 65001", +                }, +            } +        } +        return topotest.json_cmp(output, expected) + +    step("Initial BGP converge between R3 and R2") +    test_func = functools.partial(_bgp_converge) +    _, result = topotest.run_and_expect(test_func, None, count=60, wait=0.5) +    assert result is None, "Failed to filter BGP UPDATES with community-list on R3" + + +def test_bgp_extcomm_list_limit_match(): +    tgen = get_topogen() + +    if tgen.routers_have_failure(): +        pytest.skip(tgen.errors) + +    router = tgen.gears["r3"] +    router.vtysh_cmd( +        """ +        configure terminal +        route-map r1 permit 20 +        match extcommunity-limit 3 +        """ +    ) + +    def _bgp_count(): +        output = json.loads(router.vtysh_cmd("show bgp ipv4 json")) +        expected = { +            "vrfName": "default", +            "routerId": "192.168.1.3", +            "localAS": 65003, +            "totalRoutes": 3, +            "totalPaths": 3, +        } +        return topotest.json_cmp(output, expected) + +    step("Check that 3 routes have been received on R3") +    test_func = functools.partial(_bgp_count) +    _, result = topotest.run_and_expect(test_func, None, count=60, wait=0.5) +    assert result is None, "Failed to check that 3 routes have been received on R3" + + +def test_bgp_comm_list_reset_limit_match(): +    tgen = get_topogen() + +    if tgen.routers_have_failure(): +        pytest.skip(tgen.errors) + +    router = tgen.gears["r3"] +    router.vtysh_cmd( +        """ +        configure terminal +        route-map r1 permit 20 +        no match extcommunity-limit +        """ +    ) + +    def _bgp_count_two(): +        output = json.loads(router.vtysh_cmd("show bgp ipv4 json")) +        expected = { +            "vrfName": "default", +            "routerId": "192.168.1.3", +            "localAS": 65003, +            "totalRoutes": 4, +            "totalPaths": 4, +        } +        return topotest.json_cmp(output, expected) + +    step("Check that 4 routes have been received on R3") +    test_func = functools.partial(_bgp_count_two) +    _, result = topotest.run_and_expect(test_func, None, count=60, wait=0.5) +    assert result is None, "Failed to check that 4 routes have been received on R3" + + +if __name__ == "__main__": +    args = ["-s"] + sys.argv[1:] +    sys.exit(pytest.main(args)) diff --git a/yang/frr-bgp-route-map.yang b/yang/frr-bgp-route-map.yang index efb0b2fa08..12b175a5ea 100644 --- a/yang/frr-bgp-route-map.yang +++ b/yang/frr-bgp-route-map.yang @@ -166,6 +166,12 @@ module frr-bgp-route-map {        "Match BGP extcommunity list";    } +  identity match-extcommunity-limit { +    base frr-route-map:rmap-match-type; +    description +      "Match BGP extcommunity limit count"; +  } +    identity as-path-list {      base frr-route-map:rmap-match-type;      description @@ -814,7 +820,18 @@ identity set-extcommunity-color {          "Match BGP updates when the list of communities count is less than the configured limit.";        leaf community-limit {          type uint16 { -          range "1..1024"; +          range "0..1024"; +        } +      } +    } + +    case extcommunity-limit { +      when "derived-from-or-self(../frr-route-map:condition, 'frr-bgp-route-map:match-extcommunity-limit')"; +      description +        "Match BGP updates when the list of extended communities count is less than the configured limit."; +      leaf extcommunity-limit { +        type uint16 { +          range "0..1024";          }        }      }  | 
