diff options
| author | Philippe Guibert <philippe.guibert@6wind.com> | 2023-06-13 14:53:03 +0200 | 
|---|---|---|
| committer | Philippe Guibert <philippe.guibert@6wind.com> | 2023-06-19 18:04:44 +0200 | 
| commit | 92550adfc77d9d01a2d7a96d67d8a5d27f7b6877 (patch) | |
| tree | 03aba5159dd9ba8a06f27793648f166596715c53 | |
| parent | 0fb16305200113a92e3862e05d6833217f935211 (diff) | |
bgpd: add 'set as-path exclude all' command
It is not possible to flush all the incoming as-path list
from a given BGP update.
Add a route-map set command to remove all as-paths
from a given AS path. Add the necessary tests.
Signed-off-by: Philippe Guibert <philippe.guibert@6wind.com>
| -rw-r--r-- | bgpd/bgp_aspath.c | 18 | ||||
| -rw-r--r-- | bgpd/bgp_aspath.h | 1 | ||||
| -rw-r--r-- | bgpd/bgp_routemap.c | 74 | ||||
| -rw-r--r-- | doc/user/bgp.rst | 5 | ||||
| -rw-r--r-- | tests/topotests/bgp_set_aspath_exclude/__init__.py | 0 | ||||
| -rw-r--r-- | tests/topotests/bgp_set_aspath_exclude/r1/bgpd.conf | 17 | ||||
| -rw-r--r-- | tests/topotests/bgp_set_aspath_exclude/r1/zebra.conf | 6 | ||||
| -rw-r--r-- | tests/topotests/bgp_set_aspath_exclude/r2/bgpd.conf | 8 | ||||
| -rw-r--r-- | tests/topotests/bgp_set_aspath_exclude/r2/zebra.conf | 9 | ||||
| -rw-r--r-- | tests/topotests/bgp_set_aspath_exclude/r3/bgpd.conf | 9 | ||||
| -rw-r--r-- | tests/topotests/bgp_set_aspath_exclude/r3/zebra.conf | 10 | ||||
| -rw-r--r-- | tests/topotests/bgp_set_aspath_exclude/test_bgp_set_aspath_exclude.py | 89 | 
12 files changed, 241 insertions, 5 deletions
diff --git a/bgpd/bgp_aspath.c b/bgpd/bgp_aspath.c index 2c0de43c9b..22c9fe0e66 100644 --- a/bgpd/bgp_aspath.c +++ b/bgpd/bgp_aspath.c @@ -1598,6 +1598,24 @@ struct aspath *aspath_filter_exclude(struct aspath *source,  	return newpath;  } +struct aspath *aspath_filter_exclude_all(struct aspath *source) +{ +	struct aspath *newpath; + +	newpath = aspath_new(source->asnotation); + +	aspath_str_update(newpath, false); +	/* We are happy returning even an empty AS_PATH, because the +	 * administrator +	 * might expect this very behaviour. There's a mean to avoid this, if +	 * necessary, +	 * by having a match rule against certain AS_PATH regexps in the +	 * route-map index. +	 */ +	aspath_free(source); +	return newpath; +} +  /* Add specified AS to the leftmost of aspath. */  static struct aspath *aspath_add_asns(struct aspath *aspath, as_t asno,  				      uint8_t type, unsigned num) diff --git a/bgpd/bgp_aspath.h b/bgpd/bgp_aspath.h index 18af375c13..a3aae14f8f 100644 --- a/bgpd/bgp_aspath.h +++ b/bgpd/bgp_aspath.h @@ -76,6 +76,7 @@ extern struct aspath *aspath_aggregate(struct aspath *as1, struct aspath *as2);  extern struct aspath *aspath_prepend(struct aspath *as1, struct aspath *as2);  extern struct aspath *aspath_filter_exclude(struct aspath *source,  					    struct aspath *exclude_list); +extern struct aspath *aspath_filter_exclude_all(struct aspath *source);  extern struct aspath *aspath_add_seq_n(struct aspath *aspath, as_t asno,  				       unsigned num);  extern struct aspath *aspath_add_seq(struct aspath *aspath, as_t asno); diff --git a/bgpd/bgp_routemap.c b/bgpd/bgp_routemap.c index 7db110be93..d29b91b48f 100644 --- a/bgpd/bgp_routemap.c +++ b/bgpd/bgp_routemap.c @@ -2300,6 +2300,31 @@ static const struct route_map_rule_cmd route_set_aspath_prepend_cmd = {  };  /* `set as-path exclude ASn' */ +struct aspath_exclude { +	struct aspath *aspath; +	bool exclude_all; +}; + +static void *route_aspath_exclude_compile(const char *arg) +{ +	struct aspath_exclude *ase; +	const char *str = arg; + +	ase = XCALLOC(MTYPE_ROUTE_MAP_COMPILED, sizeof(struct aspath_exclude)); +	if (!strmatch(str, "all")) +		ase->aspath = aspath_str2aspath(str, bgp_get_asnotation(NULL)); +	else +		ase->exclude_all = true; +	return ase; +} + +static void route_aspath_exclude_free(void *rule) +{ +	struct aspath_exclude *ase = rule; + +	aspath_free(ase->aspath); +	XFREE(MTYPE_ROUTE_MAP_COMPILED, ase); +}  /* For ASN exclude mechanism.   * Iterate over ASns requested and filter them from the given AS_PATH one by @@ -2309,16 +2334,28 @@ static const struct route_map_rule_cmd route_set_aspath_prepend_cmd = {  static enum route_map_cmd_result_t  route_set_aspath_exclude(void *rule, const struct prefix *dummy, void *object)  { -	struct aspath *new_path, *exclude_path; +	struct aspath *new_path;  	struct bgp_path_info *path; +	struct aspath_exclude *ase = rule; -	exclude_path = rule;  	path = object; + +	if (path->peer->sort != BGP_PEER_EBGP) { +		zlog_warn( +			"`set as-path exclude` is supported only for EBGP peers"); +		return RMAP_NOOP; +	} +  	if (path->attr->aspath->refcnt)  		new_path = aspath_dup(path->attr->aspath);  	else  		new_path = path->attr->aspath; -	path->attr->aspath = aspath_filter_exclude(new_path, exclude_path); + +	if (ase->aspath) +		path->attr->aspath = +			aspath_filter_exclude(new_path, ase->aspath); +	else if (ase->exclude_all) +		path->attr->aspath = aspath_filter_exclude_all(new_path);  	return RMAP_OKAY;  } @@ -2327,8 +2364,8 @@ route_set_aspath_exclude(void *rule, const struct prefix *dummy, void *object)  static const struct route_map_rule_cmd route_set_aspath_exclude_cmd = {  	"as-path exclude",  	route_set_aspath_exclude, -	route_aspath_compile, -	route_aspath_free, +	route_aspath_exclude_compile, +	route_aspath_exclude_free,  };  /* `set as-path replace AS-PATH` */ @@ -5910,6 +5947,32 @@ DEFUN_YANG (set_aspath_exclude,  	return ret;  } +DEFPY_YANG(set_aspath_exclude_all, set_aspath_exclude_all_cmd, +	   "[no$no] set as-path exclude all$all", +	   NO_STR SET_STR +	   "Transform BGP AS-path attribute\n" +	   "Exclude from the as-path\n" +	   "Exclude all AS numbers from the as-path\n") +{ +	int ret; +	const char *xpath = +		"./set-action[action='frr-bgp-route-map:as-path-exclude']"; +	char xpath_value[XPATH_MAXLEN]; + +	if (no) +		nb_cli_enqueue_change(vty, xpath, NB_OP_DESTROY, NULL); +	else { +		nb_cli_enqueue_change(vty, xpath, NB_OP_CREATE, NULL); +		snprintf(xpath_value, sizeof(xpath_value), +			 "%s/rmap-set-action/frr-bgp-route-map:exclude-as-path", +			 xpath); +		nb_cli_enqueue_change(vty, xpath_value, NB_OP_MODIFY, all); +	} +	ret = nb_cli_apply_changes(vty, NULL); + +	return ret; +} +  DEFUN_YANG (no_set_aspath_exclude,  	    no_set_aspath_exclude_cmd,  	    "no set as-path exclude ASNUM...", @@ -7436,6 +7499,7 @@ void bgp_route_map_init(void)  	install_element(RMAP_NODE, &set_aspath_prepend_asn_cmd);  	install_element(RMAP_NODE, &set_aspath_prepend_lastas_cmd);  	install_element(RMAP_NODE, &set_aspath_exclude_cmd); +	install_element(RMAP_NODE, &set_aspath_exclude_all_cmd);  	install_element(RMAP_NODE, &set_aspath_replace_asn_cmd);  	install_element(RMAP_NODE, &no_set_aspath_prepend_cmd);  	install_element(RMAP_NODE, &no_set_aspath_prepend_lastas_cmd); diff --git a/doc/user/bgp.rst b/doc/user/bgp.rst index a2585f3a57..215f473c36 100644 --- a/doc/user/bgp.rst +++ b/doc/user/bgp.rst @@ -2106,6 +2106,11 @@ Using AS Path in Route Map     Replace a specific AS number to local AS number. ``any`` replaces each     AS number in the AS-PATH with the local AS number. +.. clicmd:: set as-path exclude all + +   Remove all AS numbers from the AS_PATH of the BGP path's NLRI. The no form of +   this command removes this set operation from the route-map. +  .. _bgp-communities-attribute:  Communities Attribute diff --git a/tests/topotests/bgp_set_aspath_exclude/__init__.py b/tests/topotests/bgp_set_aspath_exclude/__init__.py new file mode 100644 index 0000000000..e69de29bb2 --- /dev/null +++ b/tests/topotests/bgp_set_aspath_exclude/__init__.py diff --git a/tests/topotests/bgp_set_aspath_exclude/r1/bgpd.conf b/tests/topotests/bgp_set_aspath_exclude/r1/bgpd.conf new file mode 100644 index 0000000000..9bef24f931 --- /dev/null +++ b/tests/topotests/bgp_set_aspath_exclude/r1/bgpd.conf @@ -0,0 +1,17 @@ +! +router bgp 65001 + no bgp ebgp-requires-policy + neighbor 192.168.1.2 remote-as external + neighbor 192.168.1.2 timers 3 10 + address-family ipv4 unicast +  neighbor 192.168.1.2 route-map r2 in + exit-address-family +! +ip prefix-list p1 seq 5 permit 172.16.255.31/32 +! +route-map r2 permit 10 + match ip address prefix-list p1 + set as-path exclude 65003 +route-map r2 permit 20 + set as-path exclude all +! diff --git a/tests/topotests/bgp_set_aspath_exclude/r1/zebra.conf b/tests/topotests/bgp_set_aspath_exclude/r1/zebra.conf new file mode 100644 index 0000000000..acf120b200 --- /dev/null +++ b/tests/topotests/bgp_set_aspath_exclude/r1/zebra.conf @@ -0,0 +1,6 @@ +! +interface r1-eth0 + ip address 192.168.1.1/24 +! +ip forwarding +! diff --git a/tests/topotests/bgp_set_aspath_exclude/r2/bgpd.conf b/tests/topotests/bgp_set_aspath_exclude/r2/bgpd.conf new file mode 100644 index 0000000000..23367f94ff --- /dev/null +++ b/tests/topotests/bgp_set_aspath_exclude/r2/bgpd.conf @@ -0,0 +1,8 @@ +! +router bgp 65002 + no bgp ebgp-requires-policy + neighbor 192.168.1.1 remote-as external + neighbor 192.168.1.1 timers 3 10 + neighbor 192.168.2.1 remote-as external + neighbor 192.168.2.1 timers 3 10 +! diff --git a/tests/topotests/bgp_set_aspath_exclude/r2/zebra.conf b/tests/topotests/bgp_set_aspath_exclude/r2/zebra.conf new file mode 100644 index 0000000000..f229954341 --- /dev/null +++ b/tests/topotests/bgp_set_aspath_exclude/r2/zebra.conf @@ -0,0 +1,9 @@ +! +interface r2-eth0 + ip address 192.168.1.2/24 +! +interface r2-eth1 + ip address 192.168.2.2/24 +! +ip forwarding +! diff --git a/tests/topotests/bgp_set_aspath_exclude/r3/bgpd.conf b/tests/topotests/bgp_set_aspath_exclude/r3/bgpd.conf new file mode 100644 index 0000000000..b7a7ceda13 --- /dev/null +++ b/tests/topotests/bgp_set_aspath_exclude/r3/bgpd.conf @@ -0,0 +1,9 @@ +! +router bgp 65003 + no bgp ebgp-requires-policy + neighbor 192.168.2.2 remote-as external + neighbor 192.168.2.2 timers 3 10 + address-family ipv4 unicast +  redistribute connected + exit-address-family +! diff --git a/tests/topotests/bgp_set_aspath_exclude/r3/zebra.conf b/tests/topotests/bgp_set_aspath_exclude/r3/zebra.conf new file mode 100644 index 0000000000..3fa6c64484 --- /dev/null +++ b/tests/topotests/bgp_set_aspath_exclude/r3/zebra.conf @@ -0,0 +1,10 @@ +! +int lo + ip address 172.16.255.31/32 + ip address 172.16.255.32/32 +! +interface r3-eth0 + ip address 192.168.2.1/24 +! +ip forwarding +! diff --git a/tests/topotests/bgp_set_aspath_exclude/test_bgp_set_aspath_exclude.py b/tests/topotests/bgp_set_aspath_exclude/test_bgp_set_aspath_exclude.py new file mode 100644 index 0000000000..8af7e7d60d --- /dev/null +++ b/tests/topotests/bgp_set_aspath_exclude/test_bgp_set_aspath_exclude.py @@ -0,0 +1,89 @@ +#!/usr/bin/env python +# SPDX-License-Identifier: ISC +# +# test_bgp_set_aspath_exclude.py +# +# Copyright 2023 by 6WIND S.A. +# + +""" +Test if `set as-path exclude` is working correctly for route-maps. +""" + +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 + + +pytestmark = [pytest.mark.bgpd] + + +def build_topo(tgen): +    for routern in range(1, 5): +        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["r2"]) +    switch.add_link(tgen.gears["r3"]) + + +def setup_module(mod): +    tgen = Topogen(build_topo, mod.__name__) +    tgen.start_topology() + +    router_list = tgen.routers() + +    for i, (rname, router) in enumerate(router_list.items(), 1): +        router.load_config( +            TopoRouter.RD_ZEBRA, os.path.join(CWD, "{}/zebra.conf".format(rname)) +        ) +        router.load_config( +            TopoRouter.RD_BGP, os.path.join(CWD, "{}/bgpd.conf".format(rname)) +        ) + +    tgen.start_router() + + +def teardown_module(mod): +    tgen = get_topogen() +    tgen.stop_topology() + + +def test_bgp_set_aspath_exclude(): +    tgen = get_topogen() + +    if tgen.routers_have_failure(): +        pytest.skip(tgen.errors) + +    def _bgp_converge(router): +        output = json.loads(router.vtysh_cmd("show bgp ipv4 unicast json")) +        expected = { +            "routes": { +                "172.16.255.31/32": [{"path": "65002"}], +                "172.16.255.32/32": [{"path": ""}], +            } +        } +        return topotest.json_cmp(output, expected) + +    test_func = functools.partial(_bgp_converge, 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 route-map" + + +if __name__ == "__main__": +    args = ["-s"] + sys.argv[1:] +    sys.exit(pytest.main(args))  | 
