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.
Create a slice, and put stuffs in
If your proxy program runs as a systemd service, you can add this "Slice=" line to its configurations:
[Service]
Slice=bypass.slice
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 /sys/fs/cgroup/unified/
:
# 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 systemd-run
:
sudo systemd-run --slice bypass.slice --scope clash -c /etc/clash/config.yaml
We use --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 iptables
later!
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
Have a try
Let's run a ping in a slice:
$ sudo systemd-run --slice test2.slice --scope -S
Running scope as unit: run-r7865e1749deb48e4bcf797f1f403f396.scope
$ ping 1.1.1.1
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
iptables' time!
Parts of this paragraph comes from Dreamacro/clash#158 and this.
# 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 224.0.0.0/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
Do some curl
s and see if they are going through the proxy. Also, run
iptables -L TP-TCP -t nat -v -n
to see if any pkts
and bytes
is going through. If it works, we're done! Hooray! 🎉
Top comments (0)