High Availability WireGuard Site to Site
WireGuard makes it easy to set up a private connection between two networks, whether they’re simply different subnets in the same physical office or data center, or far-flung sites separated by continents or oceans. This article will show you how to set up multiple WireGuard routers at each connected site for redundancy — so that if one router goes down (or the link it’s using goes down) traffic will automatically failover to a router that’s still up and operational.
In the example we’ll cover, we’ll have two sites: Site A and Site B. We’ll set up a vanilla Linux box with WireGuard running on it as Router 1 in Site A, and connect it with WireGuard to a similar Router 3 in Site B. Then we’ll set up a parallel connection between a second WireGuard router in Site A, Router 2, and a second WireGuard router in Site B, Router 4:
We’ll use OSPF (Open Shortest Path First, an Internal Gateway Protocol, or IGP) to track the state of the links between the two parallel routes (Router 1 ←→ Router 3, and Router 2 ←→ Router 4), which we can propagate to the sites via OSPF or via iBGP (Interior Border Gateway Protocol). That way, if the link used by one route goes down, the site’s own LAN (Local Area Network) routers will detect it and replace it with the other route within a few seconds.
These are the steps we’ll cover:
Set Up WireGuard Routers
We’ll set up each of the four WireGuard routers all the same way: install Linux on it (any modern Linux distro will be fine and work basically the same), then:
This will build two parallel WireGuard VPNs (Virtual Private Networks): one between Router 1 and Router 3, and the other between Router 2 and Router 4:
Router 1 will have a private WireGuard IP address of 10.99.13.1
, but a public IP address of 198.51.100.10
(and an IP address of 192.168.1.101
within Site A’s LAN). Router 3 will have a WireGuard address of 10.99.13.3
, but a public IP address of 203.0.113.33
(and an IP address of 192.168.200.3
within Site B’s LAN). Router 1 will connect to Router 3’s public IP address over UDP port 51820
, and vice versa.
Similarly, Router 2 will have a WireGuard address of 10.99.24.2
, but a public IP address of 198.51.100.222
(with an IP address of 192.168.1.202
within Site A’s LAN). And Router 4 will have a WireGuard address of 10.99.24.4
, with a public IP address of 203.0.113.244
(and an IP address of 192.168.200.224
within Site B’s LAN). Router 2 will also connect to Router 4’s public IP address over UDP port 51820
, and vice versa.
Set Up WireGuard
On each router, install WireGuard. On Debian or Ubuntu, it’s as simple as sudo apt install wireguard
. For other distros, refer to the WireGuard Install page.
Then generate a WireGuard public-key pair for each router. You can do it with the following one-liner:
$ wg genkey | tee /dev/stderr | wg pubkey
0F11111111111111111111111111111111111111110=
hN+4fjPihcKbEDFQhqrMmW8oPqqaT4CUQYtyg3FUx3k=
The first line you see will be the private key (which should be much more random than the above contrived example); the second line will be the public key. The private key goes in the router’s own configuration file; the public key goes in the configuration file of the corresponding router to which it connects.
Next, create a WireGuard configuration file at /etc/wireguard/wg0.conf
. On Router 1, put the following settings in it:
# /etc/wireguard/wg0.conf on Router 1
# local settings for Router 1
[Interface]
PrivateKey = 0F11111111111111111111111111111111111111110=
Address = 10.99.13.1/24
ListenPort = 51820
Table = off
# remote settings for Router 3
[Peer]
PublicKey = IZ2rER/G68c1LCHeXIkpHqKYT7zB5bLkNULSSJ5HI1U=
AllowedIPs = 0.0.0.0/0, ::/0
Endpoint = 203.0.113.33:51820
Replace the dummy PrivateKey
value above with the actual private key you generated for Router 1, and replace the PublicKey
value with the actual public key you generated for Router 3. Replace the Endpoint
value with the actual public IP address and UDP port at which Router 1 can connect to Router 3.
You can use a different UDP port than 51820
for any or all of the routers — just make sure that the port you specify for the ListenPort
in Router 1’s config file matches the Endpoint
port you specify in Router 3`s config file, and vice versa (or that you translate the destination port from the port specified in the Endpoint
setting in one config file to the ListenPort
specified in the other at some hop between Router 1 and Router 3).
You can also use a different Address
value. This address will just be used within the private network between Router 1 and Router 3, so you can set it to anything convenient — just make sure that the addresses you choose for Router 1 and Router 3 use the same subnet (in this example, they’re both in 10.99.13.0/24
), and that their subnet doesn’t contain any addresses from Site A or Site B that they may have to route.
Do not change the Table = off
setting, which directs wg-quick
not to add any routes based on AllowedIPs
when it sets up the interface. Also do not change the AllowedIPs = 0.0.0.0/0, ::/0
setting, which directs WireGuard to pass all packets sent through the interface to Router 3, irrespective of the packet’s destination address.
On Router 3, put the following settings in /etc/wireguard/wg0.conf
:
# /etc/wireguard/wg0.conf on Router 3
# local settings for Router 3
[Interface]
PrivateKey = 2H33333333333333333333333333333333333333330=
Address = 10.99.13.3/24
ListenPort = 51820
Table = off
# remote settings for Router 1
[Peer]
PublicKey = hN+4fjPihcKbEDFQhqrMmW8oPqqaT4CUQYtyg3FUx3k=
AllowedIPs = 0.0.0.0/0, ::/0
Endpoint = 198.51.100.10:51820
Set up /etc/wireguard/wg0.conf
similarly on Router 2 and Router 4, with corresponding settings that allow them to connect to each other. On Router 2, like this:
# /etc/wireguard/wg0.conf on Router 2
# local settings for Router 2
[Interface]
PrivateKey = 2G22222222222222222222222222222222222222220=
Address = 10.99.24.2/24
ListenPort = 51820
Table = off
# remote settings for Router 4
[Peer]
PublicKey = 4QbAocAtphAuEXj5txnxL7BIYAmAmy9+Qyql0i34hiI=
AllowedIPs = 0.0.0.0/0, ::/0
Endpoint = 203.0.113.244:51820
And on Router 4, like this:
# /etc/wireguard/wg0.conf on Router 4
# local settings for Router 4
[Interface]
PrivateKey = 4I44444444444444444444444444444444444444404=
Address = 10.99.24.4/24
ListenPort = 51820
Table = off
# remote settings for Router 2
[Peer]
PublicKey = 777Ntbnv/AA9Fvd53yVR6Fsrd02CNCM2ySXgzXbbSUI=
AllowedIPs = 0.0.0.0/0, ::/0
Endpoint = 198.51.100.222:51820
Set up WireGuard to start on system boot, and start it running now, with the following systemd command:
$ sudo systemctl enable --now wg-quick@wg0.service
Once WireGuard is up and running on each router, you should be able to ping Router 3 from Router 1 (and vice versa):
$ ping -nc1 10.99.13.3
PING 10.99.13.3 (10.99.13.3) 56(84) bytes of data.
64 bytes from 10.99.13.3: icmp_seq=1 ttl=64 time=20.3 ms
--- 10.99.13.3 ping statistics ---
1 packets transmitted, 1 received, 0% packet loss, time 0ms
rtt min/avg/max/mdev = 20.325/20.325/20.325/0.000 ms
You should also be able to ping Router 4 from Router 2 (and vice versa). See the WireGuard Site to Site Configuration guide for more details about the above WireGurad settings, and for troubleshooting tips.
Configure Packet Forwarding
Next, to enabling packet forwarding, create a /etc/sysctl.d/local.conf
file, and set it to this content:
net.ipv4.conf.all.forwarding=1
Then run the following command to apply it (along with all other sysctl conf files):
sudo sysctl --system
Install BIRD
Finally, install the BIRD 2.x routing daemon. On most Linux distros, it’s available via the bird
package; on Debian and Ubuntu, it’s the bird2
package (install it with sudo apt install bird2
).
BIRD 1.x will also work, but uses slightly different configuration settings — it doesn’t support both IPv4 and IPv6 at the same time (you have to configure it to use one or the other), so it doesn’t support the ip4 {}
and ipv6 {}
channel options that the BIRD 2.x configuration requires. (Other routing daemons, like FRR or Quagga, will also work fine — just with a different configuration syntax.)
Configure OSPF Between WireGuard Routers
At this point, we have WireGuard up and running on all four WireGuard routers, with Router 1 connected to Router 3, and Router 2 connected to Router 4. But because we put Table = off
in their WireGuard configuration, none are set up yet to actually route packets beyond their own WireGuard subnet (10.99.13.0/24
for the connection between Router 1 and Router 3, and 10.99.24.0/24
for the connection between Router 2 and Router 4).
So we’ll configure the BIRD routing daemon with OSPF on each router in order to:
-
Check whether the link between each router and its pair is up
-
Exchange routes between the paired routers
-
Add the exchanged routes to the router’s main routing table
We’ll assign Router 1 a router ID of 10.99.13.1
, Router 3 a router ID of 10.99.13.3
, and use the 10.99.13.0
OSPF area for their connection to each other; and we’ll assign Router 2 a router ID of 10.99.24.2
, Router 4 a router ID of 10.99.24.4
, and use the 10.99.24.0
OSPF area for their connection to each other:
Note
|
Even though router and area IDs look like IP addresses, they’re actually just arbitrary 32-bit numbers that are by convention written in dotted-quad notation. Although it’s often a good idea to make them match the IP addresses of the routers and subnets they represent, it’s not required — for example, you could assign |
On Router 1, edit its /etc/bird/bird.conf
file to make it look like the following:
router id 10.99.13.1;
protocol device {
}
protocol kernel {
ipv4 {
export where proto = "wg";
};
}
protocol ospf v2 wg {
ipv4 {
import where net !~ 10.99.13.0/24;
export all;
};
area 10.99.13.0 {
interface "wg0";
};
}
Tip
|
White-space is not significant in BIRD configuration — for example, the above multi-line |
The router id
line sets the global ID for this router (used by both OSPF and BGP). The protocol device
block configures BIRD to periodically sync its interface list from the OS, so as to detect when interfaces are added, removed, or updated.
The protocol kernel
block syncs the OS’s main
IPv4 routing table with BIRD’s internal master4
routing table. By default all routes are imported from the OS’s table into BIRD’s table, but none are exported from BIRD’s table to the OS’s table. The export where proto = "wg";
line configures BIRD to export to the OS’s main
table all the routes it added to its master4
table from the named wg
protocol instance — in other words, to copy all the routes it learns from OSPF about the other site into its own active routing table.
Note
|
This guide assumes that the WireGuard routers themselves will be pre-configured with all the routes they need to forward packets within their own site, either statically or via DHCP (ie they can just forward packets that need to be routed to other subnets within their own site to the their own default gateway, and the default gateway will be able to forward those packets to the next hop). If the WireGuard routers themselves need to make use of the routes they receive from OSPF or iBGP about their own site, then change the |
The protocol ospf v2 wg
block sets up the OSPFv2 instance we’ll use for the WireGuard connection. Note that wg
is just an arbitrary internal ID we’ve given to this instance — BIRD would have otherwise given it an ID of ospf1
by default. And by default it will import all the routes it receives over OSPF into its master4
routing table; but export none of the routes from its master4
table to OSPF. The export all;
line in this block overrides this export default to export all of the routes from its master4
table to OSPF.
The import where net !~ 10.99.13.0/24;
line configures BIRD to avoid pulling in any routes to the 10.99.13.0/24
subnet from OSPF — this is the subnet we’re using for the WireGuard connection between Router 1 and Router 3. The route for 10.99.13.0/24
is already set up in the OS’s main
table by wg-quick
when it starts up the WireGuard connection, so the OS doesn’t need to learn this route from BIRD; and since this route is only used behind the scenes by the private connection between the WireGuard routers, there’s no need to propagate it to other routers.
The area 10.99.13.0
block configures BIRD to use the area with an ID of 10.99.13.0
for OSPF on the specified interfaces (and as mentioned above, 10.99.13.0
is just an arbitrary 32-bit number, not necessarily related to an IP address or subnet). The interface "wg0";
line specifies that the wg0
WireGuard interface we set up in the section above is included in the area definition.
Other than the custom import and export filters, this OSPF configuration just uses BIRD’s default settings for everything — which is perfect for WireGuard (as long as all WireGuard routers in the same area use the same subnet — 10.99.13.0/24
in the case of Router 1 and Router 3). It will listen for and send out OSPF broadcasts on the wg0
interface, importing the routes learned from OSPF into its master4
table, and exporting the other routes in its master4
table to share over OSPF.
Warning
|
This first few sections of this guide show how to use OSPFv2, and will not work with OSPFv3. While you can use OSPFv3 to exchange IPv4 routes, OSPFv3 exclusively uses IPv6 link-local connections to do so — so you first need to have IPv6 link-local connections set up and running between your routers before you can use OSPFv3. See the Alternatively Use IPv6 section later for OSPFv3 instructions. |
On Router 3, edit its /etc/bird/bird.conf
file to match the settings of Router 1, with the exception of using Router 3’s own unique router ID:
router id 10.99.13.3;
protocol device {
}
protocol kernel {
ipv4 {
export where proto = "wg";
};
}
protocol ospf v2 wg {
ipv4 {
import where net !~ 10.99.13.0/24;
export all;
};
area 10.99.13.0 {
interface "wg0";
};
}
On Router 2, edit its /etc/bird/bird.conf
file to set it up similarly to Router 1 and Router 3, but skipping the import of its own WireGuard subnet (10.99.24.0/24
), and using a different area ID of 10.99.24.0
for its shared area with Router 4:
router id 10.99.24.2;
protocol device {
}
protocol kernel {
ipv4 {
export where proto = "wg";
};
}
protocol ospf v2 wg {
ipv4 {
import where net !~ 10.99.24.0/24;
export all;
};
area 10.99.24.0 {
interface "wg0";
};
}
And on Router 4, edit its /etc/bird/bird.conf
file to match the settings of Router 2, except with Router 4’s own router ID:
router id 10.99.24.4;
protocol device {
}
protocol kernel {
ipv4 {
export where proto = "wg";
};
}
protocol ospf v2 wg {
ipv4 {
import where net !~ 10.99.24.0/24;
export all;
};
area 10.99.24.0 {
interface "wg0";
};
}
Run the following command on each router to start up an interactive command-line client to BIRD:
$ sudo birdc
BIRD 2.0.7 ready.
Then run the following command in the BIRD client to reload your changes to /etc/bird/bird.conf
:
bird> configure
Reading configuration from /etc/bird/bird.conf
Reconfigured
After reloading your changes on both Router 1 and Router 3, if you run the following command from Router 1, you should see that it’s communicating with Router 3:
bird> show ospf neighbors wg
wg:
Router ID Pri State DTime Interface Router IP
10.99.13.3 1 Full/PtP 35.956 wg0 10.99.13.3
But it won’t have imported any routes into BIRD’s default IPv4 routing table yet:
bird> show route
Troubleshooting
If you don’t see any neighbors, first double-check that Router 1 can ping Router 3:
$ ping -nc1 10.99.13.3
PING 10.99.13.3 (10.99.13.3) 56(84) bytes of data.
64 bytes from 10.99.13.3: icmp_seq=1 ttl=64 time=20.3 ms
--- 10.99.13.3 ping statistics ---
1 packets transmitted, 1 received, 0% packet loss, time 0ms
rtt min/avg/max/mdev = 20.325/20.325/20.325/0.000 ms
If that doesn’t work, your WireGuard connection isn’t working, and you need to go back and fix that first.
Otherwise, add a debug protocols all;
line to the top of the /etc/bird/bird.conf
file:
router id 10.99.13.1;
debug protocols all;
...
And reload your configuration:
bird> configure
Reading configuration from /etc/bird/bird.conf
Reconfigured
And then check BIRD’s syslog output. You should see periodic messages like:
$ journalctl -u bird.service
Oct 12 17:06:02 router1 bird[8106]: wg: HELLO packet sent via wg0
Oct 12 17:06:02 router1 bird[8106]: wg: HELLO packet received from nbr 10.99.13.3 on wg0
Oct 12 17:06:12 router1 bird[8106]: wg: HELLO packet sent via wg0
Oct 12 17:06:12 router1 bird[8106]: wg: HELLO packet received from nbr 10.99.13.3 on wg0
Oct 12 17:06:22 router1 bird[8106]: wg: HELLO packet sent via wg0
Oct 12 17:06:22 router1 bird[8106]: wg: HELLO packet received from nbr 10.99.13.3 on wg0
Plus occasional link-state updates.
Tip
|
You can “tail” BIRD’s syslog output with the following journald command:
|
Configure OSPF to LAN Routers
Now that the WireGuard links between Site A and Site B are up and exchanging routes, the next step is to propagate those routes to the sites. If you use OSPF at your sites, follow the instructions in this section to do so; if you use iBGP, follow the instructions from the Alternatively Configure BGP to LAN Routers section.
For this example, we’ll imagine that Router 1 is on the LAN subnet 192.168.1.64/26
, and will exchange routes with its LAN router 192.168.1.65
in OSPF area 0
; Router 2 is on the LAN subnet 192.168.1.192/26
, and will exchange routes with its LAN router 192.168.1.193
, also in area 0
; Router 3 is on the LAN subnet 192.168.200.0/26
, and will exchange routes with its LAN router 192.168.200.1
in OSPF area 0
; and Router 4 is on the LAN subnet 192.168.200.192/26
, and will exchange routes with its LAN router 192.168.200.193
, also in area 0
:
Site A also has a few other subnets, 192.168.2.0/24
and 192.168.3.0/24
, that can be accessed through both the 192.168.1.65
and 192.168.1.193
gateways; while Site B has an additional subnet 192.168.200.128.0/26
.
On each WireGuard router, edit its /etc/bird/bird.conf
file to add a second protocol ospf
block:
protocol ospf v2 lan {
ipv4 {
export all;
};
area 0 {
interface "eth0";
};
}
Replace eth0
with the actual name of the router’s LAN interface (if it’s not eth0
), and replace the area ID 0
with the actual OSPF area ID you’re using for the LAN subnet the router is on (if not 0
). Also notice that we’ve assigned this protocol
block an arbitrary internal ID of lan
(otherwise BIRD would give it an autogenerated ID like ospf2
).
The export all;
line configures BIRD to export all the routes from its master4
table to share with OSPF. By default, BIRD will also import all the IPv4 routes it receives over OSPF into its master4
table. If you don’t want to propagate all of the site’s routes to the other site, add an import
filter here to the ipv4
channel to limit the routes that BIRD will pull in from this OSPF instance.
For example, to propagate only routes in the 192.168.1.0/24
subnet, add an import where net ~ 192.168.1.0/24;
line to the ipv4
block; or to import all routes except the 10.0.0.0/16
and 10.100.0.0/16
blocks, add import where net !~ 10.0.0.0/16 && net !~ 10.100.0.0/16;
; etc. BIRD includes a complete programming language for filtering routes, if you need something more sophisticated.
The full /etc/bird/bird.conf
file on Router 1 should now look like this:
router id 10.99.13.1;
protocol device {
}
protocol kernel {
ipv4 {
export where proto = "wg";
};
}
protocol ospf v2 wg {
ipv4 {
import where net !~ 10.99.13.0/24;
export all;
};
area 10.99.13.0 {
interface "wg0";
};
}
protocol ospf v2 lan {
ipv4 {
export all;
};
area 0 {
interface "eth0";
};
}
Now run the following command in the BIRD client to reload your changes to /etc/bird/bird.conf
:
bird> configure
Reading configuration from /etc/bird/bird.conf
Reconfigured
After reloading, you should see at least one neighbor for the new lan
OSPF instance:
bird> show ospf neighbors lan
lan:
Router ID Pri State DTime Interface Router IP
192.168.1.65 10 Full/DR 39.184 eth0 192.168.1.65
If you don’t, check the OSPF configuration on the LAN router, and adjust the WireGuard router’s lan
OSPF configuration to match. In particular, make sure the area ID and hello interval match (BIRD’s default hello interval is 10 seconds).
If you’ve only added the new lan
OSPF instance to one WireGuard router (like only to Router 1), BIRD will pick up the routes from the router’s own site only:
bird> show route
Table master4:
192.168.1.64/26 unicast [lan 18:25:47.947] I (150/10) [10.99.13.1]
dev eth0
unicast [lan 18:25:47.947] E1 (150/20) [192.168.1.65]
via 192.168.1.65 on eth0
192.168.1.192/26 unicast [lan 18:25:47.947] E1 (150/30) [192.168.1.193]
via 192.168.1.65 on eth0
192.168.3.0/24 unicast [lan 18:25:47.947] E1 (150/30) [192.168.1.1]
via 192.168.1.65 on eth0
192.168.4.0/24 unicast [lan 18:25:47.947] E1 (150/30) [192.168.1.1]
via 192.168.1.65 on eth0
But once you’ve updated all the routers, each router should have at least one path to all the routes exposed by OSPF on any LAN subnet in BIRD’s master4
table:
bird> show route
Table master4:
192.168.1.64/26 unicast [lan 18:25:47.947] I (150/10) [10.99.13.1]
dev eth0
unicast [lan 18:25:47.947] E1 (150/20) [192.168.1.65]
via 192.168.1.65 on eth0
192.168.1.192/26 unicast [lan 18:25:47.947] E1 (150/30) [192.168.1.193]
via 192.168.1.65 on eth0
192.168.3.0/24 unicast [lan 18:25:47.947] E1 (150/30) [192.168.1.1]
via 192.168.1.65 on eth0
192.168.4.0/24 unicast [lan 18:25:47.947] E1 (150/30) [192.168.1.1]
via 192.168.1.65 on eth0
192.168.200.0/26 unicast [wg 19:37:19.495] E1 (150/20) [10.99.13.3]
via 10.99.13.3 on wg0
unicast [lan 20:14:51.882] E1 (150/60) [192.168.200.1]
via 192.168.1.65 on eth0
192.168.200.128/26 unicast [wg 19:37:19.495] E1 (150/30) [192.168.200.1]
via 10.99.13.3 on wg0
unicast [lan 20:14:51.882] E1 (150/50) [192.168.200.193]
via 192.168.1.65 on eth0
192.168.200.192/26 unicast [wg 19:37:19.495] E1 (150/40) [192.168.200.193]
via 10.99.13.3 on wg0
unicast [lan 20:14:51.882] E1 (150/40) [10.99.24.4]
via 192.168.1.65 on eth0
And each router should have the best path for each subnet at the other site in its OS’s main
table (notice that the routes exported by BIRD will be listed as proto bird
):
$ ip route
default via 192.168.1.65 dev eth0 proto dhcp src 192.168.1.101 metric 100
10.99.13.0/24 dev wg0 proto kernel scope link src 10.99.13.1
192.168.1.64/26 dev eth0 proto kernel scope link src 192.168.1.101
192.168.1.65 dev eth0 proto dhcp scope link src 192.168.1.101 metric 100
192.168.200.0/26 via 10.99.13.3 dev wg0 proto bird metric 32
192.168.200.128/26 via 10.99.13.3 dev wg0 proto bird metric 32
192.168.200.192/26 via 10.99.13.3 dev wg0 proto bird metric 32
So you should now be able to ping Endpoint B (at 192.168.200.22
) in Site B from Endpoint A (at 192.168.1.91
) in Site A (provided you don’t have any firewalls blocking ICMP packets on some hop between the two hosts):
$ ping -nc1 192.168.200.22
PING 192.168.200.22 (192.168.200.22) 56(84) bytes of data.
64 bytes from 192.168.200.22: icmp_seq=1 ttl=59 time=23.3 ms
--- 192.168.200.22 ping statistics ---
1 packets transmitted, 1 received, 0% packet loss, time 0ms
rtt min/avg/max/mdev = 23.279/23.279/23.279/0.000 ms
And if you kill one of the WireGuard links between Site A and Site B (like for testing purposes, run sudo wg-quick down wg0
on Router 1), you should still be able to ping Endpoint B from Endpoint A — Endpoint A’s LAN router should receive the link-state update via OSPF, and re-route the traffic between Endpoint A and Endpoint B through the other WireGuard link.
Alternatively Configure BGP to LAN Routers
If you use use iBGP to exchange internal routes at one site (or both sites), you can use BGP instead of OSPF to propagate the routes exchanged by the WireGuard routers to the rest of the site.
For example, let’s say that Site A uses iBGP. We can configure Router 1 and Router 2 to use BGP, and connect them to a dedicated Route Reflector (RR) used by all the LAN routers at the site. The WireGuard routers will propagate the routes from Site B to the RR (and vice versa), and the RR will propagate the routes to the rest of the site:
The RR will be at 192.168.3.1
, and will use a private Autonomous System Number (ASN) of 65000
.
On Router 1 and Router 2, edit their /etc/bird/bird.conf
files to add a protocol direct
and a protocol bgp
block:
protocol direct {
ipv4;
interface "eth0";
}
protocol bgp {
ipv4 {
export all;
next hop self;
};
local as 65000;
neighbor 192.168.3.1 internal;
}
Replace eth0
in the protocol direct
block with the actual name of the router’s LAN interface (or if the WireGuard router has multiple interfaces connected to different parts of its site, use an interface pattern that encompasses them all, like "eth*", "gre*"
, etc). The protocol direct
block is needed to import the direct interface routes for the LAN interface into BIRD’s master4
routing table, so that it can add the correct next hop to the routes from the other site that it will share via BGP.
In the protocol bgp
block, replace 65000
with the actual private ASN you use, and replace 192.168.3.1
with the actual IP address of the iBGP server with which the router should exchange routes. Add multiple protocol bgp
blocks if you have multiple iBGP servers with which the WireGuard routers need to exchange routes (one block for each iBGP server).
The export all;
line directs BIRD to share all the routes in its master4
table with the other iBGP server. The next hop self;
line directs it to adjust those routes to make the WireGuard router itself the next hop in all those routes. This will share the routes the WireGuard router learned from the other site, using the WireGuard router itself as the link to the site.
By default, BIRD will also import all the IPv4 routes it receives over BGP into its master4
table (to be shared back to its paired WireGuard router at the other site via OSPF). If you don’t want to propagate all of this site’s routes to the other site, add an import
filter to ipv4
channel to limit the routes that BIRD will pull in from BGP.
For example, to propagate only routes in the 192.168.1.0/24
subnet, add an import where net ~ 192.168.1.0/24;
line to the ipv4
block; or to import all routes except the 10.0.0.0/16
and 10.100.0.0/16
blocks, add import where net !~ 10.0.0.0/16 && net !~ 10.100.0.0/16;
; etc. See BIRD’s filter reference if you need something more complicated.
The full /etc/bird/bird.conf
file on Router 1 should now look like this:
router id 10.99.13.1;
protocol device {
}
protocol kernel {
ipv4 {
export where proto = "wg";
};
}
protocol ospf v2 wg {
ipv4 {
import where net !~ 10.99.13.0/24;
export all;
};
area 10.99.13.0 {
interface "wg0";
};
}
protocol direct {
ipv4;
interface "eth0";
}
protocol bgp {
ipv4 {
export all;
next hop self;
};
local as 65000;
neighbor 192.168.3.1 internal;
}
Make a similar change to Router 2, and run the following command in the BIRD client on each to reload your changes:
bird> configure
Reading configuration from /etc/bird/bird.conf
Reconfigured
Once you’ve updated the routers, each router should have at least one path to all the routes exposed by BGP in BIRD’s master4
table:
bird> show route
Table master4:
192.168.1.64/26 unicast [direct1 22:21:41.501] * (240)
dev eth0
unicast [bgp1 22:30:34.341] (100) [i]
via 192.168.1.65 on eth0
192.168.1.192/26 unicast [bgp1 22:30:34.341] (100) [i]
via 192.168.1.65 on eth0
192.168.3.0/24 unicast [bgp1 22:30:34.341] (100) [i]
via 192.168.1.65 on eth0
192.168.4.0/24 unicast [bgp1 22:30:34.341] (100) [i]
via 192.168.1.65 on eth0
192.168.200.0/26 unicast [wg 19:37:19.495] E1 (150/20) [10.99.13.3]
via 10.99.13.3 on wg0
unicast [bgp1 22:30:34.341] (100) [i]
via 192.168.1.65 on eth0
192.168.200.128/26 unicast [wg 19:37:19.495] E1 (150/30) [192.168.200.1]
via 10.99.13.3 on wg0
unicast [bgp1 22:30:34.341] (100) [i]
via 192.168.1.65 on eth0
192.168.200.192/26 unicast [wg 19:37:19.495] E1 (150/40) [192.168.200.193]
via 10.99.13.3 on wg0
unicast [bgp1 22:30:34.341] (100) [i]
via 192.168.1.65 on eth0
And each router should have the best path for each subnet at the other site in its OS’s main
table (with the routes exported by BIRD listed with proto bird
):
$ ip route
default via 192.168.1.65 dev eth0 proto dhcp src 192.168.1.101 metric 100
10.99.13.0/24 dev wg0 proto kernel scope link src 10.99.13.1
192.168.1.64/26 dev eth0 proto kernel scope link src 192.168.1.101
192.168.1.65 dev eth0 proto dhcp scope link src 192.168.1.101 metric 100
192.168.200.0/26 via 10.99.13.3 dev wg0 proto bird metric 32
192.168.200.128/26 via 10.99.13.3 dev wg0 proto bird metric 32
192.168.200.192/26 via 10.99.13.3 dev wg0 proto bird metric 32
So you should now be able to successfully ping Endpoint B (at 192.168.200.22
) in Site B from Endpoint A (at 192.168.1.91
) in Site A (provided you don’t have any firewalls blocking ICMP packets on some hop between the two hosts):
$ ping -nc1 192.168.200.22
PING 192.168.200.22 (192.168.200.22) 56(84) bytes of data.
64 bytes from 192.168.200.22: icmp_seq=1 ttl=59 time=23.3 ms
--- 192.168.200.22 ping statistics ---
1 packets transmitted, 1 received, 0% packet loss, time 0ms
rtt min/avg/max/mdev = 23.279/23.279/23.279/0.000 ms
And if you kill one of the WireGuard links between Site A and Site B (like for testing purposes, run sudo wg-quick down wg0
on Router 1), you should still be able to ping Endpoint B from Endpoint A — Endpoint A’s LAN router should receive the updated route to Endpoint B via BGP, and re-route the traffic between Endpoint A and Endpoint B using the other WireGuard link.
Alternatively Use IPv6
You can use the same basic setup outlined in the above sections to exchange IPv6 routes (and to exchange routes over IPv6 links). The main difference is that you need to use OSPFv3 instead of OSPFv2 to exchange IPv6 routes between the WireGuard routers — and since OSPFv3 is meant to operate only over direct IPv6 links, you have to assign IPv6 link-local addresses to your WireGuard interfaces.
So these are the steps to follow in order to add IPv6 to (or replace IPv4 with IPv6 in) the above examples:
Enable IPv6 Packet Forwarding
To enabling IPv6 packet forwarding, add the following to your /etc/sysctl.d/local.conf
file (create it if hadn’t already):
net.ipv6.conf.all.forwarding=1
Then run the following command to apply it (along with all other sysctl conf files):
sudo sysctl --system
Add WireGuard IPv6 Addresses
You can add multiple IP addresses, including a mix of IPv4 and IPv6 addresses, to any WireGuard interface. So, for example, we can add the fe99:13::1
IPv6 address (on the link-local fe99:13::/64
subnet) to Router 1 simply by adding a second Address
entry to its interface definition in its /etc/wireguard/wg0.conf
file:
# local settings for Router 1
[Interface]
PrivateKey = 0F11111111111111111111111111111111111111110=
Address = 10.99.13.1/24
Address = fe99:13::1/64
ListenPort = 51820
Table = off
# remote settings for Router 3
[Peer]
PublicKey = IZ2rER/G68c1LCHeXIkpHqKYT7zB5bLkNULSSJ5HI1U=
AllowedIPs = 0.0.0.0/0, ::/0
Endpoint = 203.0.113.33:51820
Alternatively, you can combine multiple IP addresses in the same Address
entry by concatenating the addresses with commas:
Address = 10.99.13.1/24, fe99:13::1/64
And if you don’t need to use IPv4 with the interface, you can omit the IPv4 address entirely, and just use the IPv6 address:
Address = fe99:13::1/64
Note
|
It doesn’t matter whether the |
So add these IPv6 addresses to each WireGuard interface:
-
fe99:13::1/64
for Router 1 -
fe99:24::2/64
for Router 2 -
fe99:13::3/64
for Router 3 -
fe99:24::4/64
for Router 4
These addresses put Router 1 and Router 3 on the same fe99:13::/64
link-local subnet, and Router 2 and Router 4 on the same fe99:24::/64
link-local subnet.
If the interfaces are already up, restart them after making those changes (otherwise, start them now).
Set Up OSPFv3 in BIRD
If you don’t need to exchange IPv4 routes, you can replace the /etc/bird/bird.conf
configuration on Router 1 with the following:
router id 10.99.13.1;
protocol device {
}
protocol kernel {
ipv6 {
export where proto = "wg6";
};
}
protocol ospf v3 wg6 {
ipv6 {
export all;
};
area 10.99.13.0 {
interface "wg0";
};
}
protocol ospf v3 lan6 {
ipv6 {
export all;
};
area 0 {
interface "eth0";
};
}
The only differences between the IPv4 version of this config and the IPv6 version are:
-
It uses
v2
instead ofv3
for theprotocol ospf
blocks. -
It gives the protocol instances different IDs:
wg6
for the instance exchanging OSPFv3 routes on thewg0
interface, andlan6
for the instance exchanging OSPFv3 routes on theeth0
interface. These IDs are just arbitrary, however — you can change them to whatever you want. -
Because we’re using IPv6 link-local addresses for WireGuard, we don’t need an explicit filter in the
protocol ospf v3 wg6
block to exclude our WireGuard addresses from being imported into BIRD’smaster6
table — BIRD will filter them out automatically (since it doesn’t make sense to exchange routes to link-local addresses).
If you do need both IPv4 and IPv6, add the above IPv6 protocol kernel
and protocol ospf
blocks to the original IPv4 version of Router 1’s /etc/bird/bird.conf
file:
router id 10.99.13.1;
protocol device {
}
protocol kernel {
ipv4 {
export where proto = "wg";
};
}
protocol ospf v2 wg {
ipv4 {
import where net !~ 10.99.13.0/24;
export all;
};
area 10.99.13.0 {
interface "wg0";
};
}
protocol ospf v2 lan {
ipv4 {
export all;
};
area 0 {
interface "eth0";
};
}
protocol kernel {
ipv6 {
export where proto = "wg6";
};
}
protocol ospf v3 wg6 {
ipv6 {
export all;
};
area 10.99.13.0 {
interface "wg0";
};
}
protocol ospf v3 lan6 {
ipv6 {
export all;
};
area 0 {
interface "eth0";
};
}
Make similar changes to the /etc/bird/bird.conf
on all the WireGuard routers; the IPv6-only version on Router 2 would look like this:
router id 10.99.24.2;
protocol device {
}
protocol kernel {
ipv6 {
export where proto = "wg6";
};
}
protocol ospf v3 wg6 {
ipv6 {
export all;
};
area 10.99.24.0 {
interface "wg0";
};
}
protocol ospf v3 lan6 {
ipv6 {
export all;
};
area 0 {
interface "eth0";
};
}
And Router 3 like this:
router id 10.99.13.3;
protocol device {
}
protocol kernel {
ipv6 {
export where proto = "wg6";
};
}
protocol ospf v3 wg6 {
ipv6 {
export all;
};
area 10.99.13.0 {
interface "wg0";
};
}
protocol ospf v3 lan6 {
ipv6 {
export all;
};
area 0 {
interface "eth0";
};
}
And Router 4 like this:
router id 10.99.24.4;
protocol device {
}
protocol kernel {
ipv6 {
export where proto = "wg6";
};
}
protocol ospf v3 wg6 {
ipv6 {
export all;
};
area 10.99.24.0 {
interface "wg0";
};
}
protocol ospf v3 lan6 {
ipv6 {
export all;
};
area 0 {
interface "eth0";
};
}
Reload your changes to each /etc/bird/bird.conf
file:
bird> configure
Reading configuration from /etc/bird/bird.conf
Reconfigured
After reloading, on each WireGuard router you should see one neighbor for the wg6
OSPF instance:
bird> show ospf neighbors wg6
wg6:
Router ID Pri State DTime Interface Router IP
10.99.13.3 1 Full/PtP 35.956 wg0 fe99:13::3
And at least one neighbor for the lan6
OSPF instance:
bird> show ospf neighbors lan6
lan6:
Router ID Pri State DTime Interface Router IP
192.168.1.65 10 Full/DR 39.184 eth0 fe80::1921:68ff:fe01:65/64
And each router should have at least one path to all the routes exposed by OSPF on any LAN subnet in BIRD’s master6
table:
bird> show route
Table master6:
2001:db8:1:100::/56 unicast [lan6 18:25:47.947] I (150/10) [10.99.13.1]
dev eth0
unicast [lan6 18:25:47.947] E1 (150/20) [192.168.1.65]
via fe80::1921:68ff:fe01:65 on eth0
2001:db8:1:300::/56 unicast [lan6 18:25:47.947] E1 (150/30) [192.168.1.193]
via fe80::1921:68ff:fe01:65 on eth0
2001:db8:3::/48 unicast [lan6 18:25:47.947] E1 (150/30) [192.168.1.1]
via fe80::1921:68ff:fe01:65 on eth0
2001:db8:4::/48 unicast [lan6 18:25:47.947] E1 (150/30) [192.168.1.1]
via fe80::1921:68ff:fe01:65 on eth0
2001:db8:200::/56 unicast [wg6 19:37:19.495] E1 (150/20) [10.99.13.3]
via fe99:13::3 on wg0
unicast [lan6 20:14:51.882] E1 (150/60) [192.168.200.1]
via fe80::1921:68ff:fe01:65 on eth0
2001:db8:200:200::/56 unicast [wg6 19:37:19.495] E1 (150/30) [192.168.200.1]
via fe99:13::3 on wg0
unicast [lan6 20:14:51.882] E1 (150/50) [192.168.200.193]
via fe80::1921:68ff:fe01:65 on eth0
2001:db8:200:300::/56 unicast [wg6 19:37:19.495] E1 (150/40) [192.168.200.193]
via fe99:13::3 on wg0
unicast [lan6 20:14:51.882] E1 (150/40) [10.99.24.4]
via fe80::1921:68ff:fe01:65 on eth0
And each router should have the best path for each subnet at the other site in its OS’s main
IPv6 table (where the routes exported by BIRD will be listed with proto bird
):
$ ip -6 route
::1 dev lo proto kernel metric 256 pref medium
2001:db8:1:100::/56 dev eth0 proto ra metric 100 pref medium
2001:db8:200::/56 via fe99:13::3 dev wg13 proto bird metric 32 pref medium
2001:db8:200:200::/56 via fe99:13::3 dev wg13 proto bird metric 32 pref medium
2001:db8:200:300::/56 via fe99:13::3 dev wg13 proto bird metric 32 pref medium
fe80::/64 dev eth0 proto kernel metric 256 pref medium
fe99::/64 dev wg0 proto kernel metric 256 pref medium
default via fe80::1921:68ff:fe01:65 dev eth0 proto ra metric 100 expires 1792sec pref medium
With that in place, you should now be able to ping the IPv6 address for Endpoint B (at 2001:db8:200:22::1
) in Site B from Endpoint A (at 2001:db8:1:91::1
) in Site A (provided you don’t have any firewalls blocking ICMPv6 packets on some hop between the two hosts):
$ ping -nc1 2001:db8:200:22::1
PING 2001:db8:200:22::1 (2001:db8:200:22::1) 56 data bytes
64 bytes from 2001:db8:200:22::1: icmp_seq=1 ttl=59 time=23.3 ms
--- 2001:db8:200:22::1 ping statistics ---
1 packets transmitted, 1 received, 0% packet loss, time 0ms
rtt min/avg/max/mdev = 23.279/23.279/23.279/0.000 ms
And if you kill one of the WireGuard links between Site A and Site B (like for testing purposes, run sudo wg-quick down wg0
on Router 1), you should still be able to ping Endpoint B from Endpoint A — Endpoint A’s LAN router should receive the link-state update via OSPF, and re-route the traffic between Endpoint A and Endpoint B through the other WireGuard link.
Alternatively Use BGP With IPv6
BGP can exchange IPv6 routes over IPv4, and IPv4 routes over IPv6, so you don’t need to change much with BIRD’s BGP configuration to use IPv6. You do need to configure the WireGuard routers to use OSPFv3 over IPv6 link-local addresses, however, as described above.
Once you do that, you can do IPv6-only exchanges of routes with a configuration like the following (for Router 1):
router id 10.99.13.1;
protocol device {
}
protocol kernel {
ipv6 {
export where proto = "wg6";
};
}
protocol ospf v3 wg6 {
ipv6 {
export all;
};
area 10.99.13.0 {
interface "wg0";
};
}
protocol direct {
ipv6;
interface "eth0";
}
protocol bgp {
ipv6 {
export all;
next hop self;
};
local as 65000;
neighbor 2001:db8:3:1::1 internal;
}
To exchange both IPv4 and IPv6 routes, you’d instead configure the router’s /etc/bird/bird.conf
like this (again for Router 1):
router id 10.99.13.1;
protocol device {
}
protocol kernel {
ipv4 {
export where proto = "wg";
};
}
protocol ospf v2 wg {
ipv4 {
import where net !~ 10.99.13.0/24;
export all;
};
area 10.99.13.0 {
interface "wg0";
};
}
protocol kernel {
ipv6 {
export where proto = "wg6";
};
}
protocol ospf v3 wg6 {
ipv6 {
export all;
};
area 10.99.13.0 {
interface "wg0";
};
}
protocol direct {
ipv4;
ipv6;
interface "eth0";
}
protocol bgp {
ipv4 {
export all;
next hop self;
};
ipv6 {
export all;
next hop self;
next hop address 2001:db8:1:111::1;
};
local as 65000;
neighbor 192.168.3.1 internal;
}
Unlike the protocol kernel
and protocol ospf
instances, the same protocol direct
and protocol bgp
instances can be used for both IPv4 and IPv6. But in the protocol bgp
block, you will need to declare the specific next hop address
to use for the WireGuard router in the channel over which the routes are not exchanged (ie if routes are exchanged over IPv4, you’ll need to specify the next-hop address for the IPv6 channel, like the example above; if routes were exchanged over IPv6, however, you’d need to specify the next-hop address for the IPv4 channel instead).