Merge pull request #17836 from pguibert6WIND/limit_comm_list_count

limit community list count
This commit is contained in:
Donatas Abraitis 2025-01-17 12:44:24 +02:00 committed by GitHub
commit 705e6f881b
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
11 changed files with 242 additions and 0 deletions

View file

@ -1303,6 +1303,61 @@ static const struct route_map_rule_cmd route_match_evpn_rd_cmd = {
route_match_rd_free
};
/* `match community-limit' */
/* Match function should return :
* - RMAP_MATCH if the bgp update community list count
* is less or equal to the configured limit.
* - RMAP_NOMATCH if the community list count is greater than the
* configured limit.
*/
static enum route_map_cmd_result_t
route_match_community_limit(void *rule, const struct prefix *prefix, void *object)
{
struct bgp_path_info *path = NULL;
struct community *picomm = NULL;
uint16_t count = 0;
uint16_t *limit_rule = rule;
path = (struct bgp_path_info *)object;
picomm = bgp_attr_get_community(path->attr);
if (picomm)
count = picomm->size;
if (count <= *limit_rule)
return RMAP_MATCH;
return RMAP_NOMATCH;
}
/* Route map `community-limit' match statement. */
static void *route_match_community_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_community_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_community_limit_cmd = {
"community-limit", route_match_community_limit,
route_match_community_limit_compile, route_match_community_limit_free
};
static enum route_map_cmd_result_t
route_set_evpn_gateway_ip(void *rule, const struct prefix *prefix, void *object)
{
@ -5707,6 +5762,25 @@ DEFPY_YANG(
return nb_cli_apply_changes(vty, NULL);
}
DEFPY_YANG(
match_community_limit, match_community_limit_cmd,
"[no$no] match community-limit ![(0-65535)$limit]",
NO_STR
MATCH_STR
"Match BGP community limit\n"
"Community limit number\n")
{
const char *xpath = "./match-condition[condition='frr-bgp-route-map:match-community-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:community-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_community, no_match_community_cmd,
"no match community [<(1-99)|(100-500)|COMMUNITY_LIST_NAME> [<exact-match$exact|any$any>]]",
@ -7905,6 +7979,7 @@ void bgp_route_map_init(void)
route_map_install_match(&route_match_evpn_vni_cmd);
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_evpn_default_route_cmd);
route_map_install_match(&route_match_vrl_source_vrf_cmd);
@ -7977,6 +8052,7 @@ void bgp_route_map_init(void)
install_element(RMAP_NODE, &no_match_alias_cmd);
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_lcommunity_cmd);
install_element(RMAP_NODE, &no_match_lcommunity_cmd);
install_element(RMAP_NODE, &match_ecommunity_cmd);

View file

