补丁以 find-replace 代码形式存在于 build.py 的 @patch 函数里(照 linkpro 模式,不用 .patch 文件),
每个补丁带命中数校验:上游漂移导致 pattern 没按预期命中,build.py 会 fail loud 中止,提示来这里更新。
| 文件 | 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 客户端填自己的构建版本:
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× Xray26.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)。
| 文件 | 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.goDstAddr加Rvs bool字段。 - (c)
conn.gosendRequest的 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 VLESS 的 Rvs 命令(
common/protocol/headers.goRvs=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.pyfail 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/。
对 mihomo 源码全量核对,确认 REALITY 版本坑只此一处,无需 patch 0002:
- 版本字节唯一:全仓
SessionId[0..2]赋值只在component/tls/reality.go那 3 行,无第二处硬编码。 - mihomo 无版本门槛逻辑:全仓无
minClientVer/MinClientVer之类的 REALITY 版本比较;服务端侧listener/reality/reality.go也不校验客户端版本(mihomo 当 REALITY 服务端是宽松的,不会因版本拒客户端)。所以坑只在"mihomo 客户端声明的版本"这一侧,已由 0001 修掉。
- 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。这是配置项,不需要改代码。
把 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)。
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(全锥) | |
|---|---|---|---|
| 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 边界)。
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→ 套 mihomovision.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)再拉起、且 monitorInterval:2s(proxy/vless/outbound/outbound.go:123/126),故 Xray-as-Bridge 需 ~2-4s 才拨——这是 Xray 反向代码的固定 sleep,与 Vision 无关。
字节级规范与集成架构见 docs/reverse-bridge-移植蓝图.md。