From 52b1db86dcbdf57cdbf99240d8b1ec1c4450a77e Mon Sep 17 00:00:00 2001 From: Philippe Guibert Date: Fri, 14 Feb 2025 10:27:11 +0100 Subject: [PATCH] bgpd: add match ecommunity options The exact-match and the any options are missing for the extended communities. Add missing options that are present on the match operations for communities and large-communities. > route-map rmap permit 1 > match extcommunity 1 > exit > ! > route-map rmap permit 2 > match extcommunity 2 any > exit > ! > route-map rmap permit 3 > match extcommunity 3 exact-match > exit Signed-off-by: Philippe Guibert --- bgpd/bgp_clist.c | 67 +++++++++++++++++++++++++++++++++++++++++++ bgpd/bgp_clist.h | 2 ++ bgpd/bgp_ecommunity.c | 31 ++++++++++++++++++-- bgpd/bgp_ecommunity.h | 4 ++- bgpd/bgp_routemap.c | 63 ++++++++++++++++++++++++++++++++++------ doc/user/bgp.rst | 12 ++++++-- lib/routemap_cli.c | 10 ++++++- 7 files changed, 175 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 17b1ba730d..f3192a4734 100644 --- a/bgpd/bgp_routemap.c +++ b/bgpd/bgp_routemap.c @@ -1912,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; } @@ -1922,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; } @@ -5920,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> []", 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); @@ -5940,6 +5970,21 @@ 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); } @@ -5967,13 +6012,15 @@ DEFPY_YANG( 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> []]", 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']"; diff --git a/doc/user/bgp.rst b/doc/user/bgp.rst index 5286acd792..ca676cd7ba 100644 --- a/doc/user/bgp.rst +++ b/doc/user/bgp.rst @@ -2918,9 +2918,17 @@ Extended Community Lists BGP Extended Communities in Route Map """"""""""""""""""""""""""""""""""""" -.. clicmd:: match extcommunity WORD +.. clicmd:: match extcommunity WORD [exact-match|any] -.. clicmd:: match extcommunity-limit (0-65535) + 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 diff --git a/lib/routemap_cli.c b/lib/routemap_cli.c index a59d504287..f045bc7e4c 100644 --- a/lib/routemap_cli.c +++ b/lib/routemap_cli.c @@ -847,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(