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> []", 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> []]", 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 @@ -221,6 +221,13 @@ const struct frr_yang_module_info frr_bgp_route_map_info = { .destroy = 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", + .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 = { 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 @@ -1666,6 +1666,57 @@ int lib_route_map_entry_set_action_rmap_set_action_distance_destroy( return NB_OK; } +/* + * 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"; } } }