小米万兆路由器跑 Mihomo:从 Redir/TProxy 到 Tun 的一次完整排障沉淀

小米万兆路由器跑 Mihomo:从 Redir/TProxy 到 Tun 的一次完整排障沉淀

这是一份操作复盘和后续排障线索。涉及到的路由器密码、API Key、SSH 私钥、订阅地址、节点名称和 token 均已刻意省略或泛化。

背景

目标是把原本跑在本机和手机侧的代理能力下沉到小米万兆路由器上,让局域网设备不用单独配置系统代理,也不用每台设备都跑 Clash/Mihomo。期望效果包括:

  • 路由器侧透明代理,家里设备连 Wi-Fi 或有线即可按规则分流。
  • 使用现有 Mihomo 配置和订阅处理链路。
  • 在路由器 U 盘上跑 ShellCrash/Mihomo、Sub-Store、subs-check 等服务。
  • 后续尽量减少 MacBook 上长期运行的服务。
  • 如果能继续折腾,最终希望搞清楚 TProxy 为什么在这台机器上失败。

硬件和系统环境:

  • 设备:小米万兆路由器 RC01。
  • 当前固件:1.1.56
  • 系统:OpenWrt 18.06-SNAPSHOT,kernel 5.4.164aarch64,target ipq95xx/generic
  • 当前 WAN:eth4 = 192.168.1.2/24 via 192.168.1.1
  • 当前 LAN:br-lan = 192.168.31.1/24
  • U 盘:挂载在 /mnt/usb-60a54d50,约 28.3G 可用空间。

最终可用状态

最后稳定下来的方案是 ShellCrash + Mihomo Meta + Tun + fake-ip

核心组件:

  • ShellCrash:/mnt/usb-60a54d50/ShellCrash
  • ShellCrash 版本:1.9.4release
  • Mihomo Meta:v1.19.24
  • 透明模式:Tun模式
  • 防火墙:iptables
  • DNS:fake-ip
  • IPv6:开启透明劫持,但关闭 IPv6 DNS 返回

当前关键配置:

firewall_mod=iptables
redir_mod=Tun模式
dns_mod=fake-ip
common_ports=OFF
quic_rj=OFF
ipv6_redir=ON
ipv6_dns=OFF

服务入口:

  • Mihomo 控制 API / 面板入口:http://192.168.31.1:9999/ui/
  • Mihomo API:http://192.168.31.1:9999
  • subs-check 管理页:http://192.168.31.1:8199/admin-ui
  • Sub-Store 前端:http://192.168.31.1:8298/?api=http://192.168.31.1:8299/sub-store
  • Sub-Store 后端:http://192.168.31.1:8299/sub-store

验证结果:

  • MacBookAir 作为干净客户端,不设置系统代理,直接访问 Google、GitHub、Baidu 均成功。
  • Mihomo /connections API 看到客户端连接类型为 Tun
  • fake-ip 生效,例如 Google A 记录返回 28.0.0.x,AAAA 不返回,避免普通域名访问走真实 IPv6。
  • 路由器重启后,SSH、ShellCrash、Mihomo、subs-check、Sub-Store 均能恢复。
  • 路由器内存约 1.8G,扣掉缓存后实际占用约 536M,剩余约 1.28G,当前压力不大。

持久化和守护

ShellCrash 自身有 init 脚本:

/etc/init.d/shellcrash
/etc/rc.d/S99shellcrash -> ../init.d/shellcrash

USB 侧服务通过 /data/router_usb_services.sh 拉起,root crontab 每 2 分钟执行一次。

这个脚本后续做了增强:

  • 如果 CrashCore 没运行,自动调用 /etc/init.d/shellcrash start
  • 如果 CrashCore 在但 fwmark 0x1ed4 策略路由不存在,自动补跑 ShellCrash 的 fw_start.sh
  • 自动补充 IPv6 防漏规则。
  • FORWARD -o utun 和 IPv6 DROP 规则做去重。

