diff --git a/bgpd/bgp_aspath.c b/bgpd/bgp_aspath.c index 4ea81216bf..2e2248cd72 100644 --- a/bgpd/bgp_aspath.c +++ b/bgpd/bgp_aspath.c @@ -1231,6 +1231,46 @@ bool aspath_private_as_check(struct aspath *aspath) return true; } +/* Replace all ASN instances of the regex rule with our own ASN */ +struct aspath *aspath_replace_regex_asn(struct aspath *aspath, + struct as_list *acl_list, as_t our_asn) +{ + struct aspath *new; + struct assegment *cur_seg; + struct as_list *cur_as_list; + struct as_filter *cur_as_filter; + char str_buf[ASPATH_STR_DEFAULT_LEN]; + uint32_t i; + + new = aspath_dup(aspath); + cur_seg = new->segments; + + while (cur_seg) { + cur_as_list = acl_list; + while (cur_as_list) { + cur_as_filter = cur_as_list->head; + while (cur_as_filter) { + for (i = 0; i < cur_seg->length; i++) { + snprintfrr(str_buf, + ASPATH_STR_DEFAULT_LEN, + ASN_FORMAT(new->asnotation), + &cur_seg->as[i]); + if (!regexec(cur_as_filter->reg, + str_buf, 0, NULL, 0)) + cur_seg->as[i] = our_asn; + } + cur_as_filter = cur_as_filter->next; + } + cur_as_list = cur_as_list->next; + } + cur_seg = cur_seg->next; + } + + aspath_str_update(new, false); + return new; +} + + /* Replace all instances of the target ASN with our own ASN */ struct aspath *aspath_replace_specific_asn(struct aspath *aspath, as_t target_asn, as_t our_asn) diff --git a/bgpd/bgp_aspath.h b/bgpd/bgp_aspath.h index b1a61d5b99..ebfc7d087d 100644 --- a/bgpd/bgp_aspath.h +++ b/bgpd/bgp_aspath.h @@ -107,6 +107,9 @@ extern unsigned int aspath_get_last_as(struct aspath *aspath); extern int aspath_loop_check(struct aspath *aspath, as_t asno); extern int aspath_loop_check_confed(struct aspath *aspath, as_t asno); extern bool aspath_private_as_check(struct aspath *aspath); +extern struct aspath *aspath_replace_regex_asn(struct aspath *aspath, + struct as_list *acl_list, + as_t our_asn); extern struct aspath *aspath_replace_specific_asn(struct aspath *aspath, as_t target_asn, as_t our_asn); diff --git a/bgpd/bgp_routemap.c b/bgpd/bgp_routemap.c index 4bc173e48e..1c99495e6c 100644 --- a/bgpd/bgp_routemap.c +++ b/bgpd/bgp_routemap.c @@ -2411,11 +2411,16 @@ route_set_aspath_replace(void *rule, const struct prefix *dummy, void *object) as_t configured_asn; char *buf; char src_asn[ASN_STRING_MAX_SIZE]; + char *acl_list_name = NULL; + uint32_t acl_list_name_len = 0; + char *buf_acl_name = NULL; + static const char asp_acl[] = "as-path-access-list"; + struct as_list *aspath_acl = NULL; if (path->peer->sort != BGP_PEER_EBGP) { zlog_warn( "`set as-path replace` is supported only for EBGP peers"); - return RMAP_NOOP; + goto end_ko; } buf = strchr(replace, ' '); @@ -2423,6 +2428,46 @@ route_set_aspath_replace(void *rule, const struct prefix *dummy, void *object) configured_asn = path->peer->change_local_as ? path->peer->change_local_as : path->peer->local_as; + } else if (!strncmp(replace, asp_acl, strlen(asp_acl))) { + /* its as-path-acl-list command get the access list name */ + while (*buf == ' ') + buf++; + buf_acl_name = buf; + buf = strchr(buf_acl_name, ' '); + if (buf) + acl_list_name_len = buf - buf_acl_name; + else + acl_list_name_len = strlen(buf_acl_name); + + buf_acl_name[acl_list_name_len] = 0; + /* get the acl-list */ + aspath_acl = as_list_lookup(buf_acl_name); + if (!aspath_acl) { + zlog_warn("`set as-path replace`, invalid as-path-access-list name: %s", + buf_acl_name); + goto end_ko; + } + acl_list_name = XSTRDUP(MTYPE_TMP, buf_acl_name); + buf_acl_name[acl_list_name_len] = ' '; + + if (!buf) { + configured_asn = path->peer->change_local_as + ? path->peer->change_local_as + : path->peer->local_as; + } else { + while (*buf == ' ') + buf++; + /* get the configured asn */ + if (!asn_str2asn(buf, &configured_asn)) { + zlog_warn( + "`set as-path replace`, invalid configured AS %s", + buf); + goto end_ko; + } + } + + replace = buf; + } else { memcpy(src_asn, replace, (size_t)(buf - replace)); src_asn[(size_t)(buf - replace)] = '\0'; @@ -2432,13 +2477,14 @@ route_set_aspath_replace(void *rule, const struct prefix *dummy, void *object) zlog_warn( "`set as-path replace`, invalid configured AS %s", buf); - return RMAP_NOOP; + goto end_ko; } } - if (!strmatch(replace, "any") && !asn_str2asn(replace, &replace_asn)) { + if (replace && !strmatch(replace, "any") && + !asn_str2asn(replace, &replace_asn)) { zlog_warn("`set as-path replace`, invalid AS %s", replace); - return RMAP_NOOP; + goto end_ko; } if (path->attr->aspath->refcnt) @@ -2446,16 +2492,29 @@ route_set_aspath_replace(void *rule, const struct prefix *dummy, void *object) else aspath_new = path->attr->aspath; - if (strmatch(replace, "any")) { + if (aspath_acl) { + path->attr->aspath = aspath_replace_regex_asn(aspath_new, + aspath_acl, + configured_asn); + } else if (strmatch(replace, "any")) { path->attr->aspath = aspath_replace_all_asn(aspath_new, configured_asn); - } else + } else { path->attr->aspath = aspath_replace_specific_asn( aspath_new, replace_asn, configured_asn); - + } aspath_free(aspath_new); + + if (acl_list_name) + XFREE(MTYPE_TMP, acl_list_name); return RMAP_OKAY; + +end_ko: + if (acl_list_name) + XFREE(MTYPE_TMP, acl_list_name); + return RMAP_NOOP; + } static const struct route_map_rule_cmd route_set_aspath_replace_cmd = { @@ -6088,6 +6147,61 @@ DEFPY_YANG(no_set_aspath_replace_asn, no_set_aspath_replace_asn_cmd, return nb_cli_apply_changes(vty, NULL); } +DEFPY_YANG( + set_aspath_replace_access_list, set_aspath_replace_access_list_cmd, + "set as-path replace as-path-access-list AS_PATH_FILTER_NAME$aspath_filter_name [$configured_asn]", + SET_STR + "Transform BGP AS-path attribute\n" + "Replace AS number to local or configured AS number\n" + "Specify an as path access list name\n" + "AS path access list name\n" + "Define the configured AS number\n") +{ + char *str; + const char *xpath = + "./set-action[action='frr-bgp-route-map:as-path-replace']"; + char xpath_value[XPATH_MAXLEN]; + as_t as_configured_value; + char replace_value[ASN_STRING_MAX_SIZE * 2]; + + if (configured_asn_str && + !asn_str2asn(configured_asn_str, &as_configured_value)) { + vty_out(vty, "%% Invalid AS configured value %s\n", + configured_asn_str); + return CMD_WARNING_CONFIG_FAILED; + } + + str = argv_concat(argv, argc, 3); + + nb_cli_enqueue_change(vty, xpath, NB_OP_CREATE, NULL); + + snprintf(replace_value, sizeof(replace_value), "%s %s", aspath_filter_name, str); + + snprintf(xpath_value, sizeof(xpath_value), + "%s/rmap-set-action/frr-bgp-route-map:replace-as-path", xpath); + nb_cli_enqueue_change(vty, xpath_value, NB_OP_MODIFY, str); + + return nb_cli_apply_changes(vty, NULL); +} + +DEFPY_YANG( + no_set_aspath_replace_access_list, no_set_aspath_replace_access_list_cmd, + "no set as-path replace as-path-access-list [AS_PATH_FILTER_NAME] [$configured_asn]", + NO_STR + SET_STR + "Transform BGP AS_PATH attribute\n" + "Replace AS number to local or configured AS number\n" + "Specify an as path access list name\n" + "AS path access list name\n" + "Define the configured AS number\n") +{ + const char *xpath = + "./set-action[action='frr-bgp-route-map:as-path-replace']"; + + nb_cli_enqueue_change(vty, xpath, NB_OP_DESTROY, NULL); + return nb_cli_apply_changes(vty, NULL); +} + DEFUN_YANG (no_set_aspath_prepend, no_set_aspath_prepend_cmd, "no set as-path prepend [ASNUM]", @@ -7793,12 +7907,14 @@ void bgp_route_map_init(void) install_element(RMAP_NODE, &set_aspath_exclude_all_cmd); install_element(RMAP_NODE, &set_aspath_exclude_access_list_cmd); install_element(RMAP_NODE, &set_aspath_replace_asn_cmd); + install_element(RMAP_NODE, &set_aspath_replace_access_list_cmd); install_element(RMAP_NODE, &no_set_aspath_prepend_cmd); install_element(RMAP_NODE, &no_set_aspath_prepend_lastas_cmd); install_element(RMAP_NODE, &no_set_aspath_exclude_cmd); install_element(RMAP_NODE, &no_set_aspath_exclude_all_cmd); install_element(RMAP_NODE, &no_set_aspath_exclude_access_list_cmd); install_element(RMAP_NODE, &no_set_aspath_replace_asn_cmd); + install_element(RMAP_NODE, &no_set_aspath_replace_access_list_cmd); install_element(RMAP_NODE, &set_origin_cmd); install_element(RMAP_NODE, &no_set_origin_cmd); install_element(RMAP_NODE, &set_atomic_aggregate_cmd); diff --git a/doc/user/bgp.rst b/doc/user/bgp.rst index 09173ab2f5..651aa36d0e 100644 --- a/doc/user/bgp.rst +++ b/doc/user/bgp.rst @@ -2142,6 +2142,14 @@ Using AS Path in Route Map ``any`` replaces each AS number in the AS-PATH with either the local AS number or the configured AS number. +.. clicmd:: set as-path replace as-path-access-list WORD [] + + Replace some AS numbers from the AS_PATH of the BGP path's NLRI. Substituted + AS numbers are conformant with the regex defined in as-path access-list + WORD. Changed AS numbers are replaced either by the local AS number or the + configured AS number. + The no form of this command removes this set operation from the route-map. + .. clicmd:: set as-path exclude all Remove all AS numbers from the AS_PATH of the BGP path's NLRI. The no form of diff --git a/tests/topotests/bgp_set_aspath_replace/test_bgp_set_aspath_replace.py b/tests/topotests/bgp_set_aspath_replace/test_bgp_set_aspath_replace.py index 0433c15e0a..c0e19fa356 100644 --- a/tests/topotests/bgp_set_aspath_replace/test_bgp_set_aspath_replace.py +++ b/tests/topotests/bgp_set_aspath_replace/test_bgp_set_aspath_replace.py @@ -120,6 +120,82 @@ def test_bgp_set_aspath_replace_test2(): ), "Failed overriding incoming AS-PATH with route-map replace with configured ASN" +def test_bgp_set_aspath_replace_access_list(): + tgen = get_topogen() + + if tgen.routers_have_failure(): + pytest.skip(tgen.errors) + + rname = "r1" + r1 = tgen.gears[rname] + + r1.vtysh_cmd( + """ +conf + bgp as-path access-list FIRST permit ^65 + route-map r2 permit 20 + set as-path replace as-path-access-list FIRST 65002 + """ + ) + + expected = { + "routes": { + "172.16.255.31/32": [{"path": "65002 65500"}], + "172.16.255.32/32": [{"path": "65002 65002"}], + } + } + + def _bgp_regexp_1(router): + output = json.loads(router.vtysh_cmd("show bgp ipv4 unicast json")) + + return topotest.json_cmp(output, expected) + + test_func = functools.partial(_bgp_regexp_1, tgen.gears["r1"]) + _, result = topotest.run_and_expect(test_func, None, count=30, wait=0.5) + + assert result is None, "Failed overriding incoming AS-PATH with regex 1 route-map" + r1.vtysh_cmd( + """ +conf + bgp as-path access-list SECOND permit 2 + route-map r2 permit 10 + set as-path replace as-path-access-list SECOND 65001 + """ + ) + + expected = { + "routes": { + "172.16.255.31/32": [{"path": "65001 65003"}], + "172.16.255.32/32": [{"path": "65002 65002"}], + } + } + + test_func = functools.partial(_bgp_regexp_1, tgen.gears["r1"]) + _, result = topotest.run_and_expect(test_func, None, count=30, wait=0.5) + + assert result is None, "Failed overriding incoming AS-PATH with regex 2 route-map" + + r1.vtysh_cmd( + """ +conf + bgp as-path access-list TER permit 3 + route-map r2 permit 10 + set as-path replace as-path-access-list TER + """ + ) + expected = { + "routes": { + "172.16.255.31/32": [{"path": "65002 65001"}], + "172.16.255.32/32": [{"path": "65002 65002"}], + } + } + + test_func = functools.partial(_bgp_regexp_1, tgen.gears["r1"]) + _, result = topotest.run_and_expect(test_func, None, count=30, wait=0.5) + + assert result is None, "Failed overriding incoming AS-PATH with regex 3 route-map" + + if __name__ == "__main__": args = ["-s"] + sys.argv[1:] sys.exit(pytest.main(args))