1743 字
9 分钟
Clash配置TPROXY后抓包问题
Clash 配置 TPROXY 后,在物理网卡 eth0 抓不到出去的包。这种情况非常典型,核心原因在于 Clash 的 TPROXY 规则有效地在主机网络命名空间中拦截了容器的出站流量,并进行了本地处理(由 Clash 代理),使得流量根本没有通过主机的物理网卡 (eth0) 发往外网 。以下是详细的解释:
核心原因:TPROXY 的拦截与本地处理
- 容器的流量路径:
- 容器内的应用发起对公网网站(例如
www.example.com)的请求。 - 流量从容器的网络命名空间通过 veth pair 一端进入主机网络命名空间(root netns),并出现在 Docker 网桥接口(如你的
br-d5f487fc7222)上。这是你在网桥上能看到流量的原因。 - 正常情况下,如果没有任何干预,离开容器进入主机命名空间的流量会:
- 经过主机路由决策(
ip route):通常会默认路由到物理网卡(eth0)。 - 经过主机的
iptables/nftables规则链(尤其是FORWARD和POSTROUTING链,对于桥接流量),进行可能的过滤或 SNAT(如果 Docker 默认 SNAT 未禁用)。 - 最终由物理网卡
eth0发送到互联网。
- 经过主机路由决策(
- 容器内的应用发起对公网网站(例如
- TPROXY 规则的生效点:
- Clash 配置 TPROXY 透明代理时,会设置
iptables(或nftables)规则,通常目标链是mangle表的PREROUTING。规则类似于:Terminal window iptables -t mangle -A PREROUTING -p tcp --dport 80 -j TPROXY --on-port 7892 --tproxy-mark 1/1# ... 其他端口或协议规则 ... - 这条规则的作用是: 在内核进行路由决策
PREROUTING之前,将匹配条件的流量(目的地 TCP 80 端口的 TCP 流量)标记(--tproxy-mark 1/1)并重定向到本地7892端口监听的一个特殊套接字。 Clash 代理程序会在这个端口监听带有IP_TRANSPARENT选项的套接字。
- Clash 配置 TPROXY 透明代理时,会设置
- 流量被 TPROXY 劫持:
- 当容器发出的、目的地为公网网站(
www.example.com:80)的 TCP 包到达主机命名空间的网桥接口br-d5f487fc7222后,它还没有进入主机路由流程(ip route)。 - 包首先进入
netfilter的mangle表PREROUTING链。 - 你的 Clash TPROXY 规则匹配了这个包(目标端口是 80)。
- 规则生效:内核将包打上标记(mark=1),并 重定向
TPROXY到本地端口7892的 Clash 透明代理套接字 。 - 关键点: 这个重定向操作意味着包直接被内核“送”给了监听在
7892端口的 Clash 进程(在用户空间)。它 没有 进入后续的netfilter链(如FORWARD,POSTROUTING),也 没有 到达ip route决策阶段(需要路由到eth0)。
- 当容器发出的、目的地为公网网站(
- Clash 接管后续通信:
- Clash 在
7892端口接收到这个 TCP 包(带有原始的目的 IP 和端口www.example.com:80以及 原始的源地址 容器 IP)。 - Clash 根据自身的规则(配置、路由、出站节点等) 发起一个新的、代理后的 TCP 连接 去获取
www.example.com的内容。 - Clash 发起的这个新连接:
- 源地址不再是容器的 IP,而是 主机本身的 IP(因为 Clash 进程运行在主机命名空间)。
- 目标地址是 Clash 选定的代理节点 IP(可能是远程服务器)。这个连接最终会从主机的物理网卡
eth0发出去。 - 当收到目标网站的响应时,Clash 会将响应数据 封装/伪装 成原始请求(容器 ->
www.example.com)的响应,通过那个特殊的透明套接字 回送 给容器内的原始应用。
- Clash 在
总结为什么在 eth0 上抓不到原始容器的请求包
- 原始请求路径被中断: 容器发出的、指向公网
www.example.com:80的 TCP 包在进入主机命名空间后, 在PREROUTING链(路由决策之前)就被 TPROXY 规则成功拦截 ,并直接交给了本地的 Clash 进程处理。 - 未进入路由/转发环节: 因为这个包被重定向到用户空间的进程(Clash)了,它 根本不会经过 主机上的
FORWARD链(负责桥接转发)或POSTROUTING链(负责 SNAT 和最终发送准备),也 不需要/不会被主机路由到eth0接口 。 - 在
eth0看到的是 Clash 建立的代理连接: 你在eth0上只能抓到的包,是 Clash 进程代表原始请求向代理节点或最终目标网站建立的 新连接 的包(源地址是主机 IP,目标地址是代理 IP/网站 IP),而 不是 原始容器应用发出的那个包(源地址容器 IP,目标地址网站 IP)。 - 网桥是拦截点: 你唯一能在主机上看到原始容器包的地方,就是在容器流量进入主机命名空间的入口点 —— Docker 网桥接口 (
br-d5f487fc7222)。
为什么访问还能成功?
虽然原始包被主机拦截了,没有真的去访问 www.example.com,但访问在容器内却成功,这完全归功于 Clash 的代理机制:
- Clash 收到了容器的请求(从 TPROXY 套接字)。
- Clash 自己充当代理客户端,去访问了
www.example.com(可能通过你配置的代理节点)。 - Clash 拿到网站的响应后,通过透明的套接字机制,将响应数据正确地、透明地(让容器内的应用误以为是直接从
www.example.com回来的)返回给了容器内的原始应用。
整个过程对容器内的应用是透明的(除了潜在的网络延迟差异),所以应用认为访问成功了。
如何验证?
- 在主机上抓
lo接口: 尝试在主机上抓lo(回环) 接口 (tcpdump -i lo port 7892)。你可能会看到 Clash 与它监听的透明代理套接字之间的通信(内核把容器的包推送给 Clash,Clash 通过回环把响应数据伪装回去给容器)。 - 在
eth0上抓去往代理服务器的包: 如果你的 Clash 规则是将流量发送到远程代理服务器(如 VPS),那么在eth0上抓取目标端口是代理服务器端口(比如常见的 443)的流量,你应该能看到 Clash 进程(源 IP 为主机 IP)与代理服务器之间的加密流量。 - 在
eth0上抓直连目标网站的包: 如果目标流量恰好被 Clash 规则配置为直连(DIRECT),那么你 应该 能在eth0上看到 Clash 进程(源地址为主机 IP)与www.example.com:80的 TCP 连接(SYN, ACK, HTTP 请求等)。这就是 Clash 建立的“新连接”。
结论: 在 eth0 上抓不到容器原始出向的包是符合预期的,这恰恰证明了 Clash 的 TPROXY 透明代理在你的主机上成功工作并截获了容器的流量进行处理。你看到的网桥接口上的包是被成功截获的证明,而从 eth0 出去的是 Clash 代理处理后的新连接。