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>
This commit is contained in:
Philippe Guibert 2023-06-13 14:53:03 +02:00
parent 0fb1630520
commit 92550adfc7
12 changed files with 241 additions and 5 deletions

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -0,0 +1,6 @@
!
interface r1-eth0
ip address 192.168.1.1/24
!
ip forwarding
!

View File

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

View File

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

View File

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

View File

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

View File

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