WireGuard VPN with OSPF Dynamic Routing

This recipe builds upon the basic WireGuard Site-to-Site Example and the OSPF recipe to demonstrate a WireGuard VPN setup that exchanges dynamic routing information using OSPF.

WireGuard has special peer routing requirements which prevent a normal OSPF configuration from working as laid out in the other OSPF examples. WireGuard interfaces are non-broadcast so OSPF cannot automatically locate neighbors using multicast.

Note

BGP works with WireGuard without special handling, so the existing BGP examples are still valid for scenarios involving WireGuard.

Environment

To keep things simple, this recipe uses the same environment described out in Example WireGuard Configuration.

The OSPF configuration is similar to the OSPF recipe.

The bulk of the configuration is identical to that found in WireGuard Site-to-Site Example so while much of the commands are copied here, refer back to that example for more information on each section.

Note

Some basic setup is omitted from this recipe for brevity. Refer back to Zero-to-Ping: Getting Started or individual sections for things like basic interface, default route, DHCP, and DNS configuration.

Configure WireGuard

This is a brief summary of the WireGuard configuration demonstrated in WireGuard Site-to-Site Example. Refer back to that document for details.

Configure WireGuard on R1

r1 tnsr(config)# interface wireguard 1
r1 tnsr(config-wireguard)# description WireGuard P2P - R1-R2
r1 tnsr(config-wireguard)# source-address 203.0.113.2
r1 tnsr(config-wireguard)# port 51820
r1 tnsr(config-wireguard)# private-key base64 IPbehUo58KvYl/qmA+50bAaWeXgB+eP+8QqmDkLV9XA=
r1 tnsr(config-wireguard)# peer 1
r1 tnsr(config-wireguard-peer)# description R2
r1 tnsr(config-wireguard-peer)# endpoint-address 203.0.113.25
r1 tnsr(config-wireguard-peer)# port 51820
r1 tnsr(config-wireguard-peer)# allowed-prefix 0.0.0.0/0
r1 tnsr(config-wireguard-peer)# public-key base64 kIGM3jon1y43ZiCh9YryxNNfda/Qh5d1aBHSfKZbYTA=
r1 tnsr(config-wireguard-peer)# exit
r1 tnsr(config-wireguard)# exit
r1 tnsr(config)# interface wg1
r1 tnsr(config-interface)# enable
r1 tnsr(config-interface)# description WireGuard P2P - R1-R2
r1 tnsr(config-interface)# ip address 10.2.111.1/30
r1 tnsr(config-interface)# exit
r1 tnsr(config)# tunnel next-hops wg1
r1 tnsr(config-tunnel-nh-if)# ipv4-tunnel-destination 10.2.111.2 ipv4-next-hop-address 203.0.113.25
r1 tnsr(config-tunnel-nh-if)# exit

There are two notable differences here vs the setup in WireGuard Site-to-Site Example:

  • The allowed-prefix for the peer is 0.0.0.0/0 – this is safe as this WireGuard tunnel only has a single peer, so any traffic on this WireGuard interface must be going to/from the single peer. This allows the peers to use whichever routes OSPF exchanges without having to list each network statically.

  • There is no manual static route as the routes will be handled via OSPF.

Configure WireGuard on R2

Now configure WireGuard on R2 in a similar manner, with the same deviations from the example:

r2 tnsr(config)# interface wireguard 1
r2 tnsr(config-wireguard)# description WireGuard P2P - R2-R1
r2 tnsr(config-wireguard)# source-address 203.0.113.25
r2 tnsr(config-wireguard)# port 51820
r2 tnsr(config-wireguard)# private-key base64 EIe79EjECubUeIw+6EKkXOLeOIoFgxM33ydRyr2IJWE=
r2 tnsr(config-wireguard)# peer 1
r2 tnsr(config-wireguard-peer)# description R1
r2 tnsr(config-wireguard-peer)# endpoint-address 203.0.113.2
r2 tnsr(config-wireguard-peer)# port 51820
r2 tnsr(config-wireguard-peer)# allowed-prefix 0.0.0.0/0
r2 tnsr(config-wireguard-peer)# public-key base64 K/l2cD3PCCioSnerIe7tOSAqyRQ8dB1LAoeiJqn0uiY=
r2 tnsr(config-wireguard-peer)# exit
r2 tnsr(config-wireguard)# exit
r2 tnsr(config)# interface wg1
r2 tnsr(config-interface)# enable
r2 tnsr(config-interface)# description WireGuard P2P - R2-R1
r2 tnsr(config-interface)# ip address 10.2.111.2/30
r2 tnsr(config-interface)# exit
r2 tnsr(config)# tunnel next-hops wg1
r2 tnsr(config-tunnel-nh-if)# ipv4-tunnel-destination 10.2.111.1 ipv4-next-hop-address 203.0.113.2
r2 tnsr(config-tunnel-nh-if)# exit

