Force all network traffic through OpenVPN using iptables

Many people use OpenVPN to prevent snooping of their network traffic, such as when connected to an untrusted wireless network. But how can you be sure that no traffic ever leaks outside of the tunnel?

OpenVPN has a redirect-gateway option that directs all network traffic through the tunnel; it replaces the existing default route (that usually points to your local wireless router) with a new default route to the VPN endpoint.

It sounds perfect, but if the tunnel is broken unintentionally, the default route may change back and cause traffic to leak.

One solution is to use iptables to deny all outgoing traffic except when the traffic passes through the tunnel. If the tunnel is broken, access to the Internet is no longer possible until the tunnel is re-established.

Note

These instructions assume you’re using TUN-based routing to connect to the OpenVPN server, and that you’re using the redirect-gateway OpenVPN client option.

In this example, the remote OpenVPN server is located at 203.0.113.100 and is listening to UDP port 1194. Create a file anywhere (eg, /root/iptables.openvpn) with these contents:

*mangle
:PREROUTING ACCEPT [0:0]
:INPUT ACCEPT [0:0]
:FORWARD ACCEPT [0:0]
:OUTPUT ACCEPT [0:0]
:POSTROUTING ACCEPT [0:0]
COMMIT

*nat
:PREROUTING ACCEPT [0:0]
:INPUT ACCEPT [0:0]
:OUTPUT ACCEPT [0:0]
:POSTROUTING ACCEPT [0:0]
COMMIT

# Set a default DROP policy.
*filter
:INPUT DROP [0:0]
:FORWARD DROP [0:0]
:OUTPUT DROP [0:0]

# Allow basic INPUT traffic.
-A INPUT -m conntrack --ctstate RELATED,ESTABLISHED -j ACCEPT
-A INPUT -i lo -j ACCEPT
-A INPUT -p icmp --icmp-type 8 -m conntrack --ctstate NEW -j ACCEPT

# Allow basic OUTPUT traffic.
-A OUTPUT -m conntrack --ctstate RELATED,ESTABLISHED -j ACCEPT
-A OUTPUT -o lo -j ACCEPT
-A OUTPUT -p icmp -j ACCEPT

# Allow traffic to the OpenVPN server and via the tunnel.
-A OUTPUT -o tun+ -j ACCEPT
-A OUTPUT -p udp -m udp -d 203.0.113.100 --dport 1194 -j ACCEPT

# Reject everything else.
-A INPUT -m conntrack --ctstate INVALID -j DROP
-A INPUT -j REJECT --reject-with icmp-port-unreachable
-A FORWARD -j REJECT --reject-with icmp-port-unreachable
-A OUTPUT -j REJECT --reject-with icmp-port-unreachable
COMMIT

After you have established the OpenVPN tunnel, load the firewall rules.

# iptables-restore < /root/iptables.openvpn

Network traffic should now be confined to the tunnel, and won’t leak if the routes change or the tunnel is broken.

Integrate with libvirt

If you use local virtual machines, you may need to edit the firewall rules to allow Internet access.

Note

These instructions assume you’re using the default (NAT-based) virtual network that libvirtd creates.

For the default virtual network to function, libvirtd inserts some firewall rules. These rules aren’t present in the firewall rules above. Ask libvirtd to re-insert them by sending a SIGHUP.

# systemctl reload libvirtd.service

However, one of the rules that libvirtd inserts is too relaxed. It allows virtual machines to communicate over any network interface.

-A FORWARD -s 192.168.122.0/24 -i virbr0 -j ACCEPT

Identify the line number of the offending rule.

# iptables --line-numbers -nL FORWARD \
      | awk '$5=="192.168.122.0/24" {print $1; exit}'
2

Replace it with a rule that allows virtual machines to only access the Internet via the TUN interface.

# iptables -R FORWARD 2 -s 192.168.122.0/24 -i virbr0 -o tun+ -j ACCEPT

Bypass OpenVPN for a specific Unix user

See the next article.