Out65

学生。前端开发者。GPG 0xA725CB57CA65CAFE。

systemd.slice + iptables + redir:如何在 Arch Linux 上配置透明代理

發布於

嘛,又是久违了的技术教程文。这次说的是如何使用 redir (shadowsocks 也好,clash 也好)在 Arch Linux 设备上配置透明代理,以及方便地运行绕过代理的程序。因为要动 iptables,我就假设想这么做的你拥有 root 权限啦。

以下仅针对 IPv4 环境下的 TCP。

Cgroups 是什么?

Arch Linux Wiki :cgroups (Control groups) 是 Linux 内核的特性,允许用户管理/限制/审计一组进程。

…算了,LMGTFY 好了。

总之,我们要做的就是,把绕过代理的进程(包括代理程序本身)放到一个 cgroup 里,让 iptables 放行它们的请求,而把其外的所有请求转送给代理的 redir 端口。Arch Linux 上管理 cgroup 的最方便方法就是用 systemd,所以我们在这里用到 systemd 的 slice 来建立/访问 cgroup。

运行一个「绕过全局代理」的 systemd slice

如果你的代理是一个 systemd 服务的话,在 [Service] 下面这么写。这里我们把这个临时命名为 test2 (也可以用别的名字啦):

[Service]
Slice=test2.slice

重载(systemctl daemon-reload),然后运行这个服务,你的代理服务就跑在 test2.slice 里了。

在 cgroup 的目录里应该也会出现一个相应的 test2.slice:

# ls /sys/fs/cgroup/unified/test2.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

如果不是服务的话,就用 systemd-run

sudo systemd-run --slice test2.slice --scope clash -c /etc/clash/config.yaml

如果你想开启一个绕过代理的程序,也用这样的方法。例如运行一个 Firefox:

sudo systemd-run --slice test2.slice --scope firefox

如果想在 shell 里测试的话,直接用 systemd-run 开一个运行在某个 slice 里的 shell:

sudo systemd-run --slice test2.slice --scope -S  # -S 代表启动 shell

这样你就有了一个跑在 test2.slice 里的 shell。

快速试一试

在 test2.slice 里运行一个永不停止的 ping,然后在 iptables 里切断它的连接:

iptables -A OUTPUT -m cgroup --path "test2.slice" -j DROP

然后这个 ping 应该会无法 ping 通:

ping: sendmsg: Operation not permitted

好的,我们的 slice 和 iptables 成功地联动了。那么先把这条规则删除掉吧:

iptables -L OUTPUT --line-numbers
# 找到一行类似
# 2 DROP   all  --  anywhere  anywhere  cgroup test2.slice
# 的行,记录它开头的编号(这里是 2)
iptables -D OUTPUT 2    # 把 2 换成你记录的编号

