1743 字
9 分钟
Clash配置TPROXY后抓包问题

Clash 配置 TPROXY 后,在物理网卡 eth0 抓不到出去的包。这种情况非常典型,核心原因在于 ​​Clash 的 TPROXY 规则有效地在主机网络命名空间中拦截了容器的出站流量,并进行了本地处理(由 Clash 代理),使得流量根本没有通过主机的物理网卡 (eth0) 发往外网 ​​。以下是详细的解释:

核心原因:TPROXY 的拦截与本地处理#

  1. ​ 容器的流量路径:​
    • 容器内的应用发起对公网网站(例如 www.example.com)的请求。
    • 流量从容器的网络命名空间通过 veth pair 一端进入主机网络命名空间(root netns),并出现在 Docker 网桥接口(如你的 br-d5f487fc7222)上。这是你在网桥上能看到流量的原因。
    • 正常情况下,如果没有任何干预,离开容器进入主机命名空间的流量会:
      • 经过主机路由决策(ip route):通常会默认路由到物理网卡(eth0)。
      • 经过主机的 iptables/nftables 规则链(尤其是 FORWARDPOSTROUTING 链,对于桥接流量),进行可能的过滤或 SNAT(如果 Docker 默认 SNAT 未禁用)。
      • 最终由物理网卡 eth0 发送到互联网。
  2. ​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 选项的套接字。
  3. ​ 流量被 TPROXY 劫持:​
    • 当容器发出的、目的地为公网网站(www.example.com:80)的 TCP 包到达主机命名空间的网桥接口 br-d5f487fc7222 后,它还没有进入主机路由流程(ip route)。
    • 包首先进入 netfiltermanglePREROUTING 链。
    • 你的 Clash TPROXY 规则匹配了这个包(目标端口是 80)。
    • 规则生效:内核将包打上标记(mark=1),并 ​​ 重定向 TPROXY 到本地端口 7892 的 Clash 透明代理套接字 ​​。
    • ​ 关键点:​​ 这个重定向操作意味着包直接被内核“送”给了监听在 7892 端口的 Clash 进程(在用户空间)。它 ​​ 没有 ​​ 进入后续的 netfilter 链(如 FORWARDPOSTROUTING),也 ​​ 没有 ​​ 到达 ip route 决策阶段(需要路由到 eth0)。
  4. ​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)的响应,通过那个特殊的透明套接字 ​​ 回送 ​​ 给容器内的原始应用。

总结为什么在 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 的代理机制:

  1. Clash 收到了容器的请求(从 TPROXY 套接字)。
  2. Clash 自己充当代理客户端,去访问了 www.example.com(可能通过你配置的代理节点)。
  3. Clash 拿到网站的响应后,通过透明的套接字机制,将响应数据正确地、透明地(让容器内的应用误以为是直接从 www.example.com 回来的)返回给了容器内的原始应用。

整个过程对容器内的应用是透明的(除了潜在的网络延迟差异),所以应用认为访问成功了。

如何验证?#

  1. ​ 在主机上抓 lo 接口:​​ 尝试在主机上抓 lo (回环) 接口 (tcpdump -i lo port 7892)。你可能会看到 Clash 与它监听的透明代理套接字之间的通信(内核把容器的包推送给 Clash,Clash 通过回环把响应数据伪装回去给容器)。
  2. ​ 在 eth0 上抓去往代理服务器的包:​​ 如果你的 Clash 规则是将流量发送到远程代理服务器(如 VPS),那么在 eth0 上抓取目标端口是代理服务器端口(比如常见的 443)的流量,你应该能看到 Clash 进程(源 IP 为主机 IP)与代理服务器之间的加密流量。
  3. ​ 在 eth0 上抓直连目标网站的包:​​ 如果目标流量恰好被 Clash 规则配置为直连(DIRECT),那么你 ​​ 应该 ​​ 能在 eth0 上看到 Clash 进程(源地址为主机 IP)与 www.example.com:80 的 TCP 连接(SYN, ACK, HTTP 请求等)。这就是 Clash 建立的“新连接”。

​ 结论:​​ 在 eth0 上抓不到容器原始出向的包是符合预期的,这恰恰证明了 Clash 的 TPROXY 透明代理在你的主机上成功工作并截获了容器的流量进行处理。你看到的网桥接口上的包是被成功截获的证明,而从 eth0 出去的是 Clash 代理处理后的新连接。