I use WireGuard as a secure communication channel between two servers in different DCs to hide the existence of the end server (server B). I use nftables as a firewall management tool.
From public server A, the traffic is forwarded keeping the IP address (necessary for the application).
The destination server B receives the packets and the application processes them, but eventually, the server tries to return the packet to the original IP (the sender IP of the original packet) and this becomes a problem.
Masquerading the original IP looks like a simple solution, but it is necessary to keep the original IP already on server B to route these packets back to the WireGuard tunnel.
The tcpdump of the server B:
1:02:36.675958 wg0 In IP 1.2.3.4.54617 > 10.0.0.2.21: Flags [S], seq 1265491449, win 64240, options [mss 1452,nop,wscale 8,nop,nop,sackOK], length 0
1:02:36.675980 docker0 Out IP 1.2.3.4.54617 > 172.16.0.2.21: Flags [S], seq 1265491449, win 64240, options [mss 1452,nop,wscale 8,nop,nop,sackOK], length 0
1:02:36.676030 docker0 In IP 172.16.0.2.21 > 1.2.3.4.54617: Flags [S.], seq 1815055360, ack 1265491450, win 64240, options [mss 1460,nop,nop,sackOK,nop,wscale 7], length 0
1:02:36.676033 enp41s0 Out IP 10.0.0.2.21 > 1.2.3.4.54617: Flags [S.], seq 1815055360, ack 1265491450, win 64240, options [mss 1460,nop,nop,sackOK,nop,wscale 7], length 0
- 1.2.3.4 - original IP
- 10.0.0.2 - IP wireguard on B server
- 172.16.0.2 - docker network, let's pretend this is an application (everything works there correctly)
Unfortunately, I have not come up with a solution to this problem, so I am asking for your help. Is it still possible? If yes, by what means?
Update
I decided to use HAProxy, but I don't think that's a very high-performance solution. So I'm still in need of possible solutions to this problem.
Use systemd-networkd to configure the WireGuard tunnel:
# sudo cat /etc/systemd/network/99-wg0.netdev
[NetDev]
Name=wg0
Kind=wireguard
Description=WireGuard tunnel wg0
[WireGuard]
ListenPort=51820
PrivateKey=[key]
[WireGuardPeer]
PublicKey=[key]
PresharedKey=[key]
AllowedIPs=0.0.0.0/0
Endpoint=[server A]:51820
# sudo cat /etc/systemd/network/99-wg0.network
[Match]
Name=wg0
[Network]
Address=10.0.0.2/24
Address=fdc9:281f:04d7:9ee9::2/64
# sudo ip rule:
0: from all lookup local
32766: from all lookup main
32767: from all lookup default
nftables configurations:
# sudo nft list ruleset
[0]
table inet filter {
chain allow {
ct state invalid drop comment "early drop of invalid connections"
ct state { established, related } accept comment "allow tracked connections"
ip protocol icmp accept comment "allow icmp"
meta l4proto ipv6-icmp accept comment "allow icmp v6"
icmp type echo-request limit rate over 10/second burst 4 packets drop comment "No ping floods"
icmpv6 type echo-request limit rate over 10/second burst 4 packets drop comment "No ping floods"
}
chain wireguard {
tcp dport 21 accept
}
chain input {
type filter hook input priority filter; policy drop;
iif "lo" accept comment "allow from loopback"
tcp dport 22 ct state new limit rate 15/minute accept comment "Avoid brute force on SSH"
tcp dport 22 accept comment "allow sshd"
ip6 saddr [server A] udp dport 51820 accept comment "Accept wireguard connection from proxy1.vps-da4c9ada.ovh.zolotomc.ru"
jump allow comment "allowed traffic for input"
meta pkttype host limit rate 5/second counter packets 1047 bytes 43403 reject with icmpx admin-prohibited
reject with icmpx host-unreachable
}
chain forward {
type filter hook forward priority filter; policy drop;
iif "docker0" accept comment "allow outgoing traffic from docker"
jump allow comment "allowed traffic for forward"
iif "wg0" jump wireguard comment "Wireguard chain"
reject with icmpx host-unreachable
}
chain output {
type filter hook output priority filter; policy accept;
}
}
table inet nat {
chain prerouting {
type nat hook prerouting priority dstnat; policy accept;
iif "wg0" jump wireguard comment "Wireguard chain"
}
chain wireguard {
tcp dport 21 dnat ip to 172.16.0.2
tcp dport 21 dnat ip6 to fe80::a8d7:f6ff:fe0b:4774
}
chain input {
type nat hook input priority 100; policy accept;
}
chain output {
type nat hook output priority -100; policy accept;
}
chain postrouting {
type nat hook postrouting priority srcnat; policy accept;
iif "docker0" oif != "docker0" masquerade
}
}
I tried using Setting packet metainformation, but it reassigns the port to a new random port. And yet, I'm starting to think that it's impossible or too complicated based on just nftables rules.