How do I prevent VPN leaks using iptables?
If you’re using stock OpenVPN in Linux, especially with Network Manager, leaks are possible if the VPN connection fails, or is temporarily interrupted. Also, if your ISP provides IPv6 connectivity, but your VPN service does not, traffic to IPv6-capable sites will bypass the VPN tunnel, and identify you to websites. Given that, it’s prudent to have firewall (iptables) rules that: 1) restrict traffic to the VPN tunnel; 2) allow direct connections only to the VPN server; and 3) block IPv6 traffic.
There are many ways to manage iptables rules. The old-school standard is shell scripting. And indeed, OpenVPN has hooks to run scripts, for routing and iptables, when the VPN connects and disconnects. That’s convenient, certainly, but it’s also complicated, and it requires editing VPN configuration files. Most VPN services use the “redirect-gateway def1” option to handle routing, but they don’t touch iptables. So you need to disable “redirect-gateway def1”, and handle routing changes in your scripts.
For most customers we think using iptables-persistent is the better solution. The rules files are easy to understand, and there’s no need for complicated rules chains with custom tables. It’s easy to manage custom rules for particular circumstances (different locations, different VPNs, etc). And default rules load at reboot, which can be a lifesaver if you’re working on remote servers.
However, this approach has limitations for VPN services that specify servers by hostname (for load-balancing, etc). First, iptables does not interpret hostnames in saved rules, only in scripts for creating rules. And so you must get IPv4 addresses (using the host command) and use them in your rules file(s). Second, to prevent DNS leaks, the recommended rules do not allow DNS requests, except through the VPN tunnel. To ensure that the VPN can reconnect after interruption, you must either specify servers in configuration files by IP address, or add entries for them to /etc/hosts.
Start by installing iptables-persistent:
$ sudo su # apt-get update # apt-get -y install iptables-persistent
The current iptables rules are saved as
Then create new IPv4 rules for the VPN connection:
# nano /etc/iptables/vpn-rules.v4
# You can delete all of these comments, if you like. *filter
# You drop everything by default. :INPUT DROP [0:0] :FORWARD DROP [0:0] :OUTPUT DROP [0:0]
# Some local processes need to hear from other ones. -A INPUT -i lo -j ACCEPT
# If you are running a server on port N, and have enabled forwarding in your VPN account, you must allow inbound traffic on the VPN. You may also want to limit access to a particular IP address (a.b.c.d). There can be multiple rules, one for each permitted port and source address. -A INPUT -i tun0 -s a.b.c.d –dport N -j ACCEPT
# You may need to allow traffic from local DHCP servers. If using Wi-Fi, use “wlan0” instead of “eth0”. This isn’t needed if your router provides persistent leases. -A INPUT -i eth0 -s 255.255.255.255 -j ACCEPT
# Then you allow related/established traffic, and drop everything else, without acknowledgement to peers. -A INPUT -m state –state RELATED,ESTABLISHED -j ACCEPT -A INPUT -j DROP
# Your device isn’t a router, so don’t allow forwarding. In any case, you’d also need to allow that using sysctl. -A FORWARD -j DROP
# Some local processes need to talk to other ones. -A OUTPUT -o lo -j ACCEPT
# You need rule(s) to allow connecting to VPN server(s). You must use IP addresses. If also using Wi-Fi, add another rule, with “-o wlan0”, instead of “-o eth0”. There can be multiple rules, one for each server. -A OUTPUT -o eth0 -d e.f.g.h -j ACCEPT
# You need a rule to allow outbound traffic through the VPN tunnel. -A OUTPUT -o tun0 -j ACCEPT
# You may want rule(s) to allow LAN access. There can be multiple rules, one for each LAN that you use. If also using Wi-Fi, add another rule, with “-o wlan0”, instead of “-o eth0”. -A OUTPUT -o eth0 -d x.y.z.0/24 -j ACCEPT
# Allow outgoing traffic to local DHCP servers. If using Wi-Fi, use “wlan0” instead of “eth0”. This isn’t needed if your router provides persistent leases. -A OUTPUT -o eth0 -d 255.255.255.255 -j ACCEPT
# Then you allow related/established traffic, and drop everything else, without acknowledgement to peers. -A OUTPUT -m state –state RELATED,ESTABLISHED -j ACCEPT -A OUTPUT -j DROP
Then load the IPv4 VPN rules:
# iptables-restore < /etc/iptables/vpn-rules.v4
Now connect (or reconnect) the VPN. If it doesn’t connect, restore the default rules:
# iptables-restore < /etc/iptables/rules.v4
If the VPN connects now, there must be errors in the iptables rules.
Once the basic IPv4 setup is working, you can deal with IPv6. If you have IPv6 service from your ISP, and want to use IPv6 when you’re not using VPNs, just create new IPv6 rules for the VPN connection:
# nano /etc/iptables/vpn-rules.v6
:INPUT DROP [0:0] :FORWARD DROP [0:0] :OUTPUT DROP [0:0]
Then load the IPv6 VPN rules:
# ip6tables-restore < /etc/iptables/vpn-rules.v6
Using “iptables-restore” to restore “vpn-rules.v6” is a classic fail, by the way.
If you don’t use VPN services that route IPv6, and don’t need it, you may want to just disable it:
# echo 'net.ipv6.conf.all.disable_ipv6=1' | sudo tee -a /etc/sysctl.conf # echo 'net.ipv6.conf.default.disable_ipv6=1' | sudo tee -a /etc/sysctl.conf # echo 'net.ipv6.conf.lo.disable_ipv6=1' | sudo tee -a /etc/sysctl.conf # sudo sysctl -p
You can reverse those changes by editing
/etc/sysctl.conf, and deleting those lines.