Skip to content

Latest commit

 

History

History
173 lines (139 loc) · 13.4 KB

PATCHES.md

File metadata and controls

173 lines (139 loc) · 13.4 KB

PATCHES —— mihomo-enhanced 补丁清单

补丁以 find-replace 代码形式存在于 build.py 的 @patch 函数里(照 linkpro 模式,不用 .patch 文件), 每个补丁带命中数校验:上游漂移导致 pattern 没按预期命中,build.py 会 fail loud 中止,提示来这里更新。


0001 · REALITY 客户端版本 → 运行时可配 + 构建默认

文件 component/tls/reality.go、adapter/outbound/reality.go
补丁函数 build.py :: p0001_reality_version + latest_xray_version()(4 处 find-replace,均命中 1)
上游现状 hello.SessionId[0..2] = 1, 8, 2(REALITY 握手 SessionId 塞客户端版本,写死 1.8.2 从不更新)
改为 有 client-ver 配置用之,否则用构建默认(build.py 构建时抓 XTLS/Xray-core 最新 release,如 26,7,28;抓取失败回落 {26,7,28})

算法一致、仅版本匹配问题,所以给配置项而非写死:

  • tls.RealityConfig 加 ClientVer [3]byte 字段;SessionId[0..2] 逻辑改成 if ClientVer != [0,0,0] { 用 ClientVer } else { 用构建默认 }。
  • 出站 RealityOptions 加 client-ver 配置项(如 "26.7.28"),Parse() 解析进 ClientVer。
  • 用法:REALITY 出站 reality-opts 里加 client-ver: "26.7.28" 即可运行时覆盖,不用重编。 未配则用构建默认(过 Xray 默认 minClientVer 26.3.27)。若将来 Xray 抬门槛,加这行调高即可。

照 Xray 的做法(关键 —— 为什么这样最忠实)

  • Xray 客户端填自己的构建版本:transport/internet/reality/reality.go 里 hello.SessionId[0]=core.Version_x; [1]=Version_y; [2]=Version_z,而 core/core.go 里 Version_x=26, Version_y=7, Version_z=28。所以 Xray 客户端永远报当前版本。
  • 门槛是写死常量:infra/conf/transport_security.go → 默认 MinClientVer = []byte{26,3,27}(Xray 开发者偶尔手动抬,"change at your own risk"),服务端只比最小值、无最大值、无版本特性门控。
  • 所以本补丁照 Xray 语义:让 mihomo 也报"当前 Xray 版本"——build.py 构建时自动抓最新 Xray release 注入 SessionId[0..2]。每次 ./build.py 自动跟上、永不手调。(mihomo 自身版本是 1.x,无法用它自己的版本过 26.x 门槛,所以只能声明 Xray 系版本。)

为什么

  • mihomo 把 REALITY 客户端版本硬编码 1.8.2;Xray-core v26.7.11+(含 26.7.28)对未显式配 minClientVer 的 REALITY 服务端默认强制最低客户端版本 ≈ 26.3.27。
  • 结果:mihomo(哪怕最新 commit)连这类 Xray 服务端 一律 authentication failed,与是否用 Vision flow 无关。
  • 上游 issue MetaCubeX/mihomo#2967(wontfix)、XTLS/Xray-core#6477。
  • 本地实测(Docker):最新 mihomo alpha-1af24e9 × Xray 26.7.28——默认拒(authentication failed);服务端加 minClientVer:"1.8.0" 后放行。门槛是纯数值版本比较,故 client 声明 26.7.28 ≥ 服务端默认 26.3.27 必过。

