Internet Gateway as a Spoke
We have an endpoint with WireGuard running on it, Endpoint A, from which we want to access the Internet. To get from Endpoint A to the Internet in this scenario, however, WireGuard traffic needs to go through two hops: one through the VPN hub, Host C; and the second through a spoke of the hub, Host β. However, while we want to use Host β as the Internet gateway for Endpoint A, we don’t want that for Host C — Host C itself can use the default routes at Site C to access the Internet as needed.
Hub with an Internet Gateway Spoke
Details of the network used in this explanation
Within our WireGuard network, Endpoint A has an IPv4 address of 10.0.0.1 and an IPv6 address of fd10:0:0:1::1; Host C has an IPv4 address of 10.0.0.3, and an IPv6 address of fd10:0:0:3::1; and Host β has an IPv4 address of 10.0.0.2 and an IPv6 address of fd10:0:0:2::1. Host C has a public IP address of 192.0.2.3, allowing Endpoint A and Host β to each establish a WireGuard tunnel to it.
Endpoint A
On Endpoint A, when the WireGuard network is up, we want to send all Internet traffic through Host C, so we configure AllowedIPs = 0.0.0.0/0, ::/0 for Host C in Endpoint A’s WireGuard config:
# /etc/wireguard/wg0.conf
# local settings for Endpoint A
[Interface]
PrivateKey = AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAEE=
Address = 10.0.0.1/32, fd10:0:0:1::1/64
ListenPort = 51821
# remote settings for Host C
[Peer]
PublicKey = jUd41n3XYa3yXBzyBvWqlLhYgRef5RiBD7jwo70U+Rw=
Endpoint = 192.0.2.3:51823
AllowedIPs = 0.0.0.0/0, ::/0
0.0.0.0/0 is the entire IPv4 space, and ::/0 is the entire IPv6 space. (If you don’t care about IPv6 traffic, you can omit the IPv6 addresses and address blocks from this and the other WireGuard configurations.)
Host C
On Host C, we want to send all the traffic destined for 10.0.0.1 or fd10:0:0:1::/64 to Endpoint A, and all other traffic that comes through our WireGuard network on to Host β. So we configure WireGuard on Host C with AllowedIPs = 10.0.0.1/32, fd10:0:0:1::/64 for its Endpoint A [Peer] section, and AllowedIPs = 0.0.0.0/0, ::/0 for its Host β [Peer] section.
But since we don’t want all of Host C’s traffic to go to Host β — just traffic forwarded through the WireGuard network — we configure the routes for this WireGuard interface to use a custom routing table, via the interface’s Table = 123 setting. And we use a PreUp command to add a policy routing rule that directs the host to use this table only for traffic coming in from this WireGuard interface (ip rule add iif wg0 table 123 priority 456):
# /etc/wireguard/wg0.conf
# local settings for Host C
[Interface]
PrivateKey = CCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCGA=
Address = 10.0.0.3/32, fd10:0:0:3::1/64
ListenPort = 51823
Table = 123
# IPv4 forwarding & routing
PreUp = sysctl -w net.ipv4.ip_forward=1
PreUp = ip rule add iif wg0 table 123 priority 456
PostDown = ip rule del iif wg0 table 123 priority 456
# IPv6 forwarding & routing
PreUp = sysctl -w net.ipv6.conf.all.forwarding=1
PreUp = ip -6 rule add iif wg0 table 123 priority 456
PostDown = ip -6 rule del iif wg0 table 123 priority 456
# remote settings for Endpoint A
[Peer]
PublicKey = /TOE4TKtAqVsePRVR+5AA43HkAK5DSntkOCO7nYq5xU=
AllowedIPs = 10.0.0.1/32, fd10:0:0:1::/64
# remote settings for Host β
[Peer]
PublicKey = fE/wdxzl0klVp/IR8UcaoGUMjqaWi3jAd7KzHKFS6Ds=
AllowedIPs = 0.0.0.0/0, ::/0
Host B
Similar to the previous Site Gateway as a Spoke scenario, on Host β, we want to send all the traffic destined for the 10.0.0.0/24 and fd10::/56 networks back through Host C — so that’s what we use as the AllowedIPs setting for Host C in Host β’s WireGuard config:
# /etc/wireguard/wg0.conf
# local settings for Host β
[Interface]
PrivateKey = ABBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBFA=
Address = 10.0.0.2/32, fd10:0:0:2::1/64
ListenPort = 51822
# IPv4 forwarding
PreUp = sysctl -w net.ipv4.ip_forward=1
# IPv4 masquerading
PreUp = iptables -t mangle -A PREROUTING -i wg0 -j MARK --set-mark 0x30
PreUp = iptables -t nat -A POSTROUTING ! -o wg0 -m mark --mark 0x30 -j MASQUERADE
PostDown = iptables -t mangle -D PREROUTING -i wg0 -j MARK --set-mark 0x30
PostDown = iptables -t nat -D POSTROUTING ! -o wg0 -m mark --mark 0x30 -j MASQUERADE
# IPv6 forwarding
PreUp = sysctl -w net.ipv6.conf.all.forwarding=1
# IPv6 masquerading
PreUp = ip6tables -t mangle -A PREROUTING -i wg0 -j MARK --set-mark 0x30
PreUp = ip6tables -t nat -A POSTROUTING ! -o wg0 -m mark --mark 0x30 -j MASQUERADE
PostDown = ip6tables -t mangle -D PREROUTING -i wg0 -j MARK --set-mark 0x30
PostDown = ip6tables -t nat -D POSTROUTING ! -o wg0 -m mark --mark 0x30 -j MASQUERADE
# remote settings for Host C
[Peer]
PublicKey = jUd41n3XYa3yXBzyBvWqlLhYgRef5RiBD7jwo70U+Rw=
Endpoint = 192.0.2.3:51823
AllowedIPs = 10.0.0.0/24, fd10::/56
PersistentKeepalive = 25
Note that in Host β’s configuration for Host C, we’ve also included a PersistentKeepalive = 25 setting. This ensures that Host β pokes a hole through any NAT (or egress-only) firewalls between Host C and Host β, allowing Endpoint A to initiate connections through the WireGuard network to Host β. You can omit this setting if there are no NAT or egress-only firewalls between Host C and Host β (but if you do that, make sure you instead set up Host C’s WireGuard config to include the public IP address of Host β in its Endpoint setting for Host β).
Also note that we’ve included some iptables rules in Host β’s WireGuard configuration. These iptables rules masquerade packets from the WireGuard network when Host β forwards them out to Site B. You can omit these rules if the LAN router (or each individual endpoint) at Site B is already configured to route traffic destined for the WireGuard network (10.0.0.0/24) back through Host β.
Test It Out
After starting up our configured WireGuard interfaces on each host (Endpoint A, Host C, and Host β), we can run cURL (or a regular webrowser) from Endpoint A to access Google (or any other Internet site) through our WireGuard network:
``
$ curl google.com
301 Moved
...
```
Reference :
copy-pasted from https://www.procustodibus.com/blog/2022/06/multi-hop-wireguard/#internet-gateway-as-a-spoke (saved me from 2 weeks of unsuccessful search...)