VyOSXVI · Route Leaking Between VRFsLeaking
Route leaking configuration — static next-hop-vrf, BGP import vrf, filtering
What you'll learn
- Configure a static leak in both the interface form and the next-hop-address form
- Configure the reverse leak, and explain why it is a separate statement in a different subtree
- Configure a BGP leak with import vrf, including originating the prefixes into the source BGP RIB
- Filter what crosses with a prefix-list and a route-map, and say which side the filter is on
- Verify a leak in the receiving kernel table before believing anything FRR prints
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
Route leaking configuration — static next-hop-vrf, BGP import vrf, filtering
Lesson 1 established what a leak is: a route installed in one table whose next hop is deliberately resolved in another. This lesson is the configuration walk-through — the two mechanisms VyOS 1.5 LTS exposes, in both directions, with the filtering and the verification that make them reviewable.
The verification is not an appendix. A leak is the one change in this
course where a clean commit proves nothing, so every pattern below ends
in the kernel table of the context that is supposed to receive the route.
The reference estate
One VyOS 1.5 router, three routing contexts, carried forward from lesson 1.
| Routing context | Kernel table | Interface | Addressing |
|---|---|---|---|
| default (the main table) | 254 | eth0 | 192.0.2.2/30, transit to the service router |
mgmt | 1001 | eth1 | 10.10.0.1/24 |
tenant-a | 1002 | eth2 | 10.20.0.1/24 |
eth0 is a transit link to the service router at 192.0.2.1. Behind it
sit DNS 198.51.100.53, NTP 198.51.100.123 and syslog 198.51.100.200,
and the default context reaches all of 198.51.100.0/24 through one
static route:
set protocols static route 198.51.100.0/24 next-hop 192.0.2.1
That the services are behind a router rather than on a segment this box owns is the ordinary case, and it decides which form of the static leak each direction needs.
flowchart TB
subgraph VyOS["VyOS 1.5 LTS - single chassis"]
G["default context<br/>table 254<br/>eth0 192.0.2.2/30"]
M["vrf mgmt<br/>table 1001<br/>eth1 10.10.0.1/24"]
T["vrf tenant-a<br/>table 1002<br/>eth2 10.20.0.1/24"]
end
CORE["service router<br/>192.0.2.1"]
SEG["198.51.100.0/24<br/>DNS .53 - NTP .123 - syslog .200"]
G --- CORE
CORE --- SEG
Pattern 1 — the static leak
1a. The forward leak: a next-hop address, resolved elsewhere
The services are behind 192.0.2.1, so the leak has to name that address
— and the vrf value tells FRR which context to resolve it in. Three
services, two consumer VRFs, six statements.
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.123/32 next-hop 192.0.2.1 vrf default
set vrf name mgmt protocols static route 198.51.100.200/32 next-hop 192.0.2.1 vrf default
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
Each statement renders to FRR as one ip route line inside the VRF’s
block:
vrf mgmt
ip route 198.51.100.53/32 192.0.2.1 nexthop-vrf default
ip route 198.51.100.123/32 192.0.2.1 nexthop-vrf default
ip route 198.51.100.200/32 192.0.2.1 nexthop-vrf default
exit-vrf
nexthop-vrf default is the entire mechanism: resolve 192.0.2.1 in the
default context — where 192.0.2.0/30 is connected on eth0 — and
install the resulting route in mgmt’s table.
Note what happens if the vrf default is dropped. The statement still
commits. It still reads correctly. FRR resolves 192.0.2.1 inside mgmt,
finds table 1001 holding only the tenant’s connected route, holds the
static inactive, and never offers it to the kernel.
1b. When there is no next-hop address worth naming
The vrf leaf sits under interface as well as under next-hop, and the
interface form is the right one whenever the far side is directly
connected in the other context. That is not the case for the forward leak
here — the services sit behind 192.0.2.1 rather than on the link — but
it is exactly the case for the reverse leak, which is the next section.
# The shape, for reference: install in this VRF, egress an interface that
# belongs to another one
set vrf name VRFNAME protocols static route PREFIX interface INTERFACE vrf CONTEXT
The two forms are not interchangeable. Naming interface eth0 for
198.51.100.53/32 would tell the kernel the service is on the link, which
it is not, and ARP for it would go unanswered.
1c. The reverse leak
The forward leak makes the service reachable from mgmt. The service’s
replies are resolved in table 254, which has never heard of
10.10.0.0/24. That is a second statement, and it lives in the top-level
tree because the default context has no vrf name node.
configure
set protocols static route 10.10.0.0/24 interface eth1 vrf mgmt
set protocols static route 10.20.0.0/24 interface eth2 vrf tenant-a
commit
save
ip route 10.10.0.0/24 eth1 nexthop-vrf mgmt
ip route 10.20.0.0/24 eth2 nexthop-vrf tenant-a
Here the interface form is right: 10.10.0.0/24 is directly connected on
eth1 inside mgmt, and there is no next-hop address that would mean
anything. The same vrf leaf, the other subtree, the other spelling.
1d. Verifying pattern 1
Kernel table first. This is the only view that does not depend on knowing the current FRR output format, and it is the one that settles the question.
$ ip route show table 100110.10.0.0/24 dev eth1 proto kernel scope link src 10.10.0.1
198.51.100.53 via 192.0.2.1 dev eth0 proto static
198.51.100.123 via 192.0.2.1 dev eth0 proto static
198.51.100.200 via 192.0.2.1 dev eth0 proto staticIllustrative output
That is the signature of a leaked route and nothing else produces it: a
route in table 1001 whose output device is eth0, an interface that
belongs to the default context. ip route show vrf mgmt prints the same
table if you prefer the VRF name to the table id.
The reverse direction has the mirror signature in table 254:
$ ip route show table 254192.0.2.0/30 dev eth0 proto kernel scope link src 192.0.2.2
198.51.100.0/24 via 192.0.2.1 dev eth0 proto static
10.10.0.0/24 dev eth1 proto static
10.20.0.0/24 dev eth2 proto staticIllustrative output
FRR’s view comes second, and it is worth learning mainly for the failure
case. In show ip route vrf mgmt, a route that is selected and installed
carries > and * in its code column; a route FRR is holding because the
next hop did not resolve carries neither, and the next hop is flagged as
not resolving. The exact wording of that flag differs between FRR
releases, so read the markers rather than the sentence.
$ vtysh -c 'show ip route vrf mgmt 198.51.100.53/32'VRF mgmt:
S>* 198.51.100.53/32 [1/0] via 192.0.2.1, eth0 (vrf default)Illustrative output
A leak that did not install shows the same prefix without > and *. If
you are ever unsure which you are looking at, go back to
ip route show table 1001; the route is either in the table or it is not.
What you cannot do to a static leak
The natural next thought is to leak one broad prefix and filter it down —
leak 0.0.0.0/0 and attach a route map that permits only the three
service /32s. That does not exist, and it could not work.
A route map applied to a route can permit it, deny it, or change its
attributes. It cannot turn one route into a different set of routes. A
permitted 0.0.0.0/0 is still 0.0.0.0/0; a denied one is nothing. There
is no intermediate outcome where a default route becomes three host
routes, and no route-map leaf under a static route in the VyOS tree that
would offer one.
Pattern 2 — BGP import vrf
When the set of prefixes is large, or learned rather than known, BGP does the same job dynamically. Each routing context gets its own BGP instance, and the importing instance copies paths out of the exporting instance’s RIB.
flowchart LR
subgraph GlobalCtx["default context"]
BG["bgp instance<br/>system-as 65000<br/>network 198.51.100.0/24"]
end
subgraph MgmtCtx["vrf mgmt"]
BM["bgp instance<br/>system-as 65000<br/>network 10.10.0.0/24<br/>import vrf default"]
end
BG -->|"import vrf default"| BM
BM -->|"import vrf mgmt"| BG
There is no BGP session here and no neighbour statement. Both instances live in the same FRR process and the import is an internal copy between RIBs. That is worth saying explicitly because material that describes inter-VRF leaking as “an iBGP session between the two VRFs over an internal link” is describing a topology nobody builds on a single chassis and that VyOS does not require.
configure
# An instance in each context. Both need a system-as; the number may be identical.
set protocols bgp system-as 65000
set protocols bgp parameters router-id 192.0.2.2
set vrf name mgmt protocols bgp system-as 65000
set vrf name mgmt protocols bgp parameters router-id 10.10.0.1
# Put the prefixes into each instance's BGP RIB in the first place
set protocols bgp address-family ipv4-unicast network 198.51.100.0/24
set vrf name mgmt protocols bgp address-family ipv4-unicast network 10.10.0.0/24
# Import, in each direction
set vrf name mgmt protocols bgp address-family ipv4-unicast import vrf default
set protocols bgp address-family ipv4-unicast import vrf mgmt
commit
save
The network statements are the part that gets left out, and leaving them
out is the second-most-common way to build a leak that does nothing.
import vrf copies BGP paths. A connected route lives in zebra’s RIB,
not in BGP’s, until a network statement or a redistribute puts it
there. Import a context whose BGP instance holds nothing and you import
nothing — correctly, and without an error, because an empty import is a
valid state.
Filtering what crosses
import vrf default on an internet-facing router imports whatever the
default instance’s RIB holds, which on a box with a transit session is the
whole table. That is not a leak, it is a merge. Two filters are available,
and they sit on different sides.
Filter at the source, by originating exactly what may cross. This is the filter that cannot be renamed out from under you, because it is the absence of a route rather than the presence of a policy:
set protocols bgp address-family ipv4-unicast network 198.51.100.0/24
One statement, one prefix, and nothing else in the default instance’s RIB is a candidate for import unless something else put it there.
Filter on redistribution, when network is impractical. A
redistribute widens the RIB the moment a new route of that kind appears,
so pair it with a route map:
set policy prefix-list SHARED-SERVICES rule 10 action permit
set policy prefix-list SHARED-SERVICES rule 10 prefix 198.51.100.0/24
set policy route-map SHARED-SERVICES-ONLY rule 10 action permit
set policy route-map SHARED-SERVICES-ONLY rule 10 match ip address prefix-list SHARED-SERVICES
set protocols bgp address-family ipv4-unicast redistribute static route-map SHARED-SERVICES-ONLY
A VyOS prefix-list rule is two statements — an action and a prefix —
and the list ends in an implicit deny, so there is no final deny any rule
to write. A route map is the same shape: action on the rule, then the
match conditions.
Verifying pattern 2
The order is: is the path in the source instance, is it in the receiving instance, is it in the receiving kernel table. Skipping to the third and finding nothing tells you the leak failed but not where.
show bgp vrf default ipv4 unicast
show bgp vrf mgmt ipv4 unicast
show ip route vrf mgmt
then the kernel:
ip route show table 1001
$ vtysh -c 'show bgp vrf mgmt ipv4 unicast' Network Next Hop Metric LocPrf Weight Path
*> 10.10.0.0/24 0.0.0.0 0 32768 i
*> 198.51.100.0/24 0.0.0.0@1 0 100 0 iIllustrative output
The imported prefix is the one whose next hop carries an instance qualifier. FRR annotates the next hop of an imported path with the routing context it came from, so an entry that otherwise looks locally originated is an import rather than something this instance learned. The exact notation differs between FRR releases, so read it as a hint and settle the question in the kernel table.
An empty show bgp vrf mgmt ipv4 unicast after a clean commit almost
always means the source instance had nothing to give. Check
show bgp vrf default ipv4 unicast before you touch the import statement.
Pattern 3 — RD and RT, and when they belong
The RFC 4364 mechanism is the right answer when the VRFs live on different routers that exchange routes over an MPLS or VXLAN underlay, and the VyOS box is a genuine provider-edge router.
# Per-VRF: how this VRF's routes are stamped into and selected out of the VPN RIB
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 ipv4-unicast rd vpn export '65000:1002'
set vrf name tenant-a protocols bgp address-family ipv4-unicast route-target vpn both '65000:1002'
set vrf name tenant-a protocols bgp address-family ipv4-unicast export vpn
set vrf name tenant-a protocols bgp address-family ipv4-unicast import vpn
# Top level: the session that actually carries the VPN routes to the other PE
set protocols bgp system-as 65000
set protocols bgp parameters router-id 192.0.2.2
set protocols bgp neighbor 10.0.0.2 remote-as 65000
set protocols bgp neighbor 10.0.0.2 update-source lo
set protocols bgp neighbor 10.0.0.2 address-family ipv4-vpn
The split is the part to memorise. rd, route-target, export vpn and
import vpn describe how one VRF’s routes enter and leave the VPN RIB.
The ipv4-vpn neighbour describes how that RIB reaches another router,
and it lives in the default instance. Putting the VPN neighbour inside the
VRF does not build an L3VPN; it builds an ordinary session in the tenant’s
routing context.
How it fails
The production failure modes, in the order they are worth checking:
- The
vrfvalue is missing from a static leak. Commits, reads correctly, installs nothing.ip route show table 1001has no line for the prefix. The only fix is thevrfvalue; nothing about the firewall or the next hop’s reachability is relevant yet. - The
vrfvalue names the wrong context.vrf tenant-awheredefaultwas meant. The next hop does not resolve there either, so the symptom is identical to the previous one and the configuration looks even more convincing. - Only one direction was configured. The forward route is in the receiving table and traffic still fails. Check the other context’s table before the firewall.
import vrfwith an empty source RIB. Nonetwork, noredistributein the exporting context.show bgp vrf default ipv4 unicastis empty and the import is doing exactly what it was asked.import vrfwith no filter. The default context’s whole BGP RIB lands in the tenant. On a router with a transit session that is a full table in a VRF that was supposed to see three services.- A
networkstatement with no matching route.network 198.51.100.53/32when only a/24is connected. BGP advertises nothing, and the import that depends on it is empty. - A route map that references a renamed prefix-list. The match never
succeeds, so a permit rule behaves as a deny and the leak silently
empties.
show policy route-mapandshow policy prefix-listare the two commands that catch it. - VPN commands and
import vrfon one address family. The commit is rejected, naming both commands. Pick one; on a single chassis it isimport vrf. - A VRF that cannot be deleted. Another context imports from it, or its static leaks still reference it. Remove the dependent side in the same commit.
Rollback
configure
# Remove one static leak and its reverse, in one commit
delete vrf name mgmt protocols static route 198.51.100.53/32
delete protocols static route 10.10.0.0/24
# Remove a BGP leak - both sides in the same commit, or the remaining
# side references a context it can no longer import
delete vrf name mgmt protocols bgp address-family ipv4-unicast import vrf default
delete protocols bgp address-family ipv4-unicast import vrf mgmt
commit
save
comparebefore everycommit, and read the whole diff. A leak removal frequently trips a reference check somewhere else in the tree.commit-confirmwhen the leak carries the path your own session uses.- After
commit, verify the absence the same way you verified the presence: the receiving kernel table should no longer carry a route with a foreign output device. rollback Ninsideconfigurewhen the change is larger than a couple of statements. Roll back to a revision that was tested, not merely to the most recent one.
Production discipline
Cross-course references
vyos-xv-02-vrf-config covers VRF creation, table ids and the reference
checks that block a delete. vyos-xv-03-vrf-routing-protocols introduces
both leak mechanisms alongside per-VRF OSPF and BGP.
vyos-xvi-03-leaking-firewall is the firewall half of every pattern here.
The BGP parts XXIV-BGPSession, XXV-BGPAdvertise and
XXVIII-BGPFiltering cover origination and policy in depth; the Linux
course’s XIX-Linux-NetFoundations covers the kernel routing tables the
VRF tree wraps.
Quiz
Knowledge check · 4 questions
Q1. The syslog collector 198.51.100.200 is reached from the default context via the service router 192.0.2.1 on eth0. Which statement leaks it into the mgmt VRF?
Q2. `import vrf default` moves BGP paths between BGP RIBs, so a connected route in the default context is not importable until a `network` statement or a `redistribute` puts it into that context's BGP instance.
Q3. An operator wants the three shared-service /32s in mgmt but not the rest of 198.51.100.0/24. They configure `set vrf name mgmt protocols static route 0.0.0.0/0 next-hop 192.0.2.1 vrf default` and a route map permitting only the three /32s. Nothing filters. What went wrong?
The route map was written and committed, and the leak works - too well. Table 1001 now has a default route whose output device is eth0, so every destination the default context can route to is reachable from mgmt, including the service router itself, the rest of 198.51.100.0/24, and whatever lies beyond it. The route map is attached to nothing that could act on it, and even if it were, permitting 0.0.0.0/0 would still leave 0.0.0.0/0.
Q4. A BGP leak is configured in both directions between the default context and mgmt. Both instances have a system-as and a router id, both `import vrf` statements are present, and the commit was clean. `show bgp vrf mgmt ipv4 unicast` is empty. What is missing, and how do you tell it apart from a broken import?
The default context has 192.0.2.0/30 connected on eth0 and a static route to 198.51.100.0/24 via 192.0.2.1. No `network` statement and no `redistribute` was configured in the default instance. The mgmt instance is importing correctly from a context whose BGP RIB holds nothing at all.
Passing score: 75%. Answers are checked in this browser.