bgpd: Add "bgp bestpath peer-type multipath-relax"

This new BGP configuration is akin to "bgp bestpath aspath
multipath-relax". When applied, paths learned from different peer types
will be eligible to be considered for multipath (ECMP). Paths from all
of eBGP, iBGP, and confederation peers may be included in multipaths
if they are otherwise equal cost.

This change preserves the existing bestpath behavior of step 10's result
being returned, not the result from steps 8 and 9, in the case where
both 8+9 and 10 determine a winner.

Signed-off-by: Joanne Mikkelson <jmmikkel@arista.com>
This commit is contained in:
Joanne Mikkelson 2021-03-17 15:26:59 -07:00
parent 8baa41e571
commit ee88563ac2
33 changed files with 1150 additions and 22 deletions

View File

@ -565,6 +565,8 @@ static int bgp_path_info_cmp(struct bgp *bgp, struct bgp_path_info *new,
int internal_as_route;
int confed_as_route;
int ret = 0;
int igp_metric_ret = 0;
int peer_sort_ret = -1;
char new_buf[PATH_ADDPATH_STR_BUFFER];
char exist_buf[PATH_ADDPATH_STR_BUFFER];
uint32_t new_mm_seq;
@ -971,7 +973,9 @@ static int bgp_path_info_cmp(struct bgp *bgp, struct bgp_path_info *new,
zlog_debug(
"%s: %s wins over %s due to eBGP peer > iBGP peer",
pfx_buf, new_buf, exist_buf);
return 1;
if (!CHECK_FLAG(bgp->flags, BGP_FLAG_PEERTYPE_MULTIPATH_RELAX))
return 1;
peer_sort_ret = 1;
}
if (exist_sort == BGP_PEER_EBGP
@ -981,7 +985,9 @@ static int bgp_path_info_cmp(struct bgp *bgp, struct bgp_path_info *new,
zlog_debug(
"%s: %s loses to %s due to iBGP peer < eBGP peer",
pfx_buf, new_buf, exist_buf);
return 0;
if (!CHECK_FLAG(bgp->flags, BGP_FLAG_PEERTYPE_MULTIPATH_RELAX))
return 0;
peer_sort_ret = 0;
}
/* 8. IGP metric check. */
@ -993,19 +999,19 @@ static int bgp_path_info_cmp(struct bgp *bgp, struct bgp_path_info *new,
existm = exist->extra->igpmetric;
if (newm < existm) {
if (debug)
if (debug && peer_sort_ret < 0)
zlog_debug(
"%s: %s wins over %s due to IGP metric %u < %u",
pfx_buf, new_buf, exist_buf, newm, existm);
ret = 1;
igp_metric_ret = 1;
}
if (newm > existm) {
if (debug)
if (debug && peer_sort_ret < 0)
zlog_debug(
"%s: %s loses to %s due to IGP metric %u > %u",
pfx_buf, new_buf, exist_buf, newm, existm);
ret = 0;
igp_metric_ret = 0;
}
/* 9. Same IGP metric. Compare the cluster list length as
@ -1023,21 +1029,21 @@ static int bgp_path_info_cmp(struct bgp *bgp, struct bgp_path_info *new,
existm = BGP_CLUSTER_LIST_LENGTH(exist->attr);
if (newm < existm) {
if (debug)
if (debug && peer_sort_ret < 0)
zlog_debug(
"%s: %s wins over %s due to CLUSTER_LIST length %u < %u",
pfx_buf, new_buf, exist_buf,
newm, existm);
ret = 1;
igp_metric_ret = 1;
}
if (newm > existm) {
if (debug)
if (debug && peer_sort_ret < 0)
zlog_debug(
"%s: %s loses to %s due to CLUSTER_LIST length %u > %u",
pfx_buf, new_buf, exist_buf,
newm, existm);
ret = 0;
igp_metric_ret = 0;
}
}
}
@ -1051,7 +1057,10 @@ static int bgp_path_info_cmp(struct bgp *bgp, struct bgp_path_info *new,
zlog_debug(
"%s: %s wins over %s due to confed-external peer > confed-internal peer",
pfx_buf, new_buf, exist_buf);
return 1;
if (!CHECK_FLAG(bgp->flags,
BGP_FLAG_PEERTYPE_MULTIPATH_RELAX))
return 1;
peer_sort_ret = 1;
}
if (exist_sort == BGP_PEER_CONFED
@ -1061,7 +1070,10 @@ static int bgp_path_info_cmp(struct bgp *bgp, struct bgp_path_info *new,
zlog_debug(
"%s: %s loses to %s due to confed-internal peer < confed-external peer",
pfx_buf, new_buf, exist_buf);
return 0;
if (!CHECK_FLAG(bgp->flags,
BGP_FLAG_PEERTYPE_MULTIPATH_RELAX))
return 0;
peer_sort_ret = 0;
}
}
@ -1122,20 +1134,40 @@ static int bgp_path_info_cmp(struct bgp *bgp, struct bgp_path_info *new,
* TODO: If unequal cost ibgp multipath is enabled we can
* mark the paths as equal here instead of returning
*/
if (debug) {
if (ret == 1)
zlog_debug(
"%s: %s wins over %s after IGP metric comparison",
pfx_buf, new_buf, exist_buf);
else
zlog_debug(
"%s: %s loses to %s after IGP metric comparison",
pfx_buf, new_buf, exist_buf);
/* Prior to the addition of BGP_FLAG_PEERTYPE_MULTIPATH_RELAX,
* if either step 7 or 10 (peer type checks) yielded a winner,
* that result was returned immediately. Returning from step 10
* ignored the return value computed in steps 8 and 9 (IGP
* metric checks). In order to preserve that behavior, if
* peer_sort_ret is set, return that rather than igp_metric_ret.
*/
ret = peer_sort_ret;
if (peer_sort_ret < 0) {
ret = igp_metric_ret;
if (debug) {
if (ret == 1)
zlog_debug(
"%s: %s wins over %s after IGP metric comparison",
pfx_buf, new_buf, exist_buf);
else
zlog_debug(
"%s: %s loses to %s after IGP metric comparison",
pfx_buf, new_buf, exist_buf);
}
*reason = bgp_path_selection_igp_metric;
}
*reason = bgp_path_selection_igp_metric;
return ret;
}
/*
* At this point, the decision whether to set *paths_eq = 1 has been
* completed. If we deferred returning because of bestpath peer-type
* relax configuration, return now.
*/
if (peer_sort_ret >= 0)
return peer_sort_ret;
/* 12. If both paths are external, prefer the path that was received
first (the oldest one). This step minimizes route-flap, since a
newer path won't displace an older one, even if it was the

View File

@ -3571,6 +3571,37 @@ DEFUN_YANG (no_bgp_bestpath_aspath_multipath_relax,
return nb_cli_apply_changes(vty, NULL);
}
/* "bgp bestpath peer-type multipath-relax" configuration. */
DEFUN(bgp_bestpath_peer_type_multipath_relax,
bgp_bestpath_peer_type_multipath_relax_cmd,
"bgp bestpath peer-type multipath-relax",
BGP_STR
"Change the default bestpath selection\n"
"Peer type\n"
"Allow load sharing across routes learned from different peer types\n")
{
VTY_DECLVAR_CONTEXT(bgp, bgp);
SET_FLAG(bgp->flags, BGP_FLAG_PEERTYPE_MULTIPATH_RELAX);
bgp_recalculate_all_bestpaths(bgp);
return CMD_SUCCESS;
}
DEFUN(no_bgp_bestpath_peer_type_multipath_relax,
no_bgp_bestpath_peer_type_multipath_relax_cmd,
"no bgp bestpath peer-type multipath-relax",
NO_STR BGP_STR
"Change the default bestpath selection\n"
"Peer type\n"
"Allow load sharing across routes learned from different peer types\n")
{
VTY_DECLVAR_CONTEXT(bgp, bgp);
UNSET_FLAG(bgp->flags, BGP_FLAG_PEERTYPE_MULTIPATH_RELAX);
bgp_recalculate_all_bestpaths(bgp);
return CMD_SUCCESS;
}
/* "bgp log-neighbor-changes" configuration. */
DEFUN_YANG(bgp_log_neighbor_changes,
bgp_log_neighbor_changes_cmd,
@ -10496,6 +10527,9 @@ static void bgp_show_bestpath_json(struct bgp *bgp, json_object *json)
} else
json_object_string_add(bestpath, "multiPathRelax", "false");
if (CHECK_FLAG(bgp->flags, BGP_FLAG_PEERTYPE_MULTIPATH_RELAX))
json_object_boolean_true_add(bestpath, "peerTypeRelax");
if (CHECK_FLAG(bgp->flags, BGP_FLAG_COMPARE_ROUTER_ID))
json_object_string_add(bestpath, "compareRouterId", "true");
if (CHECK_FLAG(bgp->flags, BGP_FLAG_MED_CONFED)
@ -17656,6 +17690,10 @@ int bgp_config_write(struct vty *vty)
vty_out(vty, "\n");
}
if (CHECK_FLAG(bgp->flags, BGP_FLAG_PEERTYPE_MULTIPATH_RELAX))
vty_out(vty,
" bgp bestpath peer-type multipath-relax\n");
/* Link bandwidth handling. */
if (bgp->lb_handling == BGP_LINK_BW_IGNORE_BW)
vty_out(vty, " bgp bestpath bandwidth ignore\n");
@ -18140,6 +18178,11 @@ void bgp_vty_init(void)
install_element(BGP_NODE, &bgp_bestpath_aspath_multipath_relax_cmd);
install_element(BGP_NODE, &no_bgp_bestpath_aspath_multipath_relax_cmd);
/* "bgp bestpath peer-type multipath-relax" commands */
install_element(BGP_NODE, &bgp_bestpath_peer_type_multipath_relax_cmd);
install_element(BGP_NODE,
&no_bgp_bestpath_peer_type_multipath_relax_cmd);
/* "bgp log-neighbor-changes" commands */
install_element(BGP_NODE, &bgp_log_neighbor_changes_cmd);
install_element(BGP_NODE, &no_bgp_log_neighbor_changes_cmd);

View File

@ -478,6 +478,7 @@ struct bgp {
#define BGP_FLAG_SUPPRESS_FIB_PENDING (1 << 28)
#define BGP_FLAG_SUPPRESS_DUPLICATES (1 << 29)
#define BGP_FLAG_DEFAULT_IPV6 (1 << 30)
#define BGP_FLAG_PEERTYPE_MULTIPATH_RELAX (1 << 31)
enum global_mode GLOBAL_GR_FSM[BGP_GLOBAL_GR_MODE]
[BGP_GLOBAL_GR_EVENT_CMD];

View File

@ -394,6 +394,13 @@ Route Selection
other measures were taken to avoid these. The exact behaviour will be
sensitive to the iBGP and reflection topology.
.. clicmd:: bgp bestpath peer-type multipath-relax
This command specifies that BGP decision process should consider paths
from all peers for multipath computation. If this option is enabled,
paths learned from any of eBGP, iBGP, or confederation neighbors will
be multipath if they are otherwise considered equal cost.
.. _bgp-distance:
Administrative Distance Metrics

View File

@ -0,0 +1,53 @@
[exabgp.api]
encoder = text
highres = false
respawn = false
socket = ''
[exabgp.bgp]
openwait = 60
[exabgp.cache]
attributes = true
nexthops = true
[exabgp.daemon]
daemonize = true
pid = '/var/run/exabgp/exabgp.pid'
user = 'exabgp'
[exabgp.log]
all = false
configuration = true
daemon = true
destination = '/var/log/exabgp.log'
enable = true
level = INFO
message = false
network = true
packets = false
parser = false
processes = true
reactor = true
rib = false
routes = false
short = false
timers = false
[exabgp.pdb]
enable = false
[exabgp.profile]
enable = false
file = ''
[exabgp.reactor]
speed = 1.0
[exabgp.tcp]
acl = false
bind = ''
delay = 0
once = false
port = 179

View File

@ -0,0 +1,38 @@
#!/usr/bin/env python
"""
exa-receive.py: Save received routes form ExaBGP into file
"""
from sys import stdin, argv
from datetime import datetime
# 1st arg is peer number
peer = int(argv[1])
# When the parent dies we are seeing continual newlines, so we only access so many before stopping
counter = 0
routesavefile = open("/tmp/peer%s-received.log" % peer, "w")
while True:
try:
line = stdin.readline()
timestamp = datetime.now().strftime("%Y%m%d_%H:%M:%S - ")
routesavefile.write(timestamp + line)
routesavefile.flush()
if line == "":
counter += 1
if counter > 100:
break
continue
counter = 0
except KeyboardInterrupt:
pass
except IOError:
# most likely a signal during readline
pass
routesavefile.close()

View File

@ -0,0 +1,19 @@
#!/usr/bin/env python
"Helper script to read api commands from a pipe and feed them to ExaBGP"
import sys
if len(sys.argv) != 2:
sys.exit(1)
fifo = sys.argv[1]
while True:
pipe = open(fifo, "r")
with pipe:
line = pipe.readline().strip()
if line != "":
sys.stdout.write("{}\n".format(line))
sys.stdout.flush()
pipe.close()
sys.exit(0)

View File

@ -0,0 +1,21 @@
group controller {
process announce-routes {
run "/etc/exabgp/exa_readpipe.py /var/run/exabgp_peer1.in";
encoder text;
}
process receive-routes {
run "/etc/exabgp/exa-receive.py 1";
receive-routes;
encoder text;
}
neighbor 10.0.1.1 {
router-id 10.0.1.2;
local-address 10.0.1.2;
local-as 64510;
peer-as 64510;
}
}

View File

@ -0,0 +1,38 @@
#!/usr/bin/env python
"""
exa-receive.py: Save received routes form ExaBGP into file
"""
from sys import stdin, argv
from datetime import datetime
# 1st arg is peer number
peer = int(argv[1])
# When the parent dies we are seeing continual newlines, so we only access so many before stopping
counter = 0
routesavefile = open("/tmp/peer%s-received.log" % peer, "w")
while True:
try:
line = stdin.readline()
timestamp = datetime.now().strftime("%Y%m%d_%H:%M:%S - ")
routesavefile.write(timestamp + line)
routesavefile.flush()
if line == "":
counter += 1
if counter > 100:
break
continue
counter = 0
except KeyboardInterrupt:
pass
except IOError:
# most likely a signal during readline
pass
routesavefile.close()

View File

@ -0,0 +1,19 @@
#!/usr/bin/env python
"Helper script to read api commands from a pipe and feed them to ExaBGP"
import sys
if len(sys.argv) != 2:
sys.exit(1)
fifo = sys.argv[1]
while True:
pipe = open(fifo, "r")
with pipe:
line = pipe.readline().strip()
if line != "":
sys.stdout.write("{}\n".format(line))
sys.stdout.flush()
pipe.close()
sys.exit(0)

View File

@ -0,0 +1,21 @@
group controller {
process announce-routes {
run "/etc/exabgp/exa_readpipe.py /var/run/exabgp_peer2.in";
encoder text;
}
process receive-routes {
run "/etc/exabgp/exa-receive.py 2";
receive-routes;
encoder text;
}
neighbor 10.0.2.1 {
router-id 10.0.2.2;
local-address 10.0.2.2;
local-as 64511;
peer-as 64511;
}
}

View File

@ -0,0 +1,38 @@
#!/usr/bin/env python
"""
exa-receive.py: Save received routes form ExaBGP into file
"""
from sys import stdin, argv
from datetime import datetime
# 1st arg is peer number
peer = int(argv[1])
# When the parent dies we are seeing continual newlines, so we only access so many before stopping
counter = 0
routesavefile = open("/tmp/peer%s-received.log" % peer, "w")
while True:
try:
line = stdin.readline()
timestamp = datetime.now().strftime("%Y%m%d_%H:%M:%S - ")
routesavefile.write(timestamp + line)
routesavefile.flush()
if line == "":
counter += 1
if counter > 100:
break
continue
counter = 0
except KeyboardInterrupt:
pass
except IOError:
# most likely a signal during readline
pass
routesavefile.close()

View File

@ -0,0 +1,19 @@
#!/usr/bin/env python
"Helper script to read api commands from a pipe and feed them to ExaBGP"
import sys
if len(sys.argv) != 2:
sys.exit(1)
fifo = sys.argv[1]
while True:
pipe = open(fifo, "r")
with pipe:
line = pipe.readline().strip()
if line != "":
sys.stdout.write("{}\n".format(line))
sys.stdout.flush()
pipe.close()
sys.exit(0)

View File

@ -0,0 +1,21 @@
group controller {
process announce-routes {
run "/etc/exabgp/exa_readpipe.py /var/run/exabgp_peer3.in";
encoder text;
}
process receive-routes {
run "/etc/exabgp/exa-receive.py 3";
receive-routes;
encoder text;
}
neighbor 10.0.3.1 {
router-id 10.0.3.2;
local-address 10.0.3.2;
local-as 64502;
peer-as 64501;
}
}

View File

@ -0,0 +1,38 @@
#!/usr/bin/env python
"""
exa-receive.py: Save received routes form ExaBGP into file
"""
from sys import stdin, argv
from datetime import datetime
# 1st arg is peer number
peer = int(argv[1])
# When the parent dies we are seeing continual newlines, so we only access so many before stopping
counter = 0
routesavefile = open("/tmp/peer%s-received.log" % peer, "w")
while True:
try:
line = stdin.readline()
timestamp = datetime.now().strftime("%Y%m%d_%H:%M:%S - ")
routesavefile.write(timestamp + line)
routesavefile.flush()
if line == "":
counter += 1
if counter > 100:
break
continue
counter = 0
except KeyboardInterrupt:
pass
except IOError:
# most likely a signal during readline
pass
routesavefile.close()

View File

@ -0,0 +1,19 @@
#!/usr/bin/env python
"Helper script to read api commands from a pipe and feed them to ExaBGP"
import sys
if len(sys.argv) != 2:
sys.exit(1)
fifo = sys.argv[1]
while True:
pipe = open(fifo, "r")
with pipe:
line = pipe.readline().strip()
if line != "":
sys.stdout.write("{}\n".format(line))
sys.stdout.flush()
pipe.close()
sys.exit(0)

View File

@ -0,0 +1,21 @@
group controller {
process announce-routes {
run "/etc/exabgp/exa_readpipe.py /var/run/exabgp_peer4.in";
encoder text;
}
process receive-routes {
run "/etc/exabgp/exa-receive.py 4";
receive-routes;
encoder text;
}
neighbor 10.0.4.1 {
router-id 10.0.4.2;
local-address 10.0.4.2;
local-as 64503;
peer-as 64501;
}
}

View File

@ -0,0 +1,16 @@
!
router bgp 64510
bgp router-id 10.0.1.1
no bgp ebgp-requires-policy
bgp confederation identifier 64501
bgp confederation peers 64511
bgp bestpath as-path multipath-relax
bgp bestpath compare-routerid
bgp bestpath peer-type multipath-relax
neighbor 10.0.1.2 remote-as 64510
neighbor 10.0.3.2 remote-as 64502
neighbor 10.0.4.2 remote-as 64503
neighbor 10.0.5.2 remote-as 64511
!
line vty
!

View File

@ -0,0 +1,50 @@
{
"routes": { "203.0.113.0/30": [
{
"valid":true,
"multipath":true,
"pathFrom":"external",
"peerId":"10.0.5.2"
},
{
"valid":true,
"bestpath":true,
"selectionReason":"Peer Type",
"pathFrom":"external",
"peerId":"10.0.4.2"
},
{
"valid":true,
"multipath":true,
"pathFrom":"internal",
"peerId":"10.0.1.2"
}
],"203.0.113.4/30": [
{
"valid":true,
"bestpath":true,
"selectionReason":"Confed Peer Type",
"pathFrom":"external",
"peerId":"10.0.5.2"
},
{
"valid":true,
"multipath":true,
"pathFrom":"internal",
"peerId":"10.0.1.2"
}
],"203.0.113.8/30": [
{
"valid":true,
"multipath":true,
"pathFrom":"external",
"peerId":"10.0.4.2"
},
{
"valid":true,
"bestpath":true,
"selectionReason":"Router ID",
"pathFrom":"external",
"peerId":"10.0.3.2"
}
] } }

View File

@ -0,0 +1,50 @@
{
"routes": { "203.0.113.0/30": [
{
"valid":true,
"multipath":null,
"pathFrom":"external",
"peerId":"10.0.5.2"
},
{
"valid":true,
"bestpath":true,
"selectionReason":"Peer Type",
"pathFrom":"external",
"peerId":"10.0.4.2"
},
{
"valid":true,
"multipath":null,
"pathFrom":"internal",
"peerId":"10.0.1.2"
}
],"203.0.113.4/30": [
{
"valid":true,
"bestpath":true,
"selectionReason":"Confed Peer Type",
"pathFrom":"external",
"peerId":"10.0.5.2"
},
{
"valid":true,
"multipath":null,
"pathFrom":"internal",
"peerId":"10.0.1.2"
}
],"203.0.113.8/30": [
{
"valid":true,
"multipath":true,
"pathFrom":"external",
"peerId":"10.0.4.2"
},
{
"valid":true,
"bestpath":true,
"selectionReason":"Router ID",
"pathFrom":"external",
"peerId":"10.0.3.2"
}
] } }

View File

@ -0,0 +1,33 @@
{
"203.0.113.0\/30":[
{
"prefix":"203.0.113.0\/30",
"protocol":"bgp",
"installed":true,
"internalNextHopNum":4,
"internalNextHopActiveNum":4,
"nexthops":[
{
"ip":"198.51.100.2",
"active":true,
"recursive":true
},
{
"fib":true,
"ip":"10.0.3.2",
"active":true
},
{
"ip":"198.51.100.10",
"active":true,
"recursive":true
},
{
"fib":true,
"ip":"10.0.3.2",
"active":true
}
]
}
]
}

View File

@ -0,0 +1,33 @@
{
"203.0.113.0\/30":[
{
"prefix":"203.0.113.0\/30",
"protocol":"bgp",
"installed":true,
"internalNextHopNum":4,
"internalNextHopActiveNum":4,
"nexthops":[
{
"ip":"198.51.100.1",
"active":true,
"recursive":true
},
{
"fib":true,
"ip":"10.0.3.2",
"active":true
},
{
"ip":"198.51.100.10",
"active":true,
"recursive":true
},
{
"fib":true,
"ip":"10.0.3.2",
"active":true
}
]
}
]
}

View File

@ -0,0 +1,35 @@
{
"prefix":"203.0.113.0\/30",
"paths":[
{
"valid":false,
"peer":{
"peerId":"10.0.4.2",
"routerId":"10.0.4.2",
"type":"external"
}
},
{
"valid":true,
"multipath":true,
"bestpath":{
"overall":true,
"selectionReason":"Confed Peer Type"
},
"peer":{
"peerId":"10.0.5.2",
"routerId":"10.0.5.2",
"type":"confed-external"
}
},
{
"valid":true,
"multipath":true,
"peer":{
"peerId":"10.0.1.2",
"routerId":"10.0.1.2",
"type":"confed-internal"
}
}
]
}

View File

@ -0,0 +1,36 @@
{
"prefix":"203.0.113.0\/30",
"paths":[
{
"valid":true,
"multipath":true,
"bestpath":{
"overall":true,
"selectionReason":"Peer Type"
},
"peer":{
"peerId":"10.0.4.2",
"routerId":"10.0.4.2",
"type":"external"
}
},
{
"valid":true,
"multipath":true,
"peer":{
"peerId":"10.0.5.2",
"routerId":"10.0.5.2",
"type":"confed-external"
}
},
{
"valid":true,
"multipath":true,
"peer":{
"peerId":"10.0.1.2",
"routerId":"10.0.1.2",
"type":"confed-internal"
}
}
]
}

View File

@ -0,0 +1,33 @@
{
"prefix":"203.0.113.0\/30",
"paths":[
{
"multipath":true,
"peer":{
"peerId":"10.0.5.2",
"routerId":"10.0.5.2",
"type":"confed-external"
}
},
{
"multipath":true,
"bestpath":{
"overall":true,
"selectionReason":"Peer Type"
},
"peer":{
"peerId":"10.0.4.2",
"routerId":"10.0.4.2",
"type":"external"
}
},
{
"multipath":true,
"peer":{
"peerId":"10.0.1.2",
"routerId":"10.0.1.2",
"type":"confed-internal"
}
}
]
}

View File

@ -0,0 +1,23 @@
{
"203.0.113.8\/30":[
{
"prefix":"203.0.113.8\/30",
"protocol":"bgp",
"installed":true,
"internalNextHopNum":2,
"internalNextHopActiveNum":1,
"nexthops":[
{
"fib":true,
"ip":"10.0.3.2",
"active":true
},
{
"fib":null,
"ip":"198.51.100.10",
"active":null
}
]
}
]
}

View File

@ -0,0 +1,21 @@
{
"prefix":"203.0.113.8\/30",
"paths":[
{
"valid":false,
"peer":{
"peerId":"10.0.4.2",
"routerId":"10.0.4.2",
"type":"external"
}
},
{
"valid":true,
"peer":{
"peerId":"10.0.3.2",
"routerId":"10.0.3.2",
"type":"external"
}
}
]
}

View File

@ -0,0 +1,23 @@
{
"prefix":"203.0.113.8\/30",
"paths":[
{
"valid":true,
"multipath":true,
"peer":{
"peerId":"10.0.4.2",
"routerId":"10.0.4.2",
"type":"external"
}
},
{
"valid":true,
"multipath":true,
"peer":{
"peerId":"10.0.3.2",
"routerId":"10.0.3.2",
"type":"external"
}
}
]
}

View File

@ -0,0 +1,27 @@
!
hostname r1
!
interface r1-eth0
description ExaBGP iBGP peer1
ip address 10.0.1.1/24
no link-detect
!
interface r1-eth1
description ExaBGP peer3
ip address 10.0.3.1/24
no link-detect
!
interface r1-eth2
description ExaBGP peer4
ip address 10.0.4.1/24
no link-detect
!
interface r1-eth3
description r2 confed peer
ip address 10.0.5.1/24
no link-detect
!
ip forwarding
!
line vty
!

View File

@ -0,0 +1,19 @@
!
!log file bgpd.log
!
router bgp 64511
bgp confederation identifier 64501
bgp confederation peers 64510
bgp router-id 10.0.5.2
no bgp ebgp-requires-policy
neighbor 10.0.2.2 remote-as 64511
neighbor 10.0.5.1 remote-as 64510
!
address-family ipv4 unicast
neighbor 10.0.5.1 route-map dropall in
exit-address-family
!
route-map dropall deny 10
!
line vty
!

View File

@ -0,0 +1,4 @@
hostname r2
!
ip route 198.51.100.0/24 10.0.2.2
!

View File

@ -0,0 +1,19 @@
!
!
hostname r2
!
interface r2-eth0
description ExaBGP peer
ip address 10.0.2.1/24
no link-detect
!
interface r2-eth1
description r1 confed peer
ip address 10.0.5.2/24
no link-detect
!
ip forwarding
!
!
line vty
!

View File

@ -0,0 +1,258 @@
#!/usr/bin/env python
#
# Part of NetDEF Topology Tests
#
# Copyright (c) 2021 Arista Networks, Inc.
#
# Permission to use, copy, modify, and/or distribute this software
# for any purpose with or without fee is hereby granted, provided
# that the above copyright notice and this permission notice appear
# in all copies.
#
# THE SOFTWARE IS PROVIDED "AS IS" AND Arista Networks DISCLAIMS ALL WARRANTIES
# WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
# MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL NETDEF BE LIABLE FOR
# ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY
# DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS,
# WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS
# ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE
# OF THIS SOFTWARE.
#
"""
test_bgp_peer-type_multipath-relax.py:
Test the effects of the "bgp bestpath peer-type multipath-relax" command
- enabling the command allows eBGP, iBGP, and confed routes to be multipath
- the choice of best path is not affected
- disabling the command removes iBGP/confed routes from multipath
Topology used by the test:
eBGP +------+ iBGP
peer1 ---- | r1 | ---- peer3
| |
peer2 ---- r2 ---- | | ---- peer4
iBGP confed +------+ eBGP
r2 is present in this topology because ExaBGP does not currently support
confederations so we use FRR to advertise the required AS_CONFED_SEQUENCE.
Routes are advertised from different peers to form interesting multipaths.
peer1 peer2 peer3 peer4 multipath on r1
203.0.113.0/30 x x x all 3
203.0.113.4/30 x x confed-iBGP
203.0.113.8/30 x x eBGP-only
There is also a BGP-advertised route used only for recursively resolving
next hops.
"""
import functools
import json
import os
import pytest
import sys
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
from lib.topolog import logger
from mininet.topo import Topo
class PeerTypeRelaxTopo(Topo):
def build(self, *_args, **_opts):
"Build function"
tgen = get_topogen(self)
# Set up routers
tgen.add_router("r1") # DUT
tgen.add_router("r2")
# Set up peers
for peern in range(1, 5):
peer = tgen.add_exabgp_peer(
"peer{}".format(peern),
ip="10.0.{}.2/24".format(peern),
defaultRoute="via 10.0.{}.1".format(peern),
)
if peern == 2:
tgen.add_link(tgen.gears["r2"], peer)
else:
tgen.add_link(tgen.gears["r1"], peer)
tgen.add_link(tgen.gears["r1"], tgen.gears["r2"])
def setup_module(mod):
"Sets up the pytest environment"
tgen = Topogen(PeerTypeRelaxTopo, mod.__name__)
tgen.start_topology()
# For all registered routers, load the zebra configuration file
for rname, router in tgen.routers().items():
router.run("/bin/bash {}/setup_vrfs".format(CWD))
router.load_config(
TopoRouter.RD_ZEBRA, os.path.join(CWD, "{}/zebra.conf".format(rname))
)
router.load_config(
TopoRouter.RD_STATIC, os.path.join(CWD, "{}/staticd.conf".format(rname))
)
router.load_config(
TopoRouter.RD_BGP, os.path.join(CWD, "{}/bgpd.conf".format(rname))
)
# After loading the configurations, this function loads configured daemons.
tgen.start_router()
# Start up exabgp peers
peers = tgen.exabgp_peers()
for peer in peers:
fifo_in = "/var/run/exabgp_{}.in".format(peer)
if os.path.exists(fifo_in):
os.remove(fifo_in)
os.mkfifo(fifo_in, 0o777)
logger.info("Starting ExaBGP on peer {}".format(peer))
peer_dir = os.path.join(CWD, peer)
env_file = os.path.join(CWD, "exabgp.env")
peers[peer].start(peer_dir, env_file)
def teardown_module(mod):
"Teardown the pytest environment"
tgen = get_topogen()
# This function tears down the whole topology.
tgen.stop_topology()
def test_bgp_peer_type_multipath_relax():
tgen = get_topogen()
# Don't run this test if we have any failure.
if tgen.routers_have_failure():
pytest.skip(tgen.errors)
def exabgp_cmd(peer, cmd):
pipe = open("/run/exabgp_{}.in".format(peer), "w")
with pipe:
pipe.write(cmd)
pipe.close()
# Prefixes used in the test
prefix1 = "203.0.113.0/30"
prefix2 = "203.0.113.4/30"
prefix3 = "203.0.113.8/30"
# Next hops used for iBGP/confed routes
resolved_nh1 = "198.51.100.1"
resolved_nh2 = "198.51.100.2"
# BGP route used for recursive resolution
bgp_resolving_prefix = "198.51.100.0/24"
# Next hop that will require non-connected recursive resolution
ebgp_resolved_nh = "198.51.100.10"
# Send a non-connected route to resolve others
exabgp_cmd(
"peer3", "announce route {} next-hop self\n".format(bgp_resolving_prefix)
)
router = tgen.gears["r1"]
# It seems that if you write to the exabgp socket too quickly in
# succession, requests get lost. So verify prefix1 now instead of
# after all the prefixes are advertised.
logger.info("Create and verify mixed-type multipaths")
exabgp_cmd(
"peer1",
"announce route {} next-hop {} as-path [ 64499 ]\n".format(
prefix1, resolved_nh1
),
)
exabgp_cmd(
"peer2",
"announce route {} next-hop {} as-path [ 64499 ]\n".format(
prefix1, resolved_nh2
),
)
exabgp_cmd("peer4", "announce route {} next-hop self\n".format(prefix1))
reffile = os.path.join(CWD, "r1/prefix1.json")
expected = json.loads(open(reffile).read())
test_func = functools.partial(
topotest.router_json_cmp,
router,
"show ip bgp {} json".format(prefix1),
expected,
)
_, res = topotest.run_and_expect(test_func, None, count=10, wait=1)
assertMsg = "Mixed-type multipath not found"
assert res is None, assertMsg
logger.info("Create and verify eBGP and iBGP+confed multipaths")
exabgp_cmd(
"peer1",
"announce route {} next-hop {} as-path [ 64499 ]\n".format(
prefix2, resolved_nh1
),
)
exabgp_cmd(
"peer2",
"announce route {} next-hop {} as-path [ 64499 ]\n".format(
prefix2, resolved_nh2
),
)
exabgp_cmd("peer3", "announce route {} next-hop self".format(prefix3))
exabgp_cmd("peer4", "announce route {} next-hop self".format(prefix3))
reffile = os.path.join(CWD, "r1/multipath.json")
expected = json.loads(open(reffile).read())
test_func = functools.partial(
topotest.router_json_cmp, router, "show ip bgp json", expected
)
_, res = topotest.run_and_expect(test_func, None, count=10, wait=1)
assertMsg = "Not all expected multipaths found"
assert res is None, assertMsg
logger.info("Toggle peer-type multipath-relax and verify the changes")
router.vtysh_cmd(
"conf\n router bgp 64510\n no bgp bestpath peer-type multipath-relax\n"
)
# This file verifies "multipath" is not set
reffile = os.path.join(CWD, "r1/not-multipath.json")
expected = json.loads(open(reffile).read())
test_func = functools.partial(
topotest.router_json_cmp, router, "show ip bgp json", expected
)
_, res = topotest.run_and_expect(test_func, None, count=10, wait=1)
assertMsg = "Disabling peer-type multipath-relax did not take effect"
assert res is None, assertMsg
router.vtysh_cmd(
"conf\n router bgp 64510\n bgp bestpath peer-type multipath-relax\n"
)
reffile = os.path.join(CWD, "r1/multipath.json")
expected = json.loads(open(reffile).read())
test_func = functools.partial(
topotest.router_json_cmp, router, "show ip bgp json", expected
)
_, res = topotest.run_and_expect(test_func, None, count=10, wait=1)
assertMsg = "Reenabling peer-type multipath-relax did not take effect"
assert res is None, assertMsg
def test_memory_leak():
"Run the memory leak test and report results."
tgen = get_topogen()
if not tgen.is_memleak_enabled():
pytest.skip("Memory leak test/report is disabled")
tgen.report_memory_leaks()
if __name__ == "__main__":
args = ["-s"] + sys.argv[1:]
sys.exit(pytest.main(args))