]> git.puffer.fish Git - matthieu/frr.git/commitdiff
bgpd: Add "bgp bestpath peer-type multipath-relax"
authorJoanne Mikkelson <jmmikkel@arista.com>
Wed, 17 Mar 2021 22:26:59 +0000 (15:26 -0700)
committerJoanne Mikkelson <jmmikkel@arista.com>
Tue, 23 Mar 2021 15:59:33 +0000 (08:59 -0700)
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>
33 files changed:
bgpd/bgp_route.c
bgpd/bgp_vty.c
bgpd/bgpd.h
doc/user/bgp.rst
tests/topotests/bgp_peer-type_multipath-relax/exabgp.env [new file with mode: 0644]
tests/topotests/bgp_peer-type_multipath-relax/peer1/exa-receive.py [new file with mode: 0755]
tests/topotests/bgp_peer-type_multipath-relax/peer1/exa_readpipe.py [new file with mode: 0644]
tests/topotests/bgp_peer-type_multipath-relax/peer1/exabgp.cfg [new file with mode: 0644]
tests/topotests/bgp_peer-type_multipath-relax/peer2/exa-receive.py [new file with mode: 0755]
tests/topotests/bgp_peer-type_multipath-relax/peer2/exa_readpipe.py [new file with mode: 0644]
tests/topotests/bgp_peer-type_multipath-relax/peer2/exabgp.cfg [new file with mode: 0644]
tests/topotests/bgp_peer-type_multipath-relax/peer3/exa-receive.py [new file with mode: 0755]
tests/topotests/bgp_peer-type_multipath-relax/peer3/exa_readpipe.py [new file with mode: 0644]
tests/topotests/bgp_peer-type_multipath-relax/peer3/exabgp.cfg [new file with mode: 0644]
tests/topotests/bgp_peer-type_multipath-relax/peer4/exa-receive.py [new file with mode: 0755]
tests/topotests/bgp_peer-type_multipath-relax/peer4/exa_readpipe.py [new file with mode: 0644]
tests/topotests/bgp_peer-type_multipath-relax/peer4/exabgp.cfg [new file with mode: 0644]
tests/topotests/bgp_peer-type_multipath-relax/r1/bgpd.conf [new file with mode: 0644]
tests/topotests/bgp_peer-type_multipath-relax/r1/multipath.json [new file with mode: 0644]
tests/topotests/bgp_peer-type_multipath-relax/r1/not-multipath.json [new file with mode: 0644]
tests/topotests/bgp_peer-type_multipath-relax/r1/prefix1-eBGP-confed.json [new file with mode: 0644]
tests/topotests/bgp_peer-type_multipath-relax/r1/prefix1-eBGP-iBGP.json [new file with mode: 0644]
tests/topotests/bgp_peer-type_multipath-relax/r1/prefix1-no-recursive.json [new file with mode: 0644]
tests/topotests/bgp_peer-type_multipath-relax/r1/prefix1-recursive.json [new file with mode: 0644]
tests/topotests/bgp_peer-type_multipath-relax/r1/prefix1.json [new file with mode: 0644]
tests/topotests/bgp_peer-type_multipath-relax/r1/prefix3-ip-route.json [new file with mode: 0644]
tests/topotests/bgp_peer-type_multipath-relax/r1/prefix3-no-recursive.json [new file with mode: 0644]
tests/topotests/bgp_peer-type_multipath-relax/r1/prefix3-recursive.json [new file with mode: 0644]
tests/topotests/bgp_peer-type_multipath-relax/r1/zebra.conf [new file with mode: 0644]
tests/topotests/bgp_peer-type_multipath-relax/r2/bgpd.conf [new file with mode: 0644]
tests/topotests/bgp_peer-type_multipath-relax/r2/staticd.conf [new file with mode: 0644]
tests/topotests/bgp_peer-type_multipath-relax/r2/zebra.conf [new file with mode: 0644]
tests/topotests/bgp_peer-type_multipath-relax/test_bgp_peer-type_multipath-relax.py [new file with mode: 0755]

