VyOSXVI · Route Leaking Between VRFsLeaking
Anti-patterns — leaking everything, leaking without firewall, MTU-naive leaking
What you'll learn
- Recognise the leak anti-patterns production estates fall into, and why each one survives review
- Explain why a static leak cannot be filtered, and what that implies for how leaks are scoped
- Replace each anti-pattern with a configuration that VyOS 1.5 actually accepts
- Run a leak review checklist that ends in evidence rather than in intent
Prerequisites
- Ethernet, MAC and ARP — the Layer 2 the routing engineer must read
- Route leaking between VRFs — concept, RFC 4364, shared services
- Route leaking configuration — static next-hop-vrf, BGP import vrf, filtering
- Leaking and firewall — dispatch by interface, state, asymmetric paths
- IPv6 leaking — dual-stack leaks, RDNSS sharing, prefix delegation
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
Anti-patterns — leaking everything, leaking without firewall, MTU-naive leaking
Every anti-pattern below has shipped, in a real estate, past a real review. None of them is the result of carelessness; each is a reasonable-looking shortcut whose cost is invisible at commit time and obvious at 03:00 some months later.
The estate throughout is the one Part XVI has used: the default
routing context on table 254 with eth0 toward the shared services
at 198.51.100.53 (DNS), 198.51.100.123 (NTP) and
198.51.100.200 (syslog) via the transit next-hop 192.0.2.1;
mgmt on table 1001 with eth1; tenant-a on table 1002 with
eth2.
Anti-pattern 1 — leaking a default route
The operator wants tenants to reach the shared services and writes the shortest thing that achieves it:
configure
set vrf name tenant-a protocols static route 0.0.0.0/0 next-hop 192.0.2.1 vrf default
commit
save
The intent was DNS and NTP. The delivery is every destination the default table can reach, which on a router with an upstream is the internet plus every management address the operator has ever configured. For outbound traffic from the tenant, the VRF has been dissolved.
flowchart LR
H["host on tenant-a"] -->|"0.0.0.0/0 leak"| G["default table"]
H -.->|"intended"| DNS["DNS 198.51.100.53"]
H -.->|"intended"| NTP["NTP 198.51.100.123"]
H -.->|"delivered as well"| X["RADIUS, backup targets,<br/>licence servers, the upstream,<br/>every other tenant's gateway"]
The replacement is to write the prefixes you mean:
configure
set vrf name tenant-a protocols static route 198.51.100.53/32 next-hop 192.0.2.1 vrf default
set vrf name tenant-a protocols static route 198.51.100.123/32 next-hop 192.0.2.1 vrf default
set vrf name tenant-a protocols static route 198.51.100.200/32 next-hop 192.0.2.1 vrf default
commit
save
Anti-pattern 2 — leaking without a firewall pair
The leak is committed, a ping succeeds, the change is closed.
set vrf name mgmt protocols static route 198.51.100.53/32 next-hop 192.0.2.1 vrf default
The routing is correct and narrow: one /32. What is missing is any
statement about which flows may use it. Every host on the mgmt
segment can now reach every port on 198.51.100.53 — DNS on UDP/53,
whatever administrative interface it exposes on TCP/22 or TCP/443,
and anything else listening.
A route is reachability. A firewall is policy. Leaking without the second means the policy is “whatever the service happens to be listening on”, decided by the service owner, months ago, for a different threat model.
configure
set firewall ipv4 forward filter default-action drop
set firewall ipv4 forward filter rule 5 action accept
set firewall ipv4 forward filter rule 5 description 'Return traffic for established flows'
set firewall ipv4 forward filter rule 5 state established
set firewall ipv4 forward filter rule 5 state related
set firewall ipv4 forward filter rule 10 action accept
set firewall ipv4 forward filter rule 10 description 'mgmt to shared DNS'
set firewall ipv4 forward filter rule 10 source address 10.10.0.0/24
set firewall ipv4 forward filter rule 10 destination address 198.51.100.53
set firewall ipv4 forward filter rule 10 destination port 53
set firewall ipv4 forward filter rule 10 protocol udp
set firewall ipv4 forward filter rule 10 state new
commit
save
Then prove it rather than assuming it. show firewall ipv4 forward filter prints each rule with its packet and byte counters; run one
query from a host and read which counter moved. A rule whose counter
stays at zero is not protecting anything, whatever it says.
Anti-pattern 3 — MTU-naive leaking
A leak joins two interfaces that were provisioned separately, often by different teams and sometimes years apart. When their MTUs differ, small packets cross and large ones do not.
ping 198.51.100.53 vrf mgmt count 3 size 1200 do-not-fragment
ping 198.51.100.53 vrf mgmt count 3 size 1450 do-not-fragment
Two runs differing only in size. If the first answers and the second does not, the path cannot carry the larger packet and the sender is not being told — which means the ICMP that carries that news is being dropped somewhere along the leak.
The symptom is the worst kind: intermittent and correlated with nothing the application team can see. Interactive SSH is fine; a file copy over the same session stalls. Ordinary DNS answers arrive; DNSSEC and zone transfers time out.
configure
# Let the message PMTUD depends on through: type 3 code 4,
# destination unreachable / fragmentation needed.
set firewall ipv4 forward filter rule 6 action accept
set firewall ipv4 forward filter rule 6 description 'ICMP frag-needed - required for PMTUD'
set firewall ipv4 forward filter rule 6 protocol icmp
set firewall ipv4 forward filter rule 6 icmp type 3
set firewall ipv4 forward filter rule 6 icmp code 4
# And stop TCP from depending on it at all.
set interfaces ethernet eth1 ip adjust-mss clamp-mss-to-pmtu
set interfaces ethernet eth1 ipv6 adjust-mss clamp-mss-to-pmtu
commit
save
Clamping repairs TCP and does nothing for UDP, which is why the firewall rule comes first and the clamp second. Raising the smaller MTU is also an option where both ends are yours, but on a leak whose far side belongs to another team it is a negotiation, not a change.
Anti-pattern 4 — IPv4-only leaking for a dual-stack service
The IPv4 leak is configured and the IPv6 one is not, because the service was reached successfully during the change window and nobody checked which family carried the test.
# What was configured
set vrf name mgmt protocols static route 198.51.100.53/32 next-hop 192.0.2.1 vrf default
Nothing fails. Clients that prefer IPv6 — which on a modern host is most of them — attempt the AAAA first, wait out a connect timeout, and fall back. The estate runs at that penalty indefinitely, and the day the IPv4 path has a problem, there is no fallback in the other direction because the v6 leak was never there.
configure
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
commit
save
route and route6 are independent trees and nothing keeps them in
step: no dual-stack switch, no validator, no warning. And because a
leak is also unidirectional, a service that must answer needs the
return leak in each family too — four statements for one dual-stack
service, as Part XVI’s IPv6 lesson counts them.
Anti-pattern 5 — leaking without a documented reason
A leak is configured, works, and is never described anywhere. Six months later a cleanup change removes “an unused static route” and a service breaks.
VyOS has somewhere to put the reason. The static-route tree carries
description:
configure
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 route 198.51.100.53/32 description 'Shared DNS for mgmt - CHG-12345, runbook RB-2026-08-001'
commit
save
The description travels with the configuration. It appears in
show configuration commands, it survives backup and restore, and
it is visible to the person considering the deletion at the moment
they are considering it — which is more than can be said for a wiki
page.
Anti-pattern 6 — leaking the enclosing subnet
The three services sit inside 198.51.100.0/24, so the operator
leaks the /24. One line instead of three, same result.
Not the same result. The /24 contains 256 addresses, and the three that were meant are the only ones anybody reasoned about. Everything else on that segment — the RADIUS server, the backup target, the licence server, the out-of-band management address of the service router itself — becomes reachable from the tenant, and does so without appearing anywhere in the change description.
# Delivered
set vrf name tenant-a protocols static route 198.51.100.0/24 next-hop 192.0.2.1 vrf default
# Intended
set vrf name tenant-a protocols static route 198.51.100.53/32 next-hop 192.0.2.1 vrf default
set vrf name tenant-a protocols static route 198.51.100.123/32 next-hop 192.0.2.1 vrf default
set vrf name tenant-a protocols static route 198.51.100.200/32 next-hop 192.0.2.1 vrf default
This is the same anti-pattern as the default-route leak, scaled down
far enough to look reasonable, and it is worth naming separately for
exactly that reason: the /24 version passes review and the
0.0.0.0/0 version sometimes does not.
Since a static leak carries no filter, the three-line form is the policy, and it is reviewable at a glance. If the number of prefixes grows past what you are willing to enumerate, that is the signal to move to BGP with an import route-map — not the signal to widen the mask.
Anti-pattern 7 — validation that proves nothing
Three versions of the same mistake, in increasing order of how often they appear:
The configuration as evidence. The commit succeeded, so the leak
exists. It does not follow. A static leak that is missing the vrf
leaf commits cleanly, appears in show ip route, and installs
nothing. The only output that distinguishes configured from working
is the kernel table:
ip route show table 1001
ip route show table 254
A leaked route is visible there as an entry whose output device does not belong to that VRF. Both tables, because a leak is unidirectional and half of them ship one-way.
The ping as a service test. ICMP reaching a host tells you the route resolves. It says nothing about whether the service answers, whether the firewall permits the actual port, or whether the reply path exists for a stateful protocol. Test the protocol you care about, from a host on the segment that cares about it:
dig @198.51.100.53 example.com
The test from the router. Traffic the router originates is not
in a VRF unless you say so. ping 198.51.100.53 from the CLI uses
the default table, which is not the table under test, so it can
succeed while every host in the tenant fails — and occasionally the
reverse.
ping 198.51.100.53 vrf mgmt count 3
Even that is a weaker test than the same query from a host, because it skips the segment, the host’s own routing, and the forward chain the tenant’s traffic actually traverses.
The review checklist
flowchart TD
A["Leak change proposed"] --> B["1. Is every leaked prefix one somebody named?"]
B --> C["2. Is there a firewall rule for each permitted flow?"]
C --> D["3. Both directions configured, and both kernel tables checked?"]
D --> E["4. Both address families, if the service is dual-stack?"]
E --> F["5. Does each route carry a description naming the change and runbook?"]
F --> G["6. Has a large packet crossed, with do-not-fragment set?"]
G --> H["7. Was the real service tested from a host, not pinged from the router?"]
H --> I{"All seven answered with evidence?"}
I -- "No" --> J["Reopen the change"]
I -- "Yes" --> K["Commit, and paste the evidence into the record"]
The checklist is deliberately answerable only with output. “Yes, we
configured the return leak” is not an answer to question 3;
ip route show table 254 is.
How the anti-patterns fail
- Default-route leak exposed the management plane. A compromised tenant workload reached RADIUS and the backup target. The leak was one line, it worked on the first try, and it was six months old.
- No firewall pair allowed lateral movement. A host on
mgmtreached the administrative port of a shared service because nothing denied it; the route had been scoped carefully and the policy never written. - MTU-naive leak caused intermittent DNSSEC failures. Blamed on
the upstream resolver for three weeks, resolved by two
pingruns that differed only in size. - IPv4-only leak degraded every dual-stack client. Nobody noticed the added connect timeout until an unrelated latency investigation found it.
- Undocumented leak deleted during cleanup. The route looked unused, the runbook was in a wiki nobody searched, and the outage lasted as long as it took to work out what had been removed.
- The /24 that was meant to be three /32s. Discovered during an audit, not an incident — which is the good outcome, and it is luck.
- A leak that never installed. Reviewed, committed, closed, and
reported broken two days later by the tenant. The
vrfleaf was missing and no kernel table was ever consulted.
Rollback
- Narrowing a wildcard leak: add the specific prefixes first, confirm them in
ip route show table 1002, thendelete vrf name tenant-a protocols static route 0.0.0.0/0andcommit. Adding before deleting keeps the tenant reachable throughout. - Backing out a broad prefix: the same order — the /32s go in, they appear in the kernel table, the /24 comes out.
- Adding a firewall pair to a live leak: commit the
state establishedandstate relatedrule before changingdefault-actiontodrop, or the commit that tightens the chain also tears down every flow currently crossing the leak. - Removing a leak entirely:
delete vrf name tenant-a protocols static route 198.51.100.53/32, thencommit. Verify the kernel table, and expect the service to fail immediately — that is the leak doing its job, and it is why the description names the runbook. - Undoing an MTU or clamp change:
delete interfaces ethernet eth1 ip adjust-mssandcommit. Clamping affects only new TCP connections, so the rollback is not visible on established sessions and needs a fresh one to test.
Every one of these is reversible with rollback plus commit, and
every one of them can black-hole a tenant on the way past. They
belong in a change window.
Production discipline
Cross-course references
vyos-xvi-01-leaking-concept through vyos-xvi-05-leaking-troubleshoot
build the mechanism and the diagnostic order this lesson assumes;
vyos-xv-03-vrf-routing-protocols is where the vrf leaf and
import vrf are established, including why only one of them can be
filtered. Parts XXVIII and XXXIII cover the prefix-list and route-map
hygiene the BGP form depends on, and the Linux course’s
XXII-Linux-NetTroubleshoot covers PMTUD and conntrack as host
behaviour rather than router configuration.
Quiz
Knowledge check · 4 questions
Q1. Which of these commits the canonical leak anti-pattern — the one that dissolves the tenant VRF's isolation for outbound traffic?
Q2. A successful `ping` to the service address is sufficient validation for a route leak.
Q3. An operator writes `set vrf name tenant-a protocols static route 198.51.100.0/24 next-hop 192.0.2.1 vrf default`, intending to give the tenant the three shared services that live on that segment. What has actually been delivered, and what should replace it?
The /24 covers 256 addresses and the operator reasoned about three of them. Everything else on that segment is now reachable from every host in tenant-a: RADIUS, the backup target, the licence server, and the out-of-band address of the service router itself. A lookup does not know what was intended - a packet for 198.51.100.180 matches the leaked prefix exactly as well as one for 198.51.100.53.
Q4. An IPv4 leak gives tenant hosts a working resolver at 198.51.100.53. The service also publishes 2001:db8:100::53 and the tenant segment is dual-stack. Nothing is reported as broken. What is wrong, and what does the complete configuration look like?
The IPv6 leak was never configured, and the failure mode is degradation rather than an outage. A dual-stack client resolves the AAAA, tries it first, waits out a connect timeout with no route to the destination, and falls back to IPv4. Every lookup pays that penalty, no alarm fires, and the estate has no fallback at all on the day the IPv4 path has a problem.
Passing score: 75%. Answers are checked in this browser.