Skip to main content

WireGuard & VRFs

Encrypted tunnels, optionally isolated in their own routing domain. The common case is reaching a RADIUS server across the internet without exposing it: the tunnel lives in a dedicated VRF, and RADIUS is pointed at that VRF.

VRFs

A VRF declared here is created and owned by the BNG — abngd builds the master device and its routing table on commit, and rebuilds it at startup so it survives a reboot. VRFs created outside the config (the management VRF from the installer, the CGNAT VRF) are left alone.

vrfs:
vrf-radius:
table: 300
description: "RADIUS transport over WireGuard"
abng# set vrfs vrf-radius table 300
abng# set vrfs vrf-radius description "RADIUS transport over WireGuard"
Table ids must be unique

Two VRFs sharing a routing table silently merge their routes. On this platform that exact collision (the management VRF and a ZeroTier VRF both on table 100) crashed zebra and took BGP down. A duplicate table is therefore refused at commit, not warned about.

WireGuard tunnels

wireguard:
wg-radius:
enabled: true
private_key: "ACQ2t/…="
addresses: ["10.255.10.2/30"]
mtu: 1420 # default
vrf: vrf-radius # tunnel interface is enslaved here
listen_port: 51820 # optional — omit for outbound-only
peers:
- public_key: "kYwptim8…="
endpoint: "160.30.37.132:51820"
allowed_ips: ["10.255.10.1/32"]
persistent_keepalive: 25

Everything is settable from the CLI:

abng# set vrfs vrf-radius table 300
abng# set wireguard wg-radius enabled true
abng# set wireguard wg-radius private_key ACQ2t/…=
abng# set wireguard wg-radius addresses 10.255.10.2/30
abng# set wireguard wg-radius vrf vrf-radius
abng# set wireguard wg-radius peers kYwptim8…= endpoint 160.30.37.132:51820
abng# set wireguard wg-radius peers kYwptim8…= allowed_ips 10.255.10.1/32
abng# set wireguard wg-radius peers kYwptim8…= persistent_keepalive 25
abng# commit

What the commit does

The tunnel is reconciled to exactly what the config declares:

  • the interface is created if missing, and removed if it is deleted from the config or set enabled: false
  • peers no longer in the config are removed from the running tunnel
  • allowed_ips is always re-sent, so withdrawing a prefix actually withdraws it
  • addresses are flushed and re-applied (the IPv6 link-local is preserved)
  • the interface is enslaved to its VRF before addressing, because moving an interface between VRFs flushes its addresses

A tunnel that fails to apply produces a warning and does not abort the commit — one bad peer should not roll back an unrelated change.

Overlay vs underlay

vrf enslaves the tunnel interface, so the traffic inside the tunnel is in that VRF. The encrypted UDP to the peer's endpoint still leaves by the global routing table — which is what you want when the peer is reached over the upstream. Use fwmark if you need to policy-route the encrypted side too.

Keys

private_key and preshared_key are base64 WireGuard keys (44 characters ending in =); a malformed key is rejected at commit rather than by wg halfway through applying. They are stored in the running config alongside the RADIUS secrets, which is written 0640 and owned by the service user. abngd writes keys to a 0600 temp file to hand to wg, never on the command line, and removes it immediately.

Generate a keypair with:

wg genkey | tee private.key | wg pubkey > public.key

Putting RADIUS in the tunnel

Point RADIUS at the far end of the tunnel and tell it which VRF to use:

abng# set radius servers 10.255.10.1 secret <shared-secret>
abng# set radius vrf vrf-radius
abng# set radius source_address 10.255.10.2
abng# commit

radius vrf policy-routes the server addresses into that VRF's table, and source_address fixes the source the server's client list is keyed on. See RADIUS.

note

The RADIUS server must be listening on its tunnel address. If it is bound only to its public interface you will see the tunnel work but the request get ICMP port unreachable:

10.255.10.2.45255 > 10.255.10.1.1812: RADIUS, Access-Request
10.255.10.1 > 10.255.10.2: ICMP udp port 1812 unreachable

Verifying

wg show wg-radius                              # handshake + transfer counters
ip -br addr show wg-radius # address
ip -o link show wg-radius | grep master # VRF membership
ip vrf exec vrf-radius ping 10.255.10.1 # reachability inside the VRF
ip route show table 300 # the VRF's routes

A healthy tunnel shows a recent latest handshake and rising transfer counters. No handshake means the encrypted underlay is not reaching the endpoint — check that the peer's endpoint is routable from the global table and that UDP is not being filtered.

Requirements

wireguard-tools (a package dependency) and the in-tree wireguard kernel module, which is standard on the Debian 13 kernel this BNG runs.