Transparent proxy on Arch Linux, with iptables and systemd slice
Tags: linux, iptables, cgroups, proxy
Published: 2020-02-25 12:16:24

---
title: "Transparent proxy on Arch Linux, with iptables and systemd slice"
published: true
description: "This post shows how to build a transparent proxy using iptables and systemd.slice without much complexity"
tags: 
- linux
- iptables
- cgroups
- proxy
---

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:

``` systemd
[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`：

``` sh
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:

``` sh
sudo systemd-run --slice bypass.slice --scope firefox
```

Or a shell which bypasses the proxy:

``` sh
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:

``` sh
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:

``` sh
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](https://github.com/Dreamacro/clash/issues/158) and [this](https://ivo-wang.github.io/2018/02/24/TP-redir/).*

``` sh
# 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! 🎉