diff --git a/bgpd/bgp_attr.c b/bgpd/bgp_attr.c index 18c7b13535d0..f2f7cfa93da5 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 9a047b9d456f..4625f15778a2 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 124e7a388b82..b717793a4568 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 e9a79766ec84..13911b687938 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 remote-as ", + "neighbor remote-as ", 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 ", + "neighbor WORD interface remote-as ", 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 ", + "neighbor WORD interface v6only remote-as ", 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 [remote-as <(1-4294967295)|internal|external>]>", + "no neighbor [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 ", + "no neighbor WORD remote-as ", 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 17d94bd57518..ca752da6b415 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 3c5826113dec..709411b06eb7 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 98834c7c2349..a569a9af2889 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 +.. clicmd:: neighbor PEER interface remote-as 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 000000000000..e69de29bb2d1 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 000000000000..aec0e76a3fd3 --- /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 000000000000..f8d19a0bfd56 --- /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 000000000000..fc6862764f4d --- /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 000000000000..b932920e09d4 --- /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 +# + +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))