]> git.ipfire.org Git - thirdparty/bird.git/commitdiff
BGP: Automatic peering based on discovered neighbors dynamic-nbrs-2
authorOndrej Zajicek <santiago@crfreenet.org>
Tue, 24 Feb 2026 22:15:06 +0000 (23:15 +0100)
committerOndrej Zajicek <santiago@crfreenet.org>
Mon, 2 Mar 2026 03:37:02 +0000 (04:37 +0100)
Extend existing dynamic BGP code to support spawning of active BGP
instances for discovered neighbors.

The existence of such dynamic BGP instances is controlled by exporting
information from a table containing neighbor entries through a newly
introduced neighbor channel. This means that the feature will only work
if there is another protocol responsible for discovering and announcing
neighbor entries (e.g. RAdv with 'router discovery' enabled).

Based on the patch from Matteo Perin <matteo.perin@canonical.com>, thanks!

doc/bird.sgml
proto/bgp/attrs.c
proto/bgp/bgp.c
proto/bgp/bgp.h
proto/bgp/config.Y

index 990aae8f3ec7b33d02980017b439740e864be48d..25afe0707f6511d2b39e3bd5381ecb8f4bf2d7dc 100644 (file)
@@ -3709,6 +3709,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/neighbors/      | <cf/neighbor/      | ---                     | ---      | ---
 </tabular>
 </table>
 
@@ -3730,6 +3731,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/neighbors/ channel is a special channel used for BGP unnumbered
+automatic peering. Unlike traditional AFI/SAFI channels that exchange routing
+information, the <cf/neighbors/ channel is used to dynamically trigger the
+creation of new BGP sessions. When present in a dynamic BGP configuration,
+exporting neighbor objects (typically from RAdv with router discovery enabled)
+to this channel will automatically spawn new BGP sessions for each discovered
+neighbor. The lifetime of these automatic sessions is tied to the presence of
+neighbor objects in the neighbor routing table, withdrawing an object will
+trigger deprecation of the corresponding BGP session. 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>
@@ -4240,6 +4252,47 @@ protocol bgp {
 }
 </code>
 
+<p>Example configuration for BGP unnumbered automatic peering using router discovery:
+
+<p><code>
+# Table for discovered peers
+neighbor table peers;
+
+# RAdv protocol with router discovery enabled
+protocol radv {
+       neighbors { table peers; };     # Export discovered peers to this table
+
+       interface "eth*" {
+               # Normal RAdv configuration options...
+               router discovery yes;   # Enable reception of ICMPv6 RAs
+       };
+}
+
+# Dynamic BGP with neighbor channel for automatic peering
+protocol bgp bgp_root {
+       local as 65000;
+       neighbor range fe80::/64 external;      # Accept link-local peers
+       dynamic name "bgp_auto_";       # Name spawned sessions with this prefix
+       dynamic name digits 2;          # Number of digits in name after prefix
+
+       ipv4 {
+               export all;
+               import all;
+               extended next hop;      # Allow IPv6 next hops for IPv4 routes
+       };
+
+       ipv6 {
+               export all;
+               import all;
+       };
+
+       neighbors {
+               table peers;            # Import peer discovery objects from this table
+               export all;             # Allow all discovered peers to trigger sessions
+       };
+}
+</code>
+
 
 <sect>BMP
 <label id="bmp">
index e853624b803c3cf2fa9ebfede654b7eff365827d..184c07775904c7e293f37a224c0f2ea6888524e2 100644 (file)
@@ -1736,6 +1736,56 @@ bgp_free_prefix(struct bgp_channel *c, struct bgp_prefix *px)
  *     BGP protocol glue
  */
 
