Long story short. To build a transparent proxy, we need to redirect every outbound request to the proxy. It seems easy with iptables... wait a second, no. Outbound requests from the proxy should not be redirected, or nothing can be sent out. How to do that? Usually an exception on the proxy server IP is set. But what if you have multi proxy servers? What if your proxy servers are changing overtime? Wouldn't it be a pain editing iptables rules?
Well, we have
cgroups. We can put our proxy program in a cgroup, and give an exception to this cgroup on iptables. On Arch Linux, the easist way to create cgroups is via
systemd. Therefore, we are picking the systemd PANTS.
Here shows an example for a TCP IPv4 transparent proxy.
If your proxy program runs as a systemd service, you can add this "Slice=" line to its configurations:
After daemon-reloading and restarting, your proxy program will be in the "bypass.slice" group.
You can also see a new directory named bypass.slice in
# ls /sys/fs/cgroup/unified/bypass.slice/ cgroup.controllers cgroup.max.descendants cgroup.threads io.pressure cgroup.events cgroup.procs cgroup.type memory.pressure cgroup.freeze cgroup.stat cpu.pressure run-u1925.scope/ cgroup.max.depth cgroup.subtree_control cpu.stat
If your proxy program is not a systemd service, run it with
sudo systemd-run --slice bypass.slice --scope clash -c /etc/clash/config.yaml
--scope so that you can see the stdouts and manipulate on stdins. It requires root permission (or not if you are using cgroups v2?). I would think you have got the root permission, since we're editing
The same way if you want to start an application which bypasses the proxy:
sudo systemd-run --slice bypass.slice --scope firefox
Or a shell which bypasses the proxy:
sudo systemd-run --slice bypass.slice --scope -S
Let's run a ping in a slice:
$ sudo systemd-run --slice test2.slice --scope -S Running scope as unit: run-r7865e1749deb48e4bcf797f1f403f396.scope $ ping 18.104.22.168
The ping is running here. Now we block all outbound packets from this slice:
iptables -A OUTPUT -m cgroup --path "test2.slice" -j DROP
The ping will start to fail:
ping: sendmsg: Operation not permitted
Okay. We see that iptables are working with our slice (cgroup). Now delete the rule:
iptables -L OUTPUT --line-numbers # Find something like # "2 DROP all -- anywhere anywhere cgroup test2.slice" # and remember its number iptables -D OUTPUT 2
# Create a chain on the nat table. # Why nat table? Because we are doing redirects later. iptables -t nat -N TP-TCP # On this chain: # 1. Everything from the bypass.slice should be where they were iptables -t nat -A TP-TCP -m cgroup --path "bypass.slice" -j RETURN # 2. Everything to local & loopback address should be where they were iptables -t nat -A TP-TCP -d 0.0.0.0/8 -j RETURN iptables -t nat -A TP-TCP -d 127.0.0.0/8 -j RETURN iptables -t nat -A TP-TCP -d 10.0.0.0/8 -j RETURN iptables -t nat -A TP-TCP -d 169.254.0.0/16 -j RETURN iptables -t nat -A TP-TCP -d 172.16.0.0/12 -j RETURN iptables -t nat -A TP-TCP -d 192.168.0.0/16 -j RETURN iptables -t nat -A TP-TCP -d 22.214.171.124/4 -j RETURN iptables -t nat -A TP-TCP -d 240.0.0.0/4 -j RETURN # 3. Everything else should go thourh the proxy port iptables -t nat -A TP-TCP -p tcp -j REDIRECT --to-ports 7892 # 4. All output packets should go through TP-TCP chain iptables -t nat -A OUTPUT -p tcp -j TP-TCP
curls and see if they are going through the proxy. Also, run
iptables -L TP-TCP -t nat -v -n
to see if any
bytes is going through. If it works, we're done! Hooray! 🎉