SRv6 in the Linux kernel: seg6 and seg6local
S2·E1The route is there and nothing moves · Dell lab, Round Rock, 21:40 on night one of the bake-off week
Builds on: SIDs as instructions: LOC:FUNCT:ARG and the behavior zoo, Compression: RFC 9800, NEXT-C-SID, and what "uSID" really means
Before you read: what do you already know?
3 quick questions. Wrong answers are fine and expected; trying first makes the lesson stick.
After this lesson you can
- Verify that a Linux host can process SRv6 at all by checking three sysctls and three kernel build options.
- Program an SRv6 headend with `ip -6 route ... encap seg6` and an endpoint with `encap seg6local` for a stated behavior.
- Distinguish the behaviors the kernel implements from those the uAPI merely declares and those the man page documents.
- Diagnose the two conditions that let a correctly installed seg6local route drop every packet: `seg6_enabled` on the ingress interface and the behavior's Segments-Left precondition.
- Predict which documentation and configuration traps make `ip -6 route add` fail outright, and name the extack string each one returns.
Episode 1 — The route is there and nothing moves
Three lab days, one answer due Friday, and the network lead has stopped talking. On the shared screen is a containerlab fabric standing in for the thirty-two-node imaging cluster he must approve: an endpoint node and a seg6local route for an End.DT46 service SID copied straight from the RFC. tcpdump shows the packets arriving with the right destination address. Nothing comes out the other side. The Dell SE, coffee cold since eight, is drafting the mail blaming the DPU. The network lead writes one line in his notebook: show me the counter.
Two things decide this, and neither is in the route table. net.ipv6.conf.<iface>.seg6_enabled decides whether SR-enabled packets addressed to this host are accepted at all, and its default is 0, so a stock host drops every one of them and logs nothing.[1] End.DT46 additionally needs a real VRF device bound to the table and net.vrf.strict_mode set to 1, or the route is refused outright.[5] Containers share the host kernel, so both checks belong on the lab machine, not inside the node.[10]
This path exists because the kernel has carried seg6local behaviors since 4.14, and it is how a customer watches an SRv6 data plane without buying anything.[5] The default of 0 is deliberate — an SRH is an instruction to forward on someone else’s behalf, and RFC 8754 treats the SR domain as a trust boundary, so a host stays inert until an operator names the interfaces where SRv6 is expected.[7]
Start where the packets are counted, not where the route is printed. Segment 1 is both.
1Nothing runs until three sysctls and three build options agree
Linux has carried seg6local endpoint behaviors since 4.14 — the man page scopes the whole action list as “Linux 4.14+ only” — and it is the reference you will reach for whenever a customer wants to see the data plane behave without buying anything.[5] It is also the implementation that fails most silently, because the two things that must be true before a packet is processed are not visible in the route table.
The first is net.ipv6.conf.<iface>.seg6_enabled. The kernel documentation describes it as a per-interface knob — “Accept or drop SR-enabled IPv6 packets on this interface. Relevant packets are those with SRH present and DA = local” — and its default is 0.[1] A stock host therefore drops every SRv6 packet addressed to it and logs nothing you will find. Two neighbours live next to it: net.ipv6.conf.<iface>.seg6_require_hmac (-1 ignore the HMAC field, 0 accept without and validate when present, 1 drop packets without one; default 0) and the global net.ipv6.seg6_flowlabel, which decides whether the outer flow label on T.Encaps is zeroed, copied from the inner packet, or computed (default 0).[1] Those three are the entire SRv6 sysctl surface; the general IPv6 sysctl documentation adds nothing else, because everything else about SRv6 is per-route lightweight-tunnel state rather than a knob.[9][1]
The practical enable line for a lab node sets all, default and the specific data interface together:
sysctl -w net.ipv6.conf.all.seg6_enabled=1 \
net.ipv6.conf.default.seg6_enabled=1 \
net.ipv6.conf.eth1.seg6_enabled=1Whether all alone would have been sufficient is [UNVERIFIED]: the kernel document describes the knob only as per-interface and says nothing about propagation from all.[1] Set both until you have tested it on the kernel in front of you — and that test is step 2 of this lesson’s lab.
The second precondition is the build. CONFIG_IPV6_SEG6_LWTUNNEL is the one that matters: “Support for encapsulation of packets within an outer IPv6 header and a Segment Routing Header using the lightweight tunnels mechanism. Also enable support for advanced local processing of SRv6 packets based on their active segment.”[2] CONFIG_IPV6_SEG6_HMAC adds the HMAC TLV, and CONFIG_IPV6_SEG6_BPF (def_bool y) adds End.BPF.[2] Separately, the VRF-backed decapsulation behaviors need CONFIG_NET_L3_MASTER_DEV: without it End.DT4 and End.DT46 have no .input handler compiled in at all, and End.DT6 falls back to plain table mode.[3]
One containerlab-specific consequence: containers share the host kernel, so every one of these checks is a host-level check. Enabling seg6_enabled inside a node’s netns configures that node’s interfaces, but the kernel options behind it are whatever the lab machine booted with.[10]
The SID is programmed and nothing forwards
2`encap seg6`: the headend
The headend is an ordinary route with a lightweight tunnel attached:
ip -6 route add PREFIX encap seg6 mode [encap|encap.red|inline|l2encap|l2encap.red] \
[tunsrc ADDRESS] segs SEGMENTS [hmac KEYID] [lookup TABLEID] dev DEV[5] The modes map one-to-one onto RFC 8986 headend behaviors. mode inline directly inserts a Segment Routing Header after the IPv6 header. mode encap encapsulates the packet in an outer IPv6 header with an SRH — that is H.Encaps. mode encap.red does the same “applying the reduced segment list. When there is only one segment and the HMAC is not present, the SRH is omitted” — H.Encaps.Red, which is why a single-segment policy costs 40 bytes and no SRH at all.[5][6]
tunsrc ADDRESS sets the outer source address and “is not used with inline mode”; if omitted, the source comes from the per-netns ip sr tunsrc set ADDRESS value or is resolved dynamically, and an explicit tunsrc takes priority.[5] lookup TABLEID selects the table used for the post-encap route lookup — the lookup of the first SID — and table id 0 is not valid.[5]
The part that catches people is segs. You write it in SR policy order: segs A,B,C means the packet visits A, then B, then C. The kernel reverses that list into the SRH, where RFC 8754 stores the segment list starting from the last segment, so Segment List[0] on the wire holds C.[5][7] If you build a policy by reading a tcpdump and typing the segments back in the order they appear, you will build the reverse path. The canonical example from the man page is worth typing once from memory:
ip -6 route add 2001:db8:1::/64 encap seg6 mode encap \
segs 2001:db8:42::1,2001:db8:ffff::2 dev eth0[5]3`encap seg6local`: the endpoint
The endpoint side uses the same route command with a different lightweight tunnel:
ip -6 route add PREFIX encap seg6local action SEG6_ACTION [PARAMS] [count] dev DEV[5] Add count to every behavior you program. It enables three counters — “1) packets correctly processed; 2) bytes correctly processed; 3) packets that cause a processing error (i.e., missing SID List, wrong SID List, etc)” — read with the -s flag on the show command.[5] In the kernel they are SEG6_LOCAL_CNT_PACKETS, SEG6_LOCAL_CNT_BYTES and SEG6_LOCAL_CNT_ERRORS, and the error counter is defined as packets that “have NOT been properly processed by an SRv6 Behavior instance”.[3] A rising error counter is the difference between “my SID is wrong” and “my SID is right and the packet is wrong”, and it costs one word to enable.
The behaviors the kernel actually implements are the entries in seg6_action_table[]: End, End.X, End.T, End.DX2, End.DX6, End.DX4, End.DT4, End.DT6, End.DT46, End.B6, End.B6.Encaps and End.BPF.[3] Each carries required attributes: End.X requires a next hop (nh6), End.T requires a table, End.DX2 requires an output interface, End.DX6 and End.DX4 require a next hop, End.DT4 and End.DT46 require a vrftable, and the two End.B6 forms require an SRH.[3]
End.DT6 is the odd one. With CONFIG_NET_L3_MASTER_DEV it has no required attributes and accepts either table or vrftable; without it, table is required. Configure it with neither and the kernel returns the extack string table or vrftable must be specified.[3]
Finally, the Segments-Left preconditions decide which packets a behavior will even look at. End, End.X, End.B6 and End.B6.Encaps “only accept packets with a non-zero Segments Left value. Other matching packets are dropped.” End.DX6, End.DT6, End.DT4 and End.DT46 “only accept packets with either a zero Segments Left value or no SRH at all.”[5] That is RFC 8986’s division between transit endpoints and service endpoints, enforced in code: a service SID that receives a packet with segments still left will silently increment the error counter.[6]
End.DT46
IANA codepoint 20Linux seg6local: supportedOne SID per VRF that decapsulates and looks the inner packet up in either the IPv4 or the IPv6 table. The per-VRF dual-stack service SID.
Defined in RFC 8986 §4.8.
`action End.DT46 vrftable 100 dev vrf100` — needs a real VRF device and `net.vrf.strict_mode=1`.
`sid vpn per-vrf export auto` (one SID for both AFs) or `sid export ... behavior dt46`: "When both address-families specify behavior dt46, the SID Manager allocates a single SID". staticd: `behavior uDT46 vrf VRF`.
Not implemented — default VRF only.
Phase 1: "Support END.DT46". SAI: SAI_MY_SID_ENTRY_ENDPOINT_TYPE_DT46.
Not applicable.
FAE angle: customers conflate End.DT46 with "the VPN label". Reframe it: SR-MPLS carries a 20-bit label, SRv6 carries a 128-bit address that IS the instruction. When an SRv6 L3VPN will not forward, check first that the egress PE's service SID is routable from the ingress PE — `show bgp nexthop detail`.
4Four traps, in the order they bite
Trap 1: the uAPI over-promises. include/uapi/linux/seg6_local.h declares sixteen action numbers, including End.BM (11), End.S (12), End.AS (13) and End.AM (14).[4] None of those four has an entry in seg6_action_table[], so configuring them fails; the numbers exist only as reserved codepoints.[3] Someone reading the header — or an LLM that read the header — will tell a customer that Linux does SR-MPLS stitching with End.BM. It does not.
Trap 2: the man page under-promises. ip-route(8) documents End, End.X, End.DX6, End.DT6, End.DT4, End.DT46, End.B6 and End.B6.Encaps, and omits End.T, End.DX4 and End.DX2 even though the kernel implements all three.[5][3] So documentation is wrong in both directions and the action table is the only authority. When a customer says “Linux can’t do that”, ask which document they read.
Trap 3: VRF strict mode. End.DT4, End.DT46 and End.DT6 vrftable require the VRF device to exist and the table to be configured with “the VRF strict mode turned on (net.vrf.strict_mode=1)”.[5] This is a one-line fix that presents as a total forwarding failure, and it is not mentioned anywhere near the route command that fails.
Trap 4: flavors are per-behavior. Of the three RFC 8986 flavors, Linux implements only PSP — SEG6_LOCAL_FLV8986_SUPP_OPS is SEG6_F_LOCAL_FLV_PSP, and the man page states plainly that usp and usd are “not yet supported in kernel”.[3][5] Worse, the support mask differs per behavior: SEG6_LOCAL_END_FLV_SUPP_OPS is NEXT_CSID | PSP while SEG6_LOCAL_END_X_FLV_SUPP_OPS is NEXT_CSID alone. So End flavors psp works and End.X flavors psp returns Unsupported Flavor operation(s).[3] A customer replicating a vendor uA-plus-PSP configuration hits that string and concludes the kernel is broken.
The compression story is better than the flavor story. next-csid is implemented, and the defaults are SEG6_LOCAL_LCBLOCK_DBITS 32 and SEG6_LOCAL_LCNODE_FN_DBITS 16 — exactly RFC 9800’s mandatory-to-implement F3216 profile, so a plain flavors next-csid gives you a uN with no arithmetic.[3][8] Overrides are validated in seg6_chk_next_csid_cfg(): each length must be greater than zero, at most 120, byte-aligned, and their sum must not exceed 128.[3][5] REPLACE-C-SID is not implemented at all — NEXT_CSID is the only compression flavor operation in the uAPI.[4]
The four commands worth keeping in muscle memory, all verbatim from the man page:
ip -6 route add 2001:db8:1::/64 encap seg6local action End.DT46 vrftable 100 dev vrf100
ip -6 route add 2001:db8:1::/64 encap seg6local action End flavors psp dev eth0
ip -6 route add 2001:db8:1::/64 encap seg6local action End flavors next-csid dev eth0
ip -6 route add 2001:db8:1::/64 encap seg6local action End flavors next-csid lblen 48 nflen 16 dev eth0[5]Goal: on a fresh containerlab host, make fcbb:bbbb:1:fe00::/64 an End.DT46 service SID that decapsulates into VRF table 100, and prove it with counters.
Step 1 — is the kernel even capable?
zgrep -E 'CONFIG_IPV6_SEG6|CONFIG_NET_L3_MASTER_DEV' /boot/config-$(uname -r)Expect CONFIG_IPV6_SEG6_LWTUNNEL=y and CONFIG_NET_L3_MASTER_DEV=y. If NET_L3_MASTER_DEV is missing, stop: End.DT46 has no handler compiled in and nothing you type afterwards can work.[3][2]
Step 2 — enable reception, on all and on the interface.
sysctl -w net.ipv6.conf.all.seg6_enabled=1
sysctl -w net.ipv6.conf.eth1.seg6_enabled=1
sysctl net.ipv6.conf.all.seg6_enabled net.ipv6.conf.eth1.seg6_enabledBoth must read 1. Rollback for each line is the same command with =0.[1]
Step 3 — VRF first, behavior second.
sysctl -w net.vrf.strict_mode=1
ip link add vrf100 type vrf table 100
ip link set vrf100 upStrict mode before the device, device before the route. End.DT4, End.DT46 and End.DT6 vrftable all require both.[5]
Step 4 — program the SID with counters on.
ip -6 route add fcbb:bbbb:1:fe00::/64 encap seg6local action End.DT46 vrftable 100 count dev vrf100
ip -6 route show fcbb:bbbb:1:fe00::/64Step 5 — drive traffic from the headend node and read the three numbers.
# on the source node
ip -6 route add 2001:db8:100::/64 encap seg6 mode encap segs fcbb:bbbb:1:fe00:: dev eth1
# on the endpoint
ip -s -6 route show fcbb:bbbb:1:fe00::/64Read the counters as SEG6_LOCAL_CNT_PACKETS, SEG6_LOCAL_CNT_BYTES, SEG6_LOCAL_CNT_ERRORS. Packets rising with errors flat is success. Errors rising is the behavior rejecting what arrives — most often a non-zero Segments Left on a service SID. Both flat means nothing arrived: go back to step 2.[5][3]
Same host, now build an End.X cross-connect toward fe80::2 on eth2 with the uA compression flavor, and a uN on the locator itself.
- Which sysctl must be 1 on
eth2specifically, and what is its default?____ - Does End.X need
net.vrf.strict_mode? Why or why not?____ - Program uN on the locator:
ip -6 route add fcbb:bbbb:1::/48 encap seg6local action ____ flavors ____ count dev eth1 - Program uA toward the neighbour:
ip -6 route add fcbb:bbbb:1:e000::/64 encap seg6local action ____ flavors ____ nh6 fe80::2 count dev eth2 - What happens if you add
pspto the flavor list in step 4, and what exact string comes back?____ - Which two lengths are implied by
flavors next-csidwith nolblenornflen, and which RFC 9800 profile is that?____ - Which counter do you expect to rise on the uA route when the peer’s link-local address is wrong?
____
A customer reports: “our SRv6 L3VPN endpoint on Linux forwards nothing. The route is there. tcpdump on the endpoint shows the packets arriving with the right destination address.” They send you the endpoint’s route:
ip -6 route add fcbb:bbbb:9:fe00::/64 encap seg6local action End.DT46 vrftable 200 dev eth1Produce, without access to their machine: (a) the three most likely causes ranked, each with the single command that confirms or eliminates it; (b) the one change to the route command above that would have made the diagnosis possible from their side; (c) the exact rollback line for each mutating fix you propose. Acceptance: every cause you list must be checkable with one command and must be grounded in the kernel source, the man page or the sysctl documentation — no guesses about their fabric.
Six lines and a rollback
The audit runs before the fabric does: kernel options, seg6_enabled on each data interface, a real VRF device, strict mode, count on the route, and the error counter read back.[2][1][5] Packets rising with errors flat is success; errors rising is the behavior rejecting what arrives; both flat means nothing ever got in.[3]
What you say to their network lead: the route was never the problem — add count and the host tells you which of the two preconditions it is, before anyone opens a case against the hardware. He writes it down. Behind him the night-shift operator sticks a fresh label on the endpoint’s lid, then asks why the two routers upstream report zero prefixes protected on a fabric where every node has the fast-reroute command configured.
Lab
Goal: answer “can I run SRv6 endpoints on the DPU Arm?” with a table instead of an opinion. Mutating only in sysctls; no firmware, no mode switching.
- Pre-flight inventory on each of the three kernels — the containerlab host, the Dell x86 host with DOCA installed, and the BlueField-3 Arm OS:
uname -r,uname -m,cat /etc/os-release, and the current value ofnet.ipv6.conf.all.seg6_enabledandnet.vrf.strict_mode. Write these down before changing anything; they are your rollback targets. - Run steps 1 and 2 of the no-hardware lab unchanged on all three. Record kernel version, whether
CONFIG_IPV6_SEG6_LWTUNNELis set, whetherCONFIG_IPV6_SEG6_HMACis set, and whetherCONFIG_NET_L3_MASTER_DEVis set. On the BlueField Arm the config may only be available as/proc/config.gz.[2][3] - On each kernel that has
NET_L3_MASTER_DEV, run the End.DT46 step. Rollback per line:ip -6 route del ...,ip link del vrf100,sysctl -w net.vrf.strict_mode=0,sysctl -w net.ipv6.conf.all.seg6_enabled=0.[5][1] - On each kernel, run the
End.X flavors pspprobe from step 5c. It mutates nothing when it fails, and the presence of the exact stringUnsupported Flavor operation(s)tells you the flavor masks are the modern ones.[3] - Produce the table: rows = the three kernels, columns = kernel version,
SEG6_LWTUNNEL,SEG6_HMAC,NET_L3_MASTER_DEV,End.DT46accepted,next-csidaccepted. That table is the direct answer to whether the DPU Arm can host SRv6 endpoints, and it is the artefact to attach to a customer question rather than a sentence. - Restore. Re-read every value you recorded in step 0 and confirm each host is back to it. A left-behind
seg6_enabled=1on a lab host is a small thing; a left-behindnet.vrf.strict_mode=1changes how unrelated VRF configuration behaves later.
Deliverable: a one-file audit script a customer can run before they blame the fabric. Containers share the host kernel, so run this on the containerlab host itself.[10]
- Capability.
zgrep -E 'CONFIG_IPV6_SEG6|CONFIG_NET_L3_MASTER_DEV' /boot/config-$(uname -r). Expected:CONFIG_IPV6_SEG6_LWTUNNEL=y,CONFIG_IPV6_SEG6_HMACpresent or not,CONFIG_NET_L3_MASTER_DEV=y. If not: the config may be at/proc/config.gzinstead (zcat /proc/config.gz | grep ...); ifNET_L3_MASTER_DEVis absent, record it and skip step 3.[2][3] - Reception. Print
seg6_enabledforall,defaultand each data interface:for i in all default $(ls /proc/sys/net/ipv6/conf | grep -v -E 'all|default|lo'); do printf '%s=%s\n' "$i" "$(cat /proc/sys/net/ipv6/conf/$i/seg6_enabled)"; done. Expected: all zeros on an untouched host. Then setallto 1 only, send an SRv6 packet to a per-interface-zero interface, and record whether it is processed — this settles the open question in Segment 1 for your kernel. Rollback:sysctl -w net.ipv6.conf.all.seg6_enabled=0.[1] - A real behavior.
sysctl -w net.vrf.strict_mode=1;ip link add vrf100 type vrf table 100 && ip link set vrf100 up;ip -6 route add fcbb:bbbb:1:fe00::/64 encap seg6local action End.DT46 vrftable 100 count dev vrf100. Expected: silence (success). Rollback, in order:ip -6 route del fcbb:bbbb:1:fe00::/64;ip link del vrf100;sysctl -w net.vrf.strict_mode=0.[5] - Counters. Drive traffic from a second node with
encap seg6 mode encap segs fcbb:bbbb:1:fe00::, thenip -s -6 route show fcbb:bbbb:1:fe00::/64. Expected: three numbers; map them toSEG6_LOCAL_CNT_PACKETS,SEG6_LOCAL_CNT_BYTES,SEG6_LOCAL_CNT_ERRORSin your notes.[5][3] - Break it on purpose and capture the error strings. (a)
sysctl -w net.vrf.strict_mode=0and re-add the route. (b)ip link del vrf100and re-add the route. (c)ip -6 route add fcbb:bbbb:1:e000::/64 encap seg6local action End.X flavors psp nh6 fe80::2 dev eth1. (d)ip -6 route add fcbb:bbbb:1:fe01::/64 encap seg6local action End.DT6 dev eth1with no table and no vrftable. Expected strings, respectively: a strict-mode rejection, a missing-device rejection,Unsupported Flavor operation(s), andtable or vrftable must be specified. Record each verbatim — these strings are what a customer will paste at you.[3][5] - Write the checklist. Six lines: kernel options,
seg6_enabledper interface, VRF present, strict mode,countenabled, error counter reading. Each line gets a command and a pass condition.
Retrieval check
10 questions from memory. Answer before looking anything up; misses become flashcards.
Explain it to a Dell SE
Explain to a Dell platform engineer, in five sentences, what has to be true about a Linux host before it can act as an SRv6 endpoint, and why a route that looks correct in `ip -6 route show` can still drop every packet.
Sources
Facts in this lesson were checked against Linux kernel master (net/ipv6/seg6_local.c, Kconfig, seg6-sysctl.rst re-fetched 2026-09-07), iproute2 main ip-route.8.in, RFC 8986, RFC 9800. Dates are when each page was fetched.
- Linux kernel doc: Seg6 Sysfs variables · fetched 2026-09-07
- Linux kernel: net/ipv6/Kconfig (master) · fetched 2026-09-07
- Linux kernel source: net/ipv6/seg6_local.c (master) · fetched 2026-09-07
- Linux kernel uAPI: include/uapi/linux/seg6_local.h (master) · fetched 2026-09-07
- iproute2 man source: ip-route.8.in (main) · fetched 2026-09-07
- RFC 8986 - SRv6 Network Programming · fetched 2026-09-07
- RFC 8754 - IPv6 Segment Routing Header (SRH) · fetched 2026-09-07
- RFC 9800 - Compressed SRv6 Segment List Encoding · fetched 2026-09-07
- Linux kernel doc: IP sysctl · fetched 2026-09-07
- Containerlab - Dell Enterprise SONiC (dell_sonic kind) · fetched 2026-09-07
The same idea elsewhere
Other lessons that cover this ground, sometimes from another course's angle.