I have a server lanserver
running in my private LAN that is connected to a public server publicserver
using Wireguard. publicserver
forwards TCP connections to certain ports to lanserver
through the Wireguard connection using an iptables DNAT
rule.
On lanserver
, Wireguard is set up as a NetworkManager connection. It is routing all internet traffic through Wireguard using AllowedIPs = 0.0.0.0/0, ::/0
. This causes the following ip rules to be set up on lanserver
:
[root@lanserver ~]# ip rule show
0: from all lookup local
31100: from all lookup main suppress_prefixlength 0
31101: not from all fwmark 0xcb2e lookup 52014
32766: from all lookup main
32767: from all lookup default
[root@lanserver ~]# ip route show table 52014
default dev wg0 proto static scope link metric 50
This setup works properly. An incoming TCP connection to publicserver
gets forwarded to lanserver
using the iptables rule. The response is sent back through publicserver
because the ip rule 31101
matches it.
There is one exception: When I try to open a TCP connection to publicserver
using IPv6 from my personal computer pc
, which is also inside my private LAN, it does not work. The problem seems to be that both pc
and lanserver
have an IPv6 address in the same public subnet. publicserver
forwards the connection to lanserver
successfully, but the response is not routed back through the Wireguard connection but directly to pc
due to ip rule 31100
.
How can I make sure that all responses for connections coming in through the Wireguard interface on lanserver
are also sent back through the Wireguard interface, regardless of whether their source IP is in a local subnet?
I can think of solutions in the following directions:
- Disable IPv6 on
lanserver
, causing it to not be on the same subnet aspc
. Not a very good solution. - Use
SNAT
onpublicserver
for the port forward. Not an acceptable solution, since some of the services behind the forwarded ports need to know the true source IP. - Explicitly mention the local IPv6 subnet in the
AllowedIPs
of the Wireguard peer. This does not work because the IPv6 subnet changes every 24 hours. - Add a custom IP rule that somehow matches all connections coming in through
wg0
and uses the routing table52014
for them. I'm not sure how exactly to specify such a rule. Also, the problem is that the number of the routing table changes every time the Wireguard connection is restarted. The right place to create such a rule would probably be thePostUp
script, but NetworkManager does not seem to allow specifying one.
After experimenting with different options, I decided to stop using NetworkManager and instead use wg-quick directly. On Fedora Server, this was pretty easy and I only had to remove the NetworkManager connection using
nmcli con del wg0
and enable the wg-quick service usingsystemctl enable --now wg-quick@wg0
(which reads the config from/etc/wireguard/wg0.conf
). Switching to wg-quick has the following advantages:PostUp
commands to configure a more complex routing setup51820
, even though I could not find any documentation about this. This makes it easier to set up custom ip rules.I then used the
PostUp
command inwg0.conf
to set up additional ip rules that would make sure that any Wireguard traffic would also respond through Wireguard. There are two options to achieve this:Option 1: source IP matching
The ip rules match the Wireguard packages based on their source IP addresses (which is automatically set to the IP address on which the request originally came in):
Option 2: fwmark
Some iptables rules are configured to set a
mark
on packages coming in throughwg0
:Then this
mark
is matched by the IP rule:More details about this solution can be found here.