vernesong / OpenClash

A Clash Client For OpenWrt
MIT License
15.95k stars 2.96k forks source link

[Bug] OpenClash 未正确获取并排除路由器非 PPPoE 的 IPv4 WAN 地址,导致 UDP 流量转发在 TProxy 模式下出现回环卡死 #3316

Closed ky-bd closed 1 year ago

ky-bd commented 1 year ago

Verify Steps

OpenClash Version

v0.45.121-beta

Bug on Environment

Official OpenWrt

Bug on Platform

Linux-arm64

To Reproduce

让路由器通过 非 PPPoE 方式(如静态分配或 DHCP)获得一个 非保留 IP 段 的 IPv4 WAN 地址(约等于公网地址),在 Fake-IP + TProxy 模式下打开 UDP 流量转发,向这个 IPv4 WAN 地址发送 UDP 包,此时 Clash 会占用 100% 处理器资源卡死。

Describe the Bug

OpenClash 启动时 /etc/init.d/openclash 会 在 mangle 表中加入 UDP TPROXY 目标 (L2380) 并排除本机网络地址 (L2354) (这些规则在 PREROUTING 链中被调用),而本机网络地址则包括 保留 IP 段和 WAN 地址 (L2188),IPv4 WAN 地址又是 通过 openclash_get_network.lua 脚本获取的

这个脚本 只读取了 PPPoE 分配的 WAN 地址,不包括其他方式,导致本机的 WAN 地址没有被排除,发往该地址的数据包会被 TPROXY 目标捕获并发向 Clash 内核,Clash 内核会将这个包(通过回环接口)重新发往本机 WAN 地址,然后再次被捕获,重复以上过程并卡死。

执行 ipset list localnetwork 查看 OpenClash 添加的本机 IPv4 地址,可以发现仅有保留地址段:

Name: localnetwork
Type: hash:net
Revision: 6
Header: family inet hashsize 1024 maxelem 65536
Size in memory: 1032
References: 5
Number of entries: 9
Members:
224.0.0.0/4
10.0.0.0/8
169.254.0.0/16
172.16.0.0/12
0.0.0.0/8
240.0.0.0/4
192.168.0.0/16
127.0.0.0/8
100.64.0.0/10

TCP 包不受此影响,因为 TCP 的转发规则是通过 nat 表的 PREROUTING 链调用的,从回环接口传入的包不会经过 nat 表的 PREROUTING 链。IPv6 的 UDP 转发也不存在这个问题,因为 IPv6 的 WAN 地址是通过 ifconfig 获取的

执行 ipset list localnetwork6 查看本机 IPv6 地址,包括了 WAN 地址:

Name: localnetwork6
Type: hash:net
Revision: 6
Header: family inet6 hashsize 1024 maxelem 65536
Size in memory: 2760
References: 4
Number of entries: 15
Members:
::ffff:0.0.0.0/96
2002::/16
100::/64
fc00::/7
2001::/32
::1
fd7a:115c:a1e0::e
2001:db8::/32
2001:20::/28
::
64:ff9b::/96
fe80::/10
ff00::/8
::ffff:0:0:0/96
fe80::/64
*IPv6_WAN_IP*

修改 openclash_get_network.lua ,使其能够正确读取 IPv4 WAN 地址,重新启动 OpenClash ,卡死现象消失, Clash 能够正常工作。再次执行 ipset list localnetwork 查看本机网络地址:

Name: localnetwork
Type: hash:net
Revision: 6
Header: family inet hashsize 1024 maxelem 65536
Size in memory: 1096
References: 5
Number of entries: 10
Members:
0.0.0.0/8
192.168.0.0/16
100.64.0.0/10
127.0.0.0/8
169.254.0.0/16
240.0.0.0/4
172.16.0.0/12
224.0.0.0/4
10.0.0.0/8
*IPv4_WAN_IP*