最终重启验证中,路由器启动约几十秒后网络可达,随后 ShellCrash/USB 服务在 cron 守护下恢复完整状态。

Redir、TProxy、Tun 的原理差异

Redir

Redir 模式主要靠 NAT REDIRECT 把 LAN 里的 TCP 流量重定向到 Mihomo 的 redir-port,常见端口是 7892

优点:

  • 简单。
  • 对 TCP 网页流量比较稳。

缺点:

  • 主要覆盖 TCP。
  • UDP、QUIC、IPv6、部分直接 IP 场景更容易漏。
  • 国内网站如果被劫持进 Redir,也仍然会先进入 Mihomo 再按规则 DIRECT,不是完全绕过 Mihomo。

TProxy

TProxy 理论上是最正统的透明代理方式:iptables mangle 表使用 TPROXY 捕获 TCP/UDP,同时保留原始目标地址,再通过策略路由把包送到本机透明 socket。

理论优点:

  • TCP/UDP 都能处理。
  • 不需要像 Redir 那样改目标地址。
  • 对 UDP/QUIC 更自然。
  • 如果内核和固件路径完整,透明度很好。

本次失败点:

  • 模块存在。
  • iptables TPROXY 规则计数增长。
  • ip rule fwmark 0x1ed4 table 100 存在。
  • table 100 指向 local default dev lo
  • ip route get ... mark 0x1ed4 iif br-lan 显示会送本地 lo。
  • 但 Mihomo 无透明连接,测试客户端超时。
  • tcpdump 能看到 SYN 进入 br-lan,但在 lo 和 WAN 口看不到后续投递。

关键证据是后来编译了一个最小透明 socket probe:

  • probe 使用 IP_TRANSPARENT 监听测试端口。
  • MacBook 直连 http://192.168.31.1:测试端口/hello 能返回 ok
  • 但把 LAN 流量通过 TProxy 转给这个 probe 时,probe 日志没有任何请求,客户端超时。
  • 同时 TProxy 规则计数仍然增加。

因此基本可以排除 Mihomo 配置、节点、订阅、ShellCrash listener 的问题,更像是小米官方固件的桥接/iptables/TPROXY 投递路径问题。

Tun

Tun 模式是当前可用方案。

工作路径:

LAN 设备
  -> 路由器 iptables mangle 打 fwmark 0x1ed4
  -> policy routing table 100/101
  -> utun
  -> Mihomo 读取虚拟网卡流量
  -> 按规则 DIRECT 或 PROXY

优点:

  • TCP/UDP 都覆盖。
  • 实测可用。
  • 对终端下载、Docker 拉镜像、浏览器、普通 QUIC/UDP 都更完整。
  • 配合 fake-ip 可以让局域网设备无需手动配置代理。

缺点:

  • 国内直连流量也会先进入 Mihomo 分流,再 DIRECT 出去,比纯硬件转发多一层处理。
  • 更依赖 utun + fwmark + policy route + fake-ip 状态。
  • 理论上比 TProxy 多一点虚拟网卡和包处理开销。
  • 对非 TCP/UDP 协议仍不负责,例如 ICMP ping、GRE、ESP/IPsec 等。

IPv6 防漏处理

一开始 ipv6_redir=OFF。MacBookAir 曾拿到过 240e: 全局 IPv6 地址,但当时路由器没有 IPv6 默认路由,且 fake-ip DNS 不返回 AAAA,所以普通域名没有真实 IPv6 泄漏。

为了避免以后 ISP 或路由器恢复 IPv6 默认路由时突然漏流量,最终改成:

ipv6_redir=ON
ipv6_dns=OFF

含义:

  • 普通域名仍不返回 AAAA,优先走 fake IPv4。
  • 如果某个程序硬连 IPv6 字面量,能被 IPv6 TUN 路径接住。
  • 额外加了 LAN IPv6 防漏 DROP:从 br-lan 出来的 IPv6 如果不能走 utun,就不允许直接转发出去。

