]> git.ipfire.org Git - thirdparty/bird.git/commitdiff
BGP: Add option to specify format of link-local next hop
authorOndrej Zajicek <santiago@crfreenet.org>
Mon, 24 Mar 2025 16:09:25 +0000 (17:09 +0100)
committerOndrej Zajicek <santiago@crfreenet.org>
Mon, 24 Mar 2025 17:31:05 +0000 (18:31 +0100)
When a BGP session is established using link-local next hop addresses,
there is no global IPv6 address for next hop. Implementations differ on
how to encode such next hop. This leads to interoperability problems.

Add the option 'link local next hop format' to specify which format to
use when encoding such next hops.

Based on a patch from Andrey V. Elsukov and Georgy Kirichenko, submitted
by Aleksandr Stepanov, thanks!

doc/bird.sgml
lib/alloca.h
proto/bgp/bgp.c
proto/bgp/bgp.h
proto/bgp/config.Y
proto/bgp/packets.c

index 4efca00ddd9024d39835beead457b3854fb34137..87317f266371ee81265ed7155f3de0c46b1526e8 100644 (file)
@@ -3604,6 +3604,18 @@ be used in explicit configuration.
        enabled a route may end with a link-local immediate next hop when the
        IGP route has one. Default: disabled.
 
+       <tag><label id="bgp-link-local-next-hop-format">link local next hop format native|single|double</tag>
+       For IPv6 routes, BGP assumes that the Next Hop attribute contains a
+       global IPv6 address (in the first position) and an optional link-local
+       IPv6 address (in the second position): [<m/global/, <m/link-local/].
+       When a BGP session is established using just link-local addresses, there
+       may be no global IPv6 address for the next hop. BGP implementations
+       differ on how to encode such next hops. BIRD <cf/native/ format is to
+       send [zero, <m/link-local/], <cf/single/ format is [<m/link-local/],
+       <cf/double/ format is [<m/link-local/, <m/link-local/]. BIRD accepts all
+       these variants when decoding received routes, but this option controls
+       which one it uses to encode such next hops. Default: native.
+
        <tag><label id="bgp-gateway">gateway direct|recursive</tag>
        For received routes, their <cf/gw/ (immediate next hop) attribute is
        computed from received <cf/bgp_next_hop/ attribute. This option
@@ -3827,6 +3839,7 @@ direction (re-export of routes to the BGP neighbor):
        <item><cf/next hop address/
        <item><cf/next hop self/
        <item><cf/next hop keep/
+       <item><cf/link local next hop format/
        <item><cf/aigp/
        <item><cf/aigp originate/
 </itemize>
index e5557cdbc26bdf9172b6de6fbd0d889107855a56..3491d5a6a735ddd5121fe59c01caef75a40184b9 100644 (file)
@@ -17,4 +17,6 @@
 
 #define allocz(len) ({ void *_x = alloca(len); memset(_x, 0, len); _x; })
 
+#define alloca_copy(src,len) ({ void *_x = alloca(len); memcpy(_x, src, len); })
+
 #endif
