]> git.ipfire.org Git - thirdparty/bird.git/commitdiff
BGP: Interface range bind
authorMaria Matejka <mq@ucw.cz>
Thu, 3 Jul 2025 15:16:14 +0000 (17:16 +0200)
committerMaria Matejka <mq@ucw.cz>
Thu, 13 Nov 2025 10:59:33 +0000 (11:59 +0100)
For dynamic onlink connections, we need to find out which interface
the connection came in, and we need to pin that connection to
that interface. To achieve that, we create a listening socket
bound to each interface separately, and match the incoming connection
by the socket. Otherwise, the kernel would not give us any information
on where the connection came from.

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

index 68229cb9dba8001e38ce72667af6623bfa2c8a94..d75640c6b753ed989fc880ccc62f71b5440db2bf 100644 (file)
@@ -2945,6 +2945,7 @@ protocol bgp [<name>] {
        local [<ip>] [port <number>] [as <number>];
        neighbor [<ip> | range <prefix>] [onlink] [port <number>] [as <number>] [internal|external];
        interface "<text>";
+       interface range <interface pattern>;
        onlink <switch>;
        direct;
        multihop [<number>];
@@ -3067,6 +3068,17 @@ protocol bgp [<name>] {
        used for non link-local sessions when it is necessary to explicitly
        specify an interface, but only for direct (not multihop) sessions.
 
+       <tag><label id="bgp-iface-range">interface range <m/interface pattern/</tag>
+       Set interface pattern to which the connection will be bound. This is
+       mostly useful with the <cf/neighbor range/ option and either link-local
+       addresses or with the <cf/onlink/ option where it's not known up front
+       which interface the connection comes on but it needs to stay there.
+
+       This option requires <cf/strict bind/ to be on and creates a separate
+       listening socket for every single interface matching the pattern. If the
+       local address is set, it also requires this exact address to be set
+       on that interface to create a listening socket.
+
        <tag><label id="bgp-onlink">onlink <m/switch/</tag>
        For a direct neighbor, the BGP session starts immediately without
        waiting for the neighbor's address to appear on any interface.
index c134fd5001338214bfd4fe5d8de41a14a6074bee..ee043036c942b0a07f741cbd38d9831bea4695ad 100644 (file)
@@ -159,6 +159,11 @@ static void bgp_listen_close(struct bgp_proto *, struct bgp_listen_request *);
 static int
 bgp_open(struct bgp_proto *p)
 {
+  /* Interface-patterned listening sockets are created from the
+   * interface notifier. By default, listen to nothing. */
+  if (p->cf->ipatt)
+    return 0;
+
   /* We assume that cf->iface is defined iff cf->local_ip is link-local */
   struct bgp_listen_request *req = mb_allocz(p->p.pool, sizeof *req);
   req->params = (struct bgp_socket_params) {
@@ -173,6 +178,13 @@ bgp_open(struct bgp_proto *p)
   return bgp_listen_open(p, req);
 }
 
+#define bgp_listen_debug(p, a, msg, args...) do { \
+  if ((p)->p.debug & D_IFACES) \
+    log(L_TRACE "%s: Listening socket at %I%J port %u (vrf %s) flags %u: " msg, \
+       (p)->p.name, (a)->addr, (a)->iface, (a)->port, \
+       (a)->vrf ? (a)->vrf->name : "default", (a)->flags, ## args); \
+} while (0)
+
 static int
 bgp_socket_match(const struct bgp_socket_params *a, const struct bgp_socket_params *b)
 {
@@ -195,6 +207,7 @@ bgp_listen_open(struct bgp_proto *p, struct bgp_listen_request *req)
   WALK_LIST(bs, bgp_sockets)
     if (bgp_socket_match(&bs->params, &req->params))
     {
+      bgp_listen_debug(p, &req->params, "exists: %p", bs);
       add_tail(&p->listen, &req->pn);
       add_tail(&bs->requests, &req->sn);
       req->sock = bs;
@@ -230,6 +243,8 @@ bgp_listen_open(struct bgp_proto *p, struct bgp_listen_request *req)
   add_tail(&p->listen, &req->pn);
   add_tail(&bgp_sockets, &bs->n);
 
+  bgp_listen_debug(p, &req->params, "create: %p", bs);
+
   return 0;
 
 err:
@@ -248,8 +263,12 @@ bgp_listen_close(struct bgp_proto *p UNUSED, struct bgp_listen_request *req)
   rem_node(&req->pn);
   rem_node(&req->sn);
   if (!EMPTY_LIST(bs->requests))
+  {
+    bgp_listen_debug(p, &req->params, "unlink: %p", bs);
     return;
+  }
 
+  bgp_listen_debug(p, &req->params, "free: %p", bs);
   rfree(bs->sk);
   rem_node(&bs->n);
   mb_free(bs);
@@ -999,8 +1018,8 @@ bgp_decision(void *vp)
     bgp_down(p);
 }
 
-static struct bgp_proto *
-bgp_spawn(struct bgp_proto *pp, ip_addr remote_ip)
+static int
+bgp_spawn(struct bgp_proto *pp, sock *sk)
 {
   struct symbol *sym;
   char fmt[SYM_MAX_LEN];
@@ -1017,9 +1036,15 @@ bgp_spawn(struct bgp_proto *pp, ip_addr remote_ip)
   cfg_mem = NULL;
 
   /* Just pass remote_ip to bgp_init() */
-  ((struct bgp_config *) sym->proto)->remote_ip = remote_ip;
+  struct bgp_config *cf = SKIP_BACK(struct bgp_config, c, sym->proto);
+  cf->remote_ip = sk->daddr;
+  cf->iface = sk->iface;
+
+  struct bgp_proto *p = SKIP_BACK(struct bgp_proto, p, proto_spawn(sym->proto, 0));
+  p->postponed_sk = sk;
+  rmove(sk, p->p.pool);
 
-  return (void *) proto_spawn(sym->proto, 0);
+  return 0;
 }
 
 void
@@ -1826,12 +1851,7 @@ bgp_incoming_connection(sock *sk, uint dummy UNUSED)
 
   /* For dynamic BGP, spawn new instance and postpone the socket */
   if (bgp_is_dynamic(p))
-  {
-    p = bgp_spawn(p, sk->daddr);
-    p->postponed_sk = sk;
-    rmove(sk, p->p.pool);
-    return 0;
-  }
+    return bgp_spawn(p, sk);
 
   rmove(sk, p->p.pool);
   bgp_setup_conn(p, &p->incoming_conn);
@@ -1872,6 +1892,66 @@ bgp_start_neighbor(struct bgp_proto *p)
   bgp_initiate(p);
 }
 
+static void
+bgp_iface_update(struct bgp_proto *p, uint flags, struct iface *i)
+{
+  int ps = p->p.proto_state;
+
+  ASSERT_DIE(p->cf->ipatt);
+  ASSERT_DIE(p->cf->strict_bind);
+
+  if ((ps == PS_DOWN) || (ps == PS_STOP))
+    return;
+
+  if (!iface_patt_match(p->cf->ipatt, i, NULL))
+    return;
+
+  struct bgp_socket_params params = {
+    .iface = i,
+    .vrf = p->p.vrf,
+    .addr = p->cf->local_ip,
+    .port = p->cf->local_port,
+    .flags = p->cf->free_bind ? SKF_FREEBIND : 0,
+  };
+
+  if (flags & IF_CHANGE_UP)
+  {
+    struct bgp_listen_request *req = mb_allocz(p->p.pool, sizeof *req);
+    req->params = params;
+    bgp_listen_open(p, req);
+  }
+
+  if (flags & IF_CHANGE_DOWN)
+  {
+    struct bgp_listen_request *req; node *nxt;
+    WALK_LIST2(req, nxt, p->listen, pn)
+      if (bgp_socket_match(&req->params, &params))
+      {
+       bgp_listen_close(p, req);
+       mb_free(req);
+       break;
+      }
+  }
+}
+
+static void
+bgp_if_notify(struct proto *P, uint flags, struct iface *i)
+{
+  struct bgp_proto *p = (struct bgp_proto *) P;
+  ASSERT_DIE(ipa_zero(p->cf->local_ip));
+  bgp_iface_update(p, flags, i);
+}
+
+static void
+bgp_ifa_notify(struct proto *P, uint flags, struct ifa *i)
+{
+  struct bgp_proto *p = (struct bgp_proto *) P;
+  ASSERT_DIE(!ipa_zero(p->cf->local_ip));
+
+  if (ipa_equal(i->ip, p->cf->local_ip))
+    bgp_iface_update(p, flags, i->iface);
+}
+
 static void
 bgp_neigh_notify(neighbor *n)
 {
@@ -2308,6 +2388,9 @@ bgp_init(struct proto_config *CF)
   p->remote_ip = cf->remote_ip;
   p->remote_as = cf->remote_as;
 
+  P->if_notify = (cf->ipatt && ipa_zero(cf->local_ip)) ? bgp_if_notify : NULL;
+  P->ifa_notify = (cf->ipatt && !ipa_zero(cf->local_ip)) ? bgp_ifa_notify : NULL;
+
   /* Hack: We use cf->remote_ip just to pass remote_ip from bgp_spawn() */
   if (cf->c.parent)
     cf->remote_ip = IPA_NONE;
@@ -2557,8 +2640,8 @@ bgp_postconfig(struct proto_config *CF)
   if (ipa_zero(cf->remote_ip) && !cf->remote_range)
     cf_error("Neighbor must be configured");
 
-  if (ipa_zero(cf->local_ip) && cf->strict_bind)
-    cf_error("Local address must be configured for strict bind");
+  if (ipa_zero(cf->local_ip) && !cf->ipatt && !cf->iface && cf->strict_bind)
+    cf_error("Local address or an interface must be configured for strict bind");
 
   if (!cf->remote_as && !cf->peer_type)
     cf_error("Remote AS number (or peer type) must be set");
@@ -2573,6 +2656,12 @@ bgp_postconfig(struct proto_config *CF)
                     ipa_is_link_local(cf->remote_ip)))
     cf_error("Link-local addresses require defined interface");
 
+  if (cf->iface && cf->ipatt)
+    cf_error("Interface and interface range cannot be configured together");
+
+  if (cf->ipatt && !cf->strict_bind)
+    cf_error("Interface range needs strict bind");
+
   if (!(cf->capabilities && cf->enable_as4) && (cf->remote_as > 0xFFFF))
     cf_error("Neighbor AS number out of range (AS4 not available)");
 
@@ -2613,7 +2702,7 @@ bgp_postconfig(struct proto_config *CF)
                       ipa_is_link_local(cf->remote_ip)))
     cf_error("Multihop BGP cannot be used with link-local addresses");
 
