summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorDonatas Abraitis <donatas@opensourcerouting.org>2025-02-28 10:08:27 +0200
committerGitHub <noreply@github.com>2025-02-28 10:08:27 +0200
commitd49561e32a8ea870a4597e56591cd4ca44abaa7c (patch)
treeb01db3228135f9af3b14a573b3ec8808f987e736
parente27631e10ae0f45ccada53e556c6c1331dbadd19 (diff)
parent2b71dc2d91976f947fe603fc8ec8f7d435fcac50 (diff)
Merge pull request #18159 from pguibert6WIND/bgp_ecommlist_count
Bgp ecommlist count
-rw-r--r--bgpd/bgp_clist.c67
-rw-r--r--bgpd/bgp_clist.h2
-rw-r--r--bgpd/bgp_ecommunity.c31
-rw-r--r--bgpd/bgp_ecommunity.h4
-rw-r--r--bgpd/bgp_routemap.c145
-rw-r--r--bgpd/bgp_routemap_nb.c7
-rw-r--r--bgpd/bgp_routemap_nb.h4
-rw-r--r--bgpd/bgp_routemap_nb_config.c51
-rw-r--r--doc/user/bgp.rst17
-rw-r--r--lib/routemap.h1
-rw-r--r--lib/routemap_cli.c14
-rw-r--r--tests/topotests/bgp_ecomm_list_match/r1/frr.conf51
-rw-r--r--tests/topotests/bgp_ecomm_list_match/r2/frr.conf32
-rw-r--r--tests/topotests/bgp_ecomm_list_match/r3/frr.conf26
-rw-r--r--tests/topotests/bgp_ecomm_list_match/test_bgp_ecomm_list_match.py198
-rw-r--r--yang/frr-bgp-route-map.yang19
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";
}
}
}