index 124a477248ea233c068b9e6c10a4cf556de22194..0a8292a1a9901525956468a278a06e0cde26edbd 100644 (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
index a6c00d57353ff1d7446467fcbafd392815f85a3d..01e88cbb30cc5e9b4abfec2dbc31efb9ab43dfc0 100644 (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);
index 029019dd3c470b90f82c4733ed76f8b0aa21a7f3..0085a60768de6a2595adee461c1890b920ad78be 100644 (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];
index 6c0a4306f4b543250012a3606cd43bddaefd9f2b..d6d583c9d67d9789bb54fc389cb882f27c137e36 100644 (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
diff --git a/tests/topotests/bgp_peer-type_multipath-relax/exabgp.env b/tests/topotests/bgp_peer-type_multipath-relax/exabgp.env
new file mode 100644 (file)
index 0000000..6c554f5
--- /dev/null
@@ -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
diff --git a/tests/topotests/bgp_peer-type_multipath-relax/peer1/exa-receive.py b/tests/topotests/bgp_peer-type_multipath-relax/peer1/exa-receive.py
new file mode 100755 (executable)
index 0000000..031ff45
--- /dev/null
@@ -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()
diff --git a/tests/topotests/bgp_peer-type_multipath-relax/peer1/exa_readpipe.py b/tests/topotests/bgp_peer-type_multipath-relax/peer1/exa_readpipe.py
new file mode 100644 (file)
index 0000000..9e689a2
--- /dev/null
@@ -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)
diff --git a/tests/topotests/bgp_peer-type_multipath-relax/peer1/exabgp.cfg b/tests/topotests/bgp_peer-type_multipath-relax/peer1/exabgp.cfg
new file mode 100644 (file)
index 0000000..4a7dc48
--- /dev/null
@@ -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;
+    }
+
+}
diff --git a/tests/topotests/bgp_peer-type_multipath-relax/peer2/exa-receive.py b/tests/topotests/bgp_peer-type_multipath-relax/peer2/exa-receive.py
new file mode 100755 (executable)
index 0000000..031ff45
--- /dev/null
@@ -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()
diff --git a/tests/topotests/bgp_peer-type_multipath-relax/peer2/exa_readpipe.py b/tests/topotests/bgp_peer-type_multipath-relax/peer2/exa_readpipe.py
new file mode 100644 (file)
index 0000000..9e689a2
--- /dev/null
@@ -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)
diff --git a/tests/topotests/bgp_peer-type_multipath-relax/peer2/exabgp.cfg b/tests/topotests/bgp_peer-type_multipath-relax/peer2/exabgp.cfg
new file mode 100644 (file)
index 0000000..b53b054
--- /dev/null
@@ -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;
+    }
+
+}
diff --git a/tests/topotests/bgp_peer-type_multipath-relax/peer3/exa-receive.py b/tests/topotests/bgp_peer-type_multipath-relax/peer3/exa-receive.py
new file mode 100755 (executable)
index 0000000..031ff45
--- /dev/null
@@ -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()
diff --git a/tests/topotests/bgp_peer-type_multipath-relax/peer3/exa_readpipe.py b/tests/topotests/bgp_peer-type_multipath-relax/peer3/exa_readpipe.py
new file mode 100644 (file)
index 0000000..9e689a2
--- /dev/null
@@ -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)
diff --git a/tests/topotests/bgp_peer-type_multipath-relax/peer3/exabgp.cfg b/tests/topotests/bgp_peer-type_multipath-relax/peer3/exabgp.cfg
new file mode 100644 (file)
index 0000000..6a1cc2f
--- /dev/null
@@ -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;
+    }
+
+}
diff --git a/tests/topotests/bgp_peer-type_multipath-relax/peer4/exa-receive.py b/tests/topotests/bgp_peer-type_multipath-relax/peer4/exa-receive.py
new file mode 100755 (executable)
index 0000000..031ff45
--- /dev/null
@@ -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()
diff --git a/tests/topotests/bgp_peer-type_multipath-relax/peer4/exa_readpipe.py b/tests/topotests/bgp_peer-type_multipath-relax/peer4/exa_readpipe.py
new file mode 100644 (file)
index 0000000..9e689a2
--- /dev/null
@@ -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)
diff --git a/tests/topotests/bgp_peer-type_multipath-relax/peer4/exabgp.cfg b/tests/topotests/bgp_peer-type_multipath-relax/peer4/exabgp.cfg
new file mode 100644 (file)
index 0000000..2cc26cb
--- /dev/null
@@ -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;
+    }
+
+}
diff --git a/tests/topotests/bgp_peer-type_multipath-relax/r1/bgpd.conf b/tests/topotests/bgp_peer-type_multipath-relax/r1/bgpd.conf
new file mode 100644 (file)
index 0000000..038f108
--- /dev/null
@@ -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
+!
diff --git a/tests/topotests/bgp_peer-type_multipath-relax/r1/multipath.json b/tests/topotests/bgp_peer-type_multipath-relax/r1/multipath.json
new file mode 100644 (file)
index 0000000..11dad78
--- /dev/null
@@ -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"
+  }
+] }  }
diff --git a/tests/topotests/bgp_peer-type_multipath-relax/r1/not-multipath.json b/tests/topotests/bgp_peer-type_multipath-relax/r1/not-multipath.json
new file mode 100644 (file)
index 0000000..c621832
--- /dev/null
@@ -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"
+  }
+] }  }
diff --git a/tests/topotests/bgp_peer-type_multipath-relax/r1/prefix1-eBGP-confed.json b/tests/topotests/bgp_peer-type_multipath-relax/r1/prefix1-eBGP-confed.json
new file mode 100644 (file)
index 0000000..22ec2c2
--- /dev/null
@@ -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
+        }
+      ]
+    }
+  ]
+}
diff --git a/tests/topotests/bgp_peer-type_multipath-relax/r1/prefix1-eBGP-iBGP.json b/tests/topotests/bgp_peer-type_multipath-relax/r1/prefix1-eBGP-iBGP.json
new file mode 100644 (file)
index 0000000..facddcd
--- /dev/null
@@ -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
+        }
+      ]
+    }
+  ]
+}
diff --git a/tests/topotests/bgp_peer-type_multipath-relax/r1/prefix1-no-recursive.json b/tests/topotests/bgp_peer-type_multipath-relax/r1/prefix1-no-recursive.json
new file mode 100644 (file)
index 0000000..5399cee
--- /dev/null
@@ -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"
+      }
+    }
+  ]
+}
diff --git a/tests/topotests/bgp_peer-type_multipath-relax/r1/prefix1-recursive.json b/tests/topotests/bgp_peer-type_multipath-relax/r1/prefix1-recursive.json
new file mode 100644 (file)
index 0000000..7da95ae
--- /dev/null
@@ -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"
+      }
+    }
+  ]
+}
diff --git a/tests/topotests/bgp_peer-type_multipath-relax/r1/prefix1.json b/tests/topotests/bgp_peer-type_multipath-relax/r1/prefix1.json
new file mode 100644 (file)
index 0000000..a90669a
--- /dev/null
@@ -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"
+      }
+    }
+  ]
+}
diff --git a/tests/topotests/bgp_peer-type_multipath-relax/r1/prefix3-ip-route.json b/tests/topotests/bgp_peer-type_multipath-relax/r1/prefix3-ip-route.json
new file mode 100644 (file)
index 0000000..1bf38ef
--- /dev/null
@@ -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
+        }
+      ]
+    }
+  ]
+}
diff --git a/tests/topotests/bgp_peer-type_multipath-relax/r1/prefix3-no-recursive.json b/tests/topotests/bgp_peer-type_multipath-relax/r1/prefix3-no-recursive.json
new file mode 100644 (file)
index 0000000..33d0f2d
--- /dev/null
@@ -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"
+      }
+    }
+  ] 
+}   
diff --git a/tests/topotests/bgp_peer-type_multipath-relax/r1/prefix3-recursive.json b/tests/topotests/bgp_peer-type_multipath-relax/r1/prefix3-recursive.json
new file mode 100644 (file)
index 0000000..6ac2512
--- /dev/null
@@ -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"
+      }
+    }
+  ] 
+}
diff --git a/tests/topotests/bgp_peer-type_multipath-relax/r1/zebra.conf b/tests/topotests/bgp_peer-type_multipath-relax/r1/zebra.conf
new file mode 100644 (file)
index 0000000..911aa1c
--- /dev/null
@@ -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
+!
diff --git a/tests/topotests/bgp_peer-type_multipath-relax/r2/bgpd.conf b/tests/topotests/bgp_peer-type_multipath-relax/r2/bgpd.conf
new file mode 100644 (file)
index 0000000..2362a19
--- /dev/null
@@ -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
+!
diff --git a/tests/topotests/bgp_peer-type_multipath-relax/r2/staticd.conf b/tests/topotests/bgp_peer-type_multipath-relax/r2/staticd.conf
new file mode 100644 (file)
index 0000000..35ebe0d
--- /dev/null
@@ -0,0 +1,4 @@
+hostname r2
+!
+ip route 198.51.100.0/24 10.0.2.2
+!
diff --git a/tests/topotests/bgp_peer-type_multipath-relax/r2/zebra.conf b/tests/topotests/bgp_peer-type_multipath-relax/r2/zebra.conf
new file mode 100644 (file)
index 0000000..900e7d4
--- /dev/null
@@ -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
+!
diff --git a/tests/topotests/bgp_peer-type_multipath-relax/test_bgp_peer-type_multipath-relax.py b/tests/topotests/bgp_peer-type_multipath-relax/test_bgp_peer-type_multipath-relax.py
new file mode 100755 (executable)
index 0000000..f1ffcf2
--- /dev/null
@@ -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))