From 58144c9b975e5848ff2082194f0085fc66dc85f2 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?S=C3=A9bastien=20Parisot?= <sparisot@free-mobile.fr>
Date: Mon, 9 Feb 2026 20:02:34 +0000
Subject: [PATCH 1/6] 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

