[PATCH v2 4/4] BGP: Add peers channel and sessions spawn on export to achieve automatic peering
Matteo Perin
matteo.perin at canonical.com
Thu Dec 11 10:16:39 CET 2025
This change proposal is made in order to achieve BGP automatic peering based
on discovered link-local addresses leveraging the IPv6 ND protocol.
A big part of the necessary logic is already present in the dynamic BGP implementation.
The problem is that dynamic BGP works in a passive/reactive model only, as it waits for
peers within a specified range to connect.
This integrates with this implementation by triggering the spawn of a BGP process
sending OPEN requests in order to achieve active peer connection when a valid peer
remote address has been discovered.
The discovery is done by exporting routing information from a table containing
peer data through a newly introduced peers channel. This means that the feature
will only work if there is another protocol actively importing peer data (e.g. RAdv).
The feature is also meant to work only in the presence of a valid dynamic BGP
configuration.
A new configuration paramenter has been added to the BGP protocol, persist [yes/no]
which controls the behavior of sessions shutdown on peer route withdrawal.
If enabled, a BGP connection spawned from peer discovery or dyamic BGP will be left running.
If disabled, the session will be torn down and removed.
Logic to avoid duplicate sessions from both the dynamic BGP and automatic peers
discovery paths has also been introduced.
Signed-off-by: Matteo Perin <matteo.perin at canonical.com>
---
doc/bird.sgml | 54 +++++++++++++
nest/protocol.h | 3 +
proto/bgp/attrs.c | 51 ++++++++++++
proto/bgp/bgp.c | 191 ++++++++++++++++++++++++++++++++++++++++++--
proto/bgp/bgp.h | 27 ++++++-
proto/bgp/config.Y | 12 ++-
proto/bgp/packets.c | 11 +++
7 files changed, 337 insertions(+), 12 deletions(-)
diff --git a/doc/bird.sgml b/doc/bird.sgml
index b0256b73..4e8ab1a6 100644
--- a/doc/bird.sgml
+++ b/doc/bird.sgml
@@ -3807,6 +3807,7 @@ together with their appropriate channels follows.
@ <cf/vpn6 multicast/ | <cf/vpn6/ | <cf/ipv4/ and <cf/ipv6/ | 2 | 129
@ <cf/flow4/ | <cf/flow4/ | --- | 1 | 133
@ <cf/flow6/ | <cf/flow6/ | --- | 2 | 133
+@ <cf/peers/ | <cf/peer/ | --- | --- | ---
</tabular>
</table>
@@ -3828,6 +3829,17 @@ policies of <cf/import all/ and <cf/export none/ are used in absence of explicit
configuration). Note that blanket policies like <cf/all/ or <cf/none/ can still
be used in explicit configuration.
+<p>The <cf/peers/ channel is a special channel type used for BGP unnumbered
+automatic peering. Unlike traditional AFI/SAFI channels that exchange routing
+information, the peers channel is used to dynamically trigger the creation of
+new BGP sessions. When present in a dynamic BGP configuration, exporting
+peer discovery objects (typically from RAdv with router discovery enabled) to
+this channel will automatically spawn new BGP sessions for each discovered peer.
+The lifetime of these automatic sessions is tied to the presence of the peer
+objects in the peers routing table, withdrawing an object will trigger the
+corresponding BGP session shutdown. This channel is only allowed in dynamic BGP
+instances (those configured with <cf/neighbor range/).
+
<p>BGP channels have additional config options (together with the common ones):
<descrip>
@@ -4072,6 +4084,12 @@ be used in explicit configuration.
set <ref id="bgp-max-long-lived-stale-time" name="max long lived stale time">
per AFI/SAFI pair instead of per protocol. Default: set by protocol-wide
option.
+
+ <tag><label id="bgp-peers-persist">peers persist <m/switch/</tag>
+ This option controls whether automatically spawned BGP sessions should be permanent.
+ When enabled, a BGP session that was initially created automatically through the peers
+ discovery or the dynamic BGP mechanisms will continue to exist even after it has been
+ taken down. Default: on.
</descrip>
<sect1>Reconfiguration
@@ -4338,6 +4356,42 @@ protocol bgp {
}
</code>
+<p>Example configuration for BGP unnumbered automatic peering using router discovery:
+
+<p><code>
+# Table for discovered peers
+peers table bgp_peers;
+
+# RAdv protocol with router discovery enabled
+protocol radv {
+ peers { table bgp_peers; }; # Export discovered peers to this table
+
+ interface "eth*" {
+ # Normal RAdv configuration options...
+ router discovery yes; # Enable reception of ICMPv6 RAs
+ };
+}
+
+# Dynamic BGP with peers channel for automatic peering
+protocol bgp {
+ local as 65000;
+ neighbor range fe80::/10 external; # Accept link-local peers
+ dynamic name "bgp_auto_"; # Name spawned sessions with this prefix
+
+ peers persist yes;
+
+ ipv6 {
+ export all;
+ import all;
+ };
+
+ peers {
+ table bgp_peers; # Import peer discovery objects from this table
+ export all; # Allow all discovered peers to trigger sessions
+ };
+}
+</code>
+
<sect>BMP
<label id="bmp">
diff --git a/nest/protocol.h b/nest/protocol.h
index 036e91b4..7e439dd9 100644
--- a/nest/protocol.h
+++ b/nest/protocol.h
@@ -156,6 +156,7 @@ struct proto {
TLIST_LIST(proto_neigh) neighbors; /* List of neighbor structures */
struct iface_subscription iface_sub; /* Interface notification subscription */
struct channel *mpls_channel; /* MPLS channel, when used */
+ struct channel *peers_channel; /* Peers channel for dynamic BGP peer discovery */
const char *name; /* Name of this instance (== cf->name) */
u32 debug; /* Debugging flags */
@@ -754,6 +755,8 @@ static inline struct channel_config *proto_cf_main_channel(struct proto_config *
{ return proto_cf_find_channel(pc, pc->net_type); }
static inline struct channel_config *proto_cf_mpls_channel(struct proto_config *pc)
{ return (pc->net_type != NET_MPLS) ? proto_cf_find_channel(pc, NET_MPLS) : NULL; }
+static inline struct channel_config *proto_cf_peers_channel(struct proto_config *pc)
+{ return proto_cf_find_channel(pc, NET_PEER); }
struct channel *proto_find_channel_by_table(struct proto *p, rtable *t);
struct channel *proto_find_channel_by_name(struct proto *p, const char *n);
diff --git a/proto/bgp/attrs.c b/proto/bgp/attrs.c
index f072e4c9..5de04442 100644
--- a/proto/bgp/attrs.c
+++ b/proto/bgp/attrs.c
@@ -2468,6 +2468,57 @@ bgp_rt_notify(struct proto *P, struct channel *C, const net_addr *n, rte *new, c
if (SHUTTING_DOWN)
return;
+ /* Handle peers channel - spawn BGP sessions for discovered peers */
+ if (C == P->peers_channel)
+ {
+ const net_addr_peer *peer = (const net_addr_peer *) n;
+
+ /* Only handle peer management for dynamic BGP instances and link-local addresses */
+ if (bgp_is_dynamic(p) && ipa_is_link_local(peer->addr))
+ {
+ if (new && !old)
+ {
+ /* New peer discovered - send spawn event to main loop */
+ struct iface *iface = if_find_by_index(peer->ifindex);
+
+ struct bgp_peer_spawn *ev = mb_alloc(p->p.pool, sizeof(struct bgp_peer_spawn));
+ *ev = (struct bgp_peer_spawn) {
+ .p = p,
+ .peer_addr = peer->addr,
+ .iface = iface,
+ };
+
+ /* Initialize callback to run on main_birdloop */
+ callback_init(&ev->cb, bgp_peer_spawn, &main_birdloop);
+
+ /* Send the event */
+ callback_activate(&ev->cb);
+
+ log(L_INFO "%s: Peer %I on interface %s discovered, queued for BGP session spawn",
+ p->p.name, peer->addr, iface->name);
+ }
+ else if (!new && old)
+ {
+ /* Send removal event to main loop */
+ struct bgp_peer_remove *ev = mb_alloc(p->p.pool, sizeof(struct bgp_peer_remove));
+ *ev = (struct bgp_peer_remove) {
+ .p = p,
+ .peer_addr = peer->addr,
+ };
+
+ /* Initialize callback to run on main_birdloop */
+ callback_init(&ev->cb, bgp_peer_remove, &main_birdloop);
+
+ /* Send the event */
+ callback_activate(&ev->cb);
+
+ log(L_INFO "%s: Peer %I withdrawn from channel, BGP session down",
+ p->p.name, peer->addr);
+ }
+ }
+ return;
+ }
+
/* Ignore non-BGP channels */
if (C->class != &channel_bgp)
return;
diff --git a/proto/bgp/bgp.c b/proto/bgp/bgp.c
index ad6dd334..17fcd7ce 100644
--- a/proto/bgp/bgp.c
+++ b/proto/bgp/bgp.c
@@ -173,13 +173,6 @@ static void bgp_listen_close(struct bgp_proto *, struct bgp_listen_request *);
static void bgp_graceful_restart_feed(struct bgp_channel *c);
static void bgp_restart_route_refresh(void *_bc);
-/* Dynamic BGP detection */
-#define bgp_is_dynamic(x) (_Generic((x), \
- struct bgp_proto *: ipa_zero((x)->remote_ip), \
- struct bgp_config *: ipa_zero((x)->remote_ip), \
- struct bgp_listen_request *: ipa_zero((x)->remote_ip)))
-
-
/*
* BGP Instance Management
*/
@@ -1191,6 +1184,18 @@ bgp_down(struct bgp_proto *p)
p->neigh = NULL;
}
+ /* Clean up spawned BGP sessions if peers_persist is not enabled */
+ if (p->cf->c.parent)
+ {
+ struct bgp_config *parent_cf = (struct bgp_config *) p->cf->c.parent;
+ if (!parent_cf->peers_persist)
+ {
+ BGP_TRACE(D_EVENTS, "Spawned session going down, marking for removal (peers_persist disabled)");
+ p->p.cf_new = NULL;
+ p->p.reconfiguring = 1;
+ }
+ }
+
BGP_TRACE(D_EVENTS, "Down");
proto_notify_state(&p->p, PS_FLUSH);
}
@@ -1213,12 +1218,63 @@ bgp_decision(void *vp)
bgp_down(p);
}
+/**
+ * bgp_find_existing_session - check if a dynamic BGP session already exists for a peer
+ * @peer_addr: Remote peer IP address to check
+ *
+ * Checks if there's already an existing dynamic BGP session for the given peer
+ * by walking through all BGP protocols.
+ *
+ * Returns: pointer to the existing BGP protocol, or NULL if none found
+ */
+static struct bgp_proto *
+bgp_find_existing_session(ip_addr peer_addr)
+{
+ struct config *cfg = OBSREF_GET(config);
+ if (!cfg)
+ return NULL;
+
+ struct proto_config *pc;
+ WALK_LIST(pc, cfg->protos)
+ {
+ if (pc->protocol != &proto_bgp)
+ continue;
+
+ if (pc->proto)
+ {
+ struct bgp_proto *child_p = (struct bgp_proto *) pc->proto;
+
+ if (ipa_equal(child_p->remote_ip, peer_addr))
+ {
+ log(L_DEBUG "BGP: Found existing session %s for peer %I",
+ child_p->p.name, peer_addr);
+ return child_p;
+ }
+ }
+ }
+ return NULL;
+}
+
static void
bgp_spawn(struct bgp_proto *pp, struct birdsock *sk)
{
struct symbol *sym;
char fmt[SYM_MAX_LEN];
+ /* Check if there's an existing session for this peer and shut it down.
+ * The dynamic BGP session (with the incoming socket) takes precedence. */
+ struct bgp_proto *existing = bgp_find_existing_session(sk->daddr);
+ if (existing)
+ {
+ log(L_DEBUG "BGP: Found existing session %s for %I, shutting it down to use incoming connection",
+ existing->p.name, sk->daddr);
+
+ /* Mark for deletion and disable */
+ existing->p.cf_new = NULL;
+ existing->p.reconfiguring = 1;
+ proto_disable(&existing->p);
+ }
+
bsprintf(fmt, "%s%%0%dd", pp->cf->dynamic_name, pp->cf->dynamic_name_digits);
/* This is hack, we would like to share config, but we need to copy it now */
@@ -1237,6 +1293,15 @@ bgp_spawn(struct bgp_proto *pp, struct birdsock *sk)
cf->iface = sk->iface;
cf->ipatt = NULL;
+ /* Remove peers channel from spawned session config */
+ struct channel_config *cc, *cc_next;
+ WALK_LIST_DELSAFE(cc, cc_next, cf->c.channels)
+ if (cc->net_type == NET_PEER)
+ {
+ rem_node(&cc->n);
+ break;
+ }
+
/* Create the protocol disabled initially */
SKIP_BACK_DECLARE(struct bgp_proto, p, p, proto_spawn(sym->proto, 1));
@@ -1247,6 +1312,100 @@ bgp_spawn(struct bgp_proto *pp, struct birdsock *sk)
proto_enable(&p->p);
}
+void
+bgp_peer_spawn(struct callback *cb)
+{
+ struct bgp_peer_spawn *ev = (void *) cb;
+
+ /* Check if a session for this peer already exists */
+ struct bgp_proto *existing = bgp_find_existing_session(ev->peer_addr);
+ if (existing)
+ {
+ /* If the existing session is down, enable it to re-establish the connection */
+ if (existing->p.proto_state == PS_DOWN_XX || existing->p.proto_state == PS_STOP)
+ {
+ log(L_DEBUG "BGP: Peer %I already has existing session in down state, enabling it", ev->peer_addr);
+ proto_enable(&existing->p);
+ }
+ else
+ log(L_DEBUG "BGP: Peer %I already has existing session, not spawning duplicate", ev->peer_addr);
+ return;
+ }
+
+ struct symbol *sym;
+ char fmt[SYM_MAX_LEN];
+
+ log(L_DEBUG "BGP: Spawning new peer session for %I", ev->peer_addr);
+ bsprintf(fmt, "%s%%0%dd", ev->p->cf->dynamic_name, ev->p->cf->dynamic_name_digits);
+
+ /* Clone the configuration */
+ new_config = OBSREF_GET(config);
+ cfg_mem = new_config->mem;
+ new_config->current_scope = new_config->root_scope;
+ sym = cf_default_name(new_config, fmt, &(ev->p->dynamic_name_counter));
+ proto_clone_config(sym, ev->p->p.cf);
+ new_config = NULL;
+ cfg_mem = NULL;
+
+ /* Configure for active connection to discovered peer */
+ struct bgp_config *cf = SKIP_BACK(struct bgp_config, c, sym->proto);
+ cf->remote_ip = ev->peer_addr;
+ cf->local_ip = ev->p->cf->local_ip;
+ cf->iface = ev->iface;
+ cf->ipatt = NULL;
+ cf->passive = 0;
+
+ /* Remove peers channel from spawned session config */
+ struct channel_config *cc, *cc_next;
+ WALK_LIST_DELSAFE(cc, cc_next, cf->c.channels)
+ if (cc->net_type == NET_PEER)
+ {
+ rem_node(&cc->n);
+ break;
+ }
+
+ /* Create and enable the protocol */
+ SKIP_BACK_DECLARE(struct bgp_proto, p, p, proto_spawn(sym->proto, 1));
+
+ proto_enable(&p->p);
+}
+
+void
+bgp_peer_remove(struct callback *cb)
+{
+ struct bgp_peer_remove *ev = (void *) cb;
+
+ /* Find ALL BGP sessions for this peer and remove them */
+ struct config *cfg = OBSREF_GET(config);
+ if (!cfg)
+ return;
+
+ struct proto_config *pc;
+ WALK_LIST(pc, cfg->protos)
+ {
+ if (pc->protocol != &proto_bgp)
+ continue;
+
+ /* Check protocol instance */
+ if (pc->proto)
+ {
+ struct bgp_proto *bgp_p = (struct bgp_proto *) pc->proto;
+
+ /* Found a BGP session with matching peer address */
+ if (ipa_equal(bgp_p->remote_ip, ev->peer_addr))
+ {
+ /* Skip parent protocol */
+ if (bgp_is_dynamic(bgp_p))
+ continue;
+
+ log(L_INFO "%s: Stopping BGP session %s for withdrawn peer %I",
+ ev->p->p.name, pc->name, ev->peer_addr);
+ proto_disable(&bgp_p->p);
+ }
+ }
+ }
+}
+
void
bgp_stop(struct bgp_proto *p, int subcode, byte *data, uint len)
{
@@ -2664,6 +2823,14 @@ bgp_start_locked(void *_p)
birdloop_leave(sk_loop);
}
+ if (bgp_is_dynamic(p)) {
+ /* Start peers channel immediately */
+ if (p->p.peers_channel && !p->p.peers_channel->disabled)
+ {
+ channel_set_state(p->p.peers_channel, CS_UP);
+ }
+ }
+
if (cf->multihop || bgp_is_dynamic(p))
{
/* Multi-hop sessions do not use neighbor entries */
@@ -2951,6 +3118,14 @@ bgp_init(struct proto_config *CF)
/* Add MPLS channel */
proto_configure_mpls_channel(P, CF, RTS_BGP);
+ /* Add Peers channel for dynamic BGP peer discovery (only for parent protocol) */
+ if (!cf->c.parent)
+ {
+ struct channel_config *peers_cf = proto_cf_peers_channel(CF);
+ if (peers_cf)
+ proto_configure_channel(P, &P->peers_channel, peers_cf);
+ }
+
/* Export public info */
ea_list *pes = p->p.ea_state;
ea_set_attr(&pes, EA_LITERAL_STORE_ADATA(&ea_bgp_rem_ip, 0, &cf->remote_ip, sizeof(ip_addr)));
@@ -4090,7 +4265,7 @@ struct protocol proto_bgp = {
.name = "BGP",
.template = "bgp%d",
.preference = DEF_PREF_BGP,
- .channel_mask = NB_IP | NB_VPN | NB_FLOW | NB_MPLS,
+ .channel_mask = NB_IP | NB_VPN | NB_FLOW | NB_MPLS | NB_PEER,
.proto_size = sizeof(struct bgp_proto),
.config_size = sizeof(struct bgp_config),
.postconfig = bgp_postconfig,
diff --git a/proto/bgp/bgp.h b/proto/bgp/bgp.h
index 8f76e7a5..c106db71 100644
--- a/proto/bgp/bgp.h
+++ b/proto/bgp/bgp.h
@@ -26,6 +26,7 @@ struct eattr;
#define BGP_AFI_IPV4 1
#define BGP_AFI_IPV6 2
+#define BGP_AFI_PEER 3
#define BGP_SAFI_UNICAST 1
#define BGP_SAFI_MULTICAST 2
@@ -52,7 +53,7 @@ struct eattr;
#define BGP_AF_VPN6_MC BGP_AF( BGP_AFI_IPV6, BGP_SAFI_VPN_MULTICAST )
#define BGP_AF_FLOW4 BGP_AF( BGP_AFI_IPV4, BGP_SAFI_FLOW )
#define BGP_AF_FLOW6 BGP_AF( BGP_AFI_IPV6, BGP_SAFI_FLOW )
-
+#define BGP_AF_PEER BGP_AF( BGP_AFI_PEER, BGP_SAFI_UNICAST )
struct bgp_write_state;
struct bgp_parse_state;
@@ -117,6 +118,7 @@ struct bgp_config {
int enforce_first_as; /* Enable check for neighbor AS as first AS in AS_PATH */
int gr_mode; /* Graceful restart mode (BGP_GR_*) */
int llgr_mode; /* Long-lived graceful restart mode (BGP_LLGR_*) */
+ int peers_persist; /* Keep spawned BGP sessions after peer/dynamic withdrawal */
int auth_type; /* Authentication type (BGP_AUTH_*) */
int setkey; /* Set MD5 password to system SA/SP database */
u8 local_role; /* Set peering role with neighbor [RFC 9234] */
@@ -401,6 +403,23 @@ struct bgp_incoming_socket {
sock *sk; /* The actual socket */
};
+struct bgp_peer_spawn {
+ callback cb;
+ struct bgp_proto *p; /* Parent protocol */
+ ip_addr peer_addr; /* Peer address to spawn session for */
+ struct iface *iface; /* Interface for link-local peers */
+};
+
+struct bgp_peer_remove {
+ callback cb;
+ struct bgp_proto *p; /* Parent protocol */
+ ip_addr peer_addr; /* Peer address to remove session for */
+};
+
+/* Callback hooks (implemented in bgp.c) */
+void bgp_peer_spawn(struct callback *cb);
+void bgp_peer_remove(struct callback *cb);
+
struct bgp_listen_request {
node pn; /* Node in bgp_proto listen list */
node sn; /* Node in bgp_socket requests list */
@@ -978,5 +997,11 @@ enum bgp_attr_id {
#define ORIGIN_EGP 1
#define ORIGIN_INCOMPLETE 2
+/* Dynamic BGP detection */
+
+#define bgp_is_dynamic(x) (_Generic((x), \
+ struct bgp_proto *: ipa_zero((x)->remote_ip), \
+ struct bgp_config *: ipa_zero((x)->remote_ip), \
+ struct bgp_listen_request *: ipa_zero((x)->remote_ip)))
#endif
diff --git a/proto/bgp/config.Y b/proto/bgp/config.Y
index c7316d86..9d1169f9 100644
--- a/proto/bgp/config.Y
+++ b/proto/bgp/config.Y
@@ -33,9 +33,9 @@ CF_KEYWORDS(BGP, LOCAL, NEIGHBOR, AS, HOLD, TIME, CONNECT, RETRY, KEEPALIVE,
STRICT, BIND, CONFEDERATION, MEMBER, MULTICAST, FLOW4, FLOW6, LONG,
LIVED, STALE, IMPORT, IBGP, EBGP, MANDATORY, INTERNAL, EXTERNAL, SETS,
DYNAMIC, RANGE, NAME, DIGITS, AIGP, ORIGINATE, COST, ENFORCE,
- FIRST, FREE, VALIDATE, BASE, ROLE, ROLES, PEER, PROVIDER, CUSTOMER,
+ FIRST, FREE, VALIDATE, BASE, ROLE, ROLES, PEER, PEERS, PROVIDER, CUSTOMER,
RS_SERVER, RS_CLIENT, REQUIRE, BGP_OTC, GLOBAL, SEND, RECV, MIN, MAX,
- TX, SIZE, WARNING,
+ TX, SIZE, WARNING, PERSIST,
AUTHENTICATION, NONE, MD5, AO, FORMAT, NATIVE, SINGLE, DOUBLE)
CF_KEYWORDS(KEY, KEYS, SECRET, DEPRECATED, PREFERRED, ALGORITHM, CMAC, AES128)
@@ -92,6 +92,7 @@ bgp_proto_start: proto_start BGP {
BGP_CFG->check_link = -1;
BGP_CFG->send_hold_time = -1;
BGP_CFG->tx_size_warning = 0;
+ BGP_CFG->peers_persist = 1;
}
;
@@ -157,6 +158,7 @@ bgp_proto:
BGP_CFG->local_ip = $3;
if ($4) BGP_CFG->iface = $4;
}
+ | bgp_proto PEERS PERSIST bool ';' { BGP_CFG->peers_persist = $4; }
| bgp_proto NEIGHBOR bgp_nbr_opts ';'
| bgp_proto NEIGHBOR ipa ipa_scope bgp_nbr_opts ';' {
if (ipa_nonzero(BGP_CFG->remote_ip) || BGP_CFG->remote_range)
@@ -270,6 +272,7 @@ bgp_afi:
| VPN6 MULTICAST { $$ = BGP_AF_VPN6_MC; }
| FLOW4 { $$ = BGP_AF_FLOW4; }
| FLOW6 { $$ = BGP_AF_FLOW6; }
+ | PEERS { $$ = BGP_AF_PEER; }
;
tcp_ao_key_start: KEY {
@@ -398,7 +401,10 @@ bgp_channel_start: bgp_afi
{
BGP_CC->c.in_filter = FILTER_UNDEF;
BGP_CC->c.out_filter = FILTER_UNDEF;
- BGP_CC->c.ra_mode = RA_UNDEF;
+ if ($1 == BGP_AF_PEER)
+ BGP_CC->c.ra_mode = RA_ANY;
+ else
+ BGP_CC->c.ra_mode = RA_UNDEF;
BGP_CC->afi = $1;
BGP_CC->desc = desc;
BGP_CC->next_hop_keep = 0xff; /* undefined */
diff --git a/proto/bgp/packets.c b/proto/bgp/packets.c
index bdbeabd1..87a5b777 100644
--- a/proto/bgp/packets.c
+++ b/proto/bgp/packets.c
@@ -2342,6 +2342,17 @@ static const struct bgp_af_desc bgp_af_table[] = {
.decode_next_hop = bgp_decode_next_hop_none,
.update_next_hop = bgp_update_next_hop_none,
},
+ {
+ .afi = BGP_AF_PEER,
+ .net = NET_PEER,
+ .no_igp = 1,
+ .name = "peers",
+ .encode_nlri = NULL,
+ .decode_nlri = NULL,
+ .encode_next_hop = NULL,
+ .decode_next_hop = NULL,
+ .update_next_hop = NULL,
+ },
};
const struct bgp_af_desc *
--
2.43.0
More information about the Bird-users
mailing list