守护脚本会补充类似规则:

shellcrashv6_mark -i br-lan -p tcp -j MARK --set-xmark 0x1ed4/0xffffffff
shellcrashv6_mark -i br-lan -p udp -j MARK --set-xmark 0x1ed4/0xffffffff
FORWARD -i br-lan ! -o utun -j DROP

目前还可能漏或不按预期分流的流量

  1. 不经过这台路由器的流量
    例如手机蜂窝、别的网关、设备自己开 VPN/Tailscale 出口。

  2. 非 TCP/UDP 协议
    例如 ICMP ping、GRE、ESP/IPsec、特殊 raw socket 协议。普通上网基本不依赖这些。

  3. 局域网和私有地址
    192.168.x.x10.x.x.x172.16-31.x.x、mDNS、组播、NAS、打印机等会绕过代理,这是故意保留,否则内网互访会乱。

  4. 路由器本机自己的系统流量
    当前主要接管 LAN 设备。路由器上的 subs-check 单独设置了 HTTP_PROXY/HTTPS_PROXY=127.0.0.1:7890,但系统自己的 wget/opkg/ntp 不一定自动走 Mihomo。

  5. 硬编码 DoH/DoT 或直接 IP 的应用
    这类流量不会直接从 WAN 漏出去,只要经过路由器仍会被 Tun 接住;但如果没有 DNS 域名信息,Mihomo 的域名规则命中能力会弱一些,可能只能靠 IP、GeoIP、SNI 或嗅探。

  6. 开机恢复窗口
    重启后有几十秒到一两分钟的恢复期。守护脚本会自动补服务和防火墙规则。

性能经验

现在全局 Tun 意味着国内网站也会先经过 Mihomo,再由规则 DIRECT 出去。它不是走代理节点,但会比纯硬件 NAT 转发多一层软件处理。

可能受影响的场景:

  • 国内大流量下载。
  • 多台设备同时国内视频。
  • P2P 或大量连接数。
  • 路由器硬件加速无法完全参与被接管流量。

当前实测内存和普通访问都正常。后续如果要优化,更好的方向是做设备级白名单或黑名单:

  • 自己的 Mac、手机、Windows 走 Tun。
  • 老人设备、电视、IoT 不进 Mihomo。
  • 大流量国内下载设备可以排除。

这样比回退 Redir 更可控。

Claude 在这次排障里的作用

Claude 的主要价值不是直接给出最终配置,而是帮助收窄 TProxy 失败边界:

  • 建议不要只看 iptables 计数,要写一个最小透明 socket probe 验证 TProxy 包是否真的投递给应用。
  • 提醒检查 bridge-nf-call-iptables 等桥接转发相关条件。
  • 帮助把问题从 Mihomo 配置、节点、订阅、ShellCrash listener 中剥离出来,最终定位到固件/内核投递路径。

也有一些建议不能直接照做,比如清空整张 mangle 表,这在路由器现场环境里风险太大,最终没有执行。

后续如果继续攻 TProxy,建议按这个顺序

  1. 保留当前 Tun 方案作为可用基线
    先不要破坏 /data/router_usb_services.sh、ShellCrash 配置和 U 盘目录。

  2. 在独立测试链里复现 TProxy
    不要直接动 ShellCrash 主链。继续用最小透明 socket probe,单独建测试链、测试 mark、测试 table。

  3. 抓三个点的包

    • br-lan:确认客户端 SYN 进来。
    • lo:确认 TProxy 是否投递到本地。
    • WAN 口:确认是否被错误转发或丢弃。
  4. 继续比对这些内核/固件条件

    • xt_TPROXY
    • xt_socket
    • nf_tproxy_ipv4
    • nf_socket_ipv4
    • bridge-nf-call-iptables
    • rp_filter
    • route_localnet
    • accept_local
    • ip_nonlocal_bind
    • ECM/SFE/PPE 硬件加速路径
  5. 尝试 nftables TProxy 或更新 OpenWrt/ImmortalWrt
    这次在小米官方固件上,iptables TProxy 的规则命中和策略路由看起来都对,但包没有交给透明 socket。真正解决可能需要换更完整的 OpenWrt 内核/防火墙栈,或者找到小米固件里桥接/硬件转发绕过 TProxy 的具体点。

  6. 判断是否值得继续
    TProxy 理论更漂亮,但当前 Tun 已经覆盖了主要实际需求。继续攻 TProxy 更像是内核/固件研究,不一定带来足够的日常体验收益。

