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.

The BGP peers channel has two additional configuration parameters:
- from [iface]: determines the interface used for automatic BGP session 
establishment
- persist [yes/no]: controls the behavior on peer route withdrawal. If enabled 
the
BGP connection spawned from peer discovery will be left running, if disabled the
session will be torn down.

Logic to avoid duplicate sessions from both the dynamic BGP and automatic peers
discovery paths has also been introduced.

Signed-off-by: Matteo Perin <[email protected]>
---
 proto/bgp/attrs.c   |  57 ++++++++++++++++
 proto/bgp/bgp.c     | 162 +++++++++++++++++++++++++++++++++++++++++---
 proto/bgp/bgp.h     |  28 +++++++-
 proto/bgp/config.Y  |  15 +++-
 proto/bgp/packets.c |  11 +++
 5 files changed, 262 insertions(+), 11 deletions(-)

diff --git a/proto/bgp/attrs.c b/proto/bgp/attrs.c
index f072e4c9..dcec1c45 100644
--- a/proto/bgp/attrs.c
+++ b/proto/bgp/attrs.c
@@ -2468,6 +2468,63 @@ 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->net_type == NET_PEER)
+  {
+    /* If this is a spawned child, ignore all notifications from the peers 
channel */
+    if (p->cf->c.parent)
+      return;
+
+    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 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 = bc->cf->peers_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 discovered, queued for BGP session spawn", 
+          p->p.name, peer->addr);
+      }
+      else if (!new && old)
+      {
+        /* If persist is not enabled, stop spawned sessions for this peer */
+        if (!bc->cf->peers_persist)
+        {
+          /* 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, queued for BGP 
session removal", 
+              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..e17459c6 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
  */
@@ -1213,12 +1206,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 */
@@ -1247,6 +1291,92 @@ 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 */
+  if (bgp_find_existing_session(ev->peer_addr))
+  {
+    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;
+
+  /* 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: Removing BGP session %s for withdrawn peer %I", 
+            ev->p->p.name, pc->name, ev->peer_addr);
+        
+        /* Mark protocol for deletion and disable it */
+        bgp_p->p.cf_new = NULL;
+        bgp_p->p.reconfiguring = 1;
+        proto_disable(&bgp_p->p);
+      }
+      else
+      {
+        log(L_DEBUG "BGP: Session %s with remote %I does not match withdrawn 
peer %I", 
+            pc->name, bgp_p->remote_ip, ev->peer_addr);
+      }
+    }
+  }
+}
+
 void
 bgp_stop(struct bgp_proto *p, int subcode, byte *data, uint len)
 {
@@ -2664,6 +2794,18 @@ bgp_start_locked(void *_p)
       birdloop_leave(sk_loop);
   }
 
+  if (bgp_is_dynamic(p)) {
+    /* Start peers channels immediately for dynamic BGP */
+    struct bgp_channel *c;
+    BGP_WALK_CHANNELS(p, c)
+    {
+      if (c->c.net_type == NET_PEER && !c->c.disabled)
+      {
+        channel_set_state(&c->c, CS_UP);
+      }
+    }
+  }
+
   if (cf->multihop || bgp_is_dynamic(p))
   {
     /* Multi-hop sessions do not use neighbor entries */
@@ -2974,6 +3116,10 @@ bgp_channel_init(struct channel *C, struct 
channel_config *CF)
   c->afi = cf->afi;
   c->desc = cf->desc;
 
+  /* Set peers channels to get announcement of any route change */
+  if (C->net_type == NET_PEER && C->ra_mode == RA_UNDEF)
+    C->ra_mode = RA_ANY;
+
   if (cf->igp_table_ip4)
     c->igp_table_ip4 = cf->igp_table_ip4->table;
 
@@ -4090,7 +4236,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..e393a34e 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;
@@ -191,6 +192,8 @@ struct bgp_channel_config {
   u32 cost;                            /* IGP cost for direct next hops */
   u8 import_table;                     /* Use c.in_table as Adj-RIB-In */
   u8 export_table;                     /* Keep Adj-RIB-Out and export it */
+  u8 peers_persist;                    /* Keep spawned BGP sessions after peer 
discovery route withdrawal */
+  struct iface *peers_iface;           /* Interface for peer discovery (for 
link-local addresses) */
 
   struct settle_config ptx_exporter_settle;    /* Settle timer for export 
dumps */
 
@@ -401,6 +404,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 +998,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..11ee90f1 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)
@@ -270,6 +270,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 {
@@ -444,6 +445,16 @@ bgp_channel_item:
     if (BGP_SAFI(BGP_CC->afi) != BGP_SAFI_FLOW)
       cf_error("Validate option limited to flowspec channels");
    }
+ | PERSIST bool {
+    BGP_CC->peers_persist = $2;
+    if (BGP_CC->c.net_type != NET_PEER)
+      cf_error("Persist option only available for peers channels");
+   }
+ | FROM text {
+    BGP_CC->peers_iface = if_get_by_name($2);
+    if (BGP_CC->c.net_type != NET_PEER)
+      cf_error("From option only available for peers channels");
+   }
  | GRACEFUL RESTART bool { BGP_CC->gr_able = $3; }
  | LONG LIVED GRACEFUL RESTART bool { BGP_CC->llgr_able = $5; }
  | LONG LIVED STALE TIME expr { BGP_CC->llgr_time = $5; if ($5 >= (1 << 24)) 
cf_error("Long-lived stale time must be less than 2^24"); }
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

Reply via email to