@ -165,6 +165,13 @@ const struct frr_yang_module_info frr_bgp_route_map_info = {
.destroy = lib_route_map_entry_match_condition_rmap_match_condition_route_distinguisher_destroy,
}
},
{
.xpath = "/frr-route-map:lib/route-map/entry/match-condition/rmap-match-condition/frr-bgp-route-map:community-limit",
.cbs = {
.modify = lib_route_map_entry_match_condition_rmap_match_condition_community_limit_modify,
.destroy = lib_route_map_entry_match_condition_rmap_match_condition_community_limit_destroy,
}
},
{
.xpath = "/frr-route-map:lib/route-map/entry/match-condition/rmap-match-condition/frr-bgp-route-map:comm-list",
.cbs = {

View file

@ -72,6 +72,10 @@ int lib_route_map_entry_match_condition_rmap_match_condition_evpn_route_type_mod
int lib_route_map_entry_match_condition_rmap_match_condition_evpn_route_type_destroy(struct nb_cb_destroy_args *args);
int lib_route_map_entry_match_condition_rmap_match_condition_route_distinguisher_modify(struct nb_cb_modify_args *args);
int lib_route_map_entry_match_condition_rmap_match_condition_route_distinguisher_destroy(struct nb_cb_destroy_args *args);
int lib_route_map_entry_match_condition_rmap_match_condition_community_limit_modify(
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_comm_list_create(
struct nb_cb_create_args *args);
int lib_route_map_entry_match_condition_rmap_match_condition_comm_list_destroy(

View file

@ -1274,6 +1274,57 @@ lib_route_map_entry_match_condition_rmap_match_condition_route_distinguisher_des
return NB_OK;
}
/*
* XPath: /frr-route-map:lib/route-map/entry/match-condition/rmap-match-condition/frr-bgp-route-map:community-limit
*/
int lib_route_map_entry_match_condition_rmap_match_condition_community_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 = "community-limit";
rhc->rhc_event = RMAP_EVENT_MATCH_DELETED;
ret = bgp_route_match_add(rhc->rhc_rmi, "community-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_community_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/match-condition/rmap-match-condition/frr-bgp-route-map:comm-list
*/

View file

@ -2693,6 +2693,12 @@ The following commands can be used in route maps:
happen only when BGP updates have completely same communities value
specified in the community list.
.. clicmd:: match community-limit (0-65535)
This command matches BGP updates that use community list, and with a community
list count less or equal than the defined limit. Setting community-limit to 0
will only match BGP updates with no community.
.. clicmd:: set community <none|COMMUNITY> additive
This command sets the community value in BGP updates. If the attribute is

View file

@ -310,6 +310,7 @@ DECLARE_QOBJ_TYPE(route_map);
(strmatch(C, "frr-bgp-route-map:ip-route-source"))
#define IS_MATCH_ROUTE_SRC_PL(C) \
(strmatch(C, "frr-bgp-route-map:ip-route-source-prefix-list"))
#define IS_MATCH_COMMUNITY_LIMIT(C) (strmatch(C, "frr-bgp-route-map:match-community-limit"))
#define IS_MATCH_COMMUNITY(C) \
(strmatch(C, "frr-bgp-route-map:match-community"))
#define IS_MATCH_LCOMMUNITY(C) \

View file

@ -810,6 +810,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:list-name"));
} else if (IS_MATCH_COMMUNITY_LIMIT(condition)) {
vty_out(vty, " match community-limit %s\n",
yang_dnode_get_string(dnode,
"./rmap-match-condition/frr-bgp-route-map:community-limit"));
} else if (IS_MATCH_COMMUNITY(condition)) {
vty_out(vty, " match community %s",
yang_dnode_get_string(

View file

@ -12,6 +12,8 @@ router bgp 65001
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
@ -24,5 +26,13 @@ route-map r2 permit 30
set community 65001:10 65001:12 65001:13
exit
route-map r2 permit 40
match ip address prefix-list p5
set community 65001:13 65001:14
exit
route-map r2 permit 50
match ip address prefix-list p6
set community 65001:16 65001:17 65001:18 65001:19
exit
route-map r2 permit 60
exit
!

View file

@ -4,6 +4,8 @@ interface lo
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

View file

@ -133,6 +133,70 @@ def test_bgp_comm_list_match_any():
assert result is None, "Failed to filter BGP UPDATES with community-list on R3"
def test_bgp_comm_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 community-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 community-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

@ -148,6 +148,12 @@ module frr-bgp-route-map {
"Match BGP community list";
}
identity match-community-limit {
base frr-route-map:rmap-match-type;
description
"Match BGP community limit count";
}
identity match-large-community {
base frr-route-map:rmap-match-type;
description
@ -802,6 +808,17 @@ identity set-extcommunity-color {
}
}
case community-limit {
when "derived-from-or-self(../frr-route-map:condition, 'frr-bgp-route-map:match-community-limit')";
description
"Match BGP updates when the list of communities count is less than the configured limit.";
leaf community-limit {
type uint16 {
range "1..1024";
}
}
}
case comm-list-name {
when "derived-from-or-self(../frr-route-map:condition, 'frr-bgp-route-map:match-community') or "
+ "derived-from-or-self(../frr-route-map:condition, 'frr-bgp-route-map:match-large-community') or "