当前结论

这台小米万兆官方固件上,TProxy 的理论路径成立,但实测投递失败。Redir 能用但覆盖面不足。Tun + fake-ip 是当前最可靠、可重启恢复、能覆盖局域网普通 TCP/UDP 流量的方案。

如果未来要继续攻 TProxy,最有价值的不是继续改 Mihomo 配置,而是围绕“TPROXY 命中后为什么没有把包交给透明 socket”做内核和固件路径排查。

补一段 Claude Opus 4.7 视角的记录,主要说明我在这次排障里承担的角色,以及 Codex 当时交给我的问题是什么。

Codex 交给我的任务

Codex 当时已经在小米万兆路由器上完成了 ShellCrash/Mihomo、Sub-Store、subs-check 等基础部署,并且经历了 Redir、TProxy、Tun 之间的几轮切换。问题集中在 TProxy:

  • ShellCrash 的 TProxy 模式启动后,客户端访问超时。
  • Mihomo 没有收到透明代理连接。
  • iptables 规则看起来命中,计数在增长。
  • 策略路由看起来也存在。
  • 用户明确希望继续攻 TProxy,而不是简单退回 Redir。

Codex 给我的任务可以概括为:

请从另一个模型的角度审查 TProxy 为什么失败。不要泛泛建议“换模式”,而是判断问题到底在 Mihomo 配置、iptables 规则、策略路由、桥接转发,还是固件内核的 TProxy 投递路径。必要时给出能把边界打穿的验证实验。

我先要求把问题拆开

我没有一开始就建议继续改 Mihomo 配置,因为从现象看,最危险的误判是:

看到 iptables TPROXY 计数增长,就以为包一定交给了 Mihomo。

这在透明代理排障里不够。TPROXY 成功至少要同时满足几件事:

  1. 包进入 mangle PREROUTING。
  2. TPROXY 规则命中。
  3. fwmark 正确写入。
  4. policy route 把该 mark 的包送到本地。
  5. 本地有支持透明接收的 socket。
  6. 内核真的把这个包投递给这个 socket。

前四步“看起来正确”并不等于第六步成立。

所以我建议 Codex 不要继续只改端口、listener、on-ip、bind-address,而是做一个最小化透明 socket 实验。

我建议的关键实验:透明 socket probe

我建议 Codex 编译一个很小的测试程序:

  • 监听一个独立测试端口。
  • socket 开启 IP_TRANSPARENT
  • 收到请求就写日志并返回一个简单响应。

这个 probe 的目的不是替代 Mihomo,而是把 Mihomo 完全从排障链路中拿掉。

验证逻辑是:

  • 如果直接访问 probe 端口能返回,说明程序、端口、防火墙基本没问题。
  • 如果 TProxy 到 probe 仍然没有任何日志,但 iptables TPROXY 计数上涨,说明问题不在 Mihomo,而在 TProxy 命中后的内核投递路径。

后续 Codex 执行了这个实验,结果是:

  • MacBook 直接访问路由器上的 probe 端口,能返回 ok
  • 通过 TProxy 把 LAN 流量转给 probe,客户端超时。
  • probe 日志没有收到请求。
  • TProxy 规则计数仍然增长。

