From fe3bb31bf240ab9cf6e1b85a56af7e6977a11c40 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 3/6] 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     | 147 +++++++++++++++++++++++++++++++++----
 2 files changed, 136 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..0a344b8 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,103 @@ 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)
+{
+  uint srh_len = sizeof(struct ipv6_sr_hdr) + sid_count * sizeof(struct in6_addr);
+  uint encap_len = sizeof(struct seg6_iptunnel_encap) + srh_len;
+  struct seg6_iptunnel_encap *encap = alloca(encap_len);
+
+  encap->mode = SEG6_IPTUN_MODE_ENCAP;
+
+  struct ipv6_sr_hdr *srh = encap->srh;
+  srh->nexthdr = 0;			/* kernel overwrites this */
+  srh->hdrlen = (srh_len / 8) - 1;	/* in 8-byte units */
+  srh->type = 4;			/* SRv6 */
+  srh->segments_left = sid_count - 1;
+  srh->first_segment = sid_count - 1;	/* last_entry */
+  srh->flags = 0;
+  srh->tag = htons(0);
+
+  for (int i = 0; i < sid_count; i++)
+    put_ip6(&srh->segments[i], sids[i]);
+
+  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;
+  }
+
+  struct seg6_iptunnel_encap *encap = RTA_DATA(enca[SEG6_IPTUNNEL_SRH]);
+  uint encap_len = RTA_PAYLOAD(enca[SEG6_IPTUNNEL_SRH]);
+
+  if (encap_len < sizeof(struct seg6_iptunnel_encap) + sizeof(struct ipv6_sr_hdr) + sizeof(struct in6_addr))
+  {
+    log(L_WARN "KRT: Received SEG6 encap too short (%d bytes)", encap_len);
+    return 0;
+  }
+
+  struct ipv6_sr_hdr *srh = encap->srh;
+  uint srh_len = encap_len - sizeof(struct seg6_iptunnel_encap);
+
+  /* hdrlen is total SRH length in 8-byte units, minus 1 */
+  uint total_len = (srh->hdrlen + 1) * 8;
+
+  if (total_len > srh_len)
+  {
+    log(L_WARN "KRT: Received SEG6 encap with invalid SRH length (%d)", total_len);
+    return 0;
+  }
+
+  /* first_segment is the index of the last segment, i.e. sid_count - 1 */
+  uint sid_count = srh->first_segment + 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 (sizeof(struct ipv6_sr_hdr) + sid_count * sizeof(struct in6_addr) > 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;
+  }
+
+  for (uint i = 0; i < sid_count; i++)
+    sids[i] = get_ip6(&srh->segments[i]);
+
+  return sid_count;
+}
 
 static inline void
 nl_add_attr_via(struct nlmsghdr *h, uint bufsize, ip_addr ipa)
@@ -638,7 +741,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 +765,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, nexthop_sid6(nh));
+  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 +783,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 +907,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], nexthop_sid6(rv));
+	    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 +1905,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], nexthop_sid6(&ra->nh));
+              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