+int
+bgp_preexport_nbr(struct bgp_proto *p, rte *e)
+{
+  const net_addr_nbr *nbr = (const net_addr_nbr *) e->net->n.addr;
+
+  if (!bgp_is_dynamic(p))
+    return -1;
+
+  /* Matching remote range */
+  if (p->cf->remote_range && !ipa_in_netX(nbr->addr, p->cf->remote_range))
+    return -1;
+
+  /* Matching interface */
+  if (p->cf->iface && (nbr->ifindex != p->cf->iface->index))
+    return -1;
+
+  /* Interface is valid */
+  struct iface *iface = if_find_by_index(nbr->ifindex);
+  if (!iface)
+    return -1;
+
+  /* Matching interface range */
+  if (p->cf->ipatt && !iface_patt_match(p->cf->ipatt, iface, NULL))
+    return -1;
+
+  return 0;
+}
+
+void
+bgp_rt_notify_nbr(struct bgp_proto *p, net *n, rte *new, rte *old)
+{
+  const net_addr_nbr *nbr = (const net_addr_nbr *) n->n.addr;
+
+  if (!bgp_is_dynamic(p))
+    return;
+
+  /* Protocol is not-yet-reconfigured */
+  if (p->p.cf->global != config)
+  {
+    log(L_WARN "%s: Ignoring neighbor %N received during reconfiguration", p->p.name, n->n.addr);
+    return;
+  }
+
+  if (new && !old)
+    return bgp_add_nbr(p, nbr);
+
+  if (!new && old)
+    return bgp_remove_nbr(p, nbr);
+}
+
 int
 bgp_preexport(struct channel *C, rte *e)
 {
@@ -1744,9 +1794,14 @@ bgp_preexport(struct channel *C, rte *e)
   struct bgp_proto *src = (SRC->proto == &proto_bgp) ? (struct bgp_proto *) SRC : NULL;
   struct bgp_channel *c = (struct bgp_channel *) C;
 
-  /* Ignore non-BGP channels */
+  /* Handle non-BGP channels */
   if (C->channel != &channel_bgp)
+  {
+    if (C->net_type == NET_NEIGHBOR)
+      return bgp_preexport_nbr(p, e);
+
     return -1;
+  }
 
   /* Reject our routes */
   if (src == p)
@@ -1945,9 +2000,14 @@ bgp_rt_notify(struct proto *P, struct channel *C, net *n, rte *new, rte *old)
   struct bgp_prefix *px;
   u32 path;
 
-  /* Ignore non-BGP channels */
+  /* Handle non-BGP channels */
   if (C->channel != &channel_bgp)
+  {
+    if (C->net_type == NET_NEIGHBOR)
+      return bgp_rt_notify_nbr(p, n, new, old);
+
     return;
+  }
 
   if (new)
   {
index 5431d08b3fdb6146424d590a1539791367885169..fe243ac092b39ac455ae9ef10ab6ab318900de5b 100644 (file)
@@ -854,6 +854,10 @@ bgp_startup(struct bgp_proto *p)
   BGP_TRACE(D_EVENTS, "Started");
   p->start_state = BSS_CONNECT;
 
+  /* For dynamic BGP, start neighbor channel immediately */
+  if (bgp_is_dynamic(p) && p->nbr_channel && !p->nbr_channel->disabled)
+    channel_set_state(p->nbr_channel, CS_UP);
+
   if (!p->passive)
     bgp_active(p);
 
@@ -1109,6 +1113,11 @@ bgp_spawn(struct bgp_proto *pp, ip_addr remote_ip, ip_addr local_ip, struct ifac
   cf->iface = iface;
   cf->ipatt = NULL;
 
+  /* Remove neighbot channel from spawned session config */
+  struct channel_config *pc = proto_cf_find_channel(&cf->c, NET_NEIGHBOR);
+  if (pc)
+    rem_node(&pc->n);
+
   return SKIP_BACK(struct bgp_proto, p, proto_spawn(sym->proto, 0));
 }
 
@@ -1123,6 +1132,75 @@ bgp_spawn_sk(struct bgp_proto *pp, sock *sk)
   return 0;
 }
 
+static void
+bgp_spawn_nbr(struct bgp_proto *pp, const net_addr_nbr *nbr)
+{
+  struct iface *iface = if_find_by_index(nbr->ifindex);
+  if (!iface)
+    return;
+
+  struct bgp_proto *p = bgp_spawn(pp, nbr->addr, pp->cf->local_ip, iface);
+  p->claimed = true;
+}
+
+static struct bgp_proto *
+bgp_find_child_proto(struct bgp_proto *pp, const net_addr_nbr *nbr)
+{
+  WALK_LIST_(struct bgp_proto, p, proto_list)
+  {
+    /* Not a BGP */
+    if (p->p.proto != &proto_bgp)
+      continue;
+
+    /* Not spawned by the parent proto */
+    if (!p->p.cf->parent || (p->p.cf->parent->proto != &pp->p))
+      continue;
+
+    /* Remote address mismatch */
+    if (!ipa_equal(p->remote_ip, nbr->addr))
+      continue;
+
+    /* The interface mismatch */
+    if (p->cf->iface && (p->cf->iface->index != nbr->ifindex))
+      continue;
+
+    return p;
+  }
+
+  return NULL;
+}
+
+void
+bgp_add_nbr(struct bgp_proto *pp, const net_addr_nbr *nbr)
+{
+  struct bgp_proto *cp = bgp_find_child_proto(pp, nbr);
+
+  if (cp)
+  {
+    if (cp->claimed)
+      return;
+
+    TRACE_(pp, D_EVENTS, "Claiming BGP instance %s for neighbor %N", cp->p.name, nbr);
+
+    cp->claimed = true;
+  }
+  else
+    bgp_spawn_nbr(pp, nbr);
+}
+
+void
+bgp_remove_nbr(struct bgp_proto *pp, const net_addr_nbr *nbr)
+{
+  struct bgp_proto *cp = bgp_find_child_proto(pp, nbr);
+  if (!cp || !cp->claimed)
+    return;
+
+  TRACE_(pp, D_EVENTS, "Releasing BGP instance %s for neighbor %N", cp->p.name, nbr);
+
+  cp->claimed = false;
+}
+
+
 void
 bgp_stop(struct bgp_proto *p, int subcode, byte *data, uint len)
 {
@@ -1785,9 +1863,6 @@ err2:
   return;
 }
 
-static inline int bgp_is_dynamic(struct bgp_proto *p)
-{ return ipa_zero(p->remote_ip); }
-
 /**
  * bgp_find_proto - find existing proto for incoming connection
  * @sk: TCP socket
@@ -2542,6 +2617,9 @@ bgp_init(struct proto_config *CF)
   /* Add MPLS channel */
   proto_configure_channel(P, &P->mpls_channel, proto_cf_mpls_channel(CF));
 
+  /* Add neighbor channel for dynamic BGP neighbor discovery */
+  proto_configure_channel(P, &p->nbr_channel, proto_cf_find_channel(CF, NET_NEIGHBOR));
+
   return P;
 }
 
