I've successfully set up Tor for transparent proxying using iptables (ie, all outgoing traffic is sent through Tor without the application's knowledge). In doing so, I discovered both an odd problem and an equally odd fix (workaround?).
My rules are as follows. No user-defined chains or other rules present, all policies are ACCEPT.
-t nat -A OUTPUT -o lo -j RETURN -t nat -A OUTPUT -p tcp -m owner --uid-owner tor -j RETURN -t nat -A OUTPUT -p tcp --syn -j REDIRECT --to-ports 54 -t nat -A OUTPUT -p udp --dport 53 -j REDIRECT --to-ports 55 -A INPUT -j ACCEPT -A OUTPUT -o lo -j ACCEPT # Rule A -A OUTPUT -d 127.0.0.1 -j ACCEPT # Rule B -A OUTPUT -p tcp -m owner --uid-owner tor -j ACCEPT -A OUTPUT -j REJECT # Rule C
In other words:
- All traffic from/to localhost is untouched.
- All traffic generated by Tor is untouched.
- All outgoing TCP connections are sent through Tor (transparent proxying port is 54).
- All outgoing DNS queries are resolved by Tor (DNS on port 55).
- To prevent leakage, all other outgoing traffic is hit on the head with a brick and dropped in a ditch.
(While this setup has some problems, like failing to respond to connections from the LAN, those are irrelevant to the issue at hand.)
If Rule B is not present, all outgoing connections are immediately rejected by Rule C. I know they are rejected by Rule C because changing the rejection type (ex.
--reject-with icmp-proto-unreachable) results in a different error. Example error messages without Rule B:
# With Rule C --reject-with icmp-port-unreachable, the default: $ curl 18.104.22.168 # (google.com; DNS also doesn't work) curl: (7) Failed connect to 22.214.171.124:80; Connection refused # With Rule C --reject-with icmp-proto-unreachable: $ curl 126.96.36.199 curl: (7) Failed connect to 188.8.131.52:80; Protocol not available
With Rule B, connections work fine.
Based on my understanding of iptables, when an outgoing TCP connection is made, the third rule in OUTPUT chain of
nat will rewrite the packet's destination to 127.0.0.1:54. The packet will then enter OUTPUT chain of
filter, and it should be matched by Rule A, since its outgoing interface is now the loopback interface. However, it is not matched by Rule A, and if Rule B (which does match it) is not present, the packet gets dragged into an alley and mugged by Rule C.
Why aren't Rule A and Rule B equivalent? How is matching the loopback interface different from matching the loopback address, in this situation?