Guides
VRF & routing
Multi-tenant routing tables (VRFs) and static routes.
A VRF in siha is a named VPP FIB table. Every packet forwarded by the appliance is looked up in a table; if you do nothing, all traffic uses table 0 (the implicit default). VRFs let you run multiple isolated routing domains on a single appliance — one per tenant, one per security zone, whatever fits your topology.
VRF resource
type: VRFs.routing.siha
metadata:
namespace: routing
id: tenant-a # your friendly name; also what other resources reference
spec:
table: 100 # VPP FIB table id, 1–4294967295 (0 is reserved for default)
family: DUAL # IPv4 | IPv6 | DUAL (default when omitted)
description: "Tenant A production segment"
table is the VPP-level table id — pick any non-zero integer that is unique
across your VRFs. id is the human-readable COSI identifier that interface and
route resources reference via their vrf field.
family controls which address-family FIBs are created in VPP:
| Value | Effect |
|---|---|
DUAL (default) | both IPv4 and IPv6 FIB tables created |
IPv4 | only IPv4 FIB created |
IPv6 | only IPv6 FIB created |
Binding interfaces to a VRF
Interfaces carry a vrf field that takes the COSI id of a VRFs.routing.siha
resource. Omitting it (or leaving it empty) binds the interface to the default
table 0.
For routed interfaces (PMD, host-interface, loopback, tap, VLAN/QinQ subinterfaces):
type: Interfaces.link.siha
metadata: { namespace: link, id: ext-tenant-a }
spec:
kind: pmd
pciAddr: "0000:05:00.0"
adminUp: true
addresses: ["10.100.0.1/24"]
vrf: tenant-a # binds this interface to the tenant-a FIB table
For bridge-domain interfaces the VRF goes on the BVI (the bridge’s L3 gateway), not on the bridge itself:
type: Interfaces.link.siha
metadata: { namespace: link, id: lan }
spec:
kind: bridge
adminUp: true
members: [lan-nic]
bvi:
addresses: ["10.20.0.1/24"]
vrf: tenant-a # BVI bound to tenant-a; top-level vrf must be omitted
When an interface is bound to a VRF and pilotd assigns it an address, VPP
automatically installs a connected route for the interface’s prefix into
that table. You do not need a Route resource for it.
Static routes
Use a Route resource for destinations that are not directly connected —
a default route, a summary prefix, or a remote subnet reachable via an
upstream gateway.
type: Routes.routing.siha
metadata:
namespace: routing
id: default-tenant-a # any unique id
spec:
destination: "0.0.0.0/0" # prefix to match (CIDR notation)
gateway: "10.100.0.254" # next-hop address (must be reachable in the VRF)
vrf: tenant-a # COSI id of the target VRF (omit for default table 0)
The vrf field resolves to the VRF’s numeric table id at reconcile time. If
the referenced VRF does not exist yet, the controller surfaces an error in
RouteStatus.lastError and retries — there is no hard ordering requirement in
the manifest, but the VRF must exist before the route is installed in VPP.
A route in the default table (no VRF) omits the vrf field entirely:
type: Routes.routing.siha
metadata: { namespace: routing, id: default }
spec:
destination: "0.0.0.0/0"
gateway: "192.168.1.1"
Full example: two isolated tenants
# --- Tenant A VRF ---
type: VRFs.routing.siha
metadata: { namespace: routing, id: tenant-a }
spec:
table: 100
description: "Tenant A"
---
# --- Tenant B VRF ---
type: VRFs.routing.siha
metadata: { namespace: routing, id: tenant-b }
spec:
table: 200
description: "Tenant B"
---
# Tenant A uplink (connected /24 auto-installed, no Route needed)
type: Interfaces.link.siha
metadata: { namespace: link, id: uplink-a }
spec:
kind: pmd
pciAddr: "0000:05:00.0"
adminUp: true
addresses: ["10.100.0.1/24"]
vrf: tenant-a
---
# Tenant A default route via its upstream gateway
type: Routes.routing.siha
metadata: { namespace: routing, id: default-a }
spec:
destination: "0.0.0.0/0"
gateway: "10.100.0.254"
vrf: tenant-a
---
# Tenant B uplink
type: Interfaces.link.siha
metadata: { namespace: link, id: uplink-b }
spec:
kind: pmd
pciAddr: "0000:06:00.0"
adminUp: true
addresses: ["10.200.0.1/24"]
vrf: tenant-b
---
# Tenant B default route
type: Routes.routing.siha
metadata: { namespace: routing, id: default-b }
spec:
destination: "0.0.0.0/0"
gateway: "10.200.0.254"
vrf: tenant-b
Traffic between tenant-a and tenant-b cannot cross unless you explicitly add routes that bridge the two tables. VPP enforces the FIB isolation.
Operating
S="sihactl --sihaconfig ~/.siha/config --addr <node-ip>:6443"
# List all VRFs with their table id, family, and any error
$S get vrf
# Full spec + status for a single VRF
$S get vrf tenant-a -o yaml
# List all static routes with destination, gateway, resolved table, and errors
$S get route
# Full detail for one route
$S get route default-a -o yaml
# Watch routes as changes are applied
$S watch route
VRFStatus shows the VPP table id and address family as materialized.
RouteStatus shows the resolved vrfTable (the numeric FIB table the route
was installed in), which is useful for confirming that a VRF reference resolved
correctly.
Callouts
Table 0 is reserved. The implicit default VRF always uses table 0; siha rejects any
VRFs.routing.siharesource whosetablefield is 0. Use any non-zero integer (1–4294967295) for operator-defined VRFs.
Connected routes are automatic. When pilotd assigns an address to an interface bound to a VRF, VPP installs the connected prefix automatically. Do not add a
Routefor the interface’s own subnet — a duplicate static route is harmless but unnecessary.
VRF must exist before its routes are installed. The route controller retries on a missing VRF reference, but there is a brief window where
RouteStatus.lastErrorshows a resolution error. Apply the VRF resource first (or in the same bundle) to avoid the retry cycle.
BGP learned routes live alongside static routes in the same FIB. The BGP controller programs its best paths into VPP FIB tables using the same table ids. If you combine BGP peers with static routes in the same VRF, make sure their prefixes don’t conflict — the more-specific prefix wins in VPP’s longest-prefix match, same as any router.