At this point the WireGuard tunnel should be capable of connecting and pinging between the wgX interfaces on the peers, but not between the local networks yet.

Configure OSPF

Now it’s time to setup the dynamic routing in OSPF. This summarizes the example configuration from the OSPF ABR recipe except with WireGuard as the active OSPF interface instead of a physical port.

Configure OSPF on R1

First, setup OSPF with the appropriate areas, interfaces, etc. This part is nearly identical to the example but it uses the wg1 WireGuard interface as the backbone connection.

r1 tnsr(config)# route dynamic ospf
r1 tnsr(config-frr-ospf)# server vrf default
r1 tnsr(config-ospf)# ospf router-id 10.2.0.1
r1 tnsr(config-ospf)# passive-interface LAN
r1 tnsr(config-ospf)# neighbor 10.2.111.2
r1 tnsr(config-ospf)# area 0.0.0.2
r1 tnsr(config-ospf-area)# stub
r1 tnsr(config-ospf-area)# range 10.2.0.0/23
r1 tnsr(config-ospf-area)# exit
r1 tnsr(config-ospf)# exit
r1 tnsr(config-frr-ospf)# interface LAN
r1 tnsr(config-ospf-if)# ip address * area 0.0.0.2
r1 tnsr(config-ospf-if)# exit
r1 tnsr(config-frr-ospf)# interface wg1
r1 tnsr(config-ospf-if)# ip address * cost 5
r1 tnsr(config-ospf-if)# ip address * area 0.0.0.0
r1 tnsr(config-ospf-if)# ip network non-broadcast
r1 tnsr(config-ospf-if)# exit
r1 tnsr(config-frr-ospf)#
r1 tnsr(config-frr-ospf)# enable
r1 tnsr(config-frr-ospf)# exit

The above example is complete but contains two key differences, which are:

First, the WireGuard interface must be set to non-broadcast:

r1 tnsr(config-frr-ospf)# interface wg1
r1 tnsr(config-ospf-if)# ip network non-broadcast

Second, the WireGuard address of the peer must be explicitly configured as a neighbor since OSPF cannot automatically discover neighbors on non-broadcast interfaces.

r1 tnsr(config-ospf)# neighbor 10.2.111.2

Configure OSPF on R2

Now configure OSPF on R2 in a similar manner, with the same deviations from the example:

r2 tnsr(config)# route dynamic ospf
r2 tnsr(config-frr-ospf)# server vrf default
r2 tnsr(config-ospf)# ospf router-id 10.25.0.1
r2 tnsr(config-ospf)# passive-interface LAN
r2 tnsr(config-ospf)# neighbor 10.2.111.1
r2 tnsr(config-ospf)# area 0.0.0.25
r2 tnsr(config-ospf-area)# stub
r2 tnsr(config-ospf-area)# range 10.25.0.0/23
r2 tnsr(config-ospf-area)# exit
r2 tnsr(config-ospf)# exit
r2 tnsr(config-frr-ospf)# interface LAN
r2 tnsr(config-ospf-if)# ip address * area 0.0.0.25
r2 tnsr(config-ospf-if)# exit
r2 tnsr(config-frr-ospf)# interface wg1
r2 tnsr(config-ospf-if)# ip address * cost 5
r2 tnsr(config-ospf-if)# ip address * area 0.0.0.0
r2 tnsr(config-ospf-if)# ip network non-broadcast
r2 tnsr(config-ospf-if)# exit
r2 tnsr(config-frr-ospf)# enable
r2 tnsr(config-frr-ospf)# exit

At this point the routers should start forming an OSPF neighbor adjacency and exchanging routes.

Check Status

Ping the VPN Peer

