From a3f0a1f5ed25af4affe426a1c5f907e787bb3ae2 Mon Sep 17 00:00:00 2001 From: Philippe Guibert Date: Mon, 19 Jun 2023 16:22:55 +0200 Subject: [PATCH] bgpd: add 'set as-path replace' with a configured ASN There is no route-map set action to replace any ASN, or a part of an ASN, with a configured ASN. The current commit adds a new command to use a configured ASN as replacement, instead of using the local as number. > set as-path replace any 65500 Update the 'bgp_set_aspath_replace' test. Signed-off-by: Philippe Guibert --- bgpd/bgp_routemap.c | 88 +++++++++++++------ doc/user/bgp.rst | 7 +- .../bgp_set_aspath_replace/r1/bgpd.conf | 1 + .../test_bgp_set_aspath_replace.py | 37 +++++++- 4 files changed, 100 insertions(+), 33 deletions(-) diff --git a/bgpd/bgp_routemap.c b/bgpd/bgp_routemap.c index 8b633f8402..cf915c5d8a 100644 --- a/bgpd/bgp_routemap.c +++ b/bgpd/bgp_routemap.c @@ -2385,8 +2385,10 @@ route_set_aspath_replace(void *rule, const struct prefix *dummy, void *object) struct aspath *aspath_new; const char *replace = rule; struct bgp_path_info *path = object; - as_t own_asn = path->peer->change_local_as ? path->peer->change_local_as - : path->peer->local_as; + as_t replace_asn = 0; + as_t configured_asn; + char *buf; + char src_asn[ASN_STRING_MAX_SIZE]; if (path->peer->sort != BGP_PEER_EBGP) { zlog_warn( @@ -2394,6 +2396,29 @@ route_set_aspath_replace(void *rule, const struct prefix *dummy, void *object) return RMAP_NOOP; } + buf = strchr(replace, ' '); + if (!buf) { + configured_asn = path->peer->change_local_as + ? path->peer->change_local_as + : path->peer->local_as; + } else { + memcpy(src_asn, replace, (size_t)(buf - replace)); + src_asn[(size_t)(buf - replace)] = '\0'; + replace = src_asn; + buf++; + if (!asn_str2asn(buf, &configured_asn)) { + zlog_warn( + "`set as-path replace`, invalid configured AS %s", + buf); + return RMAP_NOOP; + } + } + + if (!strmatch(replace, "any") && !asn_str2asn(replace, &replace_asn)) { + zlog_warn("`set as-path replace`, invalid AS %s", replace); + return RMAP_NOOP; + } + if (path->attr->aspath->refcnt) aspath_new = aspath_dup(path->attr->aspath); else @@ -2401,13 +2426,10 @@ route_set_aspath_replace(void *rule, const struct prefix *dummy, void *object) if (strmatch(replace, "any")) { path->attr->aspath = - aspath_replace_all_asn(aspath_new, own_asn); - } else { - as_t replace_asn = strtoul(replace, NULL, 10); - + aspath_replace_all_asn(aspath_new, configured_asn); + } else path->attr->aspath = aspath_replace_specific_asn( - aspath_new, replace_asn, own_asn); - } + aspath_new, replace_asn, configured_asn); aspath_free(aspath_new); @@ -5875,41 +5897,49 @@ DEFUN_YANG (set_aspath_prepend_lastas, return nb_cli_apply_changes(vty, NULL); } -DEFPY_YANG (set_aspath_replace_asn, - set_aspath_replace_asn_cmd, - "set as-path replace $replace", - SET_STR - "Transform BGP AS_PATH attribute\n" - "Replace AS number to local AS number\n" - "Replace any AS number to local AS number\n" - "Replace a specific AS number in plain or dotted format to local AS number\n") +DEFPY_YANG(set_aspath_replace_asn, set_aspath_replace_asn_cmd, + "set as-path replace $replace [$configured_asn]", + SET_STR + "Transform BGP AS_PATH attribute\n" + "Replace AS number to local or configured AS number\n" + "Replace any AS number to local or configured AS number\n" + "Replace a specific AS number to local or configured AS number\n" + "Define the configured AS number\n") { const char *xpath = "./set-action[action='frr-bgp-route-map:as-path-replace']"; char xpath_value[XPATH_MAXLEN]; - as_t as_value; + as_t as_value, as_configured_value; + char replace_value[ASN_STRING_MAX_SIZE * 2]; if (!strmatch(replace, "any") && !asn_str2asn(replace, &as_value)) { vty_out(vty, "%% Invalid AS value %s\n", replace); return CMD_WARNING_CONFIG_FAILED; } - - nb_cli_enqueue_change(vty, xpath, NB_OP_CREATE, NULL); + 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; + } 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, replace); + snprintf(replace_value, sizeof(replace_value), "%s%s%s", replace, + configured_asn_str ? " " : "", + configured_asn_str ? configured_asn_str : ""); + nb_cli_enqueue_change(vty, xpath_value, NB_OP_MODIFY, replace_value); return nb_cli_apply_changes(vty, NULL); } -DEFPY_YANG (no_set_aspath_replace_asn, - no_set_aspath_replace_asn_cmd, - "no set as-path replace []", - NO_STR - SET_STR - "Transform BGP AS_PATH attribute\n" - "Replace AS number to local AS number\n" - "Replace any AS number to local AS number\n" - "Replace a specific AS number in plain or dotted format to local AS number\n") +DEFPY_YANG(no_set_aspath_replace_asn, no_set_aspath_replace_asn_cmd, + "no set as-path replace [] [$configured_asn]", + NO_STR SET_STR + "Transform BGP AS_PATH attribute\n" + "Replace AS number to local or configured AS number\n" + "Replace any AS number to local or configured AS number\n" + "Replace a specific AS number to local or configured AS number\n" + "Replace AS number with a configured AS number\n" + "Define the configured AS number\n") { const char *xpath = "./set-action[action='frr-bgp-route-map:as-path-replace']"; diff --git a/doc/user/bgp.rst b/doc/user/bgp.rst index 46b22ff452..f09512de32 100644 --- a/doc/user/bgp.rst +++ b/doc/user/bgp.rst @@ -2105,10 +2105,11 @@ Using AS Path in Route Map Prepend the existing last AS number (the leftmost ASN) to the AS_PATH. The no form of this command removes this set operation from the route-map. -.. clicmd:: set as-path replace +.. clicmd:: set as-path replace [] - Replace a specific AS number to local AS number. ``any`` replaces each - AS number in the AS-PATH with the local AS number. + Replace a specific AS number to local AS number or a configured AS number. + ``any`` replaces each AS number in the AS-PATH with either the local AS + number or the configured AS number. .. clicmd:: set as-path exclude all diff --git a/tests/topotests/bgp_set_aspath_replace/r1/bgpd.conf b/tests/topotests/bgp_set_aspath_replace/r1/bgpd.conf index 1e98f4e491..f586c1f99c 100644 --- a/tests/topotests/bgp_set_aspath_replace/r1/bgpd.conf +++ b/tests/topotests/bgp_set_aspath_replace/r1/bgpd.conf @@ -9,6 +9,7 @@ router bgp 65001 ! ip prefix-list p1 seq 5 permit 172.16.255.31/32 ! +bgp route-map delay-timer 1 route-map r2 permit 10 match ip address prefix-list p1 set as-path replace 65003 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 463df2f2a6..0433c15e0a 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 @@ -24,6 +24,7 @@ sys.path.append(os.path.join(CWD, "../")) # pylint: disable=C0413 from lib import topotest from lib.topogen import Topogen, TopoRouter, get_topogen +from lib.topolog import logger pytestmark = [pytest.mark.bgpd] @@ -63,7 +64,7 @@ def teardown_module(mod): tgen.stop_topology() -def test_bgp_maximum_prefix_out(): +def test_bgp_set_aspath_replace_test1(): tgen = get_topogen() if tgen.routers_have_failure(): @@ -85,6 +86,40 @@ def test_bgp_maximum_prefix_out(): assert result is None, "Failed overriding incoming AS-PATH with route-map" +def test_bgp_set_aspath_replace_test2(): + tgen = get_topogen() + + if tgen.routers_have_failure(): + pytest.skip(tgen.errors) + logger.info("Configuring r1 to replace the matching AS with a configured ASN") + router = tgen.gears["r1"] + router.vtysh_cmd( + "configure terminal\nroute-map r2 permit 10\nset as-path replace 65003 65500\n", + isjson=False, + ) + router.vtysh_cmd( + "configure terminal\nroute-map r2 permit 20\nset as-path replace any 65501\n", + isjson=False, + ) + + def _bgp_converge(router): + output = json.loads(router.vtysh_cmd("show bgp ipv4 unicast json")) + expected = { + "routes": { + "172.16.255.31/32": [{"path": "65002 65500"}], + "172.16.255.32/32": [{"path": "65501 65501"}], + } + } + return topotest.json_cmp(output, expected) + + test_func = functools.partial(_bgp_converge, router) + _, result = topotest.run_and_expect(test_func, None, count=30, wait=0.5) + + assert ( + result is None + ), "Failed overriding incoming AS-PATH with route-map replace with configured ASN" + + if __name__ == "__main__": args = ["-s"] + sys.argv[1:] sys.exit(pytest.main(args)) -- 2.39.5