Hello BIRD team,

This patch (for master branch / 2.18) adds SRv6 L3VPN support to BIRD. It 
applies on top of the Multiple Labels capability (RFC 8277) patch I sent 
earlier.

- Patch 0001 is preparatory work that adds a new internal attribute 
BA_RAW_MPLS_LABEL_STACK (0xfd) that stores the full 24-bit wire values from 
MPLS label fields in BGP NLRI, alongside the existing BA_MPLS_LABEL_STACK 
(0xfe) which stores decoded 20-bit label values.

The existing BA_MPLS_LABEL_STACK only preserves the 20-bit label value from 
each 24-bit NLRI label field, discarding the traffic class and BOS fields. This 
is fine for normal MPLS but is lossy for SRv6: RFC 9252 uses the NLRI label 
field to carry part of an SRv6 SID via transposition, where the full 24 bits 
are significant and BOS may not be set.

- Patch 0002 adds SRv6 to the nest, it includes SID stack in next hop, adds the 
parser, display in CLI outputs.

- Patch 0003 adds Netlink support for SEG6 routes in Linux kernel (but not 
SEG6LOCAL routes).

- Patch 0004 adds SRv6 support to static protocol.

- Patch 0005 updates l3vpn protocol to operate without MPLS.

- Patch 0006 adds Prefix SID BGP attribute support for L3VPN.

With this patch set BIRD can act as a SRv6 PE/headend, but there are some 
limitations:
- Dynamic SID allocator is not implemented (SID can be read routing table with 
netlink or configured on static routes)
- The code supports a SID stack but SRV6_MAX_SID_STACK is currently #defined to 
1 because L3VPN uses only one (no SRv6 TE support)
- The SID structure sub-sub-TLV is parsed on import (for transposition) but not 
generated on export (it is not mandatory per RFC 9252)
- Endpoint behavior is set the opaque value (0xffff) in generated SID 
information sub-TLV (it is not mandatory to specify the codepoint of actual SID 
behavior per RFC 9252)

A pair of BIRD configuration file are also included in attachment as examples. 
Example "show route" output:

65001:1 10.10.1.0/24
    unicast [static_vpn 17:37:31.789] * (200)
        via 10.0.0.100 on veth1 srv6 <fc00:a1::1>
        Type: static univ
65002:1 10.20.1.0/24
    unicast [peer2 17:37:36.192 from 10.0.0.2] * (100/) [AS65002i]
        via 10.0.0.200 on veth1 srv6 <fc00:a2::1>
        Type: BGP univ
        BGP.origin: IGP
        BGP.as_path: 65002
        BGP.next_hop: 10.0.0.200
        BGP.local_pref: 100
        BGP.prefix_sid: SRv6 L3 Service SID=fc00:a2::1, Flags=0x00, 
Behavior=0xffff
        BGP.raw_mpls_label_stack: 0x000000
        BGP.mpls_label_stack: 0

Looking forward to your feedback.

Thanks!
--
Sébastien
From 58144c9b975e5848ff2082194f0085fc66dc85f2 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?S=C3=A9bastien=20Parisot?= <[email protected]>
Date: Mon, 9 Feb 2026 20:02:34 +0000
Subject: [PATCH] bgp: Add raw MPLS label stack attribute
 (BA_RAW_MPLS_LABEL_STACK)

Store the full 24-bit wire values from MPLS label stacks in BGP NLRI as
a new internal attribute (BA_RAW_MPLS_LABEL_STACK, 0xfd), alongside the
existing decoded 20-bit labels in BA_MPLS_LABEL_STACK (0xfe).

The raw attribute preserves the exact wire encoding including traffic
class and bottom-of-stack fields. It is populated on decode and visible
via 'show route all' (formatted as hex values). The encode function is
prepared to use raw labels when activated, but the raw path is not
enabled in this patch (it is activated by the SRv6 Prefix SID patch).

This patch does not change existing routing behavior, BGP wire protocol
encoding, or forwarding. The only visible change is the additional
raw_mpls_label_stack attribute in 'show route all' output.
---
 proto/bgp/attrs.c   | 77 +++++++++++++++++++++++++++++++++++++++++++++
 proto/bgp/bgp.h     |  3 ++
 proto/bgp/packets.c | 70 ++++++++++++++++++++++++++++++-----------
 3 files changed, 132 insertions(+), 18 deletions(-)

diff --git a/proto/bgp/attrs.c b/proto/bgp/attrs.c
index 00a7788..acd3eb9 100644
--- a/proto/bgp/attrs.c
+++ b/proto/bgp/attrs.c
@@ -990,6 +990,75 @@ bgp_format_mpls_label_stack(const eattr *a, byte *buf, uint size)
   pos[lnum ? -1 : 0] = 0;
 }
 
