Project Zomboid

Project Zomboid

Not enough ratings
Linux Server: Proxy Server using Wireguard
By Lu5ck
Setting up a proxy might be a good idea should you need a DDOS capable facing server to protect your home server or to route connections.
   
Award
Favorite
Favorited
Unfavorite
Introductions
You might want to setup proxy because
  • Your game server uses a specific route that is highly congested during certain hours but it has a completely uncongested alternate route that is not easily publicly available thus you can setup another VPS as a kind of VPN to force a different uncongested route.
  • Your game server, like home server, has no DDOS protection and you want to use a VPS with DDOS protection as the face server.

There are several options available to route connections from one server to another
  • Direct Routing
  • IPIP Tunnel
  • GRE Tunnel
  • VXLAN
  • Wireguard
  • IPSec/ikev2

Out of these options, I found wireguard to be the most reliable. Wireguard is an open source UDP-based VPN therefore it has encryption which means overhead but despite the overhead, it is very reliable in latency and stable in connectivity. IPIP, GRE, and VXLAN all have some degree of latency problems, with IPIP being the worse, follow by GRE then VXLAN. IPSec/ikev2 is very complicated to setup and that complicity is not worthwhile, its ports are also fixed thus not always deployable in all environments. Direct Routing seems to subject to instability.

Wireguard, despite really good compare to other options, it also has downside. As mentioned, there's encryption and this encryption is CPU-bound thus you need decent CPU for it to perform. Fortunately, it is multi threaded thus having more cores can improve its performance easily.
Requirements
  • Two servers
  • FirewallD (I like FirewallD)
RHEL-based Linux
dnf install firewalld iproute2 wireguard-tools
Debian-based Linux
apt install firewalld iproute2 wireguard
Basics
Open SSH on both servers since you gonna do similar things and need keys from each server.

Server A
On your public facing server, we call this server A. We do the followings

Enable IP forwarding
echo 'net.ipv4.ip_forward=1' >> /etc/sysctl.conf sysctl -p

Generate Wireguard Private and Public Keys
wg genkey | tee privatekey | wg pubkey > publickey

Take note of the keys
cat privatekey cat publickey

We will add the config file, simpler than doing commandline
touch /etc/wireguard/wg0.conf nano /etc/wireguard/wg0.conf

Enter this into wg0.conf
[Interface] Address = 192.168.1.1/30 PrivateKey = <Server A's private key> Port = 51293 # Some random port between 49152-65536 [Peer] PublicKey = <Server B's public key> AllowedIPs = 192.168.1.2/30 Endpoint = <Server B's IP Address>:<Server B's Port> # Replace with the peer's details

Start the wireguard
sudo systemctl enable wg-quick@wg0 sudo systemctl start wg-quick@wg0

Setup the firewall
systemctl enable firewalld systemctl start firewalld firewall-cmd --state firewall-cmd --add-interface=wg0 --zone=trusted --permanent

Allow the ports if you haven't
firewall-cmd --zone=public --add-port=16261-16262/tcp --permanent firewall-cmd --zone=public --add-port=16261-16262/udp --permanent

Forward the port while retaining their public IP address
firewall-cmd --permanent --direct --add-rule ipv4 nat PREROUTING 0 -p udp -i eth0 --dport 16261:16262 -j DNAT --to-destination 192.168.1.2 firewall-cmd --permanent --direct --add-rule ipv4 nat PREROUTING 0 -p tcp -i eth0 --dport 16261:16262 -j DNAT --to-destination 192.168.1.2

Support outgoing connections
firewall-cmd --permanent --direct --add-rule ipv4 nat POSTROUTING 0 -s 192.168.1.0/30 '!' -o wg+ -j MASQUERADE

Finally add Server's A Wireguard Port
sudo firewall-cmd --zone=public --add-port=51293/udp --permanent

Reload the rule list
firewall-cmd --reload

On Server B
We doing similar things

Generate Wireguard Private and Public Keys
wg genkey | tee privatekey | wg pubkey > publickey

Take note of the keys
cat privatekey cat publickey

We will add the config file, simpler than doing commandline
nano /etc/wireguard/wg0.conf

Enter this into wg0.conf
[Interface] Address = 192.168.1.2/30 PrivateKey = <Server B's private key> ListenPort = 61293 # Some random port between 49152-65536 [Peer] PublicKey = <Server A's public key> AllowedIPs = 192.168.1.1/30 Endpoint = <Server A's IP Address>:<Server A's Port> # Replace with the peer's details

In order for allow wireguard to accept users' public IP, we need to modify "AllowedIPs" further by using this calculator[www.procustodibus.com].

