From 5a8826a020862ced88d2e4a301978c37bc96572f Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?S=C3=A9bastien=20Parisot?= <sparisot@free-mobile.fr>
Date: Thu, 12 Feb 2026 16:09:31 +0000
Subject: [PATCH 2/6] 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    | 21 ++++++++++++++-------
 nest/rt-attr.c  | 48 ++++++++++++++++++++++++++++++++++++++++--------
 nest/rt-show.c  | 18 ++++++++++++++----
 nest/rt-table.c | 38 +++++++++++++++++++++++++++++++++++---
 7 files changed, 127 insertions(+), 23 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..0450d6b 100644
--- a/nest/route.h
+++ b/nest/route.h
@@ -434,11 +434,18 @@ 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 */
   byte labels_orig;			/* Number of labels before hostentry was applied */
   byte labels;				/* Number of all labels */
   u32 label[0];
+  /* ip6_addr sid6[0] -- after label[], access via nexthop_sid6() */
 };
 
+/* Access SRv6 SIDs stored after MPLS labels in the flexible array */
+static inline ip6_addr *nexthop_sid6(const struct nexthop *nh)
+{ return (ip6_addr *)(&nh->label[nh->labels]); }
+
 #define RNF_ONLINK		0x1	/* Gateway is onlink regardless of IP ranges */
 
 
@@ -691,10 +698,10 @@ ea_set_attr_data(ea_list **to, struct linpool *pool, uint id, uint flags, uint t
 }
 
 
-#define NEXTHOP_MAX_SIZE (sizeof(struct nexthop) + sizeof(u32)*MPLS_MAX_LABEL_STACK)
+#define NEXTHOP_MAX_SIZE (sizeof(struct nexthop) + sizeof(u32)*MPLS_MAX_LABEL_STACK + sizeof(ip6_addr)*SRV6_MAX_SID_STACK)
 
 static inline size_t nexthop_size(const struct nexthop *nh)
-{ return sizeof(struct nexthop) + sizeof(u32)*nh->labels; }
+{ return sizeof(struct nexthop) + sizeof(u32)*nh->labels + sizeof(ip6_addr)*nh->sid6s; }
 int nexthop__same(struct nexthop *x, struct nexthop *y); /* Compare multipath nexthops */
 static inline int nexthop_same(struct nexthop *x, struct nexthop *y)
 { return (x == y) || nexthop__same(x, y); }
@@ -709,8 +716,8 @@ void nexthop_insert(struct nexthop **n, struct nexthop *y);
 int nexthop_is_sorted(struct nexthop *x);
 
 void rta_init(void);
-static inline size_t rta_size(const rta *a) { return sizeof(rta) + sizeof(u32)*a->nh.labels; }
-#define RTA_MAX_SIZE (sizeof(rta) + sizeof(u32)*MPLS_MAX_LABEL_STACK)
+static inline size_t rta_size(const rta *a) { return sizeof(rta) + sizeof(u32)*a->nh.labels + sizeof(ip6_addr)*a->nh.sid6s; }
+#define RTA_MAX_SIZE (sizeof(rta) + sizeof(u32)*MPLS_MAX_LABEL_STACK + sizeof(ip6_addr)*SRV6_MAX_SID_STACK)
 rta *rta_lookup(rta *);			/* Get rta equivalent to this one, uc++ */
 static inline int rta_is_cached(rta *r) { return r->cached; }
 static inline rta *rta_clone(rta *r) { r->uc++; return r; }
