Merge pull request #18159 from pguibert6WIND/bgp_ecommlist_count

Bgp ecommlist count
This commit is contained in:
Donatas Abraitis 2025-02-28 10:08:27 +02:00 committed by GitHub
commit d49561e32a
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
16 changed files with 655 additions and 14 deletions

View file

@ -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)

View file

@ -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);

View file

@ -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;

View file

@ -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 *);

View file

@ -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 (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);

View file

@ -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 = {

View file

@ -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(

View file

@ -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

View file

@ -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

View file

@ -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) \

View file

@ -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(

View file

@ -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
!

View file

@ -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
!

View file

@ -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
!

View file

@ -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))

View file

@ -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";
}
}
}