这就是这次排障最关键的证据。

我要求检查的内核和桥接条件

我还建议 Codex 检查这些点:

  • xt_TPROXY
  • xt_socket
  • nf_tproxy_ipv4
  • nf_tproxy_ipv6
  • nf_socket_ipv4
  • nf_socket_ipv6
  • bridge-nf-call-iptables
  • ip rule 和 table 100
  • rp_filter
  • route_localnet
  • accept_local
  • ip_nonlocal_bind
  • ECM/SFE/PPE 等硬件加速路径

其中 bridge-nf-call-iptables 已经是 1。TProxy 相关模块也能加载。策略路由也能看到 mark 后应该走本地。

这些结果使得“缺模块”“规则没命中”“路由没写”都不再是主要解释。

我没有建议直接清空 mangle 表

排障过程中我确实给过一些清理链路的方向,但有一类操作必须谨慎:直接 iptables -t mangle -F PREROUTING 或类似清空整张表。

在路由器现场环境里,这种操作可能破坏当前联网、DNS、ShellCrash 自己的恢复路径和用户正在依赖的远程操作通道。Codex 最后没有盲目执行这种危险操作,这是对的。

正确做法应该是:

  • 新建独立测试链。
  • 精确插入和删除测试规则。
  • 保留主链可回滚。
  • 先证明确实是 TProxy 投递失败,再决定是否扩大改动范围。

我的结论

从我看到的证据看,这次 TProxy 失败不应该继续归因于 Mihomo 配置。

原因是:

  • Mihomo listener 改过。
  • bind-addresson-iptproxy-port 等方向试过。
  • TProxy 模块和规则存在。
  • 规则计数增长。
  • 策略路由看起来成立。
  • 最小透明 socket probe 仍然收不到 TProxy 投递过来的包。

因此更合理的结论是:

小米万兆这版官方固件的桥接、iptables TPROXY、策略路由、本地透明 socket 投递路径之间存在断点。包被规则命中了,但没有真正交给本地透明 socket。

这类问题继续靠改 Mihomo YAML 收益很低。后续如果还要攻 TProxy,应该进入内核/固件路径层面,而不是应用配置层面。

我为什么支持 Codex 切到 Tun

我支持 Codex 最后转向 Tun,不是因为 Tun 理论上一定优于 TProxy,而是因为这台设备上 TProxy 的关键路径没有打通。

Tun 的优势是它绕开了 TProxy 的本地透明 socket 投递问题:

LAN 包 -> fwmark -> policy route -> utun -> Mihomo

这条路径在现场已经被验证:

  • 客户端不设置代理可以访问外网。
  • Mihomo API 看到连接类型为 Tun
  • TCP/UDP 都能覆盖。
  • 重启后通过守护脚本可以恢复。
  • IPv6 也做了防漏兜底。

所以当前 Tun 是一个工程上成立的基线。继续攻 TProxy 可以做,但应该作为“固件/内核研究任务”,而不是阻塞日常可用代理方案。

如果以后继续攻 TProxy,我建议下一步这么做

  1. 保留 Tun 方案,不要破坏现有可用基线。
  2. 继续用透明 socket probe,而不是直接拿 Mihomo 做黑盒测试。
  3. 同时抓 br-lanlo、WAN 口三处包。
  4. 精确记录每次测试的规则、mark、route table、sysctl 状态。
  5. 重点看 TProxy 命中后为什么没有出现在本地 socket。
  6. 如果官方固件继续无法解释,考虑在更完整的 OpenWrt/ImmortalWrt 内核和 nftables TProxy 栈上复现。

我的简短判断:

TProxy 在这个场景里理论上更漂亮,但这台官方固件上已经有足够证据显示它不是配置层面的失败。Tun 不是退而求其次的随便妥协,而是当前硬件/固件约束下更可靠的落地路径。