+
+static void
+bgp_export_raw_mpls_label_stack(struct bgp_export_state *s, eattr *a)
+{
+  net_addr *n = s->route->net->n.addr;
+  u32 *raw_labels = (u32 *) a->u.ptr->data;
+  uint rlnum = a->u.ptr->length / 4;
+
+  /* Perhaps we should just ignore it? */
+  if (!s->mpls)
+    REJECT("Unexpected raw MPLS stack");
+
+  /* Empty MPLS stack is not allowed */
+  if (!rlnum)
+    REJECT("Malformed raw MPLS stack - empty");
+
+  /* This is ugly, but we must ensure that labels fit into NLRI field */
+  if ((24*rlnum + (net_is_vpn(n) ? 64 : 0) + net_pxlen(n)) > 255)
+    REJECT("Malformed raw MPLS stack - too many labels (%u)", rlnum);
+
+  for (uint i = 0; i < rlnum; i++)
+  {
+    if (raw_labels[i] > 0xffffff)
+      REJECT("Malformed raw MPLS stack - invalid label (0x%x)", raw_labels[i]);
+  }
+}
+
+static int
+bgp_encode_raw_mpls_label_stack(struct bgp_write_state *s, eattr *a, byte *buf UNUSED, uint size UNUSED)
+{
+  /*
+   * Raw MPLS labels are encoded as a part of the NLRI in MP_REACH_NLRI attribute,
+   * so we store RAW_MPLS_LABEL_STACK and encode it later by AFI-specific hooks.
+   */
+
+  s->raw_mpls_labels = a->u.ptr;
+  return 0;
+}
+
+static void
+bgp_decode_raw_mpls_label_stack(struct bgp_parse_state *s, uint code UNUSED, uint flags UNUSED, byte *data UNUSED, uint len UNUSED, ea_list **to UNUSED)
+{
+  DISCARD("Discarding received attribute #0");
+}
+
+static void
+bgp_format_raw_mpls_label_stack(const eattr *a, byte *buf, uint size)
+{
+  u32 *raw_labels = (u32 *) a->u.ptr->data;
+  uint rlnum = a->u.ptr->length / 4;
+  char *pos = buf;
+
+  for (uint i = 0; i < rlnum; i++)
+  {
+    if (size < 20)
+    {
+      bsprintf(pos, "...");
+      return;
+    }
+
+    uint l = bsprintf(pos, "0x%06x/", raw_labels[i]);
+    ADVANCE(pos, size, l);
+  }
+
+  /* Clear last slash or terminate empty string */
+  pos[rlnum ? -1 : 0] = 0;
+}
+
+
 static inline void
 bgp_decode_unknown(struct bgp_parse_state *s, uint code, uint flags, byte *data, uint len, ea_list **to)
 {
@@ -1141,6 +1210,14 @@ static const struct bgp_attr_desc bgp_attr_table[] = {
     .encode = bgp_encode_u32,
     .decode = bgp_decode_otc,
   },
+  [BA_RAW_MPLS_LABEL_STACK] = {
+    .name = "raw_mpls_label_stack",
+    .type = EAF_TYPE_INT_SET,
+    .export = bgp_export_raw_mpls_label_stack,
+    .encode = bgp_encode_raw_mpls_label_stack,
+    .decode = bgp_decode_raw_mpls_label_stack,
+    .format = bgp_format_raw_mpls_label_stack,
+  },
   [BA_MPLS_LABEL_STACK] = {
     .name = "mpls_label_stack",
     .type = EAF_TYPE_INT_SET,
diff --git a/proto/bgp/bgp.h b/proto/bgp/bgp.h
index ea4de2a..96286bc 100644
--- a/proto/bgp/bgp.h
+++ b/proto/bgp/bgp.h
@@ -521,6 +521,7 @@ struct bgp_write_state {
 
   eattr *mp_next_hop;
   const adata *mpls_labels;
+  const adata *raw_mpls_labels;
 };
 
 struct bgp_parse_state {
@@ -560,6 +561,7 @@ struct bgp_parse_state {
 
   struct hostentry *hostentry;
   adata *mpls_labels;
+  adata *raw_mpls_labels;
 
   /* Cached state for bgp_rte_update() */
   u32 last_id;
@@ -788,6 +790,7 @@ byte *bgp_create_end_mark_(struct bgp_channel *c, byte *buf);
 #define BA_ONLY_TO_CUSTOMER	0x23	/* RFC 9234 */
 
 /* Bird's private internal BGP attributes */
+#define BA_RAW_MPLS_LABEL_STACK	0xfd	/* raw MPLS label stack transfer attribute */
 #define BA_MPLS_LABEL_STACK	0xfe	/* MPLS label stack transfer attribute */
 
 /* BGP connection states */
diff --git a/proto/bgp/packets.c b/proto/bgp/packets.c
index eda38ef..2329bb3 100644
--- a/proto/bgp/packets.c
+++ b/proto/bgp/packets.c
@@ -1637,32 +1637,58 @@ bgp_rte_update(struct bgp_parse_state *s, const net_addr *n, u32 path_id, rta *a
 }
 
 static void
-bgp_encode_mpls_labels(struct bgp_write_state *s, const adata *mpls, byte **pos, uint *size, byte *pxlen)
+bgp_encode_mpls_labels(struct bgp_write_state *s, const adata *mpls, const adata *raw_mpls, byte **pos, uint *size, byte *pxlen)
 {
   struct bgp_channel *c = s->channel;
   const u32 dummy = 0;
   const u32 *labels = mpls ? (const u32 *) mpls->data : &dummy;
+  const u32 *raw_labels = raw_mpls ? (const u32 *) raw_mpls->data : &dummy;
   uint lnum = mpls ? (mpls->length / 4) : 1;
+  uint rlnum = raw_mpls ? (raw_mpls->length / 4) : 1;
   uint num;
+  int raw = 0;
 
-  if (lnum == 1 || c->multiple_labels || c->cf->multiple_labels >= BGP_MPLS_ML_ADVERTISE)
+  if (raw)
   {
-    num = lnum;
-    for (uint i = 0; i < lnum; i++)
+    /* raw labels */
+    if (rlnum == 1 || c->multiple_labels || c->cf->multiple_labels >= BGP_MPLS_ML_ADVERTISE)
     {
-      put_u24(*pos, labels[i] << 4);
+      num = rlnum;
+      for (uint i = 0; i < rlnum; i++)
+      {
+        put_u24(*pos, raw_labels[i]);
+        ADVANCE(*pos, *size, 3);
+      }
+    }
+    else
+    {
+      num = 1;
+      put_u24(*pos, BGP_MPLS_NULL << 4);
       ADVANCE(*pos, *size, 3);
     }
   }
   else
   {
-    num = 1;
-    put_u24(*pos, BGP_MPLS_NULL << 4);
-    ADVANCE(*pos, *size, 3);
-  }
+    /* labels */
+    if (lnum == 1 || c->multiple_labels || c->cf->multiple_labels >= BGP_MPLS_ML_ADVERTISE)
+    {
+      num = lnum;
+      for (uint i = 0; i < lnum; i++)
+      {
+        put_u24(*pos, labels[i] << 4);
+        ADVANCE(*pos, *size, 3);
+      }
+    }
+    else
+    {
+      num = 1;
+      put_u24(*pos, BGP_MPLS_NULL << 4);
+      ADVANCE(*pos, *size, 3);
+    }
 
-  /* Add bottom-of-stack flag */
-  (*pos)[-1] |= BGP_MPLS_BOS;
+    /* Add bottom-of-stack flag */
+    (*pos)[-1] |= BGP_MPLS_BOS;
+  }
 
   *pxlen += 24 * num;
 }
@@ -1671,8 +1697,8 @@ static void
 bgp_decode_mpls_labels(struct bgp_parse_state *s, byte **pos, uint *len, uint *pxlen, rta *a)
 {
   struct bgp_channel *c = s->channel;
-  u32 labels[BGP_MPLS_MAX], label;
-  uint lnum = 0;
+  u32 labels[BGP_MPLS_MAX], raw_labels[BGP_MPLS_MAX], label;
+  uint lnum = 0, rlnum = 0;
 
   do {
     if (*pxlen < 24)
@@ -1683,6 +1709,7 @@ bgp_decode_mpls_labels(struct bgp_parse_state *s, byte **pos, uint *len, uint *p
 
     label = get_u24(*pos);
     labels[lnum++] = label >> 4;
+    raw_labels[rlnum++] = label;
     ADVANCE(*pos, *len, 3);
     *pxlen -= 24;
 
@@ -1706,10 +1733,17 @@ bgp_decode_mpls_labels(struct bgp_parse_state *s, byte **pos, uint *len, uint *p
     s->mpls_labels = lp_alloc_adata(s->pool, 4*BGP_MPLS_MAX);
     bgp_set_attr_ptr(&(a->eattrs), s->pool, BA_MPLS_LABEL_STACK, 0, s->mpls_labels);
   }
+  if (!s->raw_mpls_labels)
+  {
+    s->raw_mpls_labels = lp_alloc_adata(s->pool, 4*BGP_MPLS_MAX);
+    bgp_set_attr_ptr(&(a->eattrs), s->pool, BA_RAW_MPLS_LABEL_STACK, 0, s->raw_mpls_labels);
+  }
 
-  /* Overwrite data in the attribute */
+  /* Overwrite data in the attributes */
   s->mpls_labels->length = 4*lnum;
   memcpy(s->mpls_labels->data, labels, 4*lnum);
+  s->raw_mpls_labels->length = 4*rlnum;
+  memcpy(s->raw_mpls_labels->data, raw_labels, 4*rlnum);
 
   /* Update next hop entry in rta */
   bgp_apply_mpls_labels(s, a, labels, lnum);
@@ -1744,7 +1778,7 @@ bgp_encode_nlri_ip4(struct bgp_write_state *s, struct bgp_bucket *buck, byte *bu
 
     /* Encode MPLS labels */
     if (s->mpls)
-      bgp_encode_mpls_labels(s, s->mpls_labels, &pos, &size, pos - 1);
+      bgp_encode_mpls_labels(s, s->mpls_labels, s->raw_mpls_labels, &pos, &size, pos - 1);
 
     /* Encode prefix body */
     ip4_addr a = ip4_hton(net->prefix);
@@ -1832,7 +1866,7 @@ bgp_encode_nlri_ip6(struct bgp_write_state *s, struct bgp_bucket *buck, byte *bu
 
     /* Encode MPLS labels */
     if (s->mpls)
-      bgp_encode_mpls_labels(s, s->mpls_labels, &pos, &size, pos - 1);
+      bgp_encode_mpls_labels(s, s->mpls_labels, s->raw_mpls_labels, &pos, &size, pos - 1);
 
     /* Encode prefix body */
     ip6_addr a = ip6_hton(net->prefix);
@@ -1919,7 +1953,7 @@ bgp_encode_nlri_vpn4(struct bgp_write_state *s, struct bgp_bucket *buck, byte *b
 
     /* Encode MPLS labels */
     if (s->mpls)
-      bgp_encode_mpls_labels(s, s->mpls_labels, &pos, &size, pos - 1);
+      bgp_encode_mpls_labels(s, s->mpls_labels, s->raw_mpls_labels, &pos, &size, pos - 1);
 
     /* Encode route distinguisher */
     put_rd(pos, net->rd);
@@ -2019,7 +2053,7 @@ bgp_encode_nlri_vpn6(struct bgp_write_state *s, struct bgp_bucket *buck, byte *b
 
     /* Encode MPLS labels */
     if (s->mpls)
-      bgp_encode_mpls_labels(s, s->mpls_labels, &pos, &size, pos - 1);
+      bgp_encode_mpls_labels(s, s->mpls_labels, s->raw_mpls_labels, &pos, &size, pos - 1);
 
     /* Encode route distinguisher */
     put_rd(pos, net->rd);
-- 
2.47.3

From 64e35281a8e8e4f7dddc8c8c48ebdb05de1b0da9 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?S=C3=A9bastien=20Parisot?= <[email protected]>
Date: Thu, 12 Feb 2026 16:09:31 +0000
Subject: [PATCH 1/5] nest: Add SRv6 SID stack support to nexthop

Add SRv6 SID stack type and constants (SRV6_MAX_SID_STACK,
SRV6_MAX_SID_STRING), srv6_sid_stack struct, and nexthop fields
(sid6s_orig, sid6s, sid6[]).

Extend nexthop_hash(), nexthop_equal_1(), nexthop__same(),
nexthop_compare_node(), nexthop_copy() and rta_dump() for SRv6 SID
handling. Update rta_apply_hostentry() and rta_set_recursive_next_hop()
signatures with SRv6 parameter.

Add SRv6 SID display in route show output and propagate SRv6 SIDs
through recursive next hop resolution.
---
 conf/confbase.Y | 16 ++++++++++++++++
 lib/ip.h        |  7 +++++++
 nest/mpls.c     |  2 +-
 nest/route.h    |  9 ++++++---
 nest/rt-attr.c  | 32 ++++++++++++++++++++++++++++++--
 nest/rt-show.c  | 18 ++++++++++++++----
 nest/rt-table.c | 38 +++++++++++++++++++++++++++++++++++---
 7 files changed, 109 insertions(+), 13 deletions(-)

diff --git a/conf/confbase.Y b/conf/confbase.Y
index 27c422e..3621f37 100644
--- a/conf/confbase.Y
+++ b/conf/confbase.Y
@@ -125,6 +125,7 @@ CF_DECLS
   struct timeformat tf;
   struct timeformat *tfp;
   mpls_label_stack *mls;
+  srv6_sid_stack *sid6;
   const struct adata *bs;
   struct aggr_item_node *ai;
 }
@@ -148,6 +149,7 @@ CF_DECLS
 %type <net> net_ip4_ net_ip4 net_ip6_ net_ip6 net_ip_ net_ip net_or_ipa
 %type <net_ptr> net_ net_any net_vpn4_ net_vpn6_ net_vpn_ net_roa4_ net_roa6_ net_roa_ net_ip6_sadr_ net_mpls_ net_aspa_
 %type <mls> label_stack_start label_stack
+%type <sid6> srv6_sid_stack
 
 %type <t> text opttext
 %type <bs> bytestring
@@ -442,6 +444,20 @@ label_stack:
   }
 ;
 
+srv6_sid_stack: IP6
+{
+  $$ = cfg_allocz(sizeof(srv6_sid_stack));
+  $$->len = 1;
+  $$->sid[0] = ipa_from_ip6($1);
+}
+ | srv6_sid_stack ',' IP6
+{
+  $$ = $1;
+  if ($$->len >= SRV6_MAX_SID_STACK)
+    cf_error("Too many SRv6 SIDs (max %d)", SRV6_MAX_SID_STACK);
+  $$->sid[$$->len++] = ipa_from_ip6($3);
+}
+;
 
 /* Strings */
 
diff --git a/lib/ip.h b/lib/ip.h
index bae0526..7f3c8cc 100644
--- a/lib/ip.h
+++ b/lib/ip.h
@@ -393,6 +393,13 @@ static inline ip6_addr ip6_ntoh(ip6_addr a)
 { return _MI6(ntohl(_I0(a)), ntohl(_I1(a)), ntohl(_I2(a)), ntohl(_I3(a))); }
 
 
+#define SRV6_MAX_SID_STACK 1
+#define SRV6_MAX_SID_STRING (8 /* " srv6 <" */ + IP6_MAX_TEXT_LENGTH + (1 /* "," */ + IP6_MAX_TEXT_LENGTH) * (SRV6_MAX_SID_STACK - 1) + 1 /* ">" */ + 1 /* '\0' */)
+typedef struct srv6_sid_stack {
+  uint len;
+  ip6_addr sid[SRV6_MAX_SID_STACK];
+} srv6_sid_stack;
+
 /*
  *	Unaligned data access (in network order)
  */
diff --git a/nest/mpls.c b/nest/mpls.c
index 9cdcd57..20aa09e 100644
--- a/nest/mpls.c
+++ b/nest/mpls.c
@@ -1065,7 +1065,7 @@ mpls_announce_fec(struct mpls_fec_map *m, struct mpls_fec *fec, const rta *src)
 
     /* The same hostentry, but different dependent table */
     struct hostentry *s = src->hostentry;
-    rta_set_recursive_next_hop(m->channel->table, a, s->owner, s->addr, s->link, &ms);
+    rta_set_recursive_next_hop(m->channel->table, a, s->owner, s->addr, s->link, &ms, NULL);
   }
 
   net_addr_mpls n = NET_ADDR_MPLS(fec->label);
diff --git a/nest/route.h b/nest/route.h
index 5a9e7fa..3addc17 100644
--- a/nest/route.h
+++ b/nest/route.h
@@ -434,6 +434,9 @@ struct nexthop {
   struct nexthop *next;
   byte flags;
   byte weight;
+  byte sid6s_orig;                      /* Number of SRv6 SIDs before hostentry was applied */
+  byte sid6s;                           /* Number of all SRv6 SIDs */
+  ip6_addr sid6[SRV6_MAX_SID_STACK];
   byte labels_orig;			/* Number of labels before hostentry was applied */
   byte labels;				/* Number of all labels */
   u32 label[0];
@@ -724,12 +727,12 @@ void rta_show(struct cli *, rta *);
 
 u32 rt_get_igp_metric(rte *rt);
 struct hostentry * rt_get_hostentry(rtable *tab, ip_addr a, ip_addr ll, rtable *dep);
-void rta_apply_hostentry(rta *a, struct hostentry *he, mpls_label_stack *mls);
+void rta_apply_hostentry(rta *a, struct hostentry *he, mpls_label_stack *mls, srv6_sid_stack *sid6);
 
 static inline void
-rta_set_recursive_next_hop(rtable *dep, rta *a, rtable *tab, ip_addr gw, ip_addr ll, mpls_label_stack *mls)
+rta_set_recursive_next_hop(rtable *dep, rta *a, rtable *tab, ip_addr gw, ip_addr ll, mpls_label_stack *mls, srv6_sid_stack *sid6)
 {
-  rta_apply_hostentry(a, rt_get_hostentry(tab, gw, ll, dep), mls);
+  rta_apply_hostentry(a, rt_get_hostentry(tab, gw, ll, dep), mls, sid6);
 }
 
 /*
diff --git a/nest/rt-attr.c b/nest/rt-attr.c
index e10e1ec..4a3366d 100644
--- a/nest/rt-attr.c
+++ b/nest/rt-attr.c
@@ -180,6 +180,9 @@ nexthop_hash(struct nexthop *x)
 
     for (int i = 0; i < x->labels; i++)
       h ^= x->label[i] ^ (h << 6) ^ (h >> 7);
+
+    for (int i = 0; i < x->sid6s; i++)
+      h ^= ip6_hash(x->sid6[i]) ^ (h << 7) ^ (h >> 8);
   }
 
   return h;
@@ -190,13 +193,18 @@ nexthop_equal_1(struct nexthop *x, struct nexthop *y)
 {
   if (!ipa_equal(x->gw, y->gw) || (x->iface != y->iface) ||
       (x->flags != y->flags) || (x->weight != y->weight) ||
-      (x->labels != y->labels))
+      (x->labels != y->labels) ||
+      (x->sid6s != y->sid6s))
     return 0;
 
   for (int i = 0; i < x->labels; i++)
     if (x->label[i] != y->label[i])
       return 0;
 
+  for (int i = 0; i < x->sid6s; i++)
+    if (memcmp(&x->sid6[i], &y->sid6[i], sizeof(x->sid6[0])) != 0)
+      return 0;
+
   return 1;
 }
 
@@ -217,7 +225,8 @@ nexthop__same(struct nexthop *x, struct nexthop *y)
 {
   for (; x && y; x = x->next, y = y->next)
     if (!nexthop_equal_1(x, y) ||
-	(x->labels_orig != y->labels_orig))
+	(x->labels_orig != y->labels_orig) ||
+        (x->sid6s_orig != y->sid6s_orig))
       return 0;
 
   return x == y;
@@ -255,6 +264,17 @@ nexthop_compare_node(const struct nexthop *x, const struct nexthop *y)
       return r;
   }
 
+  r = ((int) y->sid6s) - ((int) x->sid6s);
+  if (r)
+    return r;
+
+  for (int i = 0; i < y->sid6s; i++)
+  {
+    r = ip6_compare(x->sid6[i], y->sid6[i]);
+    if (r)
+      return r;
+  }
+
   return ((int) x->iface->index) - ((int) y->iface->index);
 }
 
@@ -398,6 +418,10 @@ nexthop_copy(struct nexthop *o)
       n->labels = o->labels;
       for (int i=0; i<o->labels; i++)
 	n->label[i] = o->label[i];
+      n->sid6s_orig = o->sid6s_orig;
+      n->sid6s = o->sid6s;
+      for (int i=0; i<o->sid6s; i++)
+        memcpy(&n->sid6[i], &o->sid6[i], sizeof(n->sid6[0]));
 
       *last = n;
       last = &(n->next);
@@ -1340,6 +1364,10 @@ rta_dump(struct dump_request *dreq, rta *a)
 	if (nh->labels) RDUMP(" L %d", nh->label[0]);
 	for (int i=1; i<nh->labels; i++)
 	  RDUMP("/%d", nh->label[i]);
+	if (nh->sid6s) RDUMP(" SRv6 <%I6", nh->sid6[0]);
+	for (int i=1; i<nh->sid6s; i++)
+	  RDUMP(",%I6", nh->sid6[i]);
+	if (nh->sid6s) RDUMP(">");
 	RDUMP(" [%s]", nh->iface ? nh->iface->name : "???");
       }
   if (a->eattrs)
diff --git a/nest/rt-show.c b/nest/rt-show.c
index 0ecc863..c8f07ea 100644
--- a/nest/rt-show.c
+++ b/nest/rt-show.c
@@ -73,6 +73,7 @@ rt_show_rte(struct cli *c, byte *ia, rte *e, struct rt_show_data *d, int primary
     for (nh = &(a->nh); nh; nh = nh->next)
     {
       char mpls[MPLS_MAX_LABEL_STRING], *lsp = mpls;
+      char srv6[SRV6_MAX_SID_STRING], *sid6 = srv6;
       char *onlink = (nh->flags & RNF_ONLINK) ? " onlink" : "";
       char weight[16] = "";
 
@@ -84,15 +85,24 @@ rt_show_rte(struct cli *c, byte *ia, rte *e, struct rt_show_data *d, int primary
 	}
       *lsp = '\0';
 
+      if (nh->sid6s)
+	{
+	  sid6 += bsprintf(sid6, " srv6 <%I6", nh->sid6[0]);
+	  for (int i = 1; i < nh->sid6s; ++i)
+	    sid6 += bsprintf(sid6, ",%I6", nh->sid6[i]);
+	  sid6 += bsprintf(sid6, ">");
+	}
+      *sid6 = '\0';
+
       if (a->nh.next)
 	bsprintf(weight, " weight %d", nh->weight + 1);
 
       if (ipa_nonzero(nh->gw))
-	cli_printf(c, -1007, "\tvia %I on %s%s%s%s",
-		   nh->gw, nh->iface->name, mpls, onlink, weight);
+	cli_printf(c, -1007, "\tvia %I on %s%s%s%s%s",
+		   nh->gw, nh->iface->name, mpls, srv6, onlink, weight);
       else
-	cli_printf(c, -1007, "\tdev %s%s%s",
-		   nh->iface->name, mpls,  onlink, weight);
+	cli_printf(c, -1007, "\tdev %s%s%s%s",
+		   nh->iface->name, mpls, srv6, onlink, weight);
     }
 
   if (d->verbose)
diff --git a/nest/rt-table.c b/nest/rt-table.c
index ed364d3..cdcebd5 100644
--- a/nest/rt-table.c
+++ b/nest/rt-table.c
@@ -2391,7 +2391,7 @@ rt_postconfig(struct config *c)
  */
 
 void
-rta_apply_hostentry(rta *a, struct hostentry *he, mpls_label_stack *mls)
+rta_apply_hostentry(rta *a, struct hostentry *he, mpls_label_stack *mls, srv6_sid_stack *sid6)
 {
   a->hostentry = he;
   a->dest = he->dest;
@@ -2407,10 +2407,15 @@ no_nexthop:
       a->nh.labels_orig = a->nh.labels = mls->len;
       memcpy(a->nh.label, mls->stack, mls->len * sizeof(u32));
     }
+    if (sid6)
+    {
+      a->nh.sid6s_orig = a->nh.sid6s = sid6->len;
+      memcpy(a->nh.sid6, sid6->sid, sid6->len * sizeof(sid6->sid[0]));
+    }
     return;
   }
 
-  if (((!mls) || (!mls->len)) && he->nexthop_linkable)
+  if ((((!mls) || (!mls->len)) && (!(sid6) || !(sid6->len))) && he->nexthop_linkable)
   { /* Just link the nexthop chain, no label append happens. */
     memcpy(&(a->nh), &(he->src->nh), nexthop_size(&(he->src->nh)));
     return;
@@ -2457,6 +2462,30 @@ no_nexthop:
       memcpy(nhp->label, nh->label, nh->labels * sizeof(u32));
     }
 
+    if (sid6)
+    {
+      nhp->sid6s = nh->sid6s + sid6->len;
+      nhp->sid6s_orig = sid6->len;
+      if (nhp->sid6s <= SRV6_MAX_SID_STACK)
+      {
+        memcpy(nhp->sid6, nh->sid6, nh->sid6s * sizeof(nhp->sid6[0])); /* First the hostentry SIDs */
+        memcpy(&(nhp->sid6[nh->sid6s]), sid6->sid, sid6->len * sizeof(nhp->sid6[0])); /* Then the bottom SIDs */
+      }
+      else
+      {
+        log(L_WARN "Sum of SID stack sizes %d + %d = %d exceedes allowed maximum (%d)",
+            nh->sid6s, sid6->len, nhp->sid6s, SRV6_MAX_SID_STACK);
+        skip_nexthop++;
+        continue;
+      }
+    }
+    else if (nh->sid6s)
+    {
+      nhp->sid6s = nh->sid6s;
+      nhp->sid6s_orig = 0;
+      memcpy(nhp->sid6, nh->sid6, nhp->sid6s * sizeof(nhp->sid6[0]));
+    }
+
     if (ipa_nonzero(nh->gw))
     {
       nhp->gw = nh->gw;			/* Router nexthop */
@@ -2508,7 +2537,10 @@ rt_next_hop_update_rte(rtable *tab UNUSED, rte *old)
   mpls_label_stack mls = { .len = a->nh.labels_orig };
   memcpy(mls.stack, &a->nh.label[a->nh.labels - mls.len], mls.len * sizeof(u32));
 
-  rta_apply_hostentry(a, old->attrs->hostentry, &mls);
+  srv6_sid_stack sid6 = { .len = a->nh.sid6s_orig };
+  memcpy(sid6.sid, &a->nh.sid6[a->nh.sid6s - sid6.len], sid6.len * sizeof(sid6.sid[0]));
+
+  rta_apply_hostentry(a, old->attrs->hostentry, &mls, &sid6);
   a->cached = 0;
 
   rte *e = sl_alloc(rte_slab);
-- 
2.47.3

From f531d6faad4d9d93ab130a4be89e3bf100720a18 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?S=C3=A9bastien=20Parisot?= <[email protected]>
Date: Thu, 12 Feb 2026 16:09:45 +0000
Subject: [PATCH 2/5] netlink: Add SEG6 encapsulation support for SRv6

Add SEG6 SRH encoding/decoding for SRv6 SID installation to the Linux
kernel via Netlink. Includes nl_add_attr_seg6_srh(),
nl_add_attr_seg6_encap(), nl_parse_seg6_encap() and encap_seg6_want[]
table.

SRv6 takes precedence over MPLS when both are present. Handle SEG6
in nl_add_nexthop(), nl_parse_multipath() and nl_parse_route().
---
 sysdep/linux/netlink-sys.h |   2 +
 sysdep/linux/netlink.c     | 178 ++++++++++++++++++++++++++++++++++---
 2 files changed, 167 insertions(+), 13 deletions(-)

diff --git a/sysdep/linux/netlink-sys.h b/sysdep/linux/netlink-sys.h
index 4c99307..463205f 100644
--- a/sysdep/linux/netlink-sys.h
+++ b/sysdep/linux/netlink-sys.h
@@ -18,6 +18,8 @@
 #include <linux/lwtunnel.h>
 #endif
 
+#include <linux/seg6_iptunnel.h>
+
 #ifndef MSG_TRUNC			/* Hack: Several versions of glibc miss this one :( */
 #define MSG_TRUNC 0x20
 #endif
diff --git a/sysdep/linux/netlink.c b/sysdep/linux/netlink.c
index 299f132..f8d26f4 100644
--- a/sysdep/linux/netlink.c
+++ b/sysdep/linux/netlink.c
@@ -394,6 +394,12 @@ static struct nl_want_attrs nexthop_attr_want_mpls[BIRD_RTA_MAX] = {
 static struct nl_want_attrs encap_mpls_want[BIRD_RTA_MAX] = {
   [RTA_DST]       = { 1, 0, 0 },
 };
+
+#define BIRD_SEG6_IPTUNNEL_MAX (SEG6_IPTUNNEL_SRH+1)
+
+static struct nl_want_attrs encap_seg6_want[BIRD_SEG6_IPTUNNEL_MAX] = {
+  [SEG6_IPTUNNEL_SRH] = { 1, 0, 0 },
+};
 #endif
 
 static struct nl_want_attrs rtm_attr_want4[BIRD_RTA_MAX] = {
@@ -619,6 +625,134 @@ nl_add_attr_mpls_encap(struct nlmsghdr *h, uint bufsize, int len, u32 *stack)
   nl_add_attr_mpls(h, bufsize, RTA_DST, len, stack);
   nl_close_attr(h, nest);
 }
+#endif
+
+static inline void
+nl_add_attr_seg6_srh(struct nlmsghdr *h, uint bufsize, int sid_count, ip6_addr *sids)
+{
+  /* seg6_iptunnel_encap structure expected by kernel:
+   * - mode (4 bytes): SEG6_IPTUN_MODE_ENCAP = 1
+   * - ipv6_sr_hdr:
+   *   - nexthdr (1 byte): 0 (kernel overwrites this)
+   *   - hdrlen (1 byte): (8 + sid_count * 16) / 8 - 1
+   *   - type (1 byte): 4 = SRv6
+   *   - segments_left (1 byte): sid_count - 1
+   *   - last_entry/first_segment (1 byte): sid_count - 1
+   *   - flags (1 byte): 0
+   *   - tag (2 bytes): 0
+   *   - segments[] (sid_count * 16 bytes)
+   */
+
+  uint srh_len = 8 + (sid_count * 16);
+  uint encap_len = 4 /* mode */ + srh_len /* SRH */;
+  byte *encap = alloca(encap_len);
+  byte *pos = encap;
+
+  /* mode (4 bytes, host byte order) */
+  put_u32he(pos, SEG6_IPTUN_MODE_ENCAP);
+  pos += 4;
+
+  /* SRH header (8 bytes) */
+  put_u8(pos, 0);			/* nexthdr: kernel overwrites this */
+  pos += 1;
+  put_u8(pos, (srh_len / 8) - 1);	/* hdrlen in 8-byte units */
+  pos += 1;
+  put_u8(pos, 4);			/* type: SRv6 */
+  pos += 1;
+  put_u8(pos, sid_count - 1);		/* segments_left */
+  pos += 1;
+  put_u8(pos, sid_count - 1);		/* last_entry/first_segment */
+  pos += 1;
+  put_u8(pos, 0);			/* flags */
+  pos += 1;
+  put_u16(pos, 0);			/* tag */
+  pos += 2;
+
+  /* SID list (sid_count * 16 bytes) */
+  for (int i = 0; i < sid_count; i++)
+  {
+    put_ip6(pos, sids[i]);
+    pos += 16;
+  }
+
+  nl_add_attr(h, bufsize, SEG6_IPTUNNEL_SRH, encap, encap_len);
+}
+
+static inline void
+nl_add_attr_seg6_encap(struct nlmsghdr *h, uint bufsize, int sid_count, ip6_addr *sids)
+{
+  nl_add_attr_u16(h, bufsize, RTA_ENCAP_TYPE, LWTUNNEL_ENCAP_SEG6);
+
+  struct rtattr *nest = nl_open_attr(h, bufsize, RTA_ENCAP);
+  nl_add_attr_seg6_srh(h, bufsize, sid_count, sids);
+  nl_close_attr(h, nest);
+}
+
+/*
+ * Parse SEG6 encapsulation from RTA_ENCAP into SID array.
+ * Returns number of SIDs parsed, or 0 on error.
+ */
+static int
+nl_parse_seg6_encap(struct rtattr *rta_encap, ip6_addr *sids)
+{
+  struct rtattr *enca[BIRD_SEG6_IPTUNNEL_MAX];
+  nl_attr_len = RTA_PAYLOAD(rta_encap);
+  nl_parse_attrs(RTA_DATA(rta_encap), encap_seg6_want, enca, sizeof(enca));
+
+  if (!enca[SEG6_IPTUNNEL_SRH])
+  {
+    log(L_WARN "KRT: Received SEG6 encap with missing SRH attribute");
+    return 0;
+  }
+
+  /* Payload is seg6_iptunnel_encap: mode (4 bytes) + ipv6_sr_hdr */
+  byte *tuninfo = RTA_DATA(enca[SEG6_IPTUNNEL_SRH]);
+  uint tuninfo_len = RTA_PAYLOAD(enca[SEG6_IPTUNNEL_SRH]);
+
+  if (tuninfo_len < 4 + 8 + 16)
+  {
+    log(L_WARN "KRT: Received SEG6 encap too short (%d bytes)", tuninfo_len);
+    return 0;
+  }
+
+  /* Skip mode (4 bytes) to get to SRH */
+  byte *srh = tuninfo + 4;
+  uint srh_len = tuninfo_len - 4;
+
+  /* Parse SRH header (RFC 8754):
+   *   srh[1] = Hdr Ext Len (total SRH length in 8-byte units, minus 1)
+   *   srh[4] = Last Entry / first_segment (index of last segment, i.e. sid_count - 1) */
+  uint total_len = (srh[1] + 1) * 8;
+
+  if (total_len > srh_len)
+  {
+    log(L_WARN "KRT: Received SEG6 encap with invalid SRH length (%d)", total_len);
+    return 0;
+  }
+
+  uint sid_count = srh[4] + 1;
+
+  if (sid_count > SRV6_MAX_SID_STACK)
+  {
+    log(L_WARN "KRT: Received SEG6 encap with too many SIDs (%d, max %d supported)",
+	sid_count, SRV6_MAX_SID_STACK);
+    return 0;
+  }
+
+  /* Verify SID list fits within the SRH */
+  if (8 + sid_count * 16 > total_len)
+  {
+    log(L_WARN "KRT: Received SEG6 encap with SID list exceeding SRH (%d SIDs, SRH %d bytes)",
+	sid_count, total_len);
+    return 0;
+  }
+
+  /* Extract SIDs (start at byte 8 of SRH) */
+  for (uint i = 0; i < sid_count; i++)
+    sids[i] = get_ip6(srh + 8 + (i * 16));
+
+  return sid_count;
+}
 
 static inline void
 nl_add_attr_via(struct nlmsghdr *h, uint bufsize, ip_addr ipa)
@@ -638,7 +772,6 @@ nl_add_attr_via(struct nlmsghdr *h, uint bufsize, ip_addr ipa)
     nl_add_attr(h, bufsize, RTA_VIA, via, sizeof(struct rtvia) + 16);
   }
 }
-#endif
 
 static inline struct rtnexthop *
 nl_open_nexthop(struct nlmsghdr *h, uint bufsize)
@@ -663,12 +796,16 @@ nl_close_nexthop(struct nlmsghdr *h, struct rtnexthop *nh)
 static inline void
 nl_add_nexthop(struct nlmsghdr *h, uint bufsize, struct nexthop *nh, int af UNUSED)
 {
+  if (nh->sid6s > 0)
+    nl_add_attr_seg6_encap(h, bufsize, nh->sid6s, nh->sid6);
+  else if (nh->labels > 0) {
 #ifdef HAVE_MPLS_KERNEL
-  if (nh->labels > 0)
     if (af == AF_MPLS)
       nl_add_attr_mpls(h, bufsize, RTA_NEWDST, nh->labels, nh->label);
     else
       nl_add_attr_mpls_encap(h, bufsize, nh->labels, nh->label);
+#endif
+  }
 
   if (ipa_nonzero(nh->gw))
   {
@@ -677,11 +814,6 @@ nl_add_nexthop(struct nlmsghdr *h, uint bufsize, struct nexthop *nh, int af UNUS
     else
       nl_add_attr_via(h, bufsize, nh->gw);
   }
-#else
-
-  if (ipa_nonzero(nh->gw))
-    nl_add_attr_ipa(h, bufsize, RTA_GATEWAY, nh->gw);
-#endif
 }
 
 static void
@@ -806,17 +938,29 @@ nl_parse_multipath(struct nl_parse_state *s, struct krt_proto *p, const net_addr
 #ifdef HAVE_MPLS_KERNEL
       if (a[RTA_ENCAP] && a[RTA_ENCAP_TYPE])
       {
-	if (rta_get_u16(a[RTA_ENCAP_TYPE]) != LWTUNNEL_ENCAP_MPLS)
+	switch (rta_get_u16(a[RTA_ENCAP_TYPE]))
 	{
+	case LWTUNNEL_ENCAP_MPLS:
+	  {
+	    struct rtattr *enca[BIRD_RTA_MAX];
+	    nl_attr_len = RTA_PAYLOAD(a[RTA_ENCAP]);
+	    nl_parse_attrs(RTA_DATA(a[RTA_ENCAP]), encap_mpls_want, enca, sizeof(enca));
+	    rv->labels = rta_get_mpls(enca[RTA_DST], rv->label);
+	    break;
+	  }
+	case LWTUNNEL_ENCAP_SEG6:
+	  {
+	    int sid_count = nl_parse_seg6_encap(a[RTA_ENCAP], rv->sid6);
+	    if (!sid_count)
+	      return NULL;
+	    rv->sid6s = sid_count;
+	    break;
+	  }
+	default:
 	  log(L_WARN "KRT: Received route %N with unknown encapsulation method %d",
 	      n, rta_get_u16(a[RTA_ENCAP_TYPE]));
 	  return NULL;
 	}
-
-	struct rtattr *enca[BIRD_RTA_MAX];
-	nl_attr_len = RTA_PAYLOAD(a[RTA_ENCAP]);
-	nl_parse_attrs(RTA_DATA(a[RTA_ENCAP]), encap_mpls_want, enca, sizeof(enca));
-	rv->labels = rta_get_mpls(enca[RTA_DST], rv->label);
       }
 #endif
 
@@ -1792,6 +1936,14 @@ nl_parse_route(struct nl_parse_state *s, struct nlmsghdr *h)
 	      ra->nh.labels = rta_get_mpls(enca[RTA_DST], ra->nh.label);
 	      break;
 	    }
+          case LWTUNNEL_ENCAP_SEG6:
+            {
+              int sid_count = nl_parse_seg6_encap(a[RTA_ENCAP], ra->nh.sid6);
+              if (!sid_count)
+                return;
+              ra->nh.sid6s = sid_count;
+              break;
+            }
 	  default:
 	    SKIP("unknown encapsulation method %d\n", rta_get_u16(a[RTA_ENCAP_TYPE]));
 	    break;
-- 
2.47.3

From bc34c3af4aa81d6ccdb09d0da183bffb03ac82be Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?S=C3=A9bastien=20Parisot?= <[email protected]>
Date: Thu, 12 Feb 2026 16:12:46 +0000
Subject: [PATCH 3/5] static: Add SRv6 SID support for static routes

Allow configuring SRv6 SIDs on static routes, both for direct nexthops
(via ... srv6 <sid>) and recursive routes (recursive ... srv6 <sid>).
SRv6 SIDs are propagated to the nexthop and through recursive next hop
resolution, and are compared when detecting configuration changes.
---
 doc/bird.sgml         |  2 +-
 proto/static/config.Y | 10 +++++++++-
 proto/static/static.c | 41 ++++++++++++++++++++++++++++-------------
 proto/static/static.h |  5 ++++-
 4 files changed, 42 insertions(+), 16 deletions(-)

diff --git a/doc/bird.sgml b/doc/bird.sgml
index 5761460..32c770b 100644
--- a/doc/bird.sgml
+++ b/doc/bird.sgml
@@ -6526,7 +6526,7 @@ each labeled route.
 <ref id="type-prefix" name="dependent on network type">.
 
 <descrip>
-	<tag><label id="static-route-regular">route <m/prefix/ [mpls <m/number/] via <m/ip/|<m/"interface"/ [<m/per-nexthop options/] [via ...]</tag>
+	<tag><label id="static-route-regular">route <m/prefix/ [mpls <m/number/] via <m/ip/|<m/"interface"/ [srv6 <m/srv6-sid-stack/] [<m/per-nexthop options/] [via ...]</tag>
 	Regular routes may bear one or more <ref id="route-next-hop" name="next
 	hops">. Every next hop is preceded by <cf/via/ and configured as shown.
 
diff --git a/proto/static/config.Y b/proto/static/config.Y
index f840947..1f0d1f6 100644
--- a/proto/static/config.Y
+++ b/proto/static/config.Y
@@ -47,7 +47,7 @@ static_route_finish(void)
 CF_DECLS
 
 CF_KEYWORDS(STATIC, ROUTE, VIA, DROP, REJECT, PROHIBIT, PREFERENCE, CHECK, LINK, DEV)
-CF_KEYWORDS(ONLINK, WEIGHT, RECURSIVE, IGP, TABLE, BLACKHOLE, UNREACHABLE, BFD, MPLS)
+CF_KEYWORDS(ONLINK, WEIGHT, RECURSIVE, IGP, TABLE, BLACKHOLE, UNREACHABLE, BFD, MPLS, SRV6)
 CF_KEYWORDS(TRANSIT, PROVIDERS)
 
 
@@ -97,6 +97,9 @@ stat_nexthop:
   | stat_nexthop MPLS label_stack {
     this_snh->mls = $3;
   }
+  | stat_nexthop SRV6 srv6_sid_stack {
+    this_snh->sid6 = $3;
+  }
   | stat_nexthop ONLINK bool {
     this_snh->onlink = $3;
     if (this_snh->use_bfd && this_snh->onlink)
@@ -146,6 +149,11 @@ stat_route:
       this_srt->via = $3;
       this_srt->mls = $5;
    }
+ | stat_route0 RECURSIVE ipa SRV6 srv6_sid_stack {
+      this_srt->dest = RTDX_RECURSIVE;
+      this_srt->via = $3;
+      this_srt->sid6 = $5;
+   }
  | stat_route0			{ this_srt->dest = RTD_NONE; }
  | stat_route0 DROP		{ this_srt->dest = RTD_BLACKHOLE; }
  | stat_route0 REJECT		{ this_srt->dest = RTD_UNREACHABLE; }
diff --git a/proto/static/static.c b/proto/static/static.c
index 65fdb70..6cd2a36 100644
--- a/proto/static/static.c
+++ b/proto/static/static.c
@@ -83,6 +83,11 @@ static_announce_rte(struct static_proto *p, struct static_route *r)
 	nh->labels = r2->mls->len;
 	memcpy(nh->label, r2->mls->stack, r2->mls->len * sizeof(u32));
       }
+      if (r2->sid6)
+      {
+        nh->sid6s = r2->sid6->len;
+        memcpy(nh->sid6, r2->sid6->sid, r2->sid6->len * sizeof(nh->sid6[0]));
+      }
 
       nexthop_insert(&nhs, nh);
     }
@@ -96,7 +101,7 @@ static_announce_rte(struct static_proto *p, struct static_route *r)
   if (r->dest == RTDX_RECURSIVE)
   {
     rtable *tab = ipa_is_ip4(r->via) ? p->igp_table_ip4 : p->igp_table_ip6;
-    rta_set_recursive_next_hop(p->p.main_channel->table, a, tab, r->via, IPA_NONE, r->mls);
+    rta_set_recursive_next_hop(p->p.main_channel->table, a, tab, r->via, IPA_NONE, r->mls, r->sid6);
   }
 
   if (r->net->type == NET_ASPA)
@@ -387,30 +392,40 @@ static_same_dest(struct static_route *x, struct static_route *y)
 	  (x->weight != y->weight) ||
 	  (x->use_bfd != y->use_bfd) ||
 	  (!x->mls != !y->mls) ||
-	  ((x->mls) && (y->mls) && (x->mls->len != y->mls->len)))
+	  (!x->sid6 != !y->sid6) ||
+	  ((x->mls) && (y->mls) && (x->mls->len != y->mls->len)) ||
+	  ((x->sid6) && (y->sid6) && (x->sid6->len != y->sid6->len)))
 	return 0;
 
-      if (!x->mls)
-	continue;
+      if (x->mls)
+        for (uint i = 0; i < x->mls->len; i++)
+	  if (x->mls->stack[i] != y->mls->stack[i])
+	    return 0;
 
-      for (uint i = 0; i < x->mls->len; i++)
-	if (x->mls->stack[i] != y->mls->stack[i])
-	  return 0;
+      if (x->sid6)
+        for (uint i = 0; i < x->sid6->len; i++)
+	  if (ip6_compare(x->sid6->sid[i], y->sid6->sid[i]) != 0)
+	    return 0;
     }
     return !x && !y;
 
   case RTDX_RECURSIVE:
     if (!ipa_equal(x->via, y->via) ||
 	(!x->mls != !y->mls) ||
-	((x->mls) && (y->mls) && (x->mls->len != y->mls->len)))
+	(!x->sid6 != !y->sid6) ||
+	((x->mls) && (y->mls) && (x->mls->len != y->mls->len)) ||
+	((x->sid6) && (y->sid6) && (x->sid6->len != y->sid6->len)))
       return 0;
 
-    if (!x->mls)
-      return 1;
+    if (x->mls)
+      for (uint i = 0; i < x->mls->len; i++)
+        if (x->mls->stack[i] != y->mls->stack[i])
+	  return 0;
 
-    for (uint i = 0; i < x->mls->len; i++)
-      if (x->mls->stack[i] != y->mls->stack[i])
-	return 0;
+    if (x->sid6)
+      for (uint i = 0; i < x->sid6->len; i++)
+        if (ip6_compare(x->sid6->sid[i], y->sid6->sid[i]) != 0)
+	  return 0;
 
     return 1;
 
diff --git a/proto/static/static.h b/proto/static/static.h
index edd2dc2..d7dd80a 100644
--- a/proto/static/static.h
+++ b/proto/static/static.h
@@ -51,7 +51,10 @@ struct static_route {
   uint mpls_label;			/* Local MPLS label, -1 if unused */
   struct bfd_request *bfd_req;		/* BFD request, if BFD is used */
   union {
-    mpls_label_stack *mls;		/* MPLS label stack; may be NULL */
+    struct {
+      mpls_label_stack *mls;		/* MPLS label stack; may be NULL */
+      srv6_sid_stack *sid6;		/* SRv6 SID; may be NULL */
+    };
     adata *aspa;			/* ASPA provider list; may be NULL */
   };
 };
-- 
2.47.3

From 3a634f2da8ea3eb721166bd744b6d8082eb8d271 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?S=C3=A9bastien=20Parisot?= <[email protected]>
Date: Mon, 9 Feb 2026 20:15:59 +0000
Subject: [PATCH 4/5] l3vpn: Make MPLS channel optional for SRv6 deployments

Guard all P->mpls_channel operations with NULL checks and remove
mandatory MPLS channel check in l3vpn_postconfig(). This allows
L3VPN to operate without an MPLS channel when using SRv6.
---
 proto/l3vpn/l3vpn.c | 25 +++++++++++++++----------
 1 file changed, 15 insertions(+), 10 deletions(-)

diff --git a/proto/l3vpn/l3vpn.c b/proto/l3vpn/l3vpn.c
index ece79d5..dbb1b80 100644
--- a/proto/l3vpn/l3vpn.c
+++ b/proto/l3vpn/l3vpn.c
@@ -227,8 +227,11 @@ l3vpn_rt_notify(struct proto *P, struct channel *c0, net *net, rte *new, rte *ol
 
     if (export)
     {
-      struct mpls_channel *mc = (void *) p->p.mpls_channel;
-      ea_set_attr_u32(&a->eattrs, tmp_linpool, EA_MPLS_POLICY, 0, EAF_TYPE_INT, mc->label_policy);
+      if (P->mpls_channel)
+      {
+        struct mpls_channel *mc = (void *) p->p.mpls_channel;
+        ea_set_attr_u32(&a->eattrs, tmp_linpool, EA_MPLS_POLICY, 0, EAF_TYPE_INT, mc->label_policy);
+      }
 
       struct adata *ad = l3vpn_export_targets(p, ea_get_adata(a0->eattrs, EA_BGP_EXT_COMMUNITY));
       ea_set_attr_ptr(&a->eattrs, tmp_linpool, EA_BGP_EXT_COMMUNITY, BAF_OPTIONAL | BAF_TRANSITIVE, EAF_TYPE_EC_SET, ad);
@@ -338,9 +341,6 @@ l3vpn_postconfig(struct proto_config *CF)
   if (!!proto_cf_find_channel(CF, NET_IP6) != !!proto_cf_find_channel(CF, NET_VPN6))
     cf_error("For IPv6 L3VPN, both IPv6 and VPNv6 channels must be specified");
 
-  if (!proto_cf_find_channel(CF, NET_MPLS))
-    cf_error("MPLS channel not specified");
-
   if (rd_zero(cf->rd))
     cf_error("Route distinguisher not specified");
 
@@ -389,10 +389,13 @@ l3vpn_start(struct proto *P)
   l3vpn_prepare_import_targets(p);
   l3vpn_prepare_export_targets(p);
 
-  proto_setup_mpls_map(P, RTS_L3VPN, 1);
+  if (P->mpls_channel)
+  {
+    proto_setup_mpls_map(P, RTS_L3VPN, 1);
 
-  if (P->vrf_set)
-    P->mpls_map->vrf_iface = P->vrf;
+    if (P->vrf_set)
+      P->mpls_map->vrf_iface = P->vrf;
+  }
 
   return PS_UP;
 }
@@ -402,7 +405,8 @@ l3vpn_shutdown(struct proto *P)
 {
   // struct l3vpn_proto *p = (void *) P;
 
-  proto_shutdown_mpls_map(P, 1);
+  if (P->mpls_channel)
+    proto_shutdown_mpls_map(P, 1);
 
   return PS_DOWN;
 }
@@ -430,7 +434,8 @@ l3vpn_reconfigure(struct proto *P, struct proto_config *CF)
   p->import_target = cf->import_target;
   p->export_target = cf->export_target;
 
-  proto_setup_mpls_map(P, RTS_L3VPN, 1);
+  if (P->mpls_channel)
+    proto_setup_mpls_map(P, RTS_L3VPN, 1);
 
   if (import_changed)
   {
-- 
2.47.3

From c26e62ec1e6804de582e278e86c9ce67f572b3f2 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?S=C3=A9bastien=20Parisot?= <[email protected]>
Date: Thu, 12 Feb 2026 16:10:19 +0000
Subject: [PATCH 5/5] bgp: Add SRv6 Prefix SID attribute and service TLV
 support

Add BA_PREFIX_SID (0x28) attribute with full Prefix SID TLV handling:
validation, get/set for SRv6 VPN SID (Type 4) and SRv6 L3 Service
(Type 5, RFC 9252) TLVs, including SID transposition via Data SID
Structure sub-TLV.

Extend bgp_apply_mpls_labels() for SRv6 SID extraction via Prefix SID.
Extend bgp_encode_mpls_labels() with SRv6 L3/L2 Service TLV detection
to activate the raw label path. Extend bgp_decode_mpls_labels() for
SRv6 single-label exception.

Update all bgp_encode_nlri_*() signatures and bgp_encode_nlri() wrapper
to pass eattrs. Generate SRv6 Prefix SID in bgp_update_attrs().

SRv6 routes on labeled BGP channels (SAFI 128) carry SIDs in the
Prefix SID attribute instead of MPLS labels, so the NLRI label field
is set to implicit null per RFC 9252 Section 5. Non-SRv6 routes without
labels are still rejected.
---
 doc/bird.sgml       |  16 +-
 proto/bgp/attrs.c   | 572 ++++++++++++++++++++++++++++++++++++++++++++
 proto/bgp/bgp.c     |   2 +
 proto/bgp/bgp.h     |  22 +-
 proto/bgp/packets.c | 104 +++++---
 5 files changed, 683 insertions(+), 33 deletions(-)

diff --git a/doc/bird.sgml b/doc/bird.sgml
index 32c770b..cc1b425 100644
--- a/doc/bird.sgml
+++ b/doc/bird.sgml
@@ -2868,14 +2868,16 @@ avoid routing loops.
 <item> <rfc id="7911"> &ndash; Advertisement of Multiple Paths in BGP
 <item> <rfc id="7947"> &ndash; Internet Exchange BGP Route Server
 <item> <rfc id="8092"> &ndash; BGP Large Communities Attribute
-<item> <rfc id="8277"> &ndash; Using BGP to Bind MPLS Labels to Address Prefixes
 <item> <rfc id="8212"> &ndash; Default EBGP Route Propagation Behavior without Policies
+<item> <rfc id="8277"> &ndash; Using BGP to Bind MPLS Labels to Address Prefixes
 <item> <rfc id="8654"> &ndash; Extended Message Support for BGP
+<item> <rfc id="8669"> &ndash; Segment Routing Prefix Segment Identifier Extensions for BGP [partial]
 <item> <rfc id="8950"> &ndash; Advertising IPv4 NLRI with an IPv6 Next Hop
 <item> <rfc id="9003"> &ndash; Extended BGP Administrative Shutdown Communication
 <item> <rfc id="9072"> &ndash; Extended Optional Parameters Length for BGP OPEN Message
 <item> <rfc id="9117"> &ndash; Revised Validation Procedure for BGP Flow Specifications
 <item> <rfc id="9234"> &ndash; Route Leak Prevention and Detection Using Roles
+<item> <rfc id="9252"> &ndash; BGP Overlay Services Based on Segment Routing over IPv6 (SRv6) [partial]
 <item> <rfc id="9494"> &ndash; Long-Lived Graceful Restart for BGP
 <item> <rfc id="9687"> &ndash; Send Hold Timer
 </itemize>
@@ -3923,12 +3925,16 @@ be used in explicit configuration.
 	When set to <cf/no/ (or <cf/disabled/), the capability is not
 	advertised and only single labels are used.
 
-	Default: always.
+	Note: for routes carrying SRv6 transposition data (indicated by an
+	SRv6 L3/L2 Service TLV in the Prefix SID attribute), only a single
+	label entry is read regardless of this setting, because the BOS bit is
+	not meaningful for SRv6 transposition (<rfc id="9252">). Default:
+	always.
 
 	<tag><label id="bgp-require-multiple-labels">require multiple labels <m/switch/</tag>
-	If enabled, the Multiple Labels capability (<rfc id="8277">) must be
-	announced by the BGP neighbor, otherwise the BGP session will not be
-	established. Default: off.
+	If enabled, the BGP multiple labels capability (<rfc id="8277">) must
+	be announced by the BGP neighbor for the given address family,
+	otherwise the BGP session will not be established. Default: off.
 
 	<tag><label id="bgp-aigp">aigp <m/switch/|originate</tag>
 	The BGP protocol does not use a common metric like other routing
diff --git a/proto/bgp/attrs.c b/proto/bgp/attrs.c
index acd3eb9..22968c2 100644
--- a/proto/bgp/attrs.c
+++ b/proto/bgp/attrs.c
@@ -203,6 +203,427 @@ bgp_encode_raw(struct bgp_write_state *s UNUSED, eattr *a, byte *buf, uint size)
 }
 
 
+/*
+ * 	Prefix SID handling
+ */
+
+static int
+bgp_prefix_sid_valid(byte *data, uint len, char *err, uint elen)
+{
+  byte *pos = data;
+  char *err_dsc = NULL;
+  uint err_val = 0;
+
+#define BAD(DSC,VAL) ({ err_dsc = DSC; err_val = VAL; goto bad; })
+  while (len)
+  {
+    if (len < 1 + 2)
+      BAD("TLV framing error", len);
+
+    /* Process one TLV */
+    uint ptype = get_u8(pos);
+    ADVANCE(pos, len, 1);
+    uint plen = get_u16(pos);
+    ADVANCE(pos, len, 2);
+
+    if (len < plen)
+      BAD("TLV framing error", plen);
+
+    if (0) {
+    } else if (ptype == BGP_PREFIX_SID_SRV6_VPN_SID) {
+      if ((plen - 1) % (1 + 1 + 16) != 0)
+        BAD("Incorrect SRv6-VPN SID TLV length", plen);
+    } else if (ptype == BGP_PREFIX_SID_SRV6_L3_SERVICE) {
+      if (plen < 1)
+        BAD("Incorrect SRv6 L3 Service TLV length", plen);
+
+      byte *spos = pos;
+      uint slen = plen;
+
+      /* skip fixed data */
+      ADVANCE(spos, slen, 1);
+
+      while (slen)
+      {
+        if (slen < 1 + 2)
+          BAD("Sub-TLV framing error", slen);
+
+        /* Process one Sub-TLV */
+        uint pstype = get_u8(spos);
+        ADVANCE(spos, slen, 1);
+        uint pslen = get_u16(spos);
+        ADVANCE(spos, slen, 2);
+
+        if (slen < pslen)
+          BAD("Sub-TLV framing error", slen);
+
+        if (0) {
+        } else if (pstype == BGP_PREFIX_SID_SRV6_SERVICE_SID_INFORMATION) {
+          if (pslen < (1 + 16 + 1 + 2 + 1))
+            BAD("Incorrect SID Information Sub-TLV length", pslen);
+
+          byte *sspos = spos;
+          uint sslen = pslen;
+
+          /* skip fixed data */
+          ADVANCE(sspos, sslen, 1 + 16 + 1 + 2 + 1);
+
+          while (sslen)
+          {
+            if (sslen < 1 + 2)
+              BAD("Sub-Sub-TLV framing error", sslen);
+
+            /* Process one Sub-Sub-TLV */
+            uint psstype = get_u8(sspos);
+            ADVANCE(sspos, sslen, 1);
+            uint psslen = get_u16(sspos);
+            ADVANCE(sspos, sslen, 2);
+
+            if (sslen < psslen)
+              BAD("Sub-Sub-TLV framing error", sslen);
+
+            if (0) {
+            } else if (psstype == BGP_PREFIX_SID_SRV6_SERVICE_DATA_SID_STRUCTURE) {
+              if (psslen != 1 + 1 + 1 + 1 + 1 + 1)
+                BAD("Incorrect SID Structure Sub-Sub-TLV length", psslen);
+            }
+
+            ADVANCE(sspos, sslen, psslen);
+          }
+        }
+
+        ADVANCE(spos, slen, pslen);
+      }
+    }
+
+    ADVANCE(pos, len, plen);
+  }
+#undef BAD
+
+  return 1;
+
+bad:
+  if (err)
+    if (bsnprintf(err, elen, "%s (%u) at %d", err_dsc, err_val, (int) (pos - data)) < 0)
+      err[0] = 0;
+
+  return 0;
+}
+
+/* Get TLV for BA_PREFIX_SID */
+const byte *
+bgp_prefix_sid_get_tlv(const struct adata *ad, uint type)
+{
+  if (!ad)
+    return NULL;
+
+  uint len = ad->length;
+  const byte *pos = ad->data;
+
+  while (len)
+  {
+    uint ptype = get_u8(pos);
+    uint plen = get_u16(pos + 1);
+
+    if (ptype == type)
+      return pos;
+
+    ADVANCE(pos, len, 1 + 2 + plen);
+  }
+
+  return NULL;
+}
+
+/* Get Sub-TLV for BA_PREFIX_SID -> BGP_PREFIX_SID_SRV6_L3_SERVICE; BGP_PREFIX_SID_SRV6_L2_SERVICE */
+static const byte *
+bgp_prefix_sid_srv6_service_get_subtlv(const byte *b, uint len, uint type)
+{
+  if (!b)
+    return NULL;
+
+  while (len)
+  {
+    uint stype = get_u8(b);
+    uint slen = get_u16(b + 1);
+
+    if (stype == type)
+      return b;
+
+    ADVANCE(b, len, 1 + 2 + slen);
+  }
+
+  return NULL;
+}
+
+/* Get SRv6 SID from BA_PREFIX_SID -> BGP_PREFIX_SID_SRV6_VPN_SID TLV */
+static int
+bgp_prefix_sid_get_srv6_vpn_sid(const struct adata *ad, u8 *type, u8 *flag, ip6_addr *sid)
+{
+  const byte *b;
+  uint len;
+
+  if (!(b = bgp_prefix_sid_get_tlv(ad, BGP_PREFIX_SID_SRV6_VPN_SID)))
+    return 0;
+
+  len = get_u16(b + 1);
+
+  b += 1 + 2;
+
+  if (type)
+    *type = b[1 + (1 + 1 + 16) * 0 + 0];
+  if (flag)
+    *flag = b[1 + (1 + 1 + 16) * 0 + 1];
+  if (sid)
+    *sid = get_ip6(&b[1 + (1 + 1 + 16) * 0 + 2]);
+
+  return 1;
+}
+
+/* Get SRv6 SID from BA_PREFIX_SID -> BGP_PREFIX_SID_SRV6_L3_SERVICE TLV
+ * Returns:
+ *   1: Success
+ *   0: Type 5 TLV or SID Information sub-TLV not present (caller should try fallback)
+ *  -1: Type 5 present but invalid (error, no fallback)
+ */
+static int
+bgp_prefix_sid_get_srv6_l3_service_sid(const struct adata *ad, struct ea_list *attrs, ip6_addr *sid)
+{
+  const byte *b;
+
+  if (!(b = bgp_prefix_sid_get_tlv(ad, BGP_PREFIX_SID_SRV6_L3_SERVICE)))
+    return 0;
+
+  uint len = get_u16(b + 1);
+
+  b += 1 + 2;
+
+  /* Reserved */
+  b += 1;
+  len -= 1;
+
+  const byte *sb;
+
+  if (!(sb = bgp_prefix_sid_srv6_service_get_subtlv(b, len, BGP_PREFIX_SID_SRV6_SERVICE_SID_INFORMATION)))
+    return 0;
+
+  uint slen = get_u16(sb + 1);
+
+  sb += 1 + 2;
+
+  ip6_addr __sid = get_ip6(sb + 1);
+
+  sb += 1 + 16 + 1 + 2 + 1;
+  slen -= 1 + 16 + 1 + 2 + 1;
+
+  const byte *ssb;
+
+  if ((ssb = bgp_prefix_sid_srv6_service_get_subtlv(sb, slen, BGP_PREFIX_SID_SRV6_SERVICE_DATA_SID_STRUCTURE))) {
+    uint sslen = get_u16(ssb + 1);
+
+    ssb += 1 + 2;
+
+    uint trans_len = get_u8(ssb + 4);
+    uint trans_off = get_u8(ssb + 5);
+
+    if (trans_len > 24 || trans_off + trans_len > 128)
+      return -1;
+
+    if (trans_len) {
+      eattr *mea;
+
+      if (!(mea = bgp_find_attr(attrs, BA_RAW_MPLS_LABEL_STACK)))
+        return -1;
+
+      const struct adata *mad = mea->u.ptr;
+
+      if (mad->length / 4 < 1)
+        return -1;
+
+      u32 raw_label;
+      memcpy(&raw_label, &mad->data, 4 * 1);
+      raw_label >>= (24 - trans_len);
+      raw_label &= ((1 << trans_len) - 1);
+
+      if (trans_off % 32 + trans_len <= 32) {
+        __sid.addr[trans_off / 32] &= ~(((1 << trans_len) - 1) << (32 - trans_len - trans_off % 32));
+        __sid.addr[trans_off / 32] |= raw_label << (32 - trans_off % 32 - trans_len);
+      } else {
+        __sid.addr[trans_off / 32] &= ~(((1 << trans_len) - 1) >> (trans_off % 32 + trans_len - 32));
+        __sid.addr[trans_off / 32] |= raw_label >> (trans_off % 32 + trans_len - 32);
+
+        __sid.addr[trans_off / 32 + 1] &= ~(((1 << trans_len) - 1) << (32 - trans_off % 32 - trans_len + 32));
+        __sid.addr[trans_off / 32 + 1] |= raw_label << (32 - trans_off % 32 - trans_len + 32);
+      }
+    }
+  }
+
+  *sid = __sid;
+
+  return 1;
+}
+
+/* Set TLV to BA_PREFIX_SID */
+static const struct adata *
+bgp_prefix_sid_set_tlv(struct linpool *pool, const struct adata *ad, uint type, byte *data, uint dlen)
+{
+  uint len = ad ? ad->length : 0;
+  const byte *pos = ad ? ad->data : NULL;
+  struct adata *res = lp_alloc_adata(pool, len + 3 + dlen);
+  byte *dst = res->data;
+  byte *tlv = NULL;
+  int del = 0;
+
+  while (len)
+  {
+    uint ptype = get_u8(pos);
+    uint plen = get_u16(pos + 1);
+
+    /* Find position for new TLV */
+    if ((ptype >= type) && !tlv)
+    {
+      tlv = dst;
+      dst += 1 + 2 + dlen;
+    }
+
+    /* Skip first matching TLV, copy others */
+    if ((ptype == type) && !del)
+      del = 1;
+    else
+    {
+      memcpy(dst, pos, 1 + 2 + plen);
+      dst += 1 + 2 + plen;
+    }
+
+    ADVANCE(pos, len, 1 + 2 + plen);
+  }
+
+  if (!tlv)
+  {
+    tlv = dst;
+    dst += 1 + 2 + dlen;
+  }
+
+  /* Store the TLV */
+  put_u8(tlv + 0, type);
+  put_u16(tlv + 1, dlen);
+  memcpy(tlv + 1 + 2, data, dlen);
+
+  /* Update length */
+  res->length = dst - res->data;
+
+  return res;
+}
+
+/* Set Sub-TLV to BA_PREFIX_SID -> BGP_PREFIX_SID_SRV6_L3_SERVICE; BGP_PREFIX_SID_SRV6_L2_SERVICE */
+static uint
+bgp_prefix_sid_srv6_service_set_subtlv(byte *pos, uint type, const byte *data, uint dlen)
+{
+  put_u8(pos, type);
+  put_u16(pos + 1, dlen);
+  memcpy(pos + 1 + 2, data, dlen);
+  return 1 + 2 + dlen;
+}
+
+/* Set SRv6 SID to BA_PREFIX_SID -> BGP_PREFIX_SID_SRV6_VPN_SID TLV */
+static const struct adata *
+bgp_prefix_sid_set_srv6_vpn_sid(struct linpool *pool, const struct adata *ad, u8 type, u8 flag, ip6_addr sid)
+{
+  byte data[1 + 1 + 1 + 16];
+
+  put_u8(data + 0, 0);
+
+  put_u8(data + 1, type);
+  put_u8(data + 2, flag);
+  put_ip6(data + 3, sid);
+
+  return bgp_prefix_sid_set_tlv(pool, ad, BGP_PREFIX_SID_SRV6_VPN_SID, data, sizeof(data));
+}
+
+/* Set SRv6 SID to BA_PREFIX_SID -> BGP_PREFIX_SID_SRV6_L3_SERVICE TLV */
+static const struct adata *
+bgp_prefix_sid_set_srv6_l3_service_sid(struct linpool *pool, const struct adata *ad, ip6_addr sid)
+{
+  /* RFC 9252 Type 5 TLV: SRv6 L3 Service */
+  byte data[1 + (1 + 2 + (1 + 16 + 1 + 2 + 1))];
+  byte *pos = data;
+
+  /* Outer TLV RESERVED byte (RFC 9252 Section 2) */
+  put_u8(pos, 0);
+  pos += 1;
+
+  /* Prepare SRv6 SID Information Sub-TLV data */
+  byte sid_info_data[1 + 16 + 1 + 2 + 1];
+  byte *spos = sid_info_data;
+
+  /* Sub-TLV RESERVED1 */
+  put_u8(spos, 0);
+  spos += 1;
+
+  /* SRv6 SID Value (16 bytes) */
+  put_ip6(spos, sid);
+  spos += 16;
+
+  /* SID Flags (no flags set) */
+  put_u8(spos, 0);
+  spos += 1;
+
+  /* Endpoint Behavior (0xFFFF = opaque/abstract) */
+  put_u16(spos, 0xFFFF);
+  spos += 2;
+
+  /* RESERVED2 */
+  put_u8(spos, 0);
+  spos += 1;
+
+  /* Write Sub-TLV: SRv6 SID Information (Type 1) */
+  pos += bgp_prefix_sid_srv6_service_set_subtlv(pos, BGP_PREFIX_SID_SRV6_SERVICE_SID_INFORMATION, sid_info_data, sizeof(sid_info_data));
+
+  /* No Sub-Sub-TLV (SID Structure) in this implementation */
+
+  return bgp_prefix_sid_set_tlv(pool, ad, BGP_PREFIX_SID_SRV6_L3_SERVICE, data, sizeof(data));
+}
+
+/* Get SRv6 SID for L3VPN from BA_PREFIX_SID (either BGP_PREFIX_SID_SRV6_L3_SERVICE or BGP_PREFIX_SID_SRV6_VPN_SID TLVs) */
+int
+bgp_prefix_sid_get_srv6_l3vpn_sid(struct ea_list *attrs, ip6_addr *sid)
+{
+  eattr *ea;
+
+  if (!(ea = bgp_find_attr(attrs, BA_PREFIX_SID)))
+    return 0;
+
+  /* Try BGP_PREFIX_SID_SRV6_L3_SERVICE */
+  int result = bgp_prefix_sid_get_srv6_l3_service_sid(ea->u.ptr, attrs, sid);
+  if (result != 0)
+    return result > 0 ? 1 : 0;
+
+  /* Try BGP_PREFIX_SID_SRV6_VPN_L3 */
+  u8 srv6_vpn_sid_type;
+  ip6_addr __sid;
+  if (bgp_prefix_sid_get_srv6_vpn_sid(ea->u.ptr, &srv6_vpn_sid_type, NULL, &__sid)) {
+    if (srv6_vpn_sid_type == BGP_PREFIX_SID_SRV6_VPN_L3) {
+      *sid = __sid;
+
+      return 1;
+    }
+  }
+
+  return 0;
+}
+
+/* Set SRv6 SID for L3VPN to BA_PREFIX_SID (either BGP_PREFIX_SID_SRV6_L3_SERVICE or BGP_PREFIX_SID_SRV6_VPN_SID TLVs) */
+static const struct adata *
+bgp_prefix_sid_set_srv6_l3vpn_sid(struct linpool *pool, const struct adata *ad, ip6_addr sid)
+{
+#if 1
+  /* RFC 9252 Type 5 TLV */
+  return bgp_prefix_sid_set_srv6_l3_service_sid(pool, ad, sid);
+#else
+  /* Old Type 4 TLV */
+  return bgp_prefix_sid_set_srv6_vpn_sid(pool, ad, BGP_PREFIX_SID_SRV6_VPN_L3, 0x00, sid);
+#endif
+}
+
 /*
  *	AIGP handling
  */
@@ -916,6 +1337,139 @@ bgp_decode_otc(struct bgp_parse_state *s, uint code UNUSED, uint flags, byte *da
 }
 
 
+static int
+bgp_encode_prefix_sid(struct bgp_write_state *s, eattr *a, byte *buf, uint size)
+{
+  return bgp_encode_raw(s, a, buf, size);
+  bgp_put_attr(buf, size, BA_PREFIX_SID, a->flags, a->u.ptr->data, a->u.ptr->length);
+}
+
+static void
+bgp_decode_prefix_sid(struct bgp_parse_state *s, uint code UNUSED, uint flags, byte *data, uint len, ea_list **to)
+{
+  char err[128];
+
+  if (!bgp_prefix_sid_valid(data, len, err, sizeof(err)))
+    DISCARD("Malformed Prefix SID attribute - %s", err);
+
+  bgp_set_attr_data(to, s->pool, BA_PREFIX_SID, flags, data, len);
+}
+
+static void
+bgp_format_prefix_sid(const eattr *a, byte *buf, uint size UNUSED)
+{
+  const byte *b;
+
+  *buf = '\0';
+
+  if ((b = bgp_prefix_sid_get_tlv(a->u.ptr, BGP_PREFIX_SID_SRV6_VPN_SID))) {
+    uint tlen = get_u16(b + 1);
+
+    b += 3;
+
+    if (*buf)
+      buf += bsprintf(buf, ", ");
+    buf += bsprintf(buf, "SRv6-VPN SID ");
+    if (tlen == 0 || tlen == 1) {
+      buf += bsprintf(buf, "<>");
+    } else {
+      buf += bsprintf(buf, "<");
+      for (uint i = 0; i < (tlen - 1) / (1 + 16 + 1); ++i) {
+        u8 t = get_u8(&b[1 + (1 + 16 + 1) * i + 0]);
+        u8 f = get_u8(&b[1 + (1 + 16 + 1) * i + 1]);
+        ip6_addr ip6 = get_ip6(&b[1 + (1 + 16 + 1) * i + 2]);
+
+        if (i > 0)
+          buf += bsprintf(buf, ", ");
+
+        switch (t) {
+          case BGP_PREFIX_SID_SRV6_VPN_L3:
+            buf += bsprintf(buf, "L3 ");
+            break;
+          case BGP_PREFIX_SID_SRV6_VPN_L2:
+            buf += bsprintf(buf, "L2 ");
+            break;
+          default:
+            buf += bsprintf(buf, "T%u ", (uint)t);
+            break;
+        }
+        buf += bsprintf(buf, "0x%02x %I6",
+          (uint)f, ip6);
+      }
+      buf += bsprintf(buf, ">");
+    }
+  }
+
+  if ((b = bgp_prefix_sid_get_tlv(a->u.ptr, BGP_PREFIX_SID_SRV6_L3_SERVICE))) {
+    uint tlen = get_u16(b + 1);
+
+    b += 3;
+
+    if (*buf)
+      buf += bsprintf(buf, ", ");
+    buf += bsprintf(buf, "SRv6 L3 Service ");
+
+    const byte *spos = b;
+    uint slen = tlen;
+
+    /* skip fixed data */
+    ADVANCE(spos, slen, 1);
+
+    while (slen)
+    {
+      /* Process one Sub-TLV */
+      uint pstype = get_u8(spos);
+      ADVANCE(spos, slen, 1);
+      uint pslen = get_u16(spos);
+      ADVANCE(spos, slen, 2);
+
+      if (0) {
+      } else if (pstype == BGP_PREFIX_SID_SRV6_SERVICE_SID_INFORMATION) {
+        const byte *sspos = spos;
+        uint sslen = pslen;
+
+        ip6_addr ip6 = get_ip6(&spos[1]);
+        u8 flags = get_u8(&spos[1 + 16]);
+        u16 behavior = get_u16(&spos[1 + 16 + 1]);
+
+        buf += bsprintf(buf, "SID=%I6, Flags=0x%02x, Behavior=0x%04x",
+          ip6, (uint)flags, (uint)behavior);
+
+        /* skip fixed data */
+        ADVANCE(sspos, sslen, 1 + 16 + 1 + 2 + 1);
+
+        while (sslen)
+        {
+          /* Process one Sub-Sub-TLV */
+          uint psstype = get_u8(sspos);
+          ADVANCE(sspos, sslen, 1);
+          uint psslen = get_u16(sspos);
+          ADVANCE(sspos, sslen, 2);
+
+          if (0) {
+          } else if (psstype == BGP_PREFIX_SID_SRV6_SERVICE_DATA_SID_STRUCTURE) {
+            uint loc_block_len = get_u8(&sspos[0]);
+            uint loc_node_len  = get_u8(&sspos[1]);
+            uint func_len      = get_u8(&sspos[2]);
+            uint arg_len       = get_u8(&sspos[3]);
+            uint trans_len     = get_u8(&sspos[4]);
+            uint trans_off     = get_u8(&sspos[5]);
+
+            buf += bsprintf(buf, ", Structure=block:%u,node:%u,func:%u,args:%u, Transposition=off:%u,len:%u",
+              loc_block_len, loc_node_len, func_len, arg_len,
+              trans_off, trans_len);
+          }
+
+          ADVANCE(sspos, sslen, psslen);
+        }
+      }
+
+      ADVANCE(spos, slen, pslen);
+    }
+  }
+}
+
+
 static void
 bgp_export_mpls_label_stack(struct bgp_export_state *s, eattr *a)
 {
@@ -1210,6 +1764,14 @@ static const struct bgp_attr_desc bgp_attr_table[] = {
     .encode = bgp_encode_u32,
     .decode = bgp_decode_otc,
   },
+  [BA_PREFIX_SID] = {
+    .name = "prefix_sid",
+    .type = EAF_TYPE_OPAQUE,
+    .flags = BAF_OPTIONAL | BAF_TRANSITIVE,
+    .encode = bgp_encode_prefix_sid,
+    .decode = bgp_decode_prefix_sid,
+    .format = bgp_format_prefix_sid,
+  },
   [BA_RAW_MPLS_LABEL_STACK] = {
     .name = "raw_mpls_label_stack",
     .type = EAF_TYPE_INT_SET,
@@ -1943,6 +2505,16 @@ bgp_update_attrs(struct bgp_proto *p, struct bgp_channel *c, rte *e, ea_list *at
   a = bgp_find_attr(attrs0, BA_NEXT_HOP);
   bgp_update_next_hop(&s, a, &attrs);
 
+  /* PREFIX_SID attribute */
+  if (! bgp_find_attr(attrs0, BA_PREFIX_SID)) {
+    rta *ra = e->attrs;
+    if (ra->nh.sid6s)
+    {
+      ad = bgp_prefix_sid_set_srv6_l3vpn_sid(pool, NULL, ra->nh.sid6[0]);
+      bgp_set_attr_ptr(&attrs, pool, BA_PREFIX_SID, 0, ad);
+    }
+  }
+
   /* LOCAL_PREF attribute - required for IBGP, attach if missing */
   if (p->is_interior && ! bgp_find_attr(attrs0, BA_LOCAL_PREF))
     bgp_set_attr_u32(&attrs, pool, BA_LOCAL_PREF, 0, p->cf->default_local_pref);
diff --git a/proto/bgp/bgp.c b/proto/bgp/bgp.c
index eb60cb6..7225681 100644
--- a/proto/bgp/bgp.c
+++ b/proto/bgp/bgp.c
@@ -100,6 +100,7 @@
  * RFC 8212 - Default EBGP Route Propagation Behavior without Policies
  * RFC 8277 - Using BGP to Bind MPLS Labels to Address Prefixes
  * RFC 8654 - Extended Message Support for BGP
+ * RFC 8669 - Segment Routing Prefix Segment Identifier Extensions for BGP (partial)
  * RFC 8950 - Advertising IPv4 NLRI with an IPv6 Next Hop
  * RFC 8955 - Dissemination of Flow Specification Rules
  * RFC 8956 - Dissemination of Flow Specification Rules for IPv6
@@ -107,6 +108,7 @@
  * RFC 9072 - Extended Optional Parameters Length for BGP OPEN Message
  * RFC 9117 - Revised Validation Procedure for BGP Flow Specifications
  * RFC 9234 - Route Leak Prevention and Detection Using Roles
+ * RFC 9252 - BGP Overlay Services Based on Segment Routing over IPv6 (partial)
  * RFC 9494 - Long-Lived Graceful Restart for BGP
  * RFC 9687 - Send Hold Timer
  * draft-walton-bgp-hostname-capability-02
diff --git a/proto/bgp/bgp.h b/proto/bgp/bgp.h
index 96286bc..6551589 100644
--- a/proto/bgp/bgp.h
+++ b/proto/bgp/bgp.h
@@ -66,7 +66,7 @@ struct bgp_af_desc {
   u8 mpls;
   u8 no_igp;
   const char *name;
-  uint (*encode_nlri)(struct bgp_write_state *s, struct bgp_bucket *buck, byte *buf, uint size);
+  uint (*encode_nlri)(struct bgp_write_state *s, struct bgp_bucket *buck, ea_list *eattrs, byte *buf, uint size);
   void (*decode_nlri)(struct bgp_parse_state *s, byte *pos, uint len, rta *a);
   void (*update_next_hop)(struct bgp_export_state *s, eattr *nh, ea_list **to);
   uint (*encode_next_hop)(struct bgp_write_state *s, eattr *nh, byte *buf, uint size);
@@ -732,6 +732,25 @@ bgp_total_aigp_metric(rte *r)
   return metric;
 }
 
+/* TLVs for BA_PREFIX_SID */
+#define BGP_PREFIX_SID_LABEL_INDEX         1
+#define BGP_PREFIX_SID_ORIGINATOR_SRGB     3
+#define BGP_PREFIX_SID_SRV6_VPN_SID        4 /* DEPRECATED */
+#define BGP_PREFIX_SID_SRV6_L3_SERVICE     5
+#define BGP_PREFIX_SID_SRV6_L2_SERVICE     6
+
+/* Type for BGP_PREFIX_SID_SRV6_VPN_SID */
+#define BGP_PREFIX_SID_SRV6_VPN_L3     1
+#define BGP_PREFIX_SID_SRV6_VPN_L2     2
+
+/* Sub-TLVs for BGP_PREFIX_SID_SRV6_L3_SERVICE; BGP_PREFIX_SID_SRV6_L2_SERVICE */
+#define BGP_PREFIX_SID_SRV6_SERVICE_SID_INFORMATION     1
+
+/* Sub-Sub-TLVs for BGP_PREFIX_SID_SRV6_L3_SERVICE; BGP_PREFIX_SID_SRV6_L2_SERVICE */
+#define BGP_PREFIX_SID_SRV6_SERVICE_DATA_SID_STRUCTURE     1
+
+const byte * bgp_prefix_sid_get_tlv(const struct adata *ad, uint type);
+int bgp_prefix_sid_get_srv6_l3vpn_sid(struct ea_list *attrs, ip6_addr *sid);
 
 /* packets.c */
 
@@ -788,6 +807,7 @@ byte *bgp_create_end_mark_(struct bgp_channel *c, byte *buf);
 #define BA_AIGP			0x1a	/* RFC 7311 */
 #define BA_LARGE_COMMUNITY	0x20	/* RFC 8092 */
 #define BA_ONLY_TO_CUSTOMER	0x23	/* RFC 9234 */
+#define BA_PREFIX_SID           0x28	/* RFC 8669 */
 
 /* Bird's private internal BGP attributes */
 #define BA_RAW_MPLS_LABEL_STACK	0xfd	/* raw MPLS label stack transfer attribute */
diff --git a/proto/bgp/packets.c b/proto/bgp/packets.c
index 2329bb3..1118573 100644
--- a/proto/bgp/packets.c
+++ b/proto/bgp/packets.c
@@ -1161,10 +1161,10 @@ bgp_apply_next_hop(struct bgp_parse_state *s, rta *a, ip_addr gw, ip_addr ll)
     ip_addr lla = (c->cf->next_hop_prefer == NHP_LOCAL) ? ll : IPA_NONE;
     s->hostentry = rt_get_hostentry(tab, gw, lla, c->c.table);
 
-    if (!s->mpls)
-      rta_apply_hostentry(a, s->hostentry, NULL);
+    if (!s->mpls) /* !mpls && !sid6 */
+      rta_apply_hostentry(a, s->hostentry, NULL, NULL);
 
-    /* With MPLS, hostentry is applied later in bgp_apply_mpls_labels() */
+    /* With MPLS and SRv6, hostentry is applied later in bgp_apply_mpls_labels() */
   }
 }
 
@@ -1187,16 +1187,30 @@ bgp_apply_mpls_labels(struct bgp_parse_state *s, rta *a, u32 *labels, uint lnum)
 
   if (s->channel->cf->gw_mode == GW_DIRECT)
   {
-    a->nh.labels = lnum;
-    memcpy(a->nh.label, labels, 4*lnum);
+    if (bgp_prefix_sid_get_srv6_l3vpn_sid(a->eattrs, &a->nh.sid6[0])) {
+      a->nh.labels = 0;
+      a->nh.sid6s = 1;
+    } else {
+      a->nh.labels = lnum;
+      memcpy(a->nh.label, labels, 4*lnum);
+      a->nh.sid6s = 0;
+    }
   }
   else /* GW_RECURSIVE */
   {
     mpls_label_stack ms;
+    srv6_sid_stack srv6s;
+
+    if (bgp_prefix_sid_get_srv6_l3vpn_sid(a->eattrs, &srv6s.sid[0])) {
+      ms.len = 0;
+      srv6s.len = 1;
+    } else {
+      ms.len = lnum;
+      memcpy(ms.stack, labels, 4*lnum);
+      srv6s.len = 0;
+    }
 
-    ms.len = lnum;
-    memcpy(ms.stack, labels, 4*lnum);
-    rta_apply_hostentry(a, s->hostentry, &ms);
+    rta_apply_hostentry(a, s->hostentry, &ms, srv6s.len > 0 ? &srv6s : NULL);
   }
 }
 
@@ -1357,7 +1371,17 @@ bgp_update_next_hop_ip(struct bgp_export_state *s, eattr *a, ea_list **to)
 
   /* Just check if MPLS stack */
   if (s->mpls && !bgp_find_attr(*to, BA_MPLS_LABEL_STACK))
-    REJECT(NO_LABEL_STACK);
+  {
+    /* SRv6 routes carry SIDs instead of MPLS labels; insert implicit null
+     * as placeholder for the NLRI label field (RFC 9252 Section 5) */
+    if (s->route->attrs->nh.sid6s || bgp_find_attr(*to, BA_PREFIX_SID))
+    {
+      u32 implicit_null = BGP_MPLS_NULL;
+      bgp_set_attr_data(to, s->pool, BA_MPLS_LABEL_STACK, 0, &implicit_null, 4);
+    }
+    else
+      REJECT(NO_LABEL_STACK);
+  }
 }
 
 static uint
@@ -1637,7 +1661,7 @@ bgp_rte_update(struct bgp_parse_state *s, const net_addr *n, u32 path_id, rta *a
 }
 
 static void
-bgp_encode_mpls_labels(struct bgp_write_state *s, const adata *mpls, const adata *raw_mpls, byte **pos, uint *size, byte *pxlen)
+bgp_encode_mpls_labels(struct bgp_write_state *s, ea_list *eattrs, const adata *mpls, const adata *raw_mpls, byte **pos, uint *size, byte *pxlen)
 {
   struct bgp_channel *c = s->channel;
   const u32 dummy = 0;
@@ -1648,6 +1672,15 @@ bgp_encode_mpls_labels(struct bgp_write_state *s, const adata *mpls, const adata
   uint num;
   int raw = 0;
 
+  /* if TLV SRV6 L3/L2 Service exist, we need to send raw labels */
+  eattr *psid_ea = eattrs ? bgp_find_attr(eattrs, BA_PREFIX_SID) : NULL;
+  if (psid_ea)
+  {
+    if (bgp_prefix_sid_get_tlv(psid_ea->u.ptr, BGP_PREFIX_SID_SRV6_L3_SERVICE) != NULL ||
+        bgp_prefix_sid_get_tlv(psid_ea->u.ptr, BGP_PREFIX_SID_SRV6_L2_SERVICE) != NULL)
+      raw = 1;
+  }
+
   if (raw)
   {
     /* raw labels */
@@ -1699,6 +1732,23 @@ bgp_decode_mpls_labels(struct bgp_parse_state *s, byte **pos, uint *len, uint *p
   struct bgp_channel *c = s->channel;
   u32 labels[BGP_MPLS_MAX], raw_labels[BGP_MPLS_MAX], label;
   uint lnum = 0, rlnum = 0;
+  int srv6 = 0;
+
+  /* Read multiple labels (BOS-delimited) when:
+   *  - Multiple labels capability is negotiated (strict RFC 8277), or
+   *  - Config is ALWAYS (RFC 3107 compatible, reads until BOS regardless)
+   *
+   * Exception: with SRv6 transposition (RFC 9252), the NLRI label field
+   * carries raw SID bits where the BOS bit is meaningless and may not be set.
+   * In that case we read exactly one entry. */
+  if (a)
+  {
+    eattr *psid_ea = bgp_find_attr(a->eattrs, BA_PREFIX_SID);
+    if (psid_ea &&
+	(bgp_prefix_sid_get_tlv(psid_ea->u.ptr, BGP_PREFIX_SID_SRV6_L3_SERVICE) ||
+	 bgp_prefix_sid_get_tlv(psid_ea->u.ptr, BGP_PREFIX_SID_SRV6_L2_SERVICE)))
+      srv6 = 1;
+  }
 
   do {
     if (*pxlen < 24)
@@ -1718,7 +1768,7 @@ bgp_decode_mpls_labels(struct bgp_parse_state *s, byte **pos, uint *len, uint *p
     if (!s->reach_nlri_step)
       return;
   }
-  while ((c->multiple_labels || c->cf->multiple_labels >= BGP_MPLS_ML_ADVERTISE) && !(label & BGP_MPLS_BOS));
+  while ((c->multiple_labels || c->cf->multiple_labels >= BGP_MPLS_ML_ADVERTISE) && !srv6 && !(label & BGP_MPLS_BOS));
 
   /* RFC 8277 2.1: treat-as-withdraw if more labels than our advertised count */
   if (c->multiple_labels && lnum > MPLS_MAX_LABEL_STACK)
@@ -1756,7 +1806,7 @@ bgp_decode_mpls_labels(struct bgp_parse_state *s, byte **pos, uint *len, uint *p
 }
 
 static uint
-bgp_encode_nlri_ip4(struct bgp_write_state *s, struct bgp_bucket *buck, byte *buf, uint size)
+bgp_encode_nlri_ip4(struct bgp_write_state *s, struct bgp_bucket *buck, ea_list *eattrs, byte *buf, uint size)
 {
   byte *pos = buf;
 
@@ -1778,7 +1828,7 @@ bgp_encode_nlri_ip4(struct bgp_write_state *s, struct bgp_bucket *buck, byte *bu
 
     /* Encode MPLS labels */
     if (s->mpls)
-      bgp_encode_mpls_labels(s, s->mpls_labels, s->raw_mpls_labels, &pos, &size, pos - 1);
+      bgp_encode_mpls_labels(s, eattrs, s->mpls_labels, s->raw_mpls_labels, &pos, &size, pos - 1);
 
     /* Encode prefix body */
     ip4_addr a = ip4_hton(net->prefix);
@@ -1844,7 +1894,7 @@ bgp_decode_nlri_ip4(struct bgp_parse_state *s, byte *pos, uint len, rta *a)
 
 
 static uint
-bgp_encode_nlri_ip6(struct bgp_write_state *s, struct bgp_bucket *buck, byte *buf, uint size)
+bgp_encode_nlri_ip6(struct bgp_write_state *s, struct bgp_bucket *buck, ea_list *eattrs, byte *buf, uint size)
 {
   byte *pos = buf;
 
@@ -1866,7 +1916,7 @@ bgp_encode_nlri_ip6(struct bgp_write_state *s, struct bgp_bucket *buck, byte *bu
 
     /* Encode MPLS labels */
     if (s->mpls)
-      bgp_encode_mpls_labels(s, s->mpls_labels, s->raw_mpls_labels, &pos, &size, pos - 1);
+      bgp_encode_mpls_labels(s, eattrs, s->mpls_labels, s->raw_mpls_labels, &pos, &size, pos - 1);
 
     /* Encode prefix body */
     ip6_addr a = ip6_hton(net->prefix);
@@ -1931,7 +1981,7 @@ bgp_decode_nlri_ip6(struct bgp_parse_state *s, byte *pos, uint len, rta *a)
 }
 
 static uint
-bgp_encode_nlri_vpn4(struct bgp_write_state *s, struct bgp_bucket *buck, byte *buf, uint size)
+bgp_encode_nlri_vpn4(struct bgp_write_state *s, struct bgp_bucket *buck, ea_list *eattrs, byte *buf, uint size)
 {
   byte *pos = buf;
 
@@ -1953,7 +2003,7 @@ bgp_encode_nlri_vpn4(struct bgp_write_state *s, struct bgp_bucket *buck, byte *b
 
     /* Encode MPLS labels */
     if (s->mpls)
-      bgp_encode_mpls_labels(s, s->mpls_labels, s->raw_mpls_labels, &pos, &size, pos - 1);
+      bgp_encode_mpls_labels(s, eattrs, s->mpls_labels, s->raw_mpls_labels, &pos, &size, pos - 1);
 
     /* Encode route distinguisher */
     put_rd(pos, net->rd);
@@ -2031,7 +2081,7 @@ bgp_decode_nlri_vpn4(struct bgp_parse_state *s, byte *pos, uint len, rta *a)
 
 
 static uint
-bgp_encode_nlri_vpn6(struct bgp_write_state *s, struct bgp_bucket *buck, byte *buf, uint size)
+bgp_encode_nlri_vpn6(struct bgp_write_state *s, struct bgp_bucket *buck, ea_list *eattrs, byte *buf, uint size)
 {
   byte *pos = buf;
 
@@ -2053,7 +2103,7 @@ bgp_encode_nlri_vpn6(struct bgp_write_state *s, struct bgp_bucket *buck, byte *b
 
     /* Encode MPLS labels */
     if (s->mpls)
-      bgp_encode_mpls_labels(s, s->mpls_labels, s->raw_mpls_labels, &pos, &size, pos - 1);
+      bgp_encode_mpls_labels(s, eattrs, s->mpls_labels, s->raw_mpls_labels, &pos, &size, pos - 1);
 
     /* Encode route distinguisher */
     put_rd(pos, net->rd);
@@ -2131,7 +2181,7 @@ bgp_decode_nlri_vpn6(struct bgp_parse_state *s, byte *pos, uint len, rta *a)
 
 
 static uint
-bgp_encode_nlri_flow4(struct bgp_write_state *s, struct bgp_bucket *buck, byte *buf, uint size)
+bgp_encode_nlri_flow4(struct bgp_write_state *s, struct bgp_bucket *buck, ea_list *eattrs, byte *buf, uint size)
 {
   byte *pos = buf;
 
@@ -2226,7 +2276,7 @@ bgp_decode_nlri_flow4(struct bgp_parse_state *s, byte *pos, uint len, rta *a)
 
 
 static uint
-bgp_encode_nlri_flow6(struct bgp_write_state *s, struct bgp_bucket *buck, byte *buf, uint size)
+bgp_encode_nlri_flow6(struct bgp_write_state *s, struct bgp_bucket *buck, ea_list *eattrs, byte *buf, uint size)
 {
   byte *pos = buf;
 
@@ -2461,9 +2511,9 @@ bgp_get_af_desc(u32 afi)
 }
 
 static inline uint
-bgp_encode_nlri(struct bgp_write_state *s, struct bgp_bucket *buck, byte *buf, byte *end)
+bgp_encode_nlri(struct bgp_write_state *s, struct bgp_bucket *buck, ea_list *eattrs, byte *buf, byte *end)
 {
-  return s->channel->desc->encode_nlri(s, buck, buf, end - buf);
+  return s->channel->desc->encode_nlri(s, buck, eattrs, buf, end - buf);
 }
 
 static inline uint
@@ -2505,7 +2555,7 @@ bgp_create_ip_reach(struct bgp_write_state *s, struct bgp_bucket *buck, byte *bu
   put_u16(buf+0, 0);
   put_u16(buf+2, la);
 
-  lr = bgp_encode_nlri(s, buck, buf+4+la, end);
+  lr = bgp_encode_nlri(s, buck, buck->eattrs, buf+4+la, end);
 
   return buf+4+la+lr;
 }
@@ -2559,7 +2609,7 @@ bgp_create_mp_reach(struct bgp_write_state *s, struct bgp_bucket *buck, byte *bu
   *pos++ = 0;
 
   /* Encode the NLRI */
-  lr = bgp_encode_nlri(s, buck, pos, end - la);
+  lr = bgp_encode_nlri(s, buck, buck->eattrs, pos, end - la);
   pos += lr;
 
   /* End of MP_REACH_NLRI atribute, update data length */
@@ -2589,7 +2639,7 @@ bgp_create_ip_unreach(struct bgp_write_state *s, struct bgp_bucket *buck, byte *
    *	---	IPv4 Network Layer Reachability Information (unused)
    */
 
-  uint len = bgp_encode_nlri(s, buck, buf+2, end);
+  uint len = bgp_encode_nlri(s, buck, NULL, buf+2, end);
 
   put_u16(buf+0, len);
   put_u16(buf+2+len, 0);
@@ -2613,7 +2663,7 @@ bgp_create_mp_unreach(struct bgp_write_state *s, struct bgp_bucket *buck, byte *
    *	---	IPv4 Network Layer Reachability Information (unused)
    */
 
-  uint len = bgp_encode_nlri(s, buck, buf+11, end);
+  uint len = bgp_encode_nlri(s, buck, NULL, buf+11, end);
 
   put_u16(buf+0, 0);
   put_u16(buf+2, 7+len);
-- 
2.47.3

Attachment: bird1-srv6.conf
Description: bird1-srv6.conf

Attachment: bird2-srv6.conf
Description: bird2-srv6.conf

Reply via email to