尽管没有测试,但我怀疑这个问题也可能在 LAN 或者其他接口/网络上出现,毕竟本质上和网络区域无关,只要本机使用的地址没有被彻底排除掉,应该都会触发这个问题,例如路由拿到的是一个公网 IP 段,在 LAN 接口上没有 NAT 到一个保留地址段,而是也分配了一个公网地址,只不过可能对于大部分用户而言在 IPv4 上很难遇到这种场景。

TUN 模式没有测试,也没有细看对应的 iptables 规则,不清楚是否存在类似问题。

OpenClash Log

在 Clash 核心日志中可以看到 Clash 在大量转发本机 WAN 地址上的 UDP 数据包。

[INFO] [UDP] IPV4_WAN_IP:47904 --> IPV4_WAN_IP:53478 match GeoIP(CN) using 直连[DIRECT] [INFO] [UDP] IPV4_WAN_IP:49804 --> IPV4_WAN_IP:46216 match GeoIP(CN) using 直连[DIRECT] [INFO] [UDP] IPV4_WAN_IP:48211 --> IPV4_WAN_IP:53478 match GeoIP(CN) using 直连[DIRECT] [INFO] [UDP] IPV4_WAN_IP:58416 --> IPV4_WAN_IP:46216 match GeoIP(CN) using 直连[DIRECT] [INFO] [UDP] IPV4_WAN_IP:40292 --> IPV4_WAN_IP:53478 match GeoIP(CN) using 直连[DIRECT] [INFO] [UDP] IPV4_WAN_IP:34777 --> IPV4_WAN_IP:46216 match GeoIP(CN) using 直连[DIRECT] [INFO] [UDP] IPV4_WAN_IP:58908 --> IPV4_WAN_IP:53478 match GeoIP(CN) using 直连[DIRECT] [INFO] [UDP] IPV4_WAN_IP:40106 --> IPV4_WAN_IP:46216 match GeoIP(CN) using 直连[DIRECT] [INFO] [UDP] IPV4_WAN_IP:40687 --> IPV4_WAN_IP:53478 match GeoIP(CN) using 直连[DIRECT] [INFO] [UDP] IPV4_WAN_IP:54192 --> IPV4_WAN_IP:46216 match GeoIP(CN) using 直连[DIRECT]

OpenClash Config

No response

Expected Behavior

修复后 OpenClash 应当能够完整获取 IPv4 WAN 地址并从 UDP TProxy 中排除,避免出现死循环。或许可采用与 IPv6 相似的方法通过 ifconfig 获取地址。

Screenshots

No response

deuteros-gex commented 1 year ago

基于楼主的提示,我在基于nftables的fw4环境下也测了一下 (没看源代码,直接用nft list table inet fw4导出了启动openclash后的整个防火墙设置) 应该说不管是哪个模式,openclash都是通过“把wan口ip加入名为localnetwork的ipset/nftset”的方法来防回环的。

不过非pppoe还能拿到公网ipv4。。莫非是sjtu的在校学生?

vernesong commented 1 year ago

怎么分辨是不是公网ip

ky-bd commented 1 year ago

基于楼主的提示,我在基于nftables的fw4环境下也测了一下 (没看源代码,直接用nft list table inet fw4导出了启动openclash后的整个防火墙设置) 应该说不管是哪个模式,openclash都是通过“把wan口ip加入名为localnetwork的ipset/nftset”的方法来防回环的。

~不过非pppoe还能拿到公网ipv4。。莫非是sjtu的在校学生?~

其实可以在局域网手动分配公网 ip 吧,只不过不规范以及从外面路由不到里面来罢了,正常情况应该都不会这么干。

怎么分辨是不是公网ip

我觉得核心不是分辨公网 ip ,而是让发往本机的包不要再过 TProxy 进 Clash ,所以应该可以直接像 v6 一样用 ifconfig 把本机所有网卡的 ip 都加到 localnetwork ipset 里面排除掉,这样这些包完全不会进 Clash ;或者在 openclash 链里加一个条件,排除掉从回环接口进来的包 (iptables -t mangle -A openclash -i lo -j RETURN) ,这样发往本机的包最多只会在 Clash 里过一次,不会无限循环。