好的本节课程的新知识讲完了以下是复习内容(

又是 iptables

以下大部分是抄 Dreamacro/clash#158这里 的作业,呃…

# 先建条链处理透明代理问题。因为后面要 REDIRECT,所以要在 nat 表。
iptables -t nat -N TP-TCP

# 这条链的配置:
# 1. 绕过代理的 slice 的数据包,直接放行回原来的链
iptables -t nat -A TP-TCP -m cgroup --path "test2.slice" -j RETURN
# 2. 本地的各种地址也一律放行
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. 其它的去到 redir 的端口(clash 默认 7892,你的情况可能不同(YMMV))
iptables -t nat -A TP-TCP -p tcp -j REDIRECT --to-ports 7892
# 4. 所有出口包到 TP-TCP 表上
iptables -t nat -A OUTPUT -p tcp -j TP-TCP

试着访问一些网站,看看它们是不是走了代理(把浏览器的代理设置关闭)?以及运行

iptables -L TP-TCP -t nat -v -n

看看开头的 pkts 和 bytes 是不是有所变化?如果是,那么我们的透明代理大成功!

(这里应该放一张爱酱大胜利的图)

(不是那个快凉了的爱酱…)

后续工作

配置服务绕过代理

可能有一些正在使用的服务本身需要绕过代理,我们也来修改一下配置,让它们也能绕过代理。这里以 unbound 为例:

systemctl edit unbound

加一段:

[Service]
Slice=test2.slice

然后重载(systemd daemon-reload)并重新启动服务就可以啦。

iptables 配置持久化

我们的 iptables 配置在重新启动后会消失。为了让它长期保存,我们需要对其进行持久化:

iptables-save -f /etc/iptables/iptables.rules

以及在每次启动时自动导入:

systemctl enable iptables.service

附录

注:以下内容我没试过,不保证有效性。

你的代理也向内网其它主机提供服务?

(还是只有 TCP + IPv4)

首先处理路由问题。这里假设你的内网主机在 192.168.0.0/16。注意在比较大的网络中,你的内网主机可能在 10.0.0.0/8,这种情况下请在下方进行相应的更改:

iptables -t nat -A PREROUTING -p tcp -s 192.168/16 -j TP-TCP
iptables -t nat -A POSTROUTING -s 192.168/16 -j MASQUERADE

然后你大概需要开启网卡的转发功能:

echo 'net.ipv4.ip_forward = 1' >> /etc/sysctl.conf
sysctl -p

UDP?

-j REDIRECT 是没法处理 UDP 了。为了处理 UDP,我们需要 -j TPROXY。而使用 TPROXY,我们还需要加两条 ip routeip rule,所以这里就比较复杂了。

以下命令仅供参考,不保证有效(说实话我不知道为什么其它实现总想着),我也没试过:

# 先建条链处理透明代理问题。
iptables -t mangle -N TP-UDP

# 这条链的配置:
# 1. 绕过代理的 slice 的数据包,直接放行回原来的链
iptables -t mangle -A TP-UDP -m cgroup --path "test2.slice" -j RETURN
# 2. 本地的各种地址也一律放行
iptables -t mangle -A TP-UDP -d 0.0.0.0/8 -j RETURN
iptables -t mangle -A TP-UDP -d 127.0.0.0/8 -j RETURN
iptables -t mangle -A TP-UDP -d 10.0.0.0/8 -j RETURN
iptables -t mangle -A TP-UDP -d 169.254.0.0/16 -j RETURN
iptables -t mangle -A TP-UDP -d 172.16.0.0/12 -j RETURN
iptables -t mangle -A TP-UDP -d 192.168.0.0/16 -j RETURN
iptables -t mangle -A TP-UDP -d 224.0.0.0/4 -j RETURN
iptables -t mangle -A TP-UDP -d 240.0.0.0/4 -j RETURN
# 2.5. 你很有可能会想让 DNS 不走代理
iptables -t mangle -A TP-UDP -p udp --dport 53 -j RETURN
# 3. 其它的去到 redir 的端口(clash 默认 7892,你的情况可能不同(YMMV))
iptables -t mangle -A TP-UDP -p udp -j TPROXY --tproxy-mark 0x2333/0x2333 --on-ip 127.0.0.1 --on-port 7892
# 4. 所有出口包到 TP-UDP 表上
iptables -t mangle -A OUTPUT -p udp -j TP-UDP

# 5. 还没完,我们还需要路由配置
# 新建路由表 100,将所有数据包发往 loopback 网卡
ip route add local 0/0 dev lo table 100
# 添加路由策略,让所有经 TPROXY 标记的 0x2333/0x2333 udp 数据包使用路由表 100
ip rule add fwmark 0x2333/0x2333 lookup 100

内网其它主机的连接问题,同上一节「你的代理也向内网其它主机提供服务?」。记得把命令里的 TCP 改为 UDP。

你还有 IPv6?

请将所有的 iptables 换成 ip6tables,同时各种本地地址段可能也需要改变。

本文为 Blog 上原文在发布时版本的副本。所有修改均以 Blog 原文为准。由于 IPFS 的不可变性,Matters 的副本无法更新。欲查看原文,请参见 Re:Linked
本文以 CC BY-NC-SA 4.0 协议发布。

喜歡我的文章嗎?
別忘了給點支持與讚賞,讓我知道創作的路上有你陪伴。

CC BY-NC-ND 2.0 版權聲明
3

看不過癮?

一鍵登入,即可加入全球最優質中文創作社區