Wireguard split tunneling

mydeardiary

2024-08-19T00:23:40,900200090+00:00

An introduction

On a cloud vps, access to manage the instance is provided via network. It is essential to make sure that access to the server is always possible, without the risk of being locked out because of network misconfiguration. For example, the first ufw rule to add before enabling the firewall is ufw allow ssh so that ssh service is accessible after enabling the firewall. There is also netplan --debug try to test netplan configuration before making permanent changes.

One of the high risk network configuration is setting up a VPN tunnel on a cloud server. The chance of misconfiguration is higher when specifying a tunnel as route for accessing the internet, i.e. setting up a VPN as gateway. A misconfiguration can cause the vps instance to be locked from the outside, which needs some form of physical access via serial console or ipmi interface to recover from network misconfiguration.

How wireguard routes packets

A route is created when specifying AllowedIPs in the wireguard configuration. By default, the route is added to the main route table, so all traffic except to the wireguard gateway peer will be routed through wireguard interface, which is not always the desired outcome when activating wireguard tunnel on a cloud vps instance.

Overriding wireguard default routing setup

To override this behavior, there is a Table field which can be used to specify a routing table associated with a specific wireguard interface. With Table field configured, the usual traffic is not modified so one will not lose access to the cloud vps instance, especially when specifying a catch-all ip range as AllowedIPs, i.e. 0.0.0.0/0 for ipv4 and ::/0 for ipv6.

Here is an example of /etc/wireguard/wireguard0.conf with Table field configured.

[Interface]
PrivateKey = hidden
Address = 10.0.0.2/32
DNS = 10.0.0.1
Table = 51820

[Peer]
PublicKey = redacted
AllowedIPs = 0.0.0.0/0
Endpoint = vpn.example.com:51820

On the configuration above, the Table field is configured with value of 51820. So, the usual traffic will follow the default gateway on the main routing table. If VPN use is desired, a policy routing has to be configured.

Accessing the specified routing table

In the above configuration, the wireguard0 interface will only route traffic through the tunnel if the packet flows through 51820 routing table. To redirect the flow of the packet, a specific rule based on firewall mark or based on owner can be enforced.

uid based routing rule

If a user with uid of 1001 wants to use the vpn route for the traffic owned by that user, a rule can be specified easily using ip rule tool.

# Activate the vpn tunnel
systemctl start wg-quick@wireguard0.service
# Route traffic owned by user with uid of 1001 via the vpn
ip rule add uidrange 1001-1001 table 51820
# Since the vpn doesn't provide ipv6, disable routing of ipv6 packets for uid 1001
ip -6 rule add uidrange 1001-1001 unreachable
# Otherwise, add the same rule for ipv6
# ip -6 rule add uidrange 1001-1001 table 51820

Routing based on firewall marking

Other methods of policy routing, such as marking packet using iptables via fwmark can also be used. The setup involves firewall (iptables or nft) in addition to ip-rule. Read the documentation of the specific tools such as iptables and ip-rule for more in-depth understanding about policy based routing (i.e. splitting traffic via several interfaces).

A practical example

A vps is configured as seedbox to a private tracker, but the vps provider prohibits the use of vps for this specific purpose. So a wireguard based VPN is set up to route transmission traffic through wireguard0 interface.

# On debian, the transmission-daemon package comes with a user configured for the installed service: debian-transmission
$ id debian-transmission
uid=111(debian-transmission) gid=116(debian-transmission) groups=116(debian-transmission)
$ ip route show table 51820
default dev wireguard0 scope link
# First, a rule as kill switch to prevent traffic leaking via the main route if the tunnel is down
$ sudo ip rule add uidrange 111-111 unreachable
# enforce packet owned by uid 111 to be routed via table 51820
$ sudo ip rule add uidrange 111-111 lookup 51820
$ sudo ip -6 rule add uidrange 111-111 unreachable
# Allow traffic via ipv6 if the tunnel supports ipv6 (optional)
# $ sudo ip -6 rule add uidrange 111-111 lookup 51820

This setup can be automated during tunnel bring-up via PostUp field in the /etc/wireguard/wireguard0.conf file.

[Interface]
PrivateKey = hidden
# If the tunnel provider supports IPv6, there will be IPv6 address specified here, such as ffdc:17ba:8192::0dbf/128 or 2001:db8:f4c3::1337/128
Address = 10.0.0.2/32
DNS = 10.0.0.1
Table = 51820
PostUp = ip rule add uidrange 111-111 unreachable
PostUp = ip rule add uidrange 111-111 lookup 51820
PostUp = ip -6 rule add uidrange 111-111 unreachable
# Uncomment if IPv6 will be used via the tunnel (if the tunnel provider supports it)
# PostUp = ip -6 rule add uidrange 111-111 lookup 51820
PreDown = ip rule del uidrange 111-111 unreachable
PreDown = ip rule del uidrange 111-111 lookup 51820
PreDown = ip -6 rule del uidrange 111-111 unreachable
# Uncomment if IPv6 routing was enabled before (look at previous comment)
# PreDown = ip rule del uidrange 111-111 lookup 51820

[Peer]
PublicKey = redacted
# If IPv6 will be used, add ::/0 to the AllowedIPs
AllowedIPs = 0.0.0.0/0
Endpoint = vpn.example.com:51820

To verify that the setup is working, check the public ip address returned when using the specific uid. Verify that the returned address is indeed the vpn public ip address instead of the address of the cloud vps instance.

sudo -u debian-transmission curl https://icanhazip.com

The entire transmission-daemon service can be modified to require wg-quick@wireguard0.service to make this fully automated.

sudo systemctl edit transmission-daemon.service

Add this snippet to the text editor provided by systemctl edit command. Put the snippet inside the space between ### as instructed.

[Unit]
Requires=wg-quick@wireguard0.service

[Service]
ExecStartPre=curl https://icanhazip.com

Peace of mind

With the above setups, only packets owned by uid 111 will be routed through wireguard0 interface. Other packets will use the main routing table.

The above setup is my practical use of split tunneling, inspired by Android which containerized each app in its own uid.


Tips for the author

Back to main page