树莓派 Clash 透明代理(TProxy)_
转载自:https://www.arloor.com/posts/clash-tpproxy/
clash透明代理可以使用ShellClash,这里记录下其中的核心技术,掌握核心技术才好。
前言
一开始我使用Clash tun模式,遇到的最大问题是dns污染。后面看到了Shell Clash看到了使用redir和nftables相关的东西。
利用iptables-redirct来做透明代理
clash配置
- 关闭tun
- 使用dns的fallback配置来避免dns污染相关问题
mixed-port: 7890
redir-port: 7892
authentication: [""]
allow-lan: true
mode: Rule
log-level: info
ipv6: false
external-controller: :9999
external-ui: ui
secret:
tun: {enable: false}
experimental: {ignore-resolve-fail: true, interface-name: en0}
dns: {enable: true, ipv6: false, listen: 0.0.0.0:1053, use-hosts: true, enhanced-mode: redir-host, default-nameserver: [114.114.114.114, 223.5.5.5, 127.0.0.1:53], nameserver: [114.114.114.114, 223.5.5.5], fallback: [1.0.0.1, 8.8.4.4], fallback-filter: {geoip: true}}
store-selected: true
hosts:
'localhost': 127.0.0.1
使用iptables-redirct规则
关键摘要:
- 为非网关设备:控制PREROUTING,将非网关的其他设备redirect到clash的redir端口上
- 为网关自己:控制OUTPUT链,将网关本身发出的流量也走到clash中,并重新走一次PREROUTING一边进行透明代理
- 因为使用的是redirect模式,所以需要打开内核的ip_forward特性。(iptables-tpproxy则需要根据fwmark进行增加ip route
开启内核的ip_forward
sed -i '/^net.ipv4.ip_forward=0/'d /etc/sysctl.conf
sed -n '/^net.ipv4.ip_forward=1/'p /etc/sysctl.conf | grep -q "net.ipv4.ip_forward=1"
if [ $? -ne 0 ]; then
echo -e "net.ipv4.ip_forward=1" >> /etc/sysctl.conf && sysctl -p
fi
nftables的redirect(以下只代理tcp,不代理udp)
cat > /lib/systemd/system/con.service <<EOF
[Unit]
Description=clash
After=network.target
[Service]
Type=simple
User=root
ExecStartPre=nft flush ruleset
ExecStart=/bin/su shellclash -c "/opt/con/clashnet -d /opt/con/"
ExecStartPost=nft -f /etc/nftables/nftables-redirect-clash.nft
Restart=on-failure
RestartSec=3s
LimitNOFILE=999999
[Install]
WantedBy=multi-user.target
EOF
mkdir /etc/nftables
cat > /etc/nftables/nftables-redirect-clash.nft <<EOF
table ip nat {
chain clash_dns {
meta l4proto udp redirect to :1053
}
chain PREROUTING {
type nat hook prerouting priority dstnat; policy accept;
meta l4proto udp udp dport 53 jump clash_dns
meta l4proto tcp tcp dport { 22,53,587,465,995,993,143,80,443,8080} jump clash
}
chain clash {
ip daddr 0.0.0.0/8 return
ip daddr 10.0.0.0/8 return
ip daddr 127.0.0.0/8 return
ip daddr 100.64.0.0/10 return
ip daddr 169.254.0.0/16 return
ip daddr 172.16.0.0/12 return
ip daddr 192.168.0.0/16 return
ip daddr 224.0.0.0/4 return
ip daddr 240.0.0.0/4 return
meta l4proto tcp ip saddr 192.168.0.0/16 redirect to :7892
meta l4proto tcp ip saddr 10.0.0.0/8 redirect to :7892
}
}
table ip6 nat {
chain clashv6_dns {
meta l4proto udp redirect to :1053
}
chain PREROUTING {
type nat hook prerouting priority dstnat; policy accept;
meta l4proto udp udp dport 53 jump clashv6_dns
}
}
table ip filter {
chain INPUT {
type filter hook input priority filter; policy accept;
meta l4proto tcp ip saddr 10.0.0.0/8 tcp dport 7890 accept
meta l4proto tcp ip saddr 127.0.0.0/8 tcp dport 7890 accept
meta l4proto tcp ip saddr 192.168.0.0/16 tcp dport 7890 accept
meta l4proto tcp ip saddr 172.16.0.0/12 tcp dport 7890 accept
meta l4proto tcp tcp dport 7890 reject
}
}
table ip6 filter {
chain INPUT {
type filter hook input priority filter; policy accept;
meta l4proto tcp tcp dport 7890 reject
}
}
EOF
if [ -z "$(id shellclash 2>/dev/null | grep 'root')" ];then
if ckcmd userdel useradd groupmod; then
userdel shellclash 2>/dev/null
useradd shellclash -u 7890
groupmod shellclash -g 7890
sed -Ei s/7890:7890/0:7890/g /etc/passwd
else
grep -qw shellclash /etc/passwd || echo "shellclash:x:0:7890:::" >> /etc/passwd
fi
fi
cat > /etc/nftables/nftables-redirect-clash-local.nft <<EOF
table ip nat {
chain PREROUTING {
type nat hook prerouting priority dstnat; policy accept;
meta l4proto udp udp dport 53 jump clash_dns
meta l4proto tcp tcp dport { 22,53,587,465,995,993,143,80,443,8080} jump clash
meta l4proto tcp jump clash
}
chain clash {
ip daddr 0.0.0.0/8 return
ip daddr 10.0.0.0/8 return
ip daddr 127.0.0.0/8 return
ip daddr 100.64.0.0/10 return
ip daddr 169.254.0.0/16 return
ip daddr 172.16.0.0/12 return
ip daddr 192.168.0.0/16 return
ip daddr 224.0.0.0/4 return
ip daddr 240.0.0.0/4 return
meta l4proto tcp ip saddr 192.168.0.0/16 redirect to :7892
meta l4proto tcp ip saddr 10.0.0.0/8 redirect to :7892
}
chain clash_dns {
meta l4proto udp redirect to :1053
}
chain clash_out {
skgid 7890 return
ip daddr 0.0.0.0/8 return
ip daddr 10.0.0.0/8 return
ip daddr 100.64.0.0/10 return
ip daddr 127.0.0.0/8 return
ip daddr 169.254.0.0/16 return
ip daddr 192.168.0.0/16 return
ip daddr 224.0.0.0/4 return
ip daddr 240.0.0.0/4 return
meta l4proto tcp redirect to :7892
}
chain OUTPUT {
type nat hook output priority -100; policy accept;
meta l4proto tcp jump clash_out
meta l4proto udp udp dport 53 jump clash_dns_out
}
chain clash_dns_out {
skgid 7890 return
meta l4proto udp redirect to :1053
}
}
table ip6 nat {
chain PREROUTING {
type nat hook prerouting priority dstnat; policy accept;
meta l4proto udp udp dport 53 jump clashv6_dns
}
chain clashv6_dns {
meta l4proto udp redirect to :1053
}
}
table ip filter {
chain INPUT {
type filter hook input priority filter; policy accept;
meta l4proto tcp ip saddr 10.0.0.0/8 tcp dport 7890 accept
meta l4proto tcp ip saddr 127.0.0.0/8 tcp dport 7890 accept
meta l4proto tcp ip saddr 192.168.0.0/16 tcp dport 7890 accept
meta l4proto tcp ip saddr 172.16.0.0/12 tcp dport 7890 accept
meta l4proto tcp tcp dport 7890 reject
}
}
table ip6 filter {
chain INPUT {
type filter hook input priority filter; policy accept;
meta l4proto tcp tcp dport 7890 reject
}
}
EOF
nft flush ruleset
service con restart
nft -f /etc/nftables/nftables-redirect-clash-local.nft
iptables-tpproxy
后续再补充,和redirect的主要区别是不需要ip_forward的特性,nftables的语句将主要是–tp-proxy等。因为tpproxy需要给流量标记,还需要单独对有流量标记的流量配置路由表
除了利用REDIRECT模式,Istio还提供TPROXY模式,当然也是借助Linux内核提供的功能实现的,对于TPROXY模式,实现的原理要相对复杂不少,需要借助iptables和路由:通过iptables将数据包打上mark,然后使用一个特殊的路由,将数据包指向本地,由于使用了mangle表,所以数据包的原始和目的地址都是不会被修改的。下面是一个例子:
iptables -t mangle -A PREROUTING -p tcp -j TPROXY --tproxy-mark 0x1/0x1 --on-port 8888
ip rule add fwmark 0x1/0x1 pref 100 table 100
ip route add local default dev lo table 100
一、注意事项
本文中内网 CIDR 为 192.168.0.0/16
, 即所有地址段规则、配置都是针对当前内网 CIDR 进行处理的; clash fake-ip 的 CIDR 为 198.18.0.0/16
, 请不要写错成 192
, 这是 198
(也不要问我为什么强调).
二、安装 Clash
本文所采用的透明代理方式不依赖于 TUN, 所有是否是增强版本不重要, 如果可以请尽量使用最新版本.
# x86 用户请自行替换
wget https://github.com/Dreamacro/clash/releases/download/v1.9.0/clash-linux-armv8-v1.9.0.gz
# 解压
gzip -d clash-linux-armv8-v1.9.0.gz
# 安装到系统 PATH
chmod +x clash-linux-armv8-v1.9.0
mv clash-linux-armv8-v1.9.0 /usr/bin/clash
创建专用的 clash 用户:
useradd -M -s /usr/sbin/nologin clash
编写 Systemd 配置文件:
cat > /lib/systemd/system/clash.service <<EOF
[Unit]
Description=Clash TProxy
After=network.target
[Service]
Type=simple
User=clash
Group=clash
CapabilityBoundingSet=CAP_NET_ADMIN CAP_NET_BIND_SERVICE CAP_NET_RAW
AmbientCapabilities=CAP_NET_ADMIN CAP_NET_BIND_SERVICE CAP_NET_RAW
Restart=on-failure
ExecStartPre=+/usr/bin/bash /etc/clash/clean.sh
ExecStart=/usr/bin/clash -d /etc/clash
ExecStartPost=+/usr/bin/bash /etc/clash/iptables.sh
ExecStopPost=+/usr/bin/bash /etc/clash/clean.sh
[Install]
WantedBy=multi-user.target
EOF
三、调整配置
本文中 Clash 配置文件、脚本等统一存放到 /etc/clash
目录中, 针对于 Clash 配置文件, 着重说明重点配置, 完整配置请从官方 Wiki 复制: https://github.com/Dreamacro/clash/wiki/configuration#all-configuration-options
3.1、端口配置
端口配置请尽量保持默认, 如果需要调整端口, 请同步修改后面相关脚本中的端口(TProxy):
# 注释掉 port 端口配置, 使用 mixed-port
#port: 7890
# 注释掉 socks-port 端口配置, 使用 mixed-port
#socks-port: 7891
# 注释掉 redir-port 端口配置, 因为全部采用 TProxy 模式
#redir-port: 7892
# TProxy 的透明代理端口
tproxy-port: 7893
# mixed-port 端口将同时支持 SOCKS5/HTTP
mixed-port: 7890
# 允许来自局域网的连接
allow-lan: true
# 绑定到所有接口
bind-address: '*'
3.2、DNS 配置
Clash 配置中请开启 DNS, 并使用 fake-ip
模式, 样例配置如下:
dns:
enable: true
listen: 0.0.0.0:1053
ipv6: false
default-nameserver:
- 114.114.114.114
- 8.8.8.8
enhanced-mode: fake-ip
3.3、防火墙规则
为了保证防火墙规则不被破坏, 本文采用脚本暴力操作, 如果宿主机有其他 iptables 控制程序, 则推荐手动执行并通过 iptables-persistent
等工具进行持久化;
/etc/clash/iptables.sh
: 负责启动时添加 iptables 规则
#!/usr/bin/env bash
set -ex
# ENABLE ipv4 forward
#sysctl -w net.ipv4.ip_forward=1
# ROUTE RULES
ip rule add fwmark 666 lookup 666
ip route add local 0.0.0.0/0 dev lo table 666
# clash 链负责处理转发流量
iptables -t mangle -N clash
# 目标地址为局域网或保留地址的流量跳过处理
# 保留地址参考: https://zh.wikipedia.org/wiki/%E5%B7%B2%E5%88%86%E9%85%8D%E7%9A%84/8_IPv4%E5%9C%B0%E5%9D%80%E5%9D%97%E5%88%97%E8%A1%A8
iptables -t mangle -A clash -d 0.0.0.0/8 -j RETURN
iptables -t mangle -A clash -d 127.0.0.0/8 -j RETURN
iptables -t mangle -A clash -d 10.0.0.0/8 -j RETURN
iptables -t mangle -A clash -d 172.16.0.0/12 -j RETURN
iptables -t mangle -A clash -d 192.168.0.0/16 -j RETURN
iptables -t mangle -A clash -d 169.254.0.0/16 -j RETURN
iptables -t mangle -A clash -d 224.0.0.0/4 -j RETURN
iptables -t mangle -A clash -d 240.0.0.0/4 -j RETURN
# 其他所有流量转向到 7893 端口,并打上 mark
iptables -t mangle -A clash -p tcp -j TPROXY --on-port 7893 --tproxy-mark 666
iptables -t mangle -A clash -p udp -j TPROXY --on-port 7893 --tproxy-mark 666
# 转发所有 DNS 查询到 1053 端口
# 此操作会导致所有 DNS 请求全部返回虚假 IP(fake ip 198.18.0.1/16)
#iptables -t nat -I PREROUTING -p udp --dport 53 -j REDIRECT --to 1053
# 如果想要 dig 等命令可用, 可以只处理 DNS SERVER 设置为当前内网的 DNS 请求
#iptables -t nat -I PREROUTING -p udp --dport 53 -d 192.168.0.0/16 -j REDIRECT --to 1053
iptables -t nat -I PREROUTING -p tcp -m tcp --dport 53 -j REDIRECT --to-ports 53
iptables -t nat -I PREROUTING -p udp -m udp --dport 53 -j REDIRECT --to-ports 53
# 最后让所有流量通过 clash 链进行处理
iptables -t mangle -I PREROUTING -m set --match-set china_net dst -j ACCEPT
iptables -t mangle -A PREROUTING -j clash
# clash_local 链负责处理网关本身发出的流量
iptables -t mangle -N clash_local
# nerdctl 容器流量重新路由
#iptables -t mangle -A clash_local -i nerdctl2 -p udp -j MARK --set-mark 666
#iptables -t mangle -A clash_local -i nerdctl2 -p tcp -j MARK --set-mark 666
# 跳过内网流量
iptables -t mangle -A clash_local -d 0.0.0.0/8 -j RETURN
iptables -t mangle -A clash_local -d 127.0.0.0/8 -j RETURN
iptables -t mangle -A clash_local -d 10.0.0.0/8 -j RETURN
iptables -t mangle -A clash_local -d 172.16.0.0/12 -j RETURN
iptables -t mangle -A clash_local -d 192.168.0.0/16 -j RETURN
iptables -t mangle -A clash_local -d 169.254.0.0/16 -j RETURN
iptables -t mangle -A clash_local -d 224.0.0.0/4 -j RETURN
iptables -t mangle -A clash_local -d 240.0.0.0/4 -j RETURN
# 为本机发出的流量打 mark
iptables -t mangle -A clash_local -p tcp -j MARK --set-mark 666
iptables -t mangle -A clash_local -p udp -j MARK --set-mark 666
# 跳过 clash 程序本身发出的流量, 防止死循环(clash 程序需要使用 "clash" 用户启动)
iptables -t mangle -A OUTPUT -p tcp -m owner --uid-owner clash -j RETURN
iptables -t mangle -A OUTPUT -p udp -m owner --uid-owner clash -j RETURN
# 让本机发出的流量跳转到 clash_local
# clash_local 链会为本机流量打 mark, 打过 mark 的流量会重新回到 PREROUTING 上
iptables -t mangle -A OUTPUT -j clash_local
# 修复 ICMP(ping)
# 这并不能保证 ping 结果有效(clash 等不支持转发 ICMP), 只是让它有返回结果而已
# --to-destination 设置为一个可达的地址即可
#sysctl -w net.ipv4.conf.all.route_localnet=1
#iptables -t nat -A PREROUTING -p icmp -d 198.18.0.0/16 -j DNAT --to-destination 127.0.0.1
/etc/clash/clean.sh
: 负责启动前/停止后清理 iptables 规则(暴力清理)
#!/usr/bin/env bash
set -ex
ip rule del fwmark 666 table 666 || true
ip route del local 0.0.0.0/0 dev lo table 666 || true
#iptables -t nat -F
#iptables -t nat -X
iptables -t nat -D PREROUTING 2
iptables -t nat -D PREROUTING 1
iptables -t mangle -F
iptables -t mangle -X clash || true
iptables -t mangle -X clash_local || true
3.4、最终目录结构
所有配置编写完成后, 其目录结构如下:
root@openrpi # ❯❯❯ tree -L 1 /etc/clash
/etc/clash
├── clean.sh
├── config.yaml
└── iptables.sh
最后需要修复 /etc/clash
目录权限, 因为 Clash 启动后会写入其他文件:
chown -R clash:clash /etc/clash
四、启动及测试
如果所有配置和文件安装没问题的话, 可以直接通过 Systemd 启动:
# 启动
systemctl start clash
# 查看日志
journalctl -fu clash
如果启动成功, 那么此时内网设备将网关设置到当前 Clash 所在机器即可完成透明代理; 如果 Clash 机器足够稳定, 也可以一步到位将内网路由器的 DHCP 设置中下发的网关直接填写为 Clash 机器 IP(Clash 机器需要使用静态 IP).
本作品采用 知识共享署名-相同方式共享 4.0 国际许可协议 进行许可。