index 7ae03a06a9ca91f76a5176e5cb3914abdef460bd..b5327a6937cb555eca96fe51ce750a4e2a5a06f3 100644 (file)
@@ -2324,6 +2324,7 @@ bgp_channel_reconfigure(struct channel *C, struct channel_config *CC, int *impor
   if (!ipa_equal(new->next_hop_addr, old->next_hop_addr) ||
       (new->next_hop_self != old->next_hop_self) ||
       (new->next_hop_keep != old->next_hop_keep) ||
+      (new->llnh_format != old->llnh_format) ||
       (new->aigp != old->aigp) ||
       (new->aigp_originate != old->aigp_originate))
     *export_changed = 1;
index 1585f6800554971018b7f361de20f01a96e3c8ba..5ec7a9f9460270f3744a58f43095478289c693ca 100644 (file)
@@ -164,6 +164,7 @@ struct bgp_channel_config {
   u8 next_hop_self;                    /* Always set next hop to local IP address (NH_*) */
   u8 next_hop_keep;                    /* Do not modify next hop attribute (NH_*) */
   u8 next_hop_prefer;                  /* Prefer global or link-local next hop (NHP_*) */
+  u8 llnh_format;                      /* Requested link-local next hop format (LLNH_*) */
   u8 mandatory;                                /* Channel is mandatory in capability negotiation */
   u8 gw_mode;                          /* How we compute route gateway from next_hop attr, see GW_* */
   u8 secondary;                                /* Accept also non-best routes (i.e. RA_ACCEPTED) */
@@ -207,6 +208,10 @@ struct bgp_channel_config {
 #define MLL_DROP               2
 #define MLL_IGNORE             3
 
+#define LLNH_NATIVE            0
+#define LLNH_SINGLE            1
+#define LLNH_DOUBLE            2
+
 #define GW_DIRECT              1
 #define GW_RECURSIVE           2
 
@@ -458,6 +463,7 @@ struct bgp_write_state {
   int mp_reach;
   int as4_session;
   int add_path;
+  int llnh_format;
   int mpls;
   int sham;
   int ignore_non_bgp_attrs;
index 222879e94a8e29d539b2bc2ed4b549aa98897e90..11003d4e904ff850ce6671f510d710d6f0c117c1 100644 (file)
@@ -32,9 +32,10 @@ CF_KEYWORDS(BGP, LOCAL, NEIGHBOR, AS, HOLD, TIME, CONNECT, RETRY, KEEPALIVE,
        LIVED, STALE, IMPORT, IBGP, EBGP, MANDATORY, INTERNAL, EXTERNAL, SETS,
        DYNAMIC, RANGE, NAME, DIGITS, BGP_AIGP, AIGP, ORIGINATE, COST, ENFORCE,
        FIRST, FREE, VALIDATE, BASE, ROLE, ROLES, PEER, PROVIDER, CUSTOMER,
-       RS_SERVER, RS_CLIENT, REQUIRE, BGP_OTC, GLOBAL, SEND, MIN, MAX)
+       RS_SERVER, RS_CLIENT, REQUIRE, BGP_OTC, GLOBAL, SEND, MIN, MAX,
+       FORMAT, NATIVE, SINGLE, DOUBLE)
 
-%type <i> bgp_nh
+%type <i> bgp_nh bgp_llnh
 %type <i32> bgp_afi
 
 CF_KEYWORDS(CEASE, PREFIX, LIMIT, HIT, ADMINISTRATIVE, SHUTDOWN, RESET, PEER,
@@ -281,6 +282,13 @@ bgp_nh:
    bool { $$ = $1; }
  | IBGP { $$ = NH_IBGP; }
  | EBGP { $$ = NH_EBGP; }
+ ;
+
+bgp_llnh:
+   NATIVE { $$ = LLNH_NATIVE; }
+ | SINGLE { $$ = LLNH_SINGLE; }
+ | DOUBLE { $$ = LLNH_DOUBLE; }
+ ;
 
 bgp_lladdr: SELF | DROP | IGNORE;
 
@@ -290,6 +298,7 @@ bgp_channel_item:
  | NEXT HOP SELF bgp_nh { BGP_CC->next_hop_self = $4; }
  | NEXT HOP KEEP bgp_nh { BGP_CC->next_hop_keep = $4; }
  | NEXT HOP PREFER GLOBAL { BGP_CC->next_hop_prefer = NHP_GLOBAL; }
+ | LINK LOCAL NEXT HOP FORMAT bgp_llnh { BGP_CC->llnh_format = $6; }
  | MANDATORY bool { BGP_CC->mandatory = $2; }
  | MISSING LLADDR bgp_lladdr { cf_warn("%s.%s: Missing lladdr option is deprecated and ignored, remove it", this_proto->name, this_channel->name); }
  | GATEWAY DIRECT { BGP_CC->gw_mode = GW_DIRECT; }
index 4ee7c5a5f8f507565dceb78a1a059cdaea97281a..1c846cd505044d8844db1b9a1831c9f68467ec5f 100644 (file)
@@ -1313,6 +1313,37 @@ bgp_update_next_hop_ip(struct bgp_export_state *s, eattr *a, ea_list **to)
     REJECT(NO_LABEL_STACK);
 }
 
+static uint
+bgp_prepare_link_local_next_hop(struct bgp_write_state *s, ip_addr *nh)
+{
+  /*
+   * We have link-local next-hop in nh[1]
+   *
+   * Possible variants:
+   * [ ::, fe80::XX ] - BIRD's default/internal (LLNH_NATIVE)
+   * [ fe80::XX ] - draft-white-linklocal-capability (LLNH_SINGLE)
+   * [ fe80::XX, fe80::XX ] - Used in JunoOS (LLNH_DOUBLE)
+   */
+
+  switch (s->llnh_format)
+  {
+  case LLNH_NATIVE:
+    return 32;
+
+  case LLNH_SINGLE:
+    nh[0] = nh[1];
+    return 16;
+
+  case LLNH_DOUBLE:
+    nh[0] = nh[1];
+    return 32;
+
+  default:
+    ASSERT(0);
+    return 32;
+  }
+}
+
 static uint
 bgp_encode_next_hop_ip(struct bgp_write_state *s, eattr *a, byte *buf, uint size UNUSED)
 {
@@ -1335,6 +1366,13 @@ bgp_encode_next_hop_ip(struct bgp_write_state *s, eattr *a, byte *buf, uint size
     return 4;
   }
 
+  /* Handle variants of link-local-only next hop */
+  if (ipa_zero(nh[0]) && (len == 32))
+  {
+    nh = alloca_copy(nh, 32);
+    len = bgp_prepare_link_local_next_hop(s, nh);
+  }
+
   put_ip6(buf, ipa_to_ip6(nh[0]));
 
   if (len == 32)
@@ -1411,6 +1449,13 @@ bgp_encode_next_hop_vpn(struct bgp_write_state *s, eattr *a, byte *buf, uint siz
     return 12;
   }
 
+  /* Handle variants of link-local-only next hop */
+  if (ipa_zero(nh[0]) && (len == 32))
+  {
+    nh = alloca_copy(nh, 32);
+    len = bgp_prepare_link_local_next_hop(s, nh);
+  }
+
   put_u64(buf, 0); /* VPN RD is 0 */
   put_ip6(buf+8, ipa_to_ip6(nh[0]));
 
@@ -2577,6 +2622,7 @@ again: ;
     .mp_reach = (c->afi != BGP_AF_IPV4) || c->ext_next_hop,
     .as4_session = p->as4_session,
     .add_path = c->add_path_tx,
+    .llnh_format = c->cf->llnh_format,
     .mpls = c->desc->mpls,
   };