Hello BIRD team,

Is there interest for SRv6 support in BIRD?

We have an internal fork with SRv6 L3VPN support that we have been using for 
several years in our network, we can upstream the patches. It is not a 100% 
complete support (at least the SID allocator is missing: we use static routes 
to bind the routes to a specific SID), but it is enough to act as a PE, there 
is BGP support and it installs remote SRv6 routes in the Linux kernel SRv6 
using netlink.

Please let me know what you think.

If anyone would like to have a look, I attach the patches (for master branch) 
to implement SRv6 SID stack support in the nest and static protocol. Any 
comments are welcome.

Example:
    protocol static {
            vpn4 { table vpntab4; };
            route 10:42 10.0.0.0/24 via "eth0" srv6 fc::42;
    }

    bird> show route table vpntab4 
    Table vpntab4:
    10:42 10.0.0.0/24    unicast [static1 14:17:42.184] * (200)
        dev eth0 srv6 <fc::42>

I still have some clean-up work to do before I can share the rest of the 
patches (mainly netlink and bgp).

Thanks.
--
Sébastien
From bc824ab6094ef391ce3f3dc284afb4a781e1eeed Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?S=C3=A9bastien=20Parisot?= <spari...@iliad-free.fr>
Date: Fri, 5 Sep 2025 10:55:38 +0200
Subject: [PATCH 2/2] Static: added support for SRv6 SID stack

---
 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 98a480f7..d11cc9e3 100644
--- a/doc/bird.sgml
+++ b/doc/bird.sgml
@@ -6440,7 +6440,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 f840947b..1f0d1f6a 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 ee8ecd5f..6cd2a36f 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, NULL);
+    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 edd2dc22..d7dd80a5 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.39.5

From 9653352361574e9972d7812dc9fbc3173cd2ffdd Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?S=C3=A9bastien=20Parisot?= <spari...@iliad-free.fr>
Date: Fri, 5 Sep 2025 10:46:17 +0200
Subject: [PATCH 1/2] Nest: added support for SRv6 SID stack

---
 conf/confbase.Y       |  9 +++++++++
 lib/ip.h              |  7 +++++++
 nest/mpls.c           |  2 +-
 nest/route.h          |  9 ++++++---
 nest/rt-attr.c        | 18 ++++++++++++++++--
 nest/rt-show.c        | 18 ++++++++++++++----
 nest/rt-table.c       | 38 +++++++++++++++++++++++++++++++++++---
 proto/bgp/packets.c   |  4 ++--
 proto/static/static.c |  2 +-
 9 files changed, 91 insertions(+), 16 deletions(-)

diff --git a/conf/confbase.Y b/conf/confbase.Y
index 27c422ea..096f00d7 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
@@ -455,6 +457,13 @@ opttext:
  | /* empty */ { $$ = NULL; }
  ;
 
+srv6_sid_stack: IP6
+{
+  $$ = cfg_allocz(sizeof(srv6_sid_stack));
+  $$->len = 1;
+  $$->sid[0] = ipa_from_ip6($1);
+};
+
 time:
    text {
      $$ = tm_parse_time($1);
diff --git a/lib/ip.h b/lib/ip.h
index bae05261..c99655a3 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 SRV6_MAX_SID_STACK * IP6_MAX_TEXT_LENGTH + 16
+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 9cdcd572..20aa09e5 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 5a9e7fa1..3addc174 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 e10e1ecb..ea554153 100644
--- a/nest/rt-attr.c
+++ b/nest/rt-attr.c
@@ -190,13 +190,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 +222,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;
@@ -398,6 +404,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 +1350,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 0ecc8637..882c7c33 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 ed364d35..cdcebd57 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);
diff --git a/proto/bgp/packets.c b/proto/bgp/packets.c
index 4581e1f9..b380ec5e 100644
--- a/proto/bgp/packets.c
+++ b/proto/bgp/packets.c
@@ -1119,7 +1119,7 @@ bgp_apply_next_hop(struct bgp_parse_state *s, rta *a, ip_addr gw, ip_addr ll)
     s->hostentry = rt_get_hostentry(tab, gw, lla, c->c.table);
 
     if (!s->mpls)
-      rta_apply_hostentry(a, s->hostentry, NULL);
+      rta_apply_hostentry(a, s->hostentry, NULL, NULL);
 
     /* With MPLS, hostentry is applied later in bgp_apply_mpls_labels() */
   }
@@ -1153,7 +1153,7 @@ bgp_apply_mpls_labels(struct bgp_parse_state *s, rta *a, u32 *labels, uint lnum)
 
     ms.len = lnum;
     memcpy(ms.stack, labels, 4*lnum);
-    rta_apply_hostentry(a, s->hostentry, &ms);
+    rta_apply_hostentry(a, s->hostentry, &ms, NULL);
   }
 }
 
diff --git a/proto/static/static.c b/proto/static/static.c
index 65fdb701..ee8ecd5f 100644
--- a/proto/static/static.c
+++ b/proto/static/static.c
@@ -96,7 +96,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, NULL);
   }
 
   if (r->net->type == NET_ASPA)
-- 
2.39.5

Reply via email to