SRv6 with zebra-rs (eBPF)¶
zebra-rs is a BGP, OSPF, and IS-IS routing stack written from scratch in Rust, with SRv6, SR-MPLS, L3VPN, and EVPN extensions. Its eBPF data plane is cradle-rs — an XDP/TC forwarding engine built on aya (pure Rust, no clang/libbpf) that zebra-rs launches and programs directly from its routing state.
The combination is an open-source SRv6 router: every route the protocols compute — IS-IS SRv6 locators, BGP L3VPN service SIDs, EVPN MAC routes — is installed into eBPF maps in addition to the Linux kernel FIB. Several SRv6 behaviors that the mainline kernel cannot forward at all (EVPN L2 decap, REPLACE-C-SID, egress protection, P2MP replication) run only on the eBPF path.
Versions
Facts on this page reflect zebra-rs 26.7.7 with the bundled cradle-rs 0.9.8 engine, BDD-tested on Linux 6.8. Both projects are AGPL-licensed: zebra-rs · cradle-rs.
Why zebra-rs + cradle-rs?¶
Cilium brought SRv6 to eBPF for Kubernetes, but its BGP control plane only advertises routes — it never installs learned routes into the data path. zebra-rs approaches it from the routing side: a full multi-protocol routing stack (IS-IS, OSPFv3, BGP with VPNv4/VPNv6/EVPN/SR Policy address families) is the source of truth, and the eBPF engine is simply its FIB.
What makes it distinctive:
- Learned routes program eBPF. IS-IS SRv6 locators, BGP L3VPN (VPNv4/VPNv6 with SRv6 SIDs), and BGP EVPN over SRv6 all land in the eBPF FIB through a gRPC tee — the direction Cilium's BGP control plane doesn't support.
- The widest open-source behavior set. The full RFC 8986 catalog including End.T, End.DX2/DX2V, End.DT2U/DT2M, End.B6.Encaps, plus both RFC 9800 compression flavors (NEXT-C-SID and REPLACE-C-SID), the PSP/USP/USD flavors, End.M egress protection, and RFC 9524 End.Replicate.
- A reverse channel. The data plane's MAC learning streams back up (
WatchFdb), driving EVPN Type-2 origination and withdrawal — the eBPF data plane is a control-plane participant, not just a sink. - Rust end to end. Control plane, user-space loader, and the eBPF programs themselves are Rust (aya); no clang or libbpf in the build.
Supported SRv6 Behaviors¶
All rows marked supported are BDD-proven in the projects' test suites (feature names like cradle_srv6, cradle_srv6_usid, isis_srv6_base, l3vpn_bgp_v6 run the actual daemons in network namespaces and assert forwarding).
Headend¶
| Behavior | Notes | Status |
|---|---|---|
H.Encaps | Multi-SID SRH imposition (TC stage) | |
H.Encaps.Red | The default — single SID means no SRH on the wire | |
H.Encaps.L2 / L2.Red | MAC-in-SRv6 (next-header 143) for EVPN | |
H.Insert | TI-LFA repair imposition (IPv6) |
Endpoint¶
| Behavior | Notes | Status |
|---|---|---|
End, End.X, End.T | SRH walk, adjacency cross-connect, table-scoped lookup | |
End.DT4 / DT6 / DT46 | Per-VRF decap; End.DT46 is the BGP L3VPN service SID | |
End.DX4 / DX6 | Decap + cross-connect to the CE adjacency | |
End.DX2 / DX2V | EVPN VPWS E-Line; DX2V demuxes the inner 802.1Q VID | |
End.DT2U / DT2M | EVPN unicast bridge / BUM flood (eBPF only) | |
End.B6.Encaps / .Red | Binding SID: End walk + policy push | |
End.M | Mirror SID egress protection (draft-ietf-rtgwg-srv6-egress-protection) | |
End.Replicate | RFC 9524 SR P2MP replication segment | |
End.BM, End.S, End.AN/AS/AD/AM | Service programming out of scope |
Compression (RFC 9800) and Flavors¶
| Feature | Notes | Status |
|---|---|---|
NEXT-C-SID (uSID): uN, uA, uA (LIB), uT, uDT4/6/46, uDX4/6 | 16-bit micro-SIDs, blocks 16/32/48 | |
REPLACE-C-SID: End, End.X | 32/16-bit C-SIDs — container walk + DA index argument. eBPF only — Linux 6.8 has no REPLACE seg6local op | |
| PSP / USP / USD | RFC 8986 §4.16 flavors on End / End.X / uN / uA | |
uB6 |
Architecture¶
One Routing Stack, Two FIBs¶
zebra-rs runs as a single tokio-based process; each protocol is an async task feeding a central RIB. Turning on the eBPF data plane is two config knobs:
system ebpf enabled true makes zebra-rs spawn and supervise the cradle engine as a managed child process (crash → respawn with backoff, full FIB state replayed into the fresh instance) and tees every route install to it over gRPC (cradle.v1, default endpoint unix:cradle/grpc). interface <name> ebpf enabled true attaches the XDP + TC programs to that interface; the port follows the interface's VRF binding.
XDP for Decap, TC for Encap¶
cradle-rs compiles one fully-inlined eBPF program per hook and splits SRv6 across them deliberately:
| Stage | SRv6 work | Why there |
|---|---|---|
XDP (cradle_xdp) | All endpoint behaviors: End SRH walk, uN shift-and-forward, End.DT*/DX* decap, End.DT2U/DT2M L2 decap, End.M, End.B6 policy push — plus MAC-in-SRv6 L2 encap | Native XDP re-runs eth_type_trans after the header adjustment, so the decapsulated inner packet enters TC with a correct skb->protocol |
TC (cradle_tc) | H.Encaps / H.Encaps.Red / H.Insert imposition, FIB forwarding, and the End.Replicate clone (bpf_clone_redirect is TC-only) | Encap egress needs an explicit L2 rewrite — bpf_redirect_neigh would build the L2 header from the stale inner skb->protocol |
A metadata channel (CradleXdpMeta) carries the VRF or bridge-domain from an XDP decap to the TC forwarding lookup, guarded by a per-instance random cookie so stale metadata can't cross a veth hop into a neighbor's TC stage.
SRv6 eBPF Maps¶
| eBPF Map | Key | Value | Purpose |
|---|---|---|---|
SRV6_LOCALSID | SID (LPM trie) | behavior, flavors, VRF, nexthop, SID structure | Local SID table, probed before the IPv6 FIB |
SRV6_ENCAP | Nexthop ID | Segment list (≤6 SIDs) + encap mode | Per-nexthop H.Encaps / H.Insert state |
SRV6_ENCAP_SRC | — | IPv6 address | Outer source address for imposition |
FIB4_VRF / FIB6_VRF | VRF + prefix (LPM) | FIB entry | Inner lookup for End.DT4/DT6/DT46 |
FDB | MAC + VLAN | port or remote End.DT2U SID | EVPN L2: remote entries carry the peer's SID |
XCONNECT / XCONNECT_VLAN | AC ifindex / (table, VID) | Remote End.DX2/DX2V SID | VPWS E-Line ingress |
REPL_SEG / REPL_SID | SID / ifindex | Branch list / per-copy target | RFC 9524 replication tree, EVPN BUM slots |
MIRROR | Context + prefix (LPM) | behavior, VRF | End.M egress-protection mirror contexts |
Control Plane¶
| Producer | What it programs | Reference |
|---|---|---|
| IS-IS | SRv6 Locator TLV 27 (with Algorithm for Flex-Algo), End/uN node SIDs, End.X/uA adjacency SIDs, End.T/uT for VRF-bound locators, SID Structure sub-sub-TLV | RFC 9352, RFC 9350 |
| OSPFv3 | SRv6-Locator-LSA (0xA02A), End/uN + SID Structure, per-adjacency End.X/uA in the E-Router-LSA | RFC 9513 |
| BGP L3VPN | VPNv4/VPNv6 with a per-VRF End.DT46 SID in the Prefix-SID attribute (SRv6 L3 Service TLV); VPN-IPv4 over an IPv6 next-hop | RFC 9252, RFC 8669, RFC 8950 |
| BGP EVPN | Type-2 → End.DT2U, Type-3 → End.DT2M + BUM replication slots, Type-5 → per-VRF End.DT46, VPWS Type-1 → End.DX2; MAC mobility sequencing fed by the WatchFdb learn/age stream | RFC 9252, RFC 8214, RFC 9136 |
| BGP SR Policy | Binding SID (SAFI 73) → End.B6.Encaps, color-based steering to a BSID | RFC 9256, RFC 9830 |
| TI-LFA | Repair segment lists as H.Insert/H.Encaps backups, per-Flex-Algo TI-LFA, PSP on the repair carrier | RFC 9855 |
| Egress protection | Mirror SID (End.M) advertised in IS-IS; live L3VPN failover on egress PE death | draft-ietf-rtgwg-srv6-egress-protection |
One deliberate design detail: the per-VRF BGP service-SID functions are allocated from a band (0x0040–0xDFFF) kept below the IS-IS End.X/uA range (0xE000+), following the IOS-XR uSID GIB/LIB convention — so BGP and IS-IS share a single locator without collisions.
eBPF-only behaviors
zebra-rs installs the classic behaviors (End, End.X, uN, uA, End.T, End.DT4/6/46, End.DX4/6, End.B6.Encaps, H.Encaps) into the kernel as seg6/seg6local routes and the eBPF engine. But End.DT2U/DT2M (EVPN L2), End.DX2/DX2V (VPWS), End.M, End.Replicate, uT, and the REPLACE-C-SID behaviors have no mainline-kernel equivalent — for those, cradle-rs is the only data plane. EVPN over SRv6 on Linux effectively requires it.
Installation¶
Prebuilt .deb packages exist for Ubuntu 22.04 / 24.04 / 26.04 (x86_64 and ARM64):
The cradle-rs engine ships as a separate Debian package that zebra-rs recommends and pulls in automatically; zebra-rs finds it at /usr/bin/cradle with no further setup. Building cradle-rs from source needs a nightly Rust toolchain with rust-src and bpf-linker — no clang or libbpf.
Configuration¶
Configuration is YANG-modeled with a candidate/running commit model, and the same tree can be expressed as Cisco-style CLI, YAML, JSON, or set commands (vtyctl apply -f zebra.yaml). The snippets below come from the project's book and BDD suite — they are validated, running configurations.
Step 1 — Define an SRv6 Locator¶
behavior usid selects RFC 9800 NEXT-C-SID (micro-SID) format; behavior replace selects REPLACE-C-SID; omitting it gives classic full-length RFC 8986 SIDs. Optional per-locator flavor {psp|usp|usd} and vrf <name> (which turns the node SID into End.T/uT) are supported.
Step 2 — Advertise it in the IGP¶
router {
isis {
net 49.0000.0000.0000.0001.00;
is-type level-2-only;
segment-routing {
srv6 {
locator LOC1;
}
}
interface enp0s6 {
circuit-type level-2-only;
ipv6 {
enabled true;
}
network-type point-to-point;
}
}
}
(OSPFv3 is analogous: router ospfv3 { segment-routing { srv6 { locator LOC1; } } }. OSPFv2 cannot carry SRv6 — it is IPv6-only on the wire.)
Step 3 — BGP L3VPN over SRv6¶
router bgp {
global {
as 65000;
}
segment-routing {
srv6 {
locator LOC1;
}
}
vrf vrf1 {
rd 65000:1;
encapsulation srv6;
neighbor 10.100.0.2 {
remote-as 65001;
}
afi-safi ipv4 {
network 192.168.5.0/24;
}
afi-safi ipv6 {
network 2001:db8:5::/64;
}
}
}
vrf vrf1 {
ipv4 {
route-target {
import 65000:1;
export 65000:1;
}
}
ipv6 {
route-target {
import 65000:1;
export 65000:1;
}
}
}
encapsulation srv6 switches the VRF from MPLS labels to a per-VRF End.DT46 SID — one SID serves both VPNv4 and VPNv6. EVPN L2 uses the same pattern (afi-safi evpn + encapsulation srv6) to advertise End.DT2U/End.DT2M SIDs.
Step 4 — Enable the eBPF Data Plane¶
Step 5 — Verify¶
zebra> show ebpf
eBPF data plane
System ebpf: enabled
FIB tee: enabled
Engine: managed (pid 168157), up 42s
Engine restarts: 1
...
zebra> show ebpf srv6 # SRv6 local SIDs and transit encaps
zebra> show ebpf ipv6 vrf vrf1
zebra> show ebpf stats # datapath counters, per-behavior
Every show ebpf command takes a trailing json for machine-readable output.
Full Traffic Walk¶
An L3VPN packet through a uSID fabric — ingress imposition in TC, everything else in XDP:
sequenceDiagram
participant CE1 as CE A
participant PE1 as PE1 cradle_tc<br/>(H.Encaps.Red)
participant P as P router cradle_xdp<br/>(uN)
participant PE2 as PE2 cradle_xdp<br/>(End.DT46)
participant CE2 as CE B
CE1->>PE1: IPv4 packet (VRF vrf1)
PE1->>PE1: FIB4_VRF hit → nexthop with SRv6 segs<br/>SRV6_ENCAP: outer DA fcbb:bbbb:3:2::/…
PE1->>P: [IPv6 DA=uSID carrier][IPv4] (no SRH)
P->>P: SRV6_LOCALSID LPM hit (uN)<br/>shift-and-forward: consume own uSID
P->>PE2: [IPv6 DA advanced][IPv4]
PE2->>PE2: SRV6_LOCALSID hit (End.DT46)<br/>XDP decap → VRF metadata → TC lookup in FIB4_VRF
PE2->>CE2: IPv4 packet (VRF vrf1) Comparison with Other Open-Source SRv6 Implementations¶
| Aspect | zebra-rs + cradle-rs | Cilium | FRRouting + kernel |
|---|---|---|---|
| Data plane | eBPF XDP + TC (aya, Rust) | eBPF | Linux kernel seg6 |
| Control plane | Integrated IS-IS / OSPFv3 / BGP | GoBGP-based (advertise-only) | FRR daemons |
| Learned routes → eBPF | n/a (kernel) | ||
| L3VPN over SRv6 | End.DT46 | End.DT4/DT6 | |
| EVPN L2 over SRv6 | DT2U/DT2M/DX2 | (no kernel behavior) | |
| uSID (NEXT-C-SID) | Roadmap | ||
| REPLACE-C-SID | |||
| TI-LFA / Flex-Algo | Partial | ||
| Kubernetes CNI | (kube-proxy replacement, Cilium-API compatible) |
Performance context: cradle-rs's DIR-24-8 FIB engine sustains ~51 ns lookups at 1M routes (measured via BPF_PROG_TEST_RUN on the full TC program); SRv6 forwarding itself is validated functionally by the BDD suites rather than benchmarked separately.
Observability & AI Integration¶
show ebpf statsexposes per-behavior datapath counters (SRv6 encap/decap, End, uSID shift, PSP/USP/USD, REPLACE, B6, End.M, replicate, drops), also as JSON.- Hubble compatibility: cradle-rs serves the Hubble Observer/Peer gRPC API from an eBPF flow ringbuf — the stock
hubbleCLI,hubble-relay, andhubble-uiwork against a cradle node. - MCP server: zebra-rs is the first routing daemon to ship a native Model Context Protocol server. AI agents inspect the same RIB, BGP, and IS-IS state an operator sees:
Further Reading¶
- SRv6 Support Matrix -- How router OSes and other open-source data planes compare
- uSID Compression -- NEXT-C-SID and REPLACE-C-SID, both of which this stack implements
- Network Programming -- The RFC 8986 behavior catalog
- TI-LFA -- Fast reroute, implemented here over SRv6 with PSP repair
- Cilium (eBPF) -- The other eBPF SRv6 implementation, from the Kubernetes side
- Linux Kernel -- The seg6 data plane zebra-rs programs in parallel
References¶
- zebra-rs on GitHub -- The routing stack: BGP, OSPF, IS-IS with SRv6/SR-MPLS/L3VPN/EVPN
- cradle-rs on GitHub -- The eBPF L2–L7 data plane and Kubernetes CNI
- zebra.rs documentation -- Install, configuration model, and per-protocol guides
- RFC 9252 - BGP Overlay Services Based on SRv6 -- L3VPN and EVPN service SID signaling implemented by zebra-rs
- RFC 9800 - SRv6 Segment List Compression -- NEXT-C-SID and REPLACE-C-SID, both supported in the eBPF data plane
- RFC 9524 - SR Replication Segments for P2MP -- End.Replicate, used for EVPN BUM over SRv6 P2MP
- aya -- The pure-Rust eBPF library the data plane is built on