校验点 / 维护

  • pattern 精确匹配那三行赋值(re.S + 保留缩进的分组回填),期望命中 1 次。
  • 若上游改了这段(重构 REALITY 握手、把版本改成常量/变量),命中数 ≠ 1,build.py 中止 → 来这里改 pattern。
  • 未来 Xray 抬门槛:不用手动改——build.py 每次构建自动抓当前 Xray 最新 release 注入,天然 ≥ 门槛(门槛总 ≤ 当前版本)。仅当构建机无网络/抓取失败时回落到 latest_xray_version() 的 fallback(现 [26,7,28]);若那时门槛已超过 fallback,更新 fallback 即可。
  • REALITY 的 version 字节服务端只做 minClientVer 最小值比较(无 max、无特性门控),声明当前 Xray 版本是安全且忠实的。
  • 想让运行时可调(而非构建时定):phase-2 可改成从 reality-opts 读 client-ver(需动 listener/config/vless.go + adapter/outbound/reality.go + component/tls/reality.go,不再是纯 find-replace)。

0002 · VLESS 加 Rvs 命令(反向代理 Bridge 的外层握手)

文件 transport/vless/vless.go、transport/vless/conn.go、adapter/outbound/vless.go
补丁函数 build.py :: p0002_vless_rvs(5 处 find-replace,均命中 1)
配套 overlay overlay/adapter/outbound/vless_reverse.go(DialReverse + RvsSentinel)、overlay/transport/muxcool/(Mux.cool 引擎)

给 mihomo VLESS 加 Rvs 命令(0x04),让它能发起 Xray 反向代理 Bridge 的外层握手:

  • (a) vless.go 命令常量加 CommandRvs byte = 4。
  • (b) vless.go DstAddr 加 Rvs bool 字段。
  • (c) conn.go sendRequest 的 requestLen 计算:if !vc.dst.Mux → if !vc.dst.Mux && !vc.dst.Rvs(Rvs 同 Mux,不写地址端口)。
  • (d) conn.go 命令字节分支:在 if vc.dst.Mux 前加 if vc.dst.Rvs { WriteByte(CommandRvs) },进不到 else 的写地址分支。
  • (e) vless.go(adapter/outbound)parseVlessAddr 开头:metadata.Host == RvsSentinel("v1.rvs.cool")→ 返回 &vless.DstAddr{Rvs:true}。