We cannot arbitrarily use 0.0.0.0/0 which means allow all IPs as this will include VPS's IP and gateway which will conflict with wireguard automate routing table creation, ultimately destroying our SSH connectivity. So we exclude the VPS IPs, VPS gateway IP and also wireguard IPs range because we only want the user to connect with their public IPs.

Example
This will be the result
[Interface] Address = 192.168.1.2/30 PrivateKey = <Server B's private key> ListenPort = 61293 # Some random port between 49152-65536 [Peer] PublicKey = <Server A's public key> AllowedIPs = 0.0.0.0/3, 32.0.0.0/4, 48.0.0.0/8, 49.0.0.0/9, 49.128.0.0/10, 49.192.0.0/11, 49.224.0.0/13, 49.232.0.0/16, 49.233.0.0/17, 49.233.128.0/19, 49.233.160.0/20, 49.233.176.0/22, 49.233.180.0/23, 49.233.182.0/32, 49.233.182.2/31, 49.233.182.4/30, 49.233.182.8/29, 49.233.182.16/31, 49.233.182.18/32, 49.233.182.20/30, 49.233.182.24/29, 49.233.182.32/27, 49.233.182.64/26, 49.233.182.128/25, 49.233.183.0/24, 49.233.184.0/21, 49.233.192.0/18, 49.234.0.0/15, 49.236.0.0/14, 49.240.0.0/12, 50.0.0.0/7, 52.0.0.0/6, 56.0.0.0/5, 64.0.0.0/2, 128.0.0.0/2, 192.0.0.0/9, 192.128.0.0/11, 192.160.0.0/13, 192.168.0.0/24, 192.168.1.4/30, 192.168.1.8/29, 192.168.1.16/28, 192.168.1.32/27, 192.168.1.64/26, 192.168.1.128/25, 192.168.2.0/23, 192.168.4.0/22, 192.168.8.0/21, 192.168.16.0/20, 192.168.32.0/19, 192.168.64.0/18, 192.168.128.0/17, 192.169.0.0/16, 192.170.0.0/15, 192.172.0.0/14, 192.176.0.0/12, 192.192.0.0/10, 193.0.0.0/8, 194.0.0.0/7, 196.0.0.0/6, 200.0.0.0/5, 208.0.0.0/4, 224.0.0.0/3 Endpoint = <Server A's IP Address>:<Server A's Port> # Replace with the peer's details

Start the wireguard
sudo systemctl enable wg-quick@wg0 sudo systemctl start wg-quick@wg0

Setup the firewall
systemctl enable firewalld systemctl start firewalld firewall-cmd --state firewall-cmd --add-interface=wg0 --zone=trusted --permanent

We need to add Server's B Wireguard Port
sudo firewall-cmd --zone=public --add-port=61293/udp --permanent

Verify the settings
On both server, enter this code, you should see "Last Handshake" if is connected
wg

You are done, players now can connect to server B via server A's IP.
Advanced
After you done the basics, you should have a working peer to peer wireguard connection. However, it might not be enough for you. At this point, you should realize that your game server is still advertising machine B's IP. What if you want to host the server via machine A's internet?

To do that, we need to do network namespace. While it can be complicated, has its benefit of just allowing your PZ application to run on the wireguard network while the rest of your applications will continue to run on the main network. This is good because bandwidth cost money in many locations.

Nameservers
Some VPS use their own nameservers which work for local machine but does not work for our VPN machine so we need to use public dns like cloudflare or google. Do this on both machines.

nano /etc/resolv.conf

Add this as first nameserver
nameserver 1.1.1.1 #cloudflare dns nameserver 1.0.0.1 #cloudflare dns

Network namespace
To get PZ running in wireguard network, we have to use network namespace, an isolated network container where you can get your applications to run on specific interface.

Disable wireguard (coming from basic)
systemctl disable wg-quick@wg0 systemctl stop wg-quick@wg0

Modify wg0.conf on game server. We now only block private IPs range. We can don't include our VPS IP and VPS gateway IP because network namespace is isolated from host network thus won't destroy our SSH connectivity.
[Interface] Address = 192.168.1.2/30 PrivateKey = <Server B's private key> ListenPort = 61293 # Some random port between 49152-65536 [Peer] PublicKey = <Server A's public key> AllowedIPs = 0.0.0.0/5, 8.0.0.0/7, 11.0.0.0/8, 12.0.0.0/6, 16.0.0.0/4, 32.0.0.0/3, 64.0.0.0/2, 128.0.0.0/3, 160.0.0.0/5, 168.0.0.0/6, 172.0.0.0/12, 172.32.0.0/11, 172.64.0.0/10, 172.128.0.0/9, 173.0.0.0/8, 174.0.0.0/7, 176.0.0.0/4, 192.0.0.0/9, 192.128.0.0/11, 192.160.0.0/13, 192.169.0.0/16, 192.170.0.0/15, 192.172.0.0/14, 192.176.0.0/12, 192.192.0.0/10, 193.0.0.0/8, 194.0.0.0/7, 196.0.0.0/6, 200.0.0.0/5, 208.0.0.0/4, 224.0.0.0/3 Endpoint = <Server A's IP Address>:<Server A's Port> # Replace with the peer's details

