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;
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
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. */
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
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;
}
}
}
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
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;
}
}
* 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
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,
} 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)
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");
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);
#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];
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
--- /dev/null
+
+[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
--- /dev/null
+#!/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()
--- /dev/null
+#!/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)
--- /dev/null
+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;
+ }
+
+}
--- /dev/null
+#!/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()
--- /dev/null
+#!/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)
--- /dev/null
+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;
+ }
+
+}
--- /dev/null
+#!/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()
--- /dev/null
+#!/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)
--- /dev/null
+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;
+ }
+
+}
--- /dev/null
+#!/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()
--- /dev/null
+#!/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)
--- /dev/null
+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;
+ }
+
+}
--- /dev/null
+!
+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
+!
--- /dev/null
+{
+ "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"
+ }
+] } }
--- /dev/null
+{
+ "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"
+ }
+] } }
--- /dev/null
+{
+ "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
+ }
+ ]
+ }
+ ]
+}
--- /dev/null
+{
+ "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
+ }
+ ]
+ }
+ ]
+}
--- /dev/null
+{
+ "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"
+ }
+ }
+ ]
+}
--- /dev/null
+{
+ "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"
+ }
+ }
+ ]
+}
--- /dev/null
+{
+ "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"
+ }
+ }
+ ]
+}
--- /dev/null
+{
+ "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
+ }
+ ]
+ }
+ ]
+}
--- /dev/null
+{
+ "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"
+ }
+ }
+ ]
+}
--- /dev/null
+{
+ "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"
+ }
+ }
+ ]
+}
--- /dev/null
+!
+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
+!
--- /dev/null
+!
+!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
+!
--- /dev/null
+hostname r2
+!
+ip route 198.51.100.0/24 10.0.2.2
+!
--- /dev/null
+!
+!
+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
+!
--- /dev/null
+#!/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))