summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorDonatas Abraitis <donatas@opensourcerouting.org>2024-07-04 14:42:19 +0300
committerDonatas Abraitis <donatas@opensourcerouting.org>2024-07-04 14:42:19 +0300
commit0dfe25697f5299326046fcfb66f2c6beca7c423c (patch)
tree081c260eff7eca95aef125f7197cd3f3a2a7e59b
parentd4758b3ccc76c77d0b8ba255089f79e67379bb30 (diff)
bgpd: Implement `neighbor X remote-as auto`
In some cases (large scale) it's desired to avoid changing configurations, but let the BGP to automatically handle ASN changes. `auto` means the peering can be iBGP or eBGP. It will be automatically detected and adjusted from the OPEN message. Signed-off-by: Donatas Abraitis <donatas@opensourcerouting.org>
-rw-r--r--bgpd/bgp_attr.c2
-rw-r--r--bgpd/bgp_packet.c8
-rw-r--r--bgpd/bgp_updgrp.c7
-rw-r--r--bgpd/bgp_vty.c62
-rw-r--r--bgpd/bgpd.c25
-rw-r--r--bgpd/bgpd.h10
-rw-r--r--doc/user/bgp.rst6
-rw-r--r--tests/topotests/bgp_remote_as_auto/__init__.py0
-rw-r--r--tests/topotests/bgp_remote_as_auto/r1/frr.conf17
-rw-r--r--tests/topotests/bgp_remote_as_auto/r2/frr.conf10
-rw-r--r--tests/topotests/bgp_remote_as_auto/r3/frr.conf10
-rw-r--r--tests/topotests/bgp_remote_as_auto/test_bgp_remote_as_auto.py130
12 files changed, 251 insertions, 36 deletions
diff --git a/bgpd/bgp_attr.c b/bgpd/bgp_attr.c
index 18c7b13535..f2f7cfa93d 100644
--- a/bgpd/bgp_attr.c
+++ b/bgpd/bgp_attr.c
@@ -4468,6 +4468,8 @@ bgp_size_t bgp_packet_attribute(struct bgp *bgp, struct peer *peer,
bgp_packet_mpattr_end(s, mpattrlen_pos);
}
+ (void)peer_sort(peer);
+
/* Origin attribute. */
stream_putc(s, BGP_ATTR_FLAG_TRANS);
stream_putc(s, BGP_ATTR_ORIGIN);
diff --git a/bgpd/bgp_packet.c b/bgpd/bgp_packet.c
index 9a047b9d45..4625f15778 100644
--- a/bgpd/bgp_packet.c
+++ b/bgpd/bgp_packet.c
@@ -1977,6 +1977,14 @@ static int bgp_open_receive(struct peer_connection *connection,
BGP_NOTIFY_OPEN_BAD_PEER_AS,
notify_data_remote_as, 2);
return BGP_Stop;
+ } else if (peer->as_type == AS_AUTO) {
+ if (remote_as == peer->bgp->as) {
+ peer->as = peer->local_as;
+ SET_FLAG(peer->as_type, AS_INTERNAL);
+ } else {
+ peer->as = remote_as;
+ SET_FLAG(peer->as_type, AS_EXTERNAL);
+ }
} else if (peer->as_type == AS_INTERNAL) {
if (remote_as != peer->bgp->as) {
if (bgp_debug_neighbor_events(peer))
diff --git a/bgpd/bgp_updgrp.c b/bgpd/bgp_updgrp.c
index 124e7a388b..b717793a45 100644
--- a/bgpd/bgp_updgrp.c
+++ b/bgpd/bgp_updgrp.c
@@ -343,7 +343,12 @@ static unsigned int updgrp_hash_key_make(const void *p)
key = 0;
- key = jhash_1word(peer->sort, key); /* EBGP or IBGP */
+ /* `remote-as auto` technically uses identical peer->sort.
+ * After OPEN message is parsed, this is updated accordingly, but
+ * we need to call the peer_sort() here also to properly create
+ * separate subgroups.
+ */
+ key = jhash_1word(peer_sort((struct peer *)peer), key);
key = jhash_1word(peer->sub_sort, key); /* OAD */
key = jhash_1word((peer->flags & PEER_UPDGRP_FLAGS), key);
key = jhash_1word((flags & PEER_UPDGRP_AF_FLAGS), key);
diff --git a/bgpd/bgp_vty.c b/bgpd/bgp_vty.c
index e9a79766ec..13911b6879 100644
--- a/bgpd/bgp_vty.c
+++ b/bgpd/bgp_vty.c
@@ -4871,6 +4871,9 @@ static int peer_remote_as_vty(struct vty *vty, const char *peer_str,
} else if (as_str[0] == 'e') {
as = 0;
as_type = AS_EXTERNAL;
+ } else if (as_str[0] == 'a') {
+ as = 0;
+ as_type = AS_AUTO;
} else if (!asn_str2asn(as_str, &as))
as_type = AS_UNSPECIFIED;
@@ -4976,13 +4979,14 @@ ALIAS(no_bgp_shutdown, no_bgp_shutdown_msg_cmd,
DEFUN (neighbor_remote_as,
neighbor_remote_as_cmd,
- "neighbor <A.B.C.D|X:X::X:X|WORD> remote-as <ASNUM|internal|external>",
+ "neighbor <A.B.C.D|X:X::X:X|WORD> remote-as <ASNUM|internal|external|auto>",
NEIGHBOR_STR
NEIGHBOR_ADDR_STR2
"Specify a BGP neighbor\n"
AS_STR
"Internal BGP peer\n"
- "External BGP peer\n")
+ "External BGP peer\n"
+ "Automatically detect remote ASN\n")
{
int idx_peer = 1;
int idx_remote_as = 3;
@@ -5054,6 +5058,8 @@ static int peer_conf_interface_get(struct vty *vty, const char *conf_if,
as_type = AS_INTERNAL;
} else if (as_str[0] == 'e') {
as_type = AS_EXTERNAL;
+ } else if (as_str[0] == 'a') {
+ as_type = AS_AUTO;
} else {
/* Get AS number. */
if (asn_str2asn(as_str, &as))
@@ -5170,14 +5176,15 @@ DEFUN (neighbor_interface_config_v6only,
DEFUN (neighbor_interface_config_remote_as,
neighbor_interface_config_remote_as_cmd,
- "neighbor WORD interface remote-as <ASNUM|internal|external>",
+ "neighbor WORD interface remote-as <ASNUM|internal|external|auto>",
NEIGHBOR_STR
"Interface name or neighbor tag\n"
"Enable BGP on interface\n"
"Specify a BGP neighbor\n"
AS_STR
"Internal BGP peer\n"
- "External BGP peer\n")
+ "External BGP peer\n"
+ "Automatically detect remote ASN\n")
{
int idx_word = 1;
int idx_remote_as = 4;
@@ -5187,7 +5194,7 @@ DEFUN (neighbor_interface_config_remote_as,
DEFUN (neighbor_interface_v6only_config_remote_as,
neighbor_interface_v6only_config_remote_as_cmd,
- "neighbor WORD interface v6only remote-as <ASNUM|internal|external>",
+ "neighbor WORD interface v6only remote-as <ASNUM|internal|external|auto>",
NEIGHBOR_STR
"Interface name or neighbor tag\n"
"Enable BGP with v6 link-local only\n"
@@ -5195,7 +5202,8 @@ DEFUN (neighbor_interface_v6only_config_remote_as,
"Specify a BGP neighbor\n"
AS_STR
"Internal BGP peer\n"
- "External BGP peer\n")
+ "External BGP peer\n"
+ "Automatically detect remote ASN\n")
{
int idx_word = 1;
int idx_remote_as = 5;
@@ -5232,14 +5240,15 @@ DEFUN (neighbor_peer_group,
DEFUN (no_neighbor,
no_neighbor_cmd,
- "no neighbor <WORD|<A.B.C.D|X:X::X:X> [remote-as <(1-4294967295)|internal|external>]>",
+ "no neighbor <WORD|<A.B.C.D|X:X::X:X> [remote-as <(1-4294967295)|internal|external|auto>]>",
NO_STR
NEIGHBOR_STR
NEIGHBOR_ADDR_STR2
"Specify a BGP neighbor\n"
AS_STR
"Internal BGP peer\n"
- "External BGP peer\n")
+ "External BGP peer\n"
+ "Automatically detect remote ASN\n")
{
VTY_DECLVAR_CONTEXT(bgp, bgp);
int idx_peer = 2;
@@ -5310,7 +5319,7 @@ DEFUN (no_neighbor,
DEFUN (no_neighbor_interface_config,
no_neighbor_interface_config_cmd,
- "no neighbor WORD interface [v6only] [peer-group PGNAME] [remote-as <(1-4294967295)|internal|external>]",
+ "no neighbor WORD interface [v6only] [peer-group PGNAME] [remote-as <(1-4294967295)|internal|external|auto>]",
NO_STR
NEIGHBOR_STR
"Interface name\n"
@@ -5321,7 +5330,8 @@ DEFUN (no_neighbor_interface_config,
"Specify a BGP neighbor\n"
AS_STR
"Internal BGP peer\n"
- "External BGP peer\n")
+ "External BGP peer\n"
+ "Automatically detect remote ASN\n")
{
VTY_DECLVAR_CONTEXT(bgp, bgp);
int idx_word = 2;
@@ -5378,14 +5388,15 @@ DEFUN (no_neighbor_peer_group,
DEFUN (no_neighbor_interface_peer_group_remote_as,
no_neighbor_interface_peer_group_remote_as_cmd,
- "no neighbor WORD remote-as <ASNUM|internal|external>",
+ "no neighbor WORD remote-as <ASNUM|internal|external|auto>",
NO_STR
NEIGHBOR_STR
"Interface name or neighbor tag\n"
"Specify a BGP neighbor\n"
AS_STR
"Internal BGP peer\n"
- "External BGP peer\n")
+ "External BGP peer\n"
+ "Automatically detect remote ASN\n")
{
VTY_DECLVAR_CONTEXT(bgp, bgp);
int idx_word = 2;
@@ -11887,7 +11898,7 @@ static bool bgp_show_summary_is_peer_filtered(struct peer *peer,
/* filter remote-as (internal|external) */
if (as_type != AS_UNSPECIFIED) {
if (peer->as_type == AS_SPECIFIED) {
- if (as_type == AS_INTERNAL) {
+ if (CHECK_FLAG(as_type, AS_INTERNAL)) {
if (peer->as != peer->local_as)
return true;
} else if (peer->as == peer->local_as)
@@ -12879,6 +12890,8 @@ DEFPY(show_ip_bgp_summary, show_ip_bgp_summary_cmd,
as_type = AS_INTERNAL;
else if (argv[idx + 1]->arg[0] == 'e')
as_type = AS_EXTERNAL;
+ else if (argv[idx + 1]->arg[0] == 'a')
+ as_type = AS_AUTO;
else if (!asn_str2asn(argv[idx + 1]->arg, &as)) {
vty_out(vty,
"%% Invalid neighbor remote-as value: %s\n",
@@ -14002,9 +14015,10 @@ static void bgp_show_peer(struct vty *vty, struct peer *p, bool use_json,
json_object_boolean_true_add(json_neigh,
"localAsReplaceAs");
} else {
- if ((p->as_type == AS_SPECIFIED) ||
- (p->as_type == AS_EXTERNAL) ||
- (p->as_type == AS_INTERNAL)) {
+ if (p->as_type == AS_SPECIFIED ||
+ CHECK_FLAG(p->as_type, AS_AUTO) ||
+ CHECK_FLAG(p->as_type, AS_EXTERNAL) ||
+ CHECK_FLAG(p->as_type, AS_INTERNAL)) {
vty_out(vty, "remote AS ");
vty_out(vty, ASN_FORMAT(bgp->asnotation), &p->as);
vty_out(vty, ", ");
@@ -14023,7 +14037,7 @@ static void bgp_show_peer(struct vty *vty, struct peer *p, bool use_json,
: "");
}
/* peer type internal or confed-internal */
- if ((p->as == p->local_as) || (p->as_type == AS_INTERNAL)) {
+ if ((p->as == p->local_as) || (CHECK_FLAG(p->as_type, AS_INTERNAL))) {
if (use_json) {
if (CHECK_FLAG(bgp->config, BGP_CONFIG_CONFEDERATION))
json_object_boolean_true_add(
@@ -17011,7 +17025,7 @@ static int bgp_show_one_peer_group(struct vty *vty, struct peer_group *group,
&conf->as);
vty_out(vty, "\n");
}
- } else if (conf->as_type == AS_INTERNAL) {
+ } else if (CHECK_FLAG(conf->as_type, AS_INTERNAL)) {
if (json)
asn_asn2json(json, "remoteAs", group->bgp->as,
group->bgp->asnotation);
@@ -17023,7 +17037,8 @@ static int bgp_show_one_peer_group(struct vty *vty, struct peer_group *group,
vty_out(vty, "\nBGP peer-group %s\n", group->name);
}
- if ((group->bgp->as == conf->as) || (conf->as_type == AS_INTERNAL)) {
+ if ((group->bgp->as == conf->as) ||
+ CHECK_FLAG(conf->as_type, AS_INTERNAL)) {
if (json)
json_object_string_add(json_peer_group, "type",
"internal");
@@ -18525,6 +18540,9 @@ static void bgp_config_write_peer_global(struct vty *vty, struct bgp *bgp,
} else if (peer->as_type == AS_EXTERNAL) {
vty_out(vty, " remote-as external");
if_ras_printed = true;
+ } else if (CHECK_FLAG(peer->as_type, AS_AUTO)) {
+ vty_out(vty, " remote-as auto");
+ if_ras_printed = true;
}
vty_out(vty, "\n");
@@ -18547,6 +18565,9 @@ static void bgp_config_write_peer_global(struct vty *vty, struct bgp *bgp,
vty_out(vty,
" neighbor %s remote-as external\n",
addr);
+ } else if (CHECK_FLAG(peer->as_type, AS_AUTO)) {
+ vty_out(vty, " neighbor %s remote-as auto\n",
+ addr);
}
}
@@ -18576,6 +18597,9 @@ static void bgp_config_write_peer_global(struct vty *vty, struct bgp *bgp,
vty_out(vty,
" neighbor %s remote-as external\n",
addr);
+ } else if (CHECK_FLAG(peer->as_type, AS_AUTO)) {
+ vty_out(vty, " neighbor %s remote-as auto\n",
+ addr);
}
}
}
diff --git a/bgpd/bgpd.c b/bgpd/bgpd.c
index 17d94bd575..ca752da6b4 100644
--- a/bgpd/bgpd.c
+++ b/bgpd/bgpd.c
@@ -1074,10 +1074,10 @@ static inline enum bgp_peer_sort peer_calc_sort(struct peer *peer)
/* Peer-group */
if (CHECK_FLAG(peer->sflags, PEER_STATUS_GROUP)) {
- if (peer->as_type == AS_INTERNAL)
+ if (CHECK_FLAG(peer->as_type, AS_INTERNAL))
return BGP_PEER_IBGP;
- else if (peer->as_type == AS_EXTERNAL)
+ if (CHECK_FLAG(peer->as_type, AS_EXTERNAL))
return BGP_PEER_EBGP;
else if (peer->as_type == AS_SPECIFIED && peer->as) {
@@ -1132,17 +1132,20 @@ static inline enum bgp_peer_sort peer_calc_sort(struct peer *peer)
return BGP_PEER_IBGP;
else
return BGP_PEER_EBGP;
- } else if (peer->group->conf->as_type
- == AS_INTERNAL)
+ } else if (CHECK_FLAG(peer->group->conf->as_type,
+ AS_INTERNAL))
return BGP_PEER_IBGP;
else
return BGP_PEER_EBGP;
}
/* no AS information anywhere, let caller know */
return BGP_PEER_UNSPECIFIED;
- } else if (peer->as_type != AS_SPECIFIED)
- return (peer->as_type == AS_INTERNAL ? BGP_PEER_IBGP
- : BGP_PEER_EBGP);
+ } else if (peer->as_type != AS_SPECIFIED) {
+ if (CHECK_FLAG(peer->as_type, AS_INTERNAL))
+ return BGP_PEER_IBGP;
+ else if (CHECK_FLAG(peer->as_type, AS_EXTERNAL))
+ return BGP_PEER_EBGP;
+ }
return (local_as == 0 ? BGP_PEER_INTERNAL
: local_as == peer->as ? BGP_PEER_IBGP
@@ -2201,10 +2204,10 @@ int peer_remote_as(struct bgp *bgp, union sockunion *su, const char *conf_if,
}
} else {
/* internal/external used, compare as-types */
- if (((peer_sort_type == BGP_PEER_IBGP)
- && (as_type != AS_INTERNAL))
- || ((peer_sort_type == BGP_PEER_EBGP)
- && (as_type != AS_EXTERNAL))) {
+ if (((peer_sort_type == BGP_PEER_IBGP) &&
+ !CHECK_FLAG(as_type, AS_INTERNAL)) ||
+ ((peer_sort_type == BGP_PEER_EBGP) &&
+ !CHECK_FLAG(as_type, AS_EXTERNAL))) {
*as = peer->as;
return BGP_ERR_PEER_GROUP_PEER_TYPE_DIFFERENT;
}
diff --git a/bgpd/bgpd.h b/bgpd/bgpd.h
index 3c5826113d..709411b06e 100644
--- a/bgpd/bgpd.h
+++ b/bgpd/bgpd.h
@@ -56,10 +56,12 @@ struct bgp_pbr_config;
* behavior
* in the system.
*/
-enum { AS_UNSPECIFIED = 0,
- AS_SPECIFIED,
- AS_INTERNAL,
- AS_EXTERNAL,
+enum peer_asn_type {
+ AS_UNSPECIFIED = 1,
+ AS_SPECIFIED = 2,
+ AS_INTERNAL = 4,
+ AS_EXTERNAL = 8,
+ AS_AUTO = 16,
};
/* Zebra Gracaful Restart states */
diff --git a/doc/user/bgp.rst b/doc/user/bgp.rst
index 98834c7c23..a569a9af28 100644
--- a/doc/user/bgp.rst
+++ b/doc/user/bgp.rst
@@ -1561,6 +1561,10 @@ Defining Peers
peers ASN is the same as mine as specified under the :clicmd:`router bgp ASN`
command the connection will be denied.
+.. clicmd:: neighbor PEER remote-as auto
+
+ The neighbor's ASN is detected automatically from the OPEN message.
+
.. clicmd:: neighbor PEER oad
Mark a peer belonging to the One Administrative Domain.
@@ -1699,7 +1703,7 @@ Configuring Peers
IPv4 session addresses, see the ``neighbor PEER update-source`` command
below.
-.. clicmd:: neighbor PEER interface remote-as <internal|external|ASN>
+.. clicmd:: neighbor PEER interface remote-as <internal|external|auto|ASN>
Configure an unnumbered BGP peer. ``PEER`` should be an interface name. The
session will be established via IPv6 link locals. Use ``internal`` for iBGP
diff --git a/tests/topotests/bgp_remote_as_auto/__init__.py b/tests/topotests/bgp_remote_as_auto/__init__.py
new file mode 100644
index 0000000000..e69de29bb2
--- /dev/null
+++ b/tests/topotests/bgp_remote_as_auto/__init__.py
diff --git a/tests/topotests/bgp_remote_as_auto/r1/frr.conf b/tests/topotests/bgp_remote_as_auto/r1/frr.conf
new file mode 100644
index 0000000000..aec0e76a3f
--- /dev/null
+++ b/tests/topotests/bgp_remote_as_auto/r1/frr.conf
@@ -0,0 +1,17 @@
+!
+int r1-eth0
+ ip address 192.168.1.1/24
+!
+router bgp 65001
+ no bgp ebgp-requires-policy
+ no bgp network import-check
+ neighbor 192.168.1.2 remote-as auto
+ neighbor 192.168.1.2 timers 1 3
+ neighbor 192.168.1.2 timers connect 1
+ neighbor 192.168.1.3 remote-as auto
+ neighbor 192.168.1.3 timers 1 3
+ neighbor 192.168.1.3 timers connect 1
+ address-family ipv4 unicast
+ network 10.0.0.1/32
+ exit-address-family
+!
diff --git a/tests/topotests/bgp_remote_as_auto/r2/frr.conf b/tests/topotests/bgp_remote_as_auto/r2/frr.conf
new file mode 100644
index 0000000000..f8d19a0bfd
--- /dev/null
+++ b/tests/topotests/bgp_remote_as_auto/r2/frr.conf
@@ -0,0 +1,10 @@
+!
+int r2-eth0
+ ip address 192.168.1.2/24
+!
+router bgp 65001
+ no bgp ebgp-requires-policy
+ neighbor 192.168.1.1 remote-as auto
+ neighbor 192.168.1.1 timers 1 3
+ neighbor 192.168.1.1 timers connect 1
+!
diff --git a/tests/topotests/bgp_remote_as_auto/r3/frr.conf b/tests/topotests/bgp_remote_as_auto/r3/frr.conf
new file mode 100644
index 0000000000..fc6862764f
--- /dev/null
+++ b/tests/topotests/bgp_remote_as_auto/r3/frr.conf
@@ -0,0 +1,10 @@
+!
+int r3-eth0
+ ip address 192.168.1.3/24
+!
+router bgp 65003
+ no bgp ebgp-requires-policy
+ neighbor 192.168.1.1 remote-as auto
+ neighbor 192.168.1.1 timers 1 3
+ neighbor 192.168.1.1 timers connect 1
+!
diff --git a/tests/topotests/bgp_remote_as_auto/test_bgp_remote_as_auto.py b/tests/topotests/bgp_remote_as_auto/test_bgp_remote_as_auto.py
new file mode 100644
index 0000000000..b932920e09
--- /dev/null
+++ b/tests/topotests/bgp_remote_as_auto/test_bgp_remote_as_auto.py
@@ -0,0 +1,130 @@
+#!/usr/bin/env python
+# SPDX-License-Identifier: ISC
+
+# Copyright (c) 2024 by
+# Donatas Abraitis <donatas@opensourcerouting.org>
+#
+
+import os
+import re
+import sys
+import json
+import pytest
+import functools
+
+CWD = os.path.dirname(os.path.realpath(__file__))
+sys.path.append(os.path.join(CWD, "../"))
+
+# pylint: disable=C0413
+from lib import topotest
+from lib.topogen import Topogen, get_topogen
+
+pytestmark = [pytest.mark.bgpd]
+
+
+def setup_module(mod):
+ topodef = {"s1": ("r1", "r2", "r3")}
+ tgen = Topogen(topodef, mod.__name__)
+ tgen.start_topology()
+
+ router_list = tgen.routers()
+
+ for _, (rname, router) in enumerate(router_list.items(), 1):
+ router.load_frr_config(os.path.join(CWD, "{}/frr.conf".format(rname)))
+
+ tgen.start_router()
+
+
+def teardown_module(mod):
+ tgen = get_topogen()
+ tgen.stop_topology()
+
+
+def test_bgp_remote_as_auto():
+ tgen = get_topogen()
+
+ if tgen.routers_have_failure():
+ pytest.skip(tgen.errors)
+
+ r1 = tgen.gears["r1"]
+ r2 = tgen.gears["r2"]
+ r3 = tgen.gears["r3"]
+
+ def _bgp_converge():
+ output = json.loads(r1.vtysh_cmd("show bgp ipv4 unicast summary json"))
+ expected = {
+ "peers": {
+ "192.168.1.2": {
+ "hostname": "r2",
+ "remoteAs": 65001,
+ "localAs": 65001,
+ "state": "Established",
+ },
+ "192.168.1.3": {
+ "hostname": "r3",
+ "remoteAs": 65003,
+ "localAs": 65001,
+ "state": "Established",
+ },
+ }
+ }
+ return topotest.json_cmp(output, expected)
+
+ test_func = functools.partial(
+ _bgp_converge,
+ )
+ _, result = topotest.run_and_expect(test_func, None, count=30, wait=1)
+ assert result is None, "Can't see automatic iBGP/eBGP peerings"
+
+ def _bgp_converge_internal():
+ output = json.loads(r2.vtysh_cmd("show bgp ipv4 unicast 10.0.0.1/32 json"))
+ expected = {
+ "paths": [
+ {
+ "aspath": {
+ "string": "Local",
+ },
+ "valid": True,
+ "peer": {
+ "hostname": "r1",
+ "type": "internal",
+ },
+ }
+ ]
+ }
+ return topotest.json_cmp(output, expected)
+
+ test_func = functools.partial(
+ _bgp_converge_internal,
+ )
+ _, result = topotest.run_and_expect(test_func, None, count=30, wait=1)
+ assert result is None, "Can't see automatic iBGP peering"
+
+ def _bgp_converge_external():
+ output = json.loads(r3.vtysh_cmd("show bgp ipv4 unicast 10.0.0.1/32 json"))
+ expected = {
+ "paths": [
+ {
+ "aspath": {
+ "string": "65001",
+ },
+ "valid": True,
+ "peer": {
+ "hostname": "r1",
+ "type": "external",
+ },
+ }
+ ]
+ }
+ return topotest.json_cmp(output, expected)
+
+ test_func = functools.partial(
+ _bgp_converge_external,
+ )
+ _, result = topotest.run_and_expect(test_func, None, count=30, wait=1)
+ assert result is None, "Can't see automatic eBGP peering"
+
+
+if __name__ == "__main__":
+ args = ["-s"] + sys.argv[1:]
+ sys.exit(pytest.main(args))