A simple nftables firewall to block traffic from leaking outside a wireguard VPN tunnel
Find a file
2025-04-28 02:43:23 -07:00
LICENSE update readme, add GPL license 2024-12-08 03:20:34 -08:00
README.md update readme 2025-04-28 02:43:23 -07:00
wg-lockdown.nft add empty chains for logging 2025-03-01 01:03:30 -08:00
wg-lockdown.service add empty chains for logging 2025-03-01 01:03:30 -08:00

Wireguard Lockdown Mode

If you use a wireguard VPN to proxy all internet traffic, this is a simple firewall to block traffic from leaking outside the tunnel (otherwise known as a kill switch). Works on Linux, tested on Fedora Workstation, Fedora Atomic distros (including Universal Blue distros), Debian, and Linux Mint.

Disclaimer: I am not an expert at networking, this was just something I put together because I could not find anything similar at the time. I mainly followed Mullvad VPN for my implementation, looking at the nftables rules that the official Mullvad Linux client uses, and also their document here: https://github.com/mullvad/mullvadvpn-app/blob/main/docs/security.md.

Install

First make sure your wireguard VPN interface name starts with "wg", and that FwMark (firewall mark) is enabled and set to 51820.

Then:

  1. download or clone this repo
  2. edit the variables at the top of wg-lockdown.nft and then copy the file to /etc/nftables
    • if you don't want to specify all VPN server IPs, read the section below titled "Dynamic VPN Server IPs"
  3. copy wg-lockdown.service to /etc/systemd/system
  4. temporarily enable the firewall using sudo systemctl start wg-lockdown
  5. make sure everything still works (if not, you can turn it off using sudo systemctl stop wg-lockdown, or restart your PC and your firewalls will be back to normal)
  6. permanently enable the firewall using sudo systemctl enable --now wg-lockdown

Uninstall

  1. run sudo systemctl disable --now wg-lockdown
  2. remove files /etc/systemd/system/wg-lockdown.service and /etc/nftables/wg-lockdown.nft

Troubleshooting

The firewall includes special chains that allow you to add logging for dropped packets. For example to print packets dropped from the output chain you would add to the logging-output chain using something like:

sudo nft 'add rule inet wg-lockdown logging-output log prefix "wg-lockdown: rejected output: "'

This will log dropped packets to your kernel logs, which you can view live using journalctl -k -f. To erase all changes and reset your firewall simply use systemctl restart wg-lockdown.

You can also use the logging-forward and logging-input for the forward and input chains.

If you don't want to log to your kernel logs you can also use nftrace: https://wiki.nftables.org/wiki-nftables/index.php/Ruleset_debug/tracing.

Warnings and Tips

  • we don't fully support ipv6 (like DHCPv6 and NDP), since I am not very familiar with ipv6

  • related/established traffic outside the tunnel will be dropped once wg-lockdown is activated, so be careful that your ssh connections might get dropped once you enable the kill switch

    • this is another reason why you should test the firewall using systemctl start before calling systemctl enable, so that worst case you can force reboot the system to restore it to its original state
  • we do not block incoming traffic

    • if you want to do so, I recommend using firewalld or firewall-cmd, which is better at handling connection states than nftables (for example, if you want to block incoming connections from the LAN, but still want to be able to make and establish connections towards the LAN)
    • better yet, disable services that you don't need
  • unlike Mullvad, we allow LAN traffic (including all standard local network ranges) by default

  • we allow LAN traffic to go through the tunnel, since this can be useful in some point-to-site or site-to-site configurations

    • though whether or not your OS actually sends LAN traffic through the tunnel, depends on your routing tables and your wireguard VPN's AllowedIPs configuration
      • for example, if your AllowedIPs is 0.0.0.0 and your home network is 192.168.0.0/24, then most likely scenario is that any requests to 192.168.0.0/24 will go out your eth/wifi directly, while requests to other standard local network ranges (like 10.0.0.0/8) will be sent through the tunnel
    • to block LAN traffic from going through the tunnel (but still allow LAN traffic outside of the tunnel), you can change the rule ip daddr $LAN accept to oifname != "wg*" ip daddr $LAN accept and similarly for LAN6 rules, though I haven't tested this
  • we allow forward traffic to LAN addresses, which is often needed for VMs or rootful containers running on your system

Dynamic VPN Server IPs

Right now you have to specify all your VPN server IPs at the top of the wg-lockdown.nft file. This might be cumbersome if the VPN server IPs are dynamic.

If you don't want to specify a static set of VPN server IPs inside wg-lockdown.nft, you can change the rule ip daddr $VPN_SERVERS meta mark 51820 accept to just meta mark 51820 accept. This will allow a connection to any server, but only if it has a firewall mark of 51820. So assuming that your wireguard VPN's FwMark is set to 51820, and no other application on your system is also setting those firewall marks, then this should only allow wireguard traffic out. I'm not sure though, so by default we also check the destination server just to be safe.

I checked to see what other people were doing and found that:

  • the Mullvad Linux client checks both the firewall mark and the destination server
  • if you download wireguard configs from mullvad.net with the "kill switch" option enabled, the included "kill switch" only checks the firewall mark
  • the iVPN's docs on making a custom wireguard kill switch only checks the firewall mark

Further Reading