Skip to main content
RunBook Academy

VyOSXVI · Route Leaking Between VRFsLeaking

Anti-patterns — leaking everything, leaking without firewall, MTU-naive leaking

Advanced⏱ ~20 minset vrf name protocols static routeset policy prefix-listset firewall ipv4 forward filtershow ip route vrfip route show tableshow firewall ipv4 forward filtershow configuration commands

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

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

Not yet marked complete on this device.

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 mgmt reached 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 ping runs 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 vrf leaf 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, then delete vrf name tenant-a protocols static route 0.0.0.0/0 and commit. 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 established and state related rule before changing default-action to drop, 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, then commit. 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-mss and commit. 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

  1. Q1. Which of these commits the canonical leak anti-pattern — the one that dissolves the tenant VRF's isolation for outbound traffic?

  2. Q2. A successful `ping` to the service address is sufficient validation for a route leak.

  3. 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.

  4. 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.