Create the namespace
ip netns add zomboid_ns

Activate the loopback on namespace
ip netns exec zomboid_ns ip link set lo up

Start the wireguard in the namespace and move the interface into namespace. Wireguard by default will always be created at host thus we need to do this command together or we will be locked out from SSH.
systemctl start wg-quick@wg0 && ip link set wg0 netns zomboid_ns

Set IP to the interface. Wireguard lose its IP address when moved.
ip netns exec zomboid_ns ip addr add 192.168.1.2/30 dev wg0

Start the interface. Wireguard is turned off when moved.
ip netns exec zomboid_ns ip link set wg0 up

Set wireguard as default gateway
ip netns exec zomboid_ns ip route add default dev wg0

To execute PZ in the network namespace, you do this
ip netns exec zomboid_ns ./path/to/start-server.sh

Persistence changes
IP command is not persistence thus will be gone after you reboot. To make it persistence, you need to run the commands again after reboot but we can simply create a service to do that.
touch /etc/systemd/system/zomboid-namespace-setup.service nano /etc/systemd/system/zomboid-namespace-setup.service

Add these
[Unit] Description=Setup Zomboid Network Namespace After=network.target [Service] Type=oneshot ExecStart=/bin/bash -c "ip netns add zomboid_ns \ && ip netns exec zomboid_ns ip link set lo up \ && systemctl start wg-quick@wg0 \ && ip link set wg0 netns zomboid_ns \ && ip netns exec zomboid_ns ip addr add 192.168.1.2/30 dev wg0 \ && ip netns exec zomboid_ns ip link set wg0 up \ && ip netns exec zomboid_ns ip route add default dev wg0" [Install] WantedBy=multi-user.target

Enable the service
systemctl daemon-reload systemctl enable zomboid-namespace-setup.service

Running PZ as normal user
If you want normal user to able to run "ip netns", it is a root command. You need to allow them to do so. Note that they will able to use root commands through namespace.

visudo

Add this line at the end
<username> ALL=NOPASSWD: /sbin/ip, /sbin/ip netns, /sbin/ip netns exec

Then, they can do this
sudo ip netns exec zomboid_ns sudo -u <username> ./path/to/start-server.sh
Optimizing MTU
Maximum transmission unit (MTU), it is the maximum packet size you can send. In wireguard, the default MTU is 1420 but depending on your network, 1420 might be too high or too low for you which can impact your transfer speed.

You can do a very bandwidth expensive test using https://github.com/nitred/nr-wg-mtu-finder but as mentioned, it is very bandwidth expensive thus I would recommend you to tweak the test criteria a little.

nr-wg-mtu-finder --mode server --mtu-min 1400 --mtu-max 1500 --mtu-step 10 --server-ip <ip>

nr-wg-mtu-finder --mode peer --mtu-min 1400 --mtu-max 1500 --mtu-step 2 --server-ip 10.2.0.1

We do by increment of 10 and we start from 1400 instead of the lowest 1280. Even so, this test would estimate to cost you about 200GB worth of bandwidth.

Is this test absolutely necessary? Nope. Only if you encounter noticeable gap in transfer speed with and without wireguard. Of course, you can also do this test if you want to push your network speed to its very limit.

At the end of the test, the tool will plot a graph based on the csv file, the white cell is the one you want to put your MTU value at. To assign MTU value in wireguard, you edit the conf like this.

[Interface] Address = <Local server private address> PrivateKey = <Local server private key> Port = <Local server port> # Some random port between 49152-65536 MTU = 1440 [Peer] PublicKey = <Target public key> AllowedIPs = <Target private address>, 0.0.0.0/0 Endpoint = <Target IP Address>:<Target Port> # Replace with the peer's details

In general, if you are using a standard IPv4 network and the routing between servers are not going through some major jumps which involve more encapsulations, you can set your MTU to 1440 and 1500. Wireguard has a overhead of 60 for IPv4 and this overhead is only applied on one end.