First, ping between the WireGuard interfaces to validate peer-to-peer VPN connectivity:

r1 tnsr# ping 10.2.111.2 source 10.2.111.1 count 5
PING 10.2.111.2 (10.2.111.2) from 10.2.111.1 : 56(84) bytes of data.
64 bytes from 10.2.111.2: icmp_seq=1 ttl=64 time=3.46 ms
64 bytes from 10.2.111.2: icmp_seq=2 ttl=64 time=0.247 ms
64 bytes from 10.2.111.2: icmp_seq=3 ttl=64 time=0.236 ms
64 bytes from 10.2.111.2: icmp_seq=4 ttl=64 time=0.259 ms
64 bytes from 10.2.111.2: icmp_seq=5 ttl=64 time=0.230 ms

--- 10.2.111.2 ping statistics ---
5 packets transmitted, 5 received, 0% packet loss, time 4063ms
rtt min/avg/max/mdev = 0.230/0.887/3.464/1.288 ms

Testing one direction is likely sufficient, but there is no harm in repeating the test from the other perspective.

If the ping fails, ensure the two peers can communicate outside of WireGuard, check the WireGuard configuration completely, and also check the tunnel next-hop configuration.

Tip

If the external TNSR interface (e.g. “WAN”) on one or both routers has ACLs restricting traffic, ensure that the ACLs are configured to allow the WireGuard UDP traffic to pass.

Check OSPF

First check if the two peers have formed a full neighbor adjacency.

A working setup will have output similar to the following:

r1 tnsr# show route dynamic ospf neighbor
VRF Name: default

Neighbor ID     Pri State           Dead Time Address         Interface                        RXmtL RqstL DBsmL
10.25.0.1         1 Full/DR           39.699s 10.2.111.2      wg1:10.2.111.1                     0     0     0
r2 tnsr# show route dynamic ospf neighbor
VRF Name: default

Neighbor ID     Pri State           Dead Time Address         Interface                        RXmtL RqstL DBsmL
10.2.0.1          1 Full/Backup       36.141s 10.2.111.1      wg1:10.2.111.2                      0     0     0

If there are no neighbors listed then the two peers cannot communicate. If the neighbors are listed but with a different status (e.g. ExStart, Other) then it is possible they are still negotiating. Give them a few moments and check again. If the status is not progressing, then something must be incorrect in the OSPF or interface settings. Ensure the setup matches and try again.

Check Routes

If both neighbors show a Full adjacency they should also be exchanging routes. If they exchanged routes, each peer will have a route in its table that matches the summary route configured in the stub area in OSPF on the peer.

For example:

r1 tnsr# show route table default
Route Table default  AF: ipv4  ID: 0
-----------------------------------------
[...]
10.25.0.0/23       via 10.2.111.2         wg1 weight 1 preference 20
[...]
r2 tnsr# show route table default
Route Table default  AF: ipv4  ID: 0
-----------------------------------------
[...]
10.2.0.0/23        via 10.2.111.1         wg1 weight 1 preference 20
[...]

If the two peers have a full adjacency but the routes are missing, double check the tunnel next-hop configuration for the WireGuard peers. If that is not correct OSPF will drop routes as it will believe the neighbor is not directly connected.

Ping from LAN to LAN

With the routes in the table it should be possible to ping between the local networks on each side:

r1 tnsr# ping 10.25.0.1 source 10.2.0.1 count 5
PING 10.25.0.1 (10.25.0.1) from 10.2.0.1 : 56(84) bytes of data.
64 bytes from 10.25.0.1: icmp_seq=1 ttl=64 time=0.300 ms
64 bytes from 10.25.0.1: icmp_seq=2 ttl=64 time=0.223 ms
64 bytes from 10.25.0.1: icmp_seq=3 ttl=64 time=0.217 ms
64 bytes from 10.25.0.1: icmp_seq=4 ttl=64 time=0.205 ms
64 bytes from 10.25.0.1: icmp_seq=5 ttl=64 time=0.262 ms

--- 10.25.0.1 ping statistics ---
5 packets transmitted, 5 received, 0% packet loss, time 4094ms
rtt min/avg/max/mdev = 0.205/0.241/0.300/0.034 ms

Testing one direction is likely sufficient, but there is no harm in repeating the test from the other perspective.