From f531d6faad4d9d93ab130a4be89e3bf100720a18 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: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