为什么这样(照 Xray 语义 + 复用 mihomo 传输层)

  • Xray VLESS 的 Rvs 命令(common/protocol/headers.go Rvs=0x04)后不写地址端口(encoding.go),和 Mux(0x03)一样。mihomo 现有 sendRequest 里 Mux 已经跳地址,Rvs 照抄即可。
  • DialReverse(overlay)复用整条 StreamConnContext(REALITY/TLS/ws/grpc 全复用),只把内层目标设成哨兵 v1.rvs.cool → parseVlessAddr 返回 Rvs。零重复传输层代码。
  • flush 头避免死锁:mihomo vless.Conn 惰性发头(首个 Write 才发),而 Bridge 侧 Mux.cool 服务端先读后写。DialReverse 建流后立即 c.Write([]byte{}) 触发发头,否则 Portal 收不到 VLESS 头→不发 mux 帧→双向死锁(蓝图 gotcha #2)。
  • flow 需为空:Xray Portal 对 Rvs + 空 flow 接受(inbound.go:593 拒绝条件只含 TCP/Mux);用 Vision 则整条 mux 被 Vision 包裹,MVP 不做。

校验点 / 维护

  • 5 处 pattern 各命中 1;上游改到这些点(VLESS 命令写法、DstAddr、sendRequest、parseVlessAddr)则命中数 ≠1,build.py fail loud → 来此更新。
  • 已验证:./build.py --patch-only --check 对上游 1af24e9 打补丁 + go build ./transport/vless/... ./transport/muxcool/... ./adapter/outbound/... = CHECK_OK。
  • 已对真 Xray 端到端验穿:Bridge 与 Portal 两端都接进真 mihomo 二进制,四象限(mihomo⇄Xray 角色对调)全通,见下方 0003/0004/0005 与 test/reverse-e2e/。

排查:除了 0001 还有没有别处需要改?(结论:没有)

对 mihomo 源码全量核对,确认 REALITY 版本坑只此一处,无需 patch 0002:

  • 版本字节唯一:全仓 SessionId[0..2] 赋值只在 component/tls/reality.go 那 3 行,无第二处硬编码。
  • mihomo 无版本门槛逻辑:全仓无 minClientVer/MinClientVer 之类的 REALITY 版本比较;服务端侧 listener/reality/reality.go 也不校验客户端版本(mihomo 当 REALITY 服务端是宽松的,不会因版本拒客户端)。所以坑只在"mihomo 客户端声明的版本"这一侧,已由 0001 修掉。

⚠️ 兼容点(config,不是补丁):后量子 REALITY(X25519MLKEM768)

  • mihomo 有后量子 REALITY 支持:component/tls/reality.go 的 SupportX25519MLKEM768,对应 reality-opts 里的 support-x25519mlkem768(默认 false → 握手里去掉 MLKEM768 key share,以兼容旧服务端)。
  • 默认(false)与 Xray 26.7.x 兼容——REALITY 会回落到经典 X25519。
  • 仅当你的 Xray REALITY 服务端**强制/使用后量子(ML-KEM-768 / mldsa65 密钥)**时,mihomo 客户端 proxy 里要加 support-x25519mlkem768: true。这是配置项,不需要改代码。

0003 / 0004 / 0005 · 反向连接(reverse bridge + portal)—— 已实现并双向验穿

把 Xray app/reverse(Mux.cool + VLESS Rvs 命令)移植进 mihomo,Bridge 与 Portal 双向都做了,并对真 Xray 26.7.28 四象限验穿(见 test/reverse-e2e/)。核心是 overlay 新代码(纯自包含,复用现有 codec),find-replace 补丁只做"接线":

补丁 文件 作用
0003 listener/parse.go listener switch 加 reverse-bridge / reverse-portal 两个 case(1 命中)
0004 adapter/outbound/base.go 给 autoCloseProxyAdapter 加 InnerProxyAdapter() 访问器,让 Bridge 解包拿内层 *Vless(1 命中)
0005 adapter/parser.go 出站 switch 加 reverse-portal case → outbound.NewReversePortal(1 命中)

overlay 新代码(非补丁,overlay/ 下真 .go,build.py 构建时整树叠加):

  • transport/muxcool/{frame,server,client}.go —— Mux.cool 帧 codec + ServerWorker(Bridge)+ ClientWorker(Portal),纯明文分帧,单测 + -race 全过。
  • adapter/outbound/vless_reverse.go —— DialReverse(Bridge 建 VLESS-Rvs 反连,复用 StreamConnContext)。
  • adapter/outbound/reverse_portal.go —— reverse-portal 出站(用户流量→开子流)。
  • listener/inbound/reverse_bridge.go —— reverse-bridge listener(拨 Portal + ServerWorker + 落地 tunnel)。
  • listener/inbound/reverse_portal.go —— reverse-portal listener(接受 VLESS-Rvs + ClientWorker)。
  • listener/reverse/portal.go —— Portal 运行时注册表(tag → 活跃隧道,picker)。

配置示例(格式B,VLESS-Rvs,security=none)

mihomo Bridge(拨到 Portal,落地本机内网):

proxies:  [{name: toportal, type: vless, server: <portal>, port: 10000, uuid: <uuid>, network: tcp, tls: false}]
listeners: [{name: rb, type: reverse-bridge, portal: toportal}]
rules:     [MATCH,DIRECT]

mihomo Portal(接受反连,用户经 socks 接入;支持 REALITY):

mixed-port: 10800
allow-lan: true           # 否则只绑 127.0.0.1,外部连不上
proxies:  [{name: rvout, type: reverse-portal, tag: rvpool}]
listeners:
  - name: rp
    type: reverse-portal
    listen: 0.0.0.0
    port: 10000
    uuid: <uuid>
    tag: rvpool
    reality-config:       # 可选:配了则用 REALITY 终止,否则 security=none
      dest: www.cloudflare.com:443
      private-key: <mihomo generate reality-keypair 的 PrivateKey>
      short-id: ["01ab"]
      server-names: [www.cloudflare.com]
rules:    [MATCH,rvout]

Xray 侧:Portal = VLESS 入站账号配 reverse:{tag} + 路由;Bridge = 简化式 VLESS 出站配 reverse:{tag},freedom 出站必须加 domainStrategy:UseIP + finalRules:[{action:allow}](否则 defaultBlockAllRule 静默黑洞,xray -test 查不出)。

能力矩阵(TCP/REALITY/UDP 全部对真 Xray 26.7.28 跨工具双向验穿,见 test/reverse-e2e/)

TCP REALITY UDP(全锥)
mihomo Bridge ↔ Xray Portal ✅ ✅ ✅
mihomo Bridge ↔ mihomo Portal ✅ ✅ ✅
Xray Bridge ↔ mihomo Portal ✅ ✅ ✅

已实现:REALITY-on-Portal(复用 listener/reality 包装,借证 dest 固定走 DIRECT)、UDP 全锥反向 (一个 PacketConn 按 target 开多条 Mux.cool UDP 子流,回程按 source 解复用——与 Xray reverse 的 per-target 子流一致;Bridge 经 mihomo dialer 落地、Portal ListenPacketContext)、带缓冲 pipe (去 HoL)、轮询隧道池 + IsActive 过滤 + >256 DRAIN。

边界 / 约束

  • UDP 全锥:单会话发多 target,各自独立子流、回程按 source 分发(实测一会话→两内网服务)。 Xray reverse 路径本就不用 GlobalID-XUDP(用 source/local 标识 per-target 子流),故与之字节兼容。
  • Bridge 落地 UDP 走 dialer.DialContext(mihomo 解析器)直连目标,不经 mihomo UDP 路由规则 (Bridge 即内网网关,等价 MATCH,DIRECT)。
  • REALITY-on-Portal 的 dest 需从 Portal 机可直连(借证真握手);别用 apple/microsoft(实测异常),用 cloudflare 类。
  • Portal 端用户子流走形态A(不写 source/local),对真 Xray Bridge / mihomo Bridge 双向兼容(靠 metaLen 边界)。

安全维度矩阵(reverse-portal 支持 none / tls / reality;flow 支持空 / vision)

mihomo Bridge 与 Portal 两端全维度对称打通(实测 test/reverse-e2e/matrix.sh,mihomo↔mihomo):

底层安全 × flow 结果
none(裸 TCP) ✅
tls-only(自签/证书,flow 空) ✅
reality(flow 空) ✅
rprx-vision + TLS(vision over 普通 TLS) ✅
rprx-vision + REALITY ✅
  • reverse-portal listener 现支持三种底层:none / tls:true(+可选 certificate/private-key,空则自签)/ reality-config。
  • Portal 侧 vision server 已实现:握手读到 client flow=xtls-rprx-vision → 套 mihomo vision.NewConn(portalRespConn 前置 [00][00]),自动解包。Bridge 侧 DialReverse 复用 StreamConnContext 自动套 vision。
  • 跨工具 vision+REALITY 三方向验穿:mihomo Bridge↔Xray Portal、Xray Bridge↔mihomo Portal、mihomo↔mihomo(见 test/reverse-e2e/vision/)。
  • vision 需 TLS1.3/REALITY 底层(不能裸 TCP)。
  • 建立耗时:mihomo↔mihomo 反连 ~1s(reality 与 vision+reality 一样,vision 不额外加延迟)。 当 Bridge 是 Xray 时,Xray 反向出站启动先 time.Sleep(2s) 再拉起、且 monitor Interval:2s (proxy/vless/outbound/outbound.go:123/126),故 Xray-as-Bridge 需 ~2-4s 才拨——这是 Xray 反向代码的固定 sleep,与 Vision 无关。

字节级规范与集成架构见 docs/reverse-bridge-移植蓝图.md。