@@ -3004,6 +3082,10 @@ bgp_postconfig(struct proto_config *CF)
               cc->min_llgr_time, cc->max_llgr_time);
 
   }
+
+  struct channel_config *nbr = proto_cf_find_channel(CF, NET_NEIGHBOR);
+  if (nbr && !cf->remote_range)
+    cf_error("Neighbor channel requires dynamic BGP (neighbor range option)");
 }
 
 static int
@@ -3043,6 +3125,11 @@ bgp_reconfigure(struct proto *P, struct proto_config *CF)
 
     new->iface = old->iface;
     new->ipatt = NULL;
+
+    /* Remove neighbor channel from spawned session config */
+    struct channel_config *pc = proto_cf_find_channel(&new->c, NET_NEIGHBOR);
+    if (pc)
+      rem_node(&pc->n);
   }
 
   int same = !memcmp(((byte *) old) + sizeof(struct proto_config),
@@ -3087,6 +3174,9 @@ bgp_reconfigure(struct proto *P, struct proto_config *CF)
   /* Reconfigure MPLS channel */
   same = proto_configure_channel(P, &P->mpls_channel, proto_cf_mpls_channel(CF)) && same;
 
+  /* Reconfigure neighbor channel */
+  same = proto_configure_channel(P, &p->nbr_channel, proto_cf_find_channel(CF, NET_NEIGHBOR)) && same;
+
   WALK_LIST_DELSAFE(C, C2, p->p.channels)
     if (C->stale)
       same = proto_configure_channel(P, &C, NULL) && same;
@@ -3636,7 +3726,7 @@ struct protocol proto_bgp = {
   .template =          "bgp%d",
   .class =             PROTOCOL_BGP,
   .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_NEIGHBOR,
   .proto_size =                sizeof(struct bgp_proto),
   .config_size =       sizeof(struct bgp_config),
   .postconfig =                bgp_postconfig,
index 9a206cfe818f480ed32f310856d8bb9fe265a5ed..077fbd69678f86c56049e83efd59c37824786425 100644 (file)
@@ -405,6 +405,7 @@ struct bgp_proto {
   list listen;                         /* Requests for shared listening sockets */
   struct bfd_request *bfd_req;         /* BFD request, if BFD is used */
   struct birdsock *postponed_sk;       /* Postponed incoming socket for dynamic BGP */
+  struct channel *nbr_channel;         /* Neighbor channel for dynamic BGP */
   struct bgp_ao_state ao;
   struct bgp_stats stats;              /* BGP statistics */
   btime last_established;              /* Last time of enter/leave of established state */
@@ -419,6 +420,7 @@ struct bgp_proto {
   u8 last_error_class;                         /* Error class of last error */
   u32 last_error_code;                 /* Error code of last error. BGP protocol errors
                                           are encoded as (bgp_err_code << 16 | bgp_err_subcode) */
+  bool claimed;                                /* Claimed dynamic protocols are kept, unclaimed may be removed */
 };
 
 struct bgp_channel {
@@ -610,6 +612,8 @@ void bgp_check_config(struct bgp_config *c);
 void bgp_error(struct bgp_conn *c, unsigned code, unsigned subcode, byte *data, int len);
 void bgp_close_conn(struct bgp_conn *c);
 void bgp_update_startup_delay(struct bgp_proto *p);
+void bgp_add_nbr(struct bgp_proto *pp, const net_addr_nbr *nbr);
+void bgp_remove_nbr(struct bgp_proto *pp, const net_addr_nbr *nbr);
 void bgp_conn_enter_openconfirm_state(struct bgp_conn *conn);
 void bgp_conn_enter_established_state(struct bgp_conn *conn);
 void bgp_conn_enter_close_state(struct bgp_conn *conn);
@@ -631,6 +635,12 @@ rte_resolvable(rte *rt)
   return rt->attrs->dest != RTD_UNREACHABLE;
 }
 
+static inline int
+bgp_is_dynamic(struct bgp_proto *p)
+{
+  return ipa_zero(p->remote_ip);
+}
+
 
 #ifdef LOCAL_DEBUG
 #define BGP_FORCE_DEBUG 1
index 4ffc49f6743df58888be811881c636df1a926c53..09ca85a889d48b6278f30eddaf09a0271862d6da 100644 (file)
@@ -148,6 +148,7 @@ bgp_proto:
  | bgp_proto proto_item ';'
  | bgp_proto bgp_proto_channel ';'
  | bgp_proto mpls_channel ';'
+ | bgp_proto nbrs_channel ';'
  | bgp_proto LOCAL bgp_loc_opts ';'
  | bgp_proto LOCAL ipa ipa_scope bgp_loc_opts ';' {
      BGP_CFG->local_ip = $3;