Skip to main content
Re:Linked

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

· #devops · 约 5.4k 字 · 浏览量: 805
本文距离上次更新已经超过 900 天。因此,其中的信息可能已经过时。

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

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

(English version of this post (partially) available here at dev.to.)

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,同时各种本地地址段可能也需要改变。

评论

您可以选填邮箱地址以接收关于评论回复的邮件通知。
12 条评论
  • 最新
  • 最热
  • 最早
  • 作者
关闭评论
ES 2024-03-28

大佬,比如用clash-verge这类客户端的话,要怎么运行这个 systemd slice啊?它应该不是以服务的形式运行的,但是GUI本身应该已经启动了相关的clash进程了?不知道怎么处理比较合适,特来请教!

Outvi V OVES 2024-03-30

如果是 GUI 的话,可以直接在 slice 中启动 GUI(例如 sudo systemd-run --slice bypass.slice --scope --uid 1000 clash-gui ),那么它启动的 clash 进程一般也会继承这个 slice。

ES Outvi V 2024-03-31

多谢回复!我尝试了一下,确实启动了这个slice,但是后来我按照这篇文章配置了一下,直接啥网也上不了了,不知道是什么原因?我用的以下命令:
sudo systemd-run --slice bypass.slice --scope --uid 1000 clash-verge
[sudo] password for youxing:
Running as unit: run-r8fe30a4bce52444ba03f7fd0498b903a.scope; invocation ID: a871f7e92f91472799fe81c5d3e3218a
app exists
[youxing@youxing-varch ~]$ su
Password:
[root@youxing-varch youxing]# iptables -t nat -N TP-TCP
iptables -t nat -A TP-TCP -m cgroup --path "bypass.slice" -j RETURN
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
iptables -t nat -A TP-TCP -p tcp -j REDIRECT --to-ports 7890
iptables -t nat -A OUTPUT -p tcp -j TP-TCP

阅读更多
ES ES 2024-03-31

我的clash-verge的端口设置的是mixed port: 7890, 然后其实我看了下,配置之前代理服务器的测速都是正常的,运行上面这些命令以后测速就全是error了

Outvi V OVES 2024-03-31

这个可能要检查 iptables 的方面,例如通过 iptables -v 指令来查看预期的网络包到达或者没有到达哪个预期的规则;或者查看 clash 的方面,例如通过 clash 的日志来了解 clash 在 --user 1000 下是不是未能监听特定的端口。我对 linux 网络栈和 iptables 不太了解,所以可能没有办法关联到具体的原因。

ES Outvi V 2024-03-31

好的,谢谢

阅读更多
鱼塔 2020-07-26

你好大佬,我按照你的方法成功配置了透明代理,非常感谢。

但是遇到了另外一个问题,不知道大佬有没有使用 KVM,我这里遇到的问题是当 KVM 打开一个实例时,就无法联网了,可能是 iptables 的规则,导致 DNS 出现问题,但是我也没学过计算机网络,有点挠头,不知道怎么处理了。

具体情况是几乎全抄了大佬的文章,并且在 clash 中的 dns 配置为 https://pastebin.com/tg1cuURp

如果能有所提示/回复,不胜感激。

Outvi V OV鱼塔 2020-07-27

这是一个确实存在的问题。我个人也遇到了这个问题:启动 Docker 之后 iptables 的 cgroups 过滤不工作,导致代理软件的流量在本地打环,最终导致整个系统无法连接网络。删除 OUTPUT 的那条规则(iptables -t nat -D OUTPUT 1)之后网络就不会打环了,不过透明代理也自然会失效。目前还没找到完美的解决方案,我发了封邮件给 netfilter 的邮件列表询问这个问题,如果有进展的话我会在 blog 和 Matter 更新。

鱼塔 鱼塔 2020-07-27

感谢回复,静候佳音。

所以现在想要实现同时透明代理和使用 KVM/Docker 的话,就应该寻求使用 cgroups 之外的透明代理的方法?

Outvi V OV鱼塔 2020-07-28

是的,需要 cgroups 之外的东西,不过可以接着用 iptables。因为这是 cgroups 的问题,所以我们只要把 cgroups 换成别的(例如 gid-owner)就好啦。举个例子:

iptables -t nat -A TP-TCP -m cgroup --path "test2.slice" -j RETURN

换成

iptables -t nat -A TP-TCP -m owner --gid-owner 801 -j RETURN

当然,你需要创建一个 gid 为 801 的组,也需要把 clash(和 DNS 服务)在这个组启动(例如在 systemd 的 service 文件加一行 Group=801)。

我会根据这个写一篇更新发布到本站和 Matters 上。

鱼塔 鱼塔 2020-07-28

我先去自己写写试试,不行的话再回来看大佬的更新。 哈哈哈!

鱼塔 鱼塔 2020-07-28

只需要修改提到的这两处就可以了,如果用 clash 自带的 DNS 服务,那么这点也不用管了。

阅读更多


LIKE