-  if (cf->multihop && cf->iface)
+  if (cf->multihop && (cf->iface || cf->ipatt))
     cf_error("Multihop BGP cannot be bound to interface");
 
   if (cf->multihop && cf->check_link)
@@ -2625,8 +2714,9 @@ bgp_postconfig(struct proto_config *CF)
   if (cf->multihop && cf->onlink)
     cf_error("Multihop BGP cannot be configured onlink");
 
-  if (cf->onlink && !cf->iface)
-    cf_error("Onlink BGP must have interface configured");
+  if (cf->onlink && !cf->iface && !cf->ipatt &&
+      !cf->passive && !ipa_zero(cf->remote_ip))
+    cf_error("Active onlink BGP must have interface configured");
 
   if (!cf->gr_mode && cf->llgr_mode)
     cf_error("Long-lived graceful restart requires basic graceful restart");
index ee8c01ae1c2fb96fb34c12a1351e8c0db11e586d..ad08fcd966d89d64c2eee00a48e1e7bf2ee5bd64 100644 (file)
@@ -80,6 +80,7 @@ struct bgp_config {
   ip_addr local_ip;                    /* Source address to use */
   ip_addr remote_ip;
   struct iface *iface;                 /* Interface for link-local addresses */
+  struct iface_patt *ipatt;            /* Interface pattern for dynamic strict bind */
   u16 local_port;                      /* Local listening port */
   u16 remote_port;                     /* Neighbor destination port */
   int peer_type;                       /* Internal or external BGP (BGP_PT_*, optional) */
index 31ec8b84efcb682cdc8a44b4e6c61dccb991476c..c8729544594c7374b0556307cfdbeeca80af957c 100644 (file)
@@ -168,6 +168,7 @@ bgp_proto:
      BGP_CFG->remote_range = n;
    }
  | bgp_proto INTERFACE text ';' { BGP_CFG->iface = if_get_by_name($3); }
+ | bgp_proto INTERFACE RANGE iface_patt ';' { BGP_CFG->ipatt = this_ipatt; }
  | bgp_proto ONLINK bool ';' { BGP_CFG->onlink = $3; }
  | bgp_proto RR CLUSTER ID idval ';' { BGP_CFG->rr_cluster_id = $5; }
  | bgp_proto RR CLIENT bool ';' { BGP_CFG->rr_client = $4; }