VyOSXVI · Route Leaking Between VRFsLeaking
IPv6 leaking — dual-stack leaks, RDNSS sharing, prefix delegation
What you'll learn
- Configure an IPv6 leak with route6 and the vrf leaf, alongside the IPv4 leak for the same service
- Count the statements a bidirectional dual-stack leak actually needs, and verify each one in the kernel table
- Advertise a shared RDNSS into a tenant VRF and explain why the advertisement is not the reachability
- State what VyOS exposes for DHCPv6 prefix delegation across a VRF boundary, and what it does not
- Recognise the IPv6-specific leak failure modes: link-local next-hops, ICMPv6 PTB filtering, multicast scope
Prerequisites
- Ethernet, MAC and ARP — the Layer 2 the routing engineer must read
- DHCPv6 — stateful vs stateless, IA_NA, IA_PD, RDNSS
- VRF concept — L3VPN, the kernel vrf driver, and how VyOS implements routing tables per VRF
- IPv6 inside a VRF — link-local next-hops, OSPFv3 and BGP per VRF, leaking route6
- Route leaking between VRFs — concept, RFC 4364, shared services
Verified against VyOS 1.5.x LTS (circinus) · VyOS 1.4.x (sagitta) — legacy · FRRouting 10.x (VyOS 1.5) · Linux kernel 6.6 LTS (VyOS 1.5 base) · strongSwan 5.9.x (IPsec) · WireGuard 1.0.x (kernel module + userspace tooling) · 2026-08-19
IPv6 leaking — dual-stack leaks, RDNSS sharing, prefix delegation
IPv6 leaking is the same mechanism as IPv4 leaking with a different
address family bolted to it: route6 instead of route, an IPv6
next-hop instead of a v4 one, RDNSS instead of a DHCPv4 option,
prefix delegation instead of a server-side address pool. Everything
Part XVI has said about direction, resolution and evidence still
holds.
What changes is the failure surface. IPv6 has an address scope IPv4 does not have, it has no in-path fragmentation, and its neighbour discovery is multicast on the local link. Each of those three interacts with a leak in a way that produces a symptom the operator has not seen on the IPv4 side, and this lesson is mostly about those three.
The topology, restated
Every command in this lesson runs against the same three routing contexts Part XVI has used throughout:
- default — table 254.
eth0is the transit to the shared-service block:192.0.2.2/30and2001:db8:ff::2/64, with the service router on192.0.2.1/2001:db8:ff::1. Behind it live DNS on198.51.100.53/2001:db8:100::53, NTP on198.51.100.123/2001:db8:100::123, syslog on198.51.100.200/2001:db8:100::200. - mgmt — table 1001.
eth1,10.10.0.1/24and2001:db8:10::1/64. - tenant-a — table 1002.
eth2,10.20.0.1/24and2001:db8:20::1/64.
default is not a VRF you create. It is the main routing table, and
it is the word the leak commands use when the other side of the leak
is that table.
flowchart TB
subgraph D["default (table 254)"]
E0["eth0<br/>2001:db8:ff::2/64"]
SVC["shared services<br/>2001:db8:100::53 DNS<br/>2001:db8:100::123 NTP"]
end
subgraph M["vrf mgmt (table 1001)"]
E1["eth1<br/>2001:db8:10::1/64"]
end
subgraph T["vrf tenant-a (table 1002)"]
E2["eth2<br/>2001:db8:20::1/64"]
end
E0 --- SVC
E1 -. "route6 leak toward services" .-> E0
E2 -. "route6 leak toward services" .-> E0
E0 -. "return leak toward each tenant" .-> E1
The IPv6 leak is route6 plus the vrf leaf
The whole of route leaking on VyOS sits on one idea: a route is
installed in one table and its next-hop is resolved in another.
The leaf that separates the two is vrf, and the static-route tree
carries it under both next-hop and interface, for route and
route6 alike.
configure
# Leak the DNS and NTP service addresses into mgmt.
# The next-hop 2001:db8:ff::1 exists only in the default table.
set vrf name mgmt protocols static route6 2001:db8:100::53/128 next-hop 2001:db8:ff::1 vrf default
set vrf name mgmt protocols static route6 2001:db8:100::123/128 next-hop 2001:db8:ff::1 vrf default
# The same two services into tenant-a.
set vrf name tenant-a protocols static route6 2001:db8:100::53/128 next-hop 2001:db8:ff::1 vrf default
set vrf name tenant-a protocols static route6 2001:db8:100::123/128 next-hop 2001:db8:ff::1 vrf default
commit
save
Read next-hop 2001:db8:ff::1 vrf default as one phrase: resolve
this address in the default table, install the result in mine. VyOS
renders it into FRR as an ipv6 route statement inside the VRF’s
stanza:
vrf mgmt
ipv6 route 2001:db8:100::53/128 2001:db8:ff::1 nexthop-vrf default
exit-vrf
Drop the two words vrf default and the statement still commits.
Zebra then tries to resolve 2001:db8:ff::1 inside table 1001, where
nothing matches, and the route sits in the RIB unresolved and is
never offered to the kernel. That is the characteristic route-leaking
failure and it is the reason this part of the course exists: nothing
errors, and nothing forwards.
$ show ipv6 route vrf mgmt 2001:db8:100::53/128Routing entry for 2001:db8:100::53/128
Known via "static", distance 1, metric 0, vrf mgmt, best
Last update 00:02:11 ago
* 2001:db8:ff::1, via eth0 (vrf default), weight 1Illustrative output
The line to read is the last one. It names an egress interface,
eth0, and the routing context that interface belongs to,
(vrf default). An ordinary intra-VRF static route names no VRF
there. If the parenthesis is absent, you are not looking at a leak.
The control plane can still be optimistic, so confirm in the kernel:
$ ip -6 route show table 10012001:db8:100::53 via 2001:db8:ff::1 dev eth0 proto static metric 20 pref medium
2001:db8:100::123 via 2001:db8:ff::1 dev eth0 proto static metric 20 pref medium
2001:db8:10::/64 dev eth1 proto kernel metric 256 pref medium
fe80::/64 dev eth1 proto kernel metric 256 pref mediumIllustrative output
The signature of a leak in a kernel table is an output device that
does not belong to the VRF that table represents. eth1 belongs to
mgmt; eth0 does not; table 1001 is mgmt’s. Those first two lines
could not have been produced by an ordinary static route.
The fe80::/64 line is not a leak and never becomes one. It is the
link-local scope of eth1, installed by the kernel with the address,
and it is the IPv6 counterpart of the on-link behaviour ARP provides
for v4.
A bidirectional dual-stack pair is four statements
A leak is unidirectional. It is also per address family. Multiply the two and a dual-stack service that must be reachable and able to answer needs four statements, not two.
configure
# mgmt -> services, IPv4 and IPv6
set vrf name mgmt protocols static route 198.51.100.53/32 next-hop 192.0.2.1 vrf default
set vrf name mgmt protocols static route6 2001:db8:100::53/128 next-hop 2001:db8:ff::1 vrf default
# services -> mgmt, IPv4 and IPv6. Configured in the default context,
# pointing into the VRF. The mgmt LAN is on-link on eth1 inside mgmt,
# so the interface form is the natural one here.
set protocols static route 10.10.0.0/24 interface eth1 vrf mgmt
set protocols static route6 2001:db8:10::/64 interface eth1 vrf mgmt
commit
save
The two forms in that block are both documented and each fits a
different case. Use next-hop <address> vrf X when the destination
is reached through a router that lives in X. Use
interface <name> vrf X when the destination is directly connected
on an interface that is a member of X, and there is no next-hop
address worth naming.
Missing one of the four is the most common dual-stack leak defect in production, and each omission has its own signature:
| Missing statement | What the operator sees |
|---|---|
| mgmt to services, v6 | v4 works, v6 connections hang until the client falls back |
| mgmt to services, v4 | v6 works; anything v4-only, such as an old agent, fails outright |
| services to mgmt, v6 | v6 request leaves, reply is dropped for want of a route; reads like a firewall drop |
| services to mgmt, v4 | same, on the v4 path |
The two return-path rows are the expensive ones. A missing return leak does not look like a routing fault from the tenant’s side. The query leaves, nothing comes back, and the first instinct of everyone in the incident channel is the firewall. Part XVI’s troubleshooting lesson opens with that failure for exactly this reason.
Link-local next-hops and why a leak cannot use one
An IPv6 next-hop may be link-local, and inside a single routing
context that is ordinary: fe80::1 plus the interface it was heard
on is a complete instruction. The static-route tree has an
interface leaf under next-hop precisely so the pair can be
expressed.
A leak breaks the pair apart. The whole point of vrf default is to
hand the address to another table and ask it to resolve. A
link-local address carries no information about which link it belongs
to, so there is nothing for the other table to resolve — the same
fe80::1 may exist on every interface on the box, in every VRF.
The supported shape is the one already used above: give the transit link a global address and leak toward that.
Sharing an RDNSS across VRFs
RFC 8106 lets a Router Advertisement carry the addresses of recursive
DNS servers, which is how an IPv6 host on a SLAAC network learns a
resolver without DHCPv6. On VyOS the option lives under
service router-advert, per interface:
configure
set service router-advert interface eth1 prefix 2001:db8:10::/64
set service router-advert interface eth1 name-server 2001:db8:100::53
set service router-advert interface eth1 name-server-lifetime 600
set service router-advert interface eth2 prefix 2001:db8:20::/64
set service router-advert interface eth2 name-server 2001:db8:100::53
set service router-advert interface eth2 name-server-lifetime 600
commit
save
There is no VRF selector in that tree, and none is needed. Router
Advertisements are link-local multicast sent out of an interface;
which routing table that interface consults is irrelevant to whether
the advertisement reaches the link. eth1 is in mgmt and eth2 is in
tenant-a, and both links get the same resolver address.
sequenceDiagram
participant R as VyOS
participant H as host on the mgmt LAN
R->>H: RA on eth1: prefix 2001:db8:10::/64,<br/>RDNSS 2001:db8:100::53
Note over H: host sets resolver to<br/>2001:db8:100::53
H->>R: DNS query to 2001:db8:100::53
Note over R: table 1001 lookup —<br/>answered only if the leak exists
The sequence is the lesson. The advertisement and the reachability are two independent facts, produced by two independent subsystems, and the router will happily deliver the first without the second.
DHCPv6 prefix delegation across a VRF boundary
Prefix delegation is where operators most often expect a leak and should not configure one, so it is worth being precise about what VyOS actually exposes.
The DHCPv6 client requests a prefix on the upstream interface and assigns sub-prefixes of it onto interfaces you name:
configure
# Ask upstream for a /56 on eth0, and hand SLA-ID 0 to eth2.
set interfaces ethernet eth0 dhcpv6-options pd 0 length 56
set interfaces ethernet eth0 dhcpv6-options pd 0 interface eth2 address 1
set interfaces ethernet eth0 dhcpv6-options pd 0 interface eth2 sla-id 0
commit
save
eth2 is a member of tenant-a. When the delegation arrives, the
client configures the sub-prefix on eth2, the kernel installs the
connected route for it, and — because eth2 is enslaved to the VRF —
that connected route lands in table 1002. No leak was involved and
none is needed: the prefix is in the tenant VRF because the
interface is in the tenant VRF.
$ ip -6 addr show dev eth24: eth2: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc noqueue master tenant-a state UP qlen 1000
inet6 2001:db8:abcd:ab00::1/64 scope global
valid_lft forever preferred_lft forever
inet6 fe80::5054:ff:fe12:3456/64 scope link
valid_lft forever preferred_lft foreverIllustrative output
What the tenant still needs is a route out, and that is a leak like any other — or an upstream of its own. What it does not need is a static route for its own delegated prefix.
Option 2 in configuration, for the case where the default table needs to reach the tenant’s delegated prefix:
configure
set vrf name tenant-a protocols bgp system-as 65000
set vrf name tenant-a protocols bgp parameters router-id 10.20.0.1
set vrf name tenant-a protocols bgp address-family ipv6-unicast redistribute connected
set protocols bgp system-as 65000
set protocols bgp parameters router-id 192.0.2.2
set protocols bgp address-family ipv6-unicast import vrf tenant-a
commit
save
import vrf copies paths between BGP RIBs inside the one FRR
process; there is no session between the two contexts and none to
watch. The redistribute connected line is not decoration — a
connected route lives in zebra’s RIB, not BGP’s, until something puts
it there, and importing a VRF whose BGP instance holds nothing
imports nothing, correctly and silently.
Filter it. import vrf with no route-map brings across everything
that VRF’s BGP instance holds, which on a tenant with an upstream is
more than the delegated prefix.
Serving addresses inside the tenant
If the tenant LAN is served by DHCPv6 rather than SLAAC, the server is configured per shared-network and bound to an interface:
configure
set service dhcpv6-server shared-network-name TENANT-A interface eth2
set service dhcpv6-server shared-network-name TENANT-A subnet 2001:db8:abcd:ab00::/64 subnet-id 1
set service dhcpv6-server shared-network-name TENANT-A subnet 2001:db8:abcd:ab00::/64 range 1 start 2001:db8:abcd:ab00::100
set service dhcpv6-server shared-network-name TENANT-A subnet 2001:db8:abcd:ab00::/64 range 1 stop 2001:db8:abcd:ab00::199
set service dhcpv6-server shared-network-name TENANT-A subnet 2001:db8:abcd:ab00::/64 option name-server 2001:db8:100::53
commit
save
Two things are worth naming. subnet-id is mandatory and must be
unique across the server; the commit fails without it. And the
binding is interface, not a VRF — the server tree has no VRF
selector, so per-tenant servers are distinguished by the interface
they listen on, which in a VRF estate amounts to the same thing.
The option name-server here has the same property the RDNSS did:
it hands out an address, and the leak is what makes that address
answer.
The three IPv6-specific failure modes
Neighbour discovery does not cross the leak, and does not need to
ND is link-scoped by construction: solicitations go to a
solicited-node multicast address on the local link. A host on the
mgmt LAN never performs ND for 2001:db8:100::53, and it should
not — that address is not on its link. The host resolves its default
router, sends the packet there, and the router forwards it using the
leaked route.
Where this bites is multicast. A leak is a unicast route, and
unicast routes do not carry ff02:: traffic anywhere. Anything
built on link-local multicast — mDNS, LLMNR, SSDP, service discovery
protocols generally — does not reach across a leak and cannot be
made to by adding routes. If a shared service depends on discovery
rather than a configured address, the leak is the wrong tool: give
the clients a unicast address to talk to, or run a relay.
There is no in-path fragmentation
An IPv6 router never fragments a packet it is forwarding. When a packet is too large for the next link, the router drops it and sends ICMPv6 Packet Too Big to the source, which is expected to lower its path MTU for that destination. Path MTU discovery is not an optimisation in IPv6; it is load-bearing.
A leak crosses between interfaces that may have been provisioned by different teams with different MTUs. If the ICMPv6 PTB cannot get back to the sender — because a firewall on the leak path drops it — then small packets work, large ones vanish, and the failure looks like an application bug.
configure
# Let PTB through on the forward path, both directions of the leak.
set firewall ipv6 forward filter rule 5 action accept
set firewall ipv6 forward filter rule 5 description 'ICMPv6 PTB - required for PMTUD across the leak'
set firewall ipv6 forward filter rule 5 protocol icmpv6
set firewall ipv6 forward filter rule 5 icmpv6 type-name packet-too-big
commit
save
Where the sender cannot be relied on to react — or where the traffic is TCP and you would rather not depend on PTB at all — clamp instead, on the interface:
set interfaces ethernet eth1 ipv6 adjust-mss clamp-mss-to-pmtu
Clamping only helps TCP. UDP over a leak with a broken PTB path stays broken, which is why the firewall rule is the first fix and the clamp is the second.
Scope is a property of the address, not of the VRF
An operator who has internalised “the VRF is the isolation boundary” will sometimes reason that a link-local or unique-local address is “more contained” and therefore safer to leak. Neither scope has anything to do with VRFs. Link-local is bounded by the link; unique-local is bounded by administrative convention and routes as ordinary unicast. A ULA prefix leaked between two VRFs is exactly as reachable as a global one, and the only thing keeping it off the internet is that nobody advertises it.
How the result is validated
show ipv6 route vrf mgmt
show ipv6 route vrf tenant-a
ip -6 route show table 1001
ip -6 route show table 254
ping 2001:db8:100::53 vrf mgmt count 3
ping 2001:db8:100::53 vrf mgmt count 3 size 1400 do-not-fragment
show configuration commands | match router-advert
The first two show intent as the control plane understood it. The
next two show what will forward, in both directions — table 254 is
where the return leak has to appear, and checking only the tenant
side is how one-way leaks reach production. The two ping runs
differ in one thing, size: the first proves reachability, the second
probes the MTU path that PMTUD depends on. do-not-fragment matters
on the second one even though IPv6 has no in-path fragmentation —
without it the sender is entitled to fragment locally, and a
1400-byte echo will succeed over a path that cannot carry a
1400-byte packet. The last command prints what the router is
promising the hosts.
Then leave the router. A tcpdump -i eth2 -n icmp6 while a host on
the tenant LAN boots shows the RA it actually receives, and a DNS
query from that host is the only test that exercises the
advertisement and the leak together.
How it fails
- The v6 half of a dual-stack leak was never configured. Clients that prefer IPv6 stall on the AAAA and fall back. The ticket says “slow”, and every v4 test passes.
- Only one direction was leaked. Queries leave, replies have no route home and are dropped in the default table. It reads as a firewall problem and the investigation goes to the wrong team.
- The
vrfleaf was omitted. The commit succeeds, the route appears inshow ipv6 route, and the kernel table never receives it. Reviewing the configuration diff does not reveal this; only the kernel table does. - RDNSS advertised without the matching leak. The whole segment configures a resolver it cannot reach, at the next RA.
- A PD lease changed under a static leak. The upstream re-delegated, the hard-coded prefix is stale, and the tenant is unreachable from everywhere the static leak was serving.
- ICMPv6 PTB filtered on the leak path. Small packets work, large
ones disappear, and the failure is blamed on the application for as
long as nobody tests with
size. - A discovery protocol was expected to cross. mDNS and friends are link multicast; no route will carry them between VRFs.
Rollback
- Remove one leak:
delete vrf name mgmt protocols static route6 2001:db8:100::53/128, thencommit. - Remove every IPv6 leak from a VRF:
delete vrf name mgmt protocols static route6, thencommit. Confirm withip -6 route show table 1001that only connected routes remain. - Undo a return leak:
delete protocols static route6 2001:db8:10::/64, thencommit. - Withdraw an RDNSS that has no backing route:
delete service router-advert interface eth2 name-server 2001:db8:100::53, thencommit. Hosts stop being told about a resolver they cannot reach, though those that already hold it keep it until the lifetime expires — which is whatname-server-lifetimeis for. - Back out a BGP import:
delete protocols bgp address-family ipv6-unicast import vrf tenant-a, thencommit. VyOS refuses to delete a VRF that another context imports from, so remove the import before the VRF.
Nothing in this lesson is destructive in the storage sense, and all
of it is reversible with rollback plus commit — but every one of
these commands can black-hole a tenant’s traffic on the way past, so
they belong in a change window like any other routing change.
Production discipline
Cross-course references
vyos-xv-04-vrf-ipv6 establishes IPv6 inside a VRF and introduces
the vrf leaf this lesson applies; vyos-xi-04-dhcpv6 and
vyos-xi-05-ipv6-routing cover the DHCPv6 and IPv6 routing
fundamentals. vyos-xvi-05-leaking-troubleshoot takes the failure
modes listed above and turns them into an evidence order. The Linux
course’s XIX-Linux-NetFoundations covers ND and PMTUD as kernel
behaviour rather than as router configuration.
Quiz
Knowledge check · 4 questions
Q1. Which command leaks the IPv6 service address `2001:db8:100::53/128` into the `mgmt` VRF, where the next-hop `2001:db8:ff::1` is reachable only in the default table?
Q2. Advertising an RDNSS on a VRF's interface with `set service router-advert interface eth2 name-server 2001:db8:100::53` makes that resolver reachable from hosts on that VRF.
Q3. An operator configures `set vrf name mgmt protocols static route6 2001:db8:100::53/128 next-hop fe80::1 vrf default` because `fe80::1` is what `show ipv6 neighbors` shows for the upstream router. The commit succeeds and nothing forwards. Why is a link-local next-hop the wrong tool for a leak, and what should replace it?
A link-local address is only meaningful together with the link it was heard on; the same fe80::1 can exist on every interface on the box. The `vrf default` leaf asks another routing table to resolve the address on its own, and there is nothing in a bare link-local address for that table to resolve against. The fix is not a more specific next-hop - it is a global address on the transit link, leaked toward normally.
Q4. A tenant VRF gets its addressing from DHCPv6 prefix delegation on the upstream interface. An operator writes a static leak in the default table for the delegated /64 so that a monitoring host can reach the tenant. Twenty-four hours later the upstream re-delegates a different prefix and monitoring goes dark. What is the underlying mismatch, and what does VyOS actually offer?
A static route is a constant and a delegation is a lease. VyOS has no CLI node that binds a static route to a DHCPv6-PD lease - the static tree can take a next-hop from a DHCPv4 lease, but nothing takes a prefix from a PD lease. When the delegation changes, the client re-addresses the delegatee interface and the connected route follows it, while the hand-written leak in the other table keeps pointing at a prefix that no longer exists.
Passing score: 75%. Answers are checked in this browser.