How can I block all traffic *except* Tor?

On a Linux system, is there a way to block all in and outbound traffic unless it passes through the Tor network. This includes any form of IP communication, not just TCP connections. For example I want UDP to be completely blocked since it cannot pass through Tor. I want this systems Internet usage to be entirely anonymous, and I don't want any applications leaking.

I realize this might be complicated because Tor itself needs to communicate with relay nodes somehow.


Solution 1:

Easy enough with iptables. It can have rules which match specific users, and you should have already set up tor to run under its own user ID; deb and rpm packages provided by major Linux distributions and the Tor Project set up a user for Tor already.

Complete sample, usable iptables and Tor configurations follow. This firewall can be loaded with the iptables-restore command. The end result of this configuration will transparently route all traffic originating from, or being forwarded through, the host to Tor, without needing to configure proxies. This configuration should be leak-proof; though you should of course test it thoroughly.

Note that the uid for the tor user (here, 998) is stored in numeric form by iptables. Substitute the correct uid for your tor user in each place that it appears here.

Note also that the IP address of the host needs to be given in the first rule to support incoming clearnet and LAN traffic addressed directly to the host (here shown as 198.51.100.212). If you have multiple IP addresses, repeat the rule for each address.

*nat
:PREROUTING ACCEPT [0:0]
:OUTPUT ACCEPT [0:0]
:POSTROUTING ACCEPT [0:0]
-A PREROUTING -d 198.51.100.212/32 -j RETURN
-A PREROUTING -p udp -m udp --dport 53 -j REDIRECT --to-ports 53
-A PREROUTING -p tcp -m tcp --tcp-flags FIN,SYN,RST,ACK SYN -j REDIRECT --to-ports 49151
-A OUTPUT -o lo -j RETURN
-A OUTPUT -m owner --uid-owner 998 -j RETURN
-A OUTPUT -p udp -m udp --dport 53 -j REDIRECT --to-ports 53
-A OUTPUT -p tcp -m tcp --tcp-flags FIN,SYN,RST,ACK SYN -j REDIRECT --to-ports 49151
COMMIT
*filter
:INPUT DROP [0:0]
:FORWARD DROP [0:0]
:OUTPUT DROP [0:0]
-A INPUT -m state --state RELATED,ESTABLISHED -j ACCEPT
-A INPUT -p icmp -j ACCEPT
-A INPUT -i lo -j ACCEPT
-A INPUT -p tcp -m conntrack --ctstate NEW -m tcp -d 127.0.0.1 --dport 22 -j ACCEPT
-A INPUT -j LOG --log-prefix "IPv4 REJECT INPUT: "
-A FORWARD -j LOG --log-prefix "IPv4 REJECT FORWARD: "
-A OUTPUT -m state --state RELATED,ESTABLISHED -j ACCEPT
-A OUTPUT -o lo -j ACCEPT
-A OUTPUT -d 127.0.0.1/32 -p udp -m udp --dport 53 -j ACCEPT
-A OUTPUT -d 127.0.0.1/32 -p tcp -m tcp --dport 49151 -j ACCEPT
-A OUTPUT -m owner --uid-owner 998 -m conntrack --ctstate NEW -j ACCEPT
-A OUTPUT -j LOG --log-prefix "IPv4 REJECT OUTPUT: "
COMMIT

The ssh INPUT rule only allows connections if they arrive via the local host, i.e. a Tor hidden service. If you also want to permit incoming ssh connections via clearnet, remove -d 127.0.0.1.

The corresponding torrc file is:

User toranon
SOCKSPort 9050
DNSPort 53
TransPort 49151
AutomapHostsOnResolve 1

This configuration requires that the host have a static IP address. For the expected use cases, it's likely that you have already planned for it to have a static IP address.

And finally, the output!

[root@unknown ~]# curl ifconfig.me
31.31.73.71
[root@unknown ~]# host 31.31.73.71
71.73.31.31.in-addr.arpa domain name pointer cronix.sk.
[root@unknown ~]# curl ifconfig.me
178.20.55.16
[root@unknown ~]# host 178.20.55.16
16.55.20.178.in-addr.arpa domain name pointer marcuse-1.nos-oignons.net.