Total Pageviews

Monday, 14 June 2021

用 Firewalld + systemd.slice + redir 實現 TCP + UDP 透明代理

 现在网上已经有不少用 iptables 或 nftables 实现透明代理(Tproxy)的例子了。绝大多数关于透明代理的教程都是通过 iptables 实现的,也有少量使用新一代的 nftables 内核防火墙前端来实现。然而,消费级电脑上通常会装有更简化的防火墙程序,例如 Fedora/CentOS/SUSE 自带的 Firewalld,或 Ubuntu 的 UFW。在用这些简化前端的同时,直接操作 nftables 和 iptables 的话可能会造成冲突,更好的解法是全部工作都由 firewalld/ufw 来做。

本文就以 firewalld 为例,用它的 direct rules 来实现透明代理(没错,firewalld 并不提供关于透明代理的抽象,所以并不比 iptables 有优势)。

想要实现的效果

  • TCP 和 UDP 流量自动转给 Clash(或 shadowsocks 等)
  • Clash 本身不会把流量转发给自己
  • 可以让某些 程序 不转发给 clash,直接联网

Get started

先来设置变量,指向 clash 的 redir-port

export proxy_port=7892 # 按你 clash 的设置修改

接着设置 firewalld 和 ip route。 Firewalld 的 direct rules 语法几乎等同于 iptables(因为实际上就是调用 iptables 来执行 direct rules)。所以这里直接照抄 systemd.slice + iptables + redir:如何在 Arch Linux 上配置透明代理 的规则,稍有修改:

#tcp
sudo firewall-cmd --direct --add-chain ipv4 nat clash
sudo firewall-cmd --direct --add-rule ipv4 nat clash 1 -m cgroup --path "clash.slice" -j RETURN
sudo firewall-cmd --direct --add-rule ipv4 nat clash 1 -d 0.0.0.0/8 -j RETURN
sudo firewall-cmd --direct --add-rule ipv4 nat clash 1 -d 10.0.0.0/8 -j RETURN
sudo firewall-cmd --direct --add-rule ipv4 nat clash 1 -d 127.0.0.0/8 -j RETURN
sudo firewall-cmd --direct --add-rule ipv4 nat clash 1 -d 169.254.0.0/16 -j RETURN
sudo firewall-cmd --direct --add-rule ipv4 nat clash 1 -d 172.16.0.0/12 -j RETURN
sudo firewall-cmd --direct --add-rule ipv4 nat clash 1 -d 192.168.0.0/16 -j RETURN
sudo firewall-cmd --direct --add-rule ipv4 nat clash 1 -d 224.0.0.0/4 -j RETURN
sudo firewall-cmd --direct --add-rule ipv4 nat clash 1 -d 240.0.0.0/4 -j RETURN

sudo firewall-cmd --direct --add-rule ipv4 nat clash 2 -p tcp -j REDIRECT --to-port "$proxy_port"

sudo firewall-cmd --direct --add-rule ipv4 nat OUTPUT 1 -p tcp -j clash

#udp
sudo ip rule add fwmark 1 table 100
sudo ip route add local default dev lo table 100
sudo firewall-cmd --direct --add-chain ipv4 mangle clash
sudo firewall-cmd --direct --add-rule ipv4 mangle clash 1 -m cgroup --path "clash.slice" -j RETURN
sudo firewall-cmd --direct --add-rule ipv4 mangle clash 1 -d 0.0.0.0/8 -j RETURN
sudo firewall-cmd --direct --add-rule ipv4 mangle clash 1 -d 10.0.0.0/8 -j RETURN
sudo firewall-cmd --direct --add-rule ipv4 mangle clash 1 -d 127.0.0.0/8 -j RETURN
sudo firewall-cmd --direct --add-rule ipv4 mangle clash 1 -d 169.254.0.0/16 -j RETURN
sudo firewall-cmd --direct --add-rule ipv4 mangle clash 1 -d 172.16.0.0/12 -j RETURN
sudo firewall-cmd --direct --add-rule ipv4 mangle clash 1 -d 192.168.0.0/16 -j RETURN
sudo firewall-cmd --direct --add-rule ipv4 mangle clash 1 -d 224.0.0.0/4 -j RETURN
sudo firewall-cmd --direct --add-rule ipv4 mangle clash 1 -d 240.0.0.0/4 -j RETURN
sudo firewall-cmd --direct --add-rule ipv4 mangle clash 2 -p udp -j TPROXY --on-port "$proxy_port" --tproxy-mark 1
sudo firewall-cmd --direct --add-rule ipv4 mangle OUTPUT 0 -p udp -j clash # 这里可能有错(`failed: iptables-restore: line 3 failed`),欢迎指正
sudo firewall-cmd --direct --add-chain ipv4 nat CLASH_DNS
sudo firewall-cmd --direct --remove-rules ipv4 nat CLASH_DNS 
sudo firewall-cmd --direct --add-rule ipv4 nat CLASH_DNS 1 -p udp -j REDIRECT --to-port 1053
sudo firewall-cmd --direct --add-rule ipv4 nat OUTPUT 0 -p udp --dport 53 -j CLASH_DNS

其中,sudo firewall-cmd --direct --add-rule ipv4 nat clash 1 -m cgroup --path "clash.slice" -j RETURN 将放到 clash.slice 下的 service 给绕过 clash 了。

Clash 要以 systemd unit 的形式 来运行。为了让 clash 不把自己的流量循环转回给自己,给它的 unit 分配给 clash.slice

[Service]
Slice=clash.slice

其它不想走 clash 的程序也可以以类似方式分配给 clash.slice。

from https://matters.news/@whisper/%E7%94%A8-firewalld-systemd-slice-redir-%E5%AF%A6%E7%8F%BE-tcp-udp-%E9%80%8F%E6%98%8E%E4%BB%A3%E7%90%86-bafyreig5keicgrjoall54ujagzxkd6ocmdoekbnoov7d3rzeicq3xcqv3y

------------------------

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


又是久违了的技术教程文。这次说的是如何使用 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,同时各种本地地址段可能也需要改变。

from https://matters.news/@outloudvi/systemd-slice-iptables-redir-%E5%A6%82%E4%BD%95%E5%9C%A8-arch-linux-%E4%B8%8A%E9%85%8D%E7%BD%AE%E9%80%8F%E6%98%8E%E4%BB%A3%E7%90%86-bafyreia56osrovpylifeiuzdcrxr52gmbo5rkn5zmlvlho5tr7mjejqshy

No comments:

Post a Comment