@@ -724,12 +731,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..b3c5930 100644
--- a/nest/rt-attr.c
+++ b/nest/rt-attr.c
@@ -90,8 +90,8 @@ const char * rta_dest_names[RTD_MAX] = {
 
 pool *rta_pool;
 
-static slab *rta_slab_[4];
-static slab *nexthop_slab_[4];
+static slab *rta_slab_[5];
+static slab *nexthop_slab_[5];
 static slab *rte_src_slab;
 
 static struct idm src_ids;
@@ -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(nexthop_sid6(x)[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(&nexthop_sid6(x)[i], &nexthop_sid6(y)[i], sizeof(ip6_addr)) != 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(nexthop_sid6(x)[i], nexthop_sid6(y)[i]);
+    if (r)
+      return r;
+  }
+
   return ((int) x->iface->index) - ((int) y->iface->index);
 }
 
@@ -377,7 +397,8 @@ nexthop_is_sorted(struct nexthop *x)
 static inline slab *
 nexthop_slab(struct nexthop *nh)
 {
-  return nexthop_slab_[MIN(nh->labels, 3)];
+  uint slots = nh->labels + nh->sid6s * 4;
+  return nexthop_slab_[slots <= 2 ? slots : slots <= 4 ? 3 : 4];
 }
 
 static struct nexthop *
@@ -398,6 +419,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(&nexthop_sid6(n)[i], &nexthop_sid6(o)[i], sizeof(ip6_addr));
 
       *last = n;
       last = &(n->next);
@@ -1193,7 +1218,8 @@ rta_same(rta *x, rta *y)
 static inline slab *
 rta_slab(rta *a)
 {
-  return rta_slab_[a->nh.labels > 2 ? 3 : a->nh.labels];
+  uint slots = a->nh.labels + a->nh.sid6s * 4;
+  return rta_slab_[slots <= 2 ? slots : slots <= 4 ? 3 : 4];
 }
 
 static rta *
@@ -1340,6 +1366,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", nexthop_sid6(nh)[0]);
+	for (int i=1; i<nh->sid6s; i++)
+	  RDUMP(",%I6", nexthop_sid6(nh)[i]);
+	if (nh->sid6s) RDUMP(">");
 	RDUMP(" [%s]", nh->iface ? nh->iface->name : "???");
       }
   if (a->eattrs)
@@ -1396,12 +1426,14 @@ rta_init(void)
   rta_slab_[0] = sl_new(rta_pool, sizeof(rta));
   rta_slab_[1] = sl_new(rta_pool, sizeof(rta) + sizeof(u32));
   rta_slab_[2] = sl_new(rta_pool, sizeof(rta) + sizeof(u32)*2);
-  rta_slab_[3] = sl_new(rta_pool, sizeof(rta) + sizeof(u32)*MPLS_MAX_LABEL_STACK);
+  rta_slab_[3] = sl_new(rta_pool, sizeof(rta) + sizeof(ip6_addr));
+  rta_slab_[4] = sl_new(rta_pool, RTA_MAX_SIZE);
 
   nexthop_slab_[0] = sl_new(rta_pool, sizeof(struct nexthop));
   nexthop_slab_[1] = sl_new(rta_pool, sizeof(struct nexthop) + sizeof(u32));
   nexthop_slab_[2] = sl_new(rta_pool, sizeof(struct nexthop) + sizeof(u32)*2);
-  nexthop_slab_[3] = sl_new(rta_pool, sizeof(struct nexthop) + sizeof(u32)*MPLS_MAX_LABEL_STACK);
+  nexthop_slab_[3] = sl_new(rta_pool, sizeof(struct nexthop) + sizeof(ip6_addr));
+  nexthop_slab_[4] = sl_new(rta_pool, NEXTHOP_MAX_SIZE);
 
   rta_alloc_hash();
   rte_src_init();
diff --git a/nest/rt-show.c b/nest/rt-show.c
index 0ecc863..4c810e1 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", nexthop_sid6(nh)[0]);
+	  for (int i = 1; i < nh->sid6s; ++i)
+	    sid6 += bsprintf(sid6, ",%I6", nexthop_sid6(nh)[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..f720265 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(nexthop_sid6(&a->nh), 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(nexthop_sid6(nhp), nexthop_sid6(nh), nh->sid6s * sizeof(ip6_addr)); /* First the hostentry SIDs */
+        memcpy(&(nexthop_sid6(nhp)[nh->sid6s]), sid6->sid, sid6->len * sizeof(ip6_addr)); /* 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(nexthop_sid6(nhp), nexthop_sid6(nh), nhp->sid6s * sizeof(ip6_addr));
+    }
+
     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, &nexthop_sid6(&a->nh)[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

