- go.mod/main.go/config.go/sample/CHANGELOG: 移除 类 frp/去中心化 frp 本体/像 frp 类比 - 保留 ExternalService 中真实的 frp_server 外部穿透服务(客观功能,非自比)
29 KiB
变更日志 (CHANGELOG)
2026-07-15 修正依赖的 core 模块路径(去掉失效的 /core 后缀)
背景
core 仓库 zkcoi/Meshray 的 go.mod 位于仓库根目录、内无 core/ 子目录,故其模块路径
git.zkcoi.com/zkcoi/meshray/core 无法被 go get 解析。上一版(manager 模块更名)日志称
"core 模块保持不变、仍可由 go get 正确解析" 有误,特此更正:core 模块路径实际应为根模块
git.zkcoi.com/zkcoi/meshray(仓库名大小写不敏感,zkcoi/meshray 即 zkcoi/Meshray,可正确解析)。
改动
go.mod:require/replace中git.zkcoi.com/zkcoi/meshray/core→git.zkcoi.com/zkcoi/meshray(replace 仍指向本地兄弟模块../Meshray)。- 全仓
.go:core 导入git.zkcoi.com/zkcoi/meshray/core/{engine,connect}→git.zkcoi.com/zkcoi/meshray/{engine,connect}(共 5 处:cmd/mr-wg/main.go、 internal/ctr/corebind/{registry,bind,adapter}.go)。
验证
go build ./...→ 0(manager 仓库编译通过,core 引用全部更新)。
2026-07-15 修正 manager 模块路径为 git.zkcoi.com/zkcoi/Meshray-Manager
背景
Gitea 仓库名大小写不敏感:zkcoi/meshray 与 zkcoi/Meshray(core 仓库)会被视为
同一仓库,导致原模块路径 git.zkcoi.com/zkcoi/meshray 经 go get 会解析到 core 仓库
(其根模块为 git.zkcoi.com/zkcoi/meshray/core),而非本管理器。为使模块路径与仓库名
zkcoi/Meshray-Manager 一致、可从网络正确拉取,特此更名。
改动
go.mod:module git.zkcoi.com/zkcoi/meshray→module git.zkcoi.com/zkcoi/Meshray-Manager。- 全仓
.go导入:git.zkcoi.com/zkcoi/meshray/{internal,web,pkg}/...→git.zkcoi.com/zkcoi/Meshray-Manager/...(正则meshray/(?!core/)仅替换自有包, 保留对 core 模块git.zkcoi.com/zkcoi/meshray/core的引用)。 - core 模块路径
git.zkcoi.com/zkcoi/meshray/core与仓库zkcoi/Meshray保持不变, 仍可经go get正确解析;go.mod中require/replace .../meshray/core => ../Meshray不变。 README.md/deploy/scripts/install.sh的仓库地址同步改为zkcoi/Meshray-Manager。
验证
go build ./...→ 0(全仓编译通过,core 引用未被误改)。
2026-07-15 仓库拆分:core 独立为 meshray/core,wg-go 集成迁移至 internal/ctr/corebind
背景(架构决策)
core(转发引擎)本质是一个通用的 P2P 多层传输中继核心——9 层传输只转发字节 A↔B,
不关心载荷是应用流量(形态 A:独立隧道)还是 WG 密文(形态 B:增强传输)。此前 core 直接 import wg-go
并内置 EnhancedBind,导致「集成干扰核心」:核心层被 wg-go / contract 绑架,难以独立演进。
故按用户决策:
- 项目命名统一为 meshray;wg-go 集成属于 manager / UI / GUI 范畴,不污染 core。
- 把原
core/目录拆为两个隔离仓库:Meshray(模块git.zkcoi.com/zkcoi/meshray/core):零 wireguard、零 contract 依赖的纯转发底座。Meshray-Manager(模块git.zkcoi.com/zkcoi/meshray):wg-go / contract / UI / 管控面。
改动(核心仓库 Meshray)
- 模块路径由
git.zkcoi.com/zkcoi/meshray(与 manager 冲突)改为git.zkcoi.com/zkcoi/meshray/core;core/目录重命名为engine/,全仓git.zkcoi.com/zkcoi/meshray/→git.zkcoi.com/zkcoi/meshray/core/替换。 engine/engine.go:NewEngine(logger, metrics, plugin ...transport.ProtocolPlugin)——插件改为可选变参, 形态 A 可直接空插件启动。- 移除
bind *EnhancedBind字段与wg.NewWGPlugin()导入。 - 新增集成钩子(core 不依赖 wg,由外部注入):
PeerRegistrar接口 +SetPeerRegistrar:外部 bind 注册对端地址供 NAT 学址。SetRelayInboundHandler(func(peerKey string, packet []byte)):中继入站回调交给外部。SetDirectByBind(bool)/SetListenPort(int):声明由 bind 接管直连与端口。
NotifyPeerInfo改调e.peerRegistrar.RegisterPeer(addr, peerKey)而非e.bind.RegisterPeer。
transport/relay_test.go:移除对plugins/wg的依赖,改用本地mockPlugin(实现IsControlPacket/IsDataPacket返回 false)。transport/、connect/仅注释提及 WireGuard,无任何 import 依赖。go.mod:仅依赖 gorilla/websocket、pion/stun、pion/turn/v2、pion/webrtc/v3、 quic-go、zap;移除 wireguard-go / wgctrl / meshray-contract。
改动(manager 仓库 Meshray-Manager)
internal/ctr/corebind/(package corebind,新增):把 wg-go 集成从原 core 迁出:bind.go:移植EnhancedBind,NewEnhancedBind改为通过 core 钩子接线 (SetPeerRegistrar/SetRelayInboundHandler/SetDirectByBind(true)/SetListenPort), 实现conn.Bind(Open/Send/Close/ParseEndpoint/BatchSize/SetMark等)。registry.go:移植注册逻辑 +getOrCreateEngine(用core.NewEngine(logger, core.NewMetrics())),init()注册contract.RegisterEnhancedBindFactory/contract.RegisterEngineProvider。adapter.go:meshray-contract ↔ meshray/core 类型互转 (toCoreCandidates/toCoreICEConfig/toCoreLayerOrder/toContractStatus)。
internal/ctr/core_enable.go:空白导入改为_ "git.zkcoi.com/zkcoi/meshray/internal/ctr/corebind"。cmd/mr-wg/:由原core/cmd/meshray-core/迁移,导入修正为git.zkcoi.com/zkcoi/meshray/core与internal/ctr/corebind,使用corebind.NewEnhancedBind、候选类型meshraycore.Candidate。go.mod:- 删除旧
replace git.zkcoi.com/zkcoi/meshray-core => ./core与require meshray-core。 - 新增
require git.zkcoi.com/zkcoi/meshray/core v0.0.0+replace git.zkcoi.com/zkcoi/meshray/core => ../Meshray(本地兄弟模块)。
- 删除旧
internal/ctr/wg.go、ctr.go、interface.go:仅注释/字符串提及meshray-core,无 import 依赖,保持不变。
验证
Meshray:go build ./...→ 0;go test ./transport/... ./connect/...→ ok(零 wg / contract 依赖)。Meshray-Manager:go build ./...→ 0;go build ./cmd/mr-wg/... ./internal/ctr/...→ 0。- 已知:manager
go mod tidy在 Go 1.26 下因「兄弟模块 replace + 本地 replace」触发内部 panic, 已用go mod download+ 直接路径go build ./path/...绕过,未执行 tidy。
2026-07-15 重构:Direct-UDP 改为共享监听 socket + WG 学址,原生支持 NAT 穿透(核心)
背景(用户诉求)
用户指出:NAT 后的机器与公网 VPS 通信本不应有限制(标准 UDP/WireGuard 语义:主动侧发包后 NAT 记录会话,回包可返回)。且希望 core 成为「地址权威」——集成时对端地址从 core 获取再喂给 wg-go,而非依赖 wg-go 预配置的 endpoint 可达性。
根因(旧实现为何 NAT 场景不通)
增强模式此前用「每 peer 一个 connected UDP socket」(DirectFactory.Dial →
net.DialContext)。connected socket 只接收所连对端的包:
- NAS(NAT 后)拨 VPS:51820 成功,但其源地址经 NAT 改写为
WAN:随机端口; - VPS 的 connected socket 连的是
NAS内网:51820,收到的WAN:随机端口源地址不匹配 → 丢弃。
即公网侧「收得到、回不去」。这不是 NAT 的固有限制,而是 connected socket 语义所致。
修复(回归标准 WireGuard Bind 语义)
Direct-UDP 改由 EnhancedBind 的共享未连接 UDP socket(directSock,绑定 listen_port)承载:
core/bind.go:- 新增
directSock *net.UDPConn、listenPort;inboundPacket增加ep conn.Endpoint。 Open用net.ListenUDP在 listen_port 建共享 socket(listen_port=0 时由 OS 分配并回填 引擎实际端口),启动readDirectLoop。readDirectLoop从任意来源收包,把真实源地址作为 Endpoint 投入inboundCh(NAT 穿透学址核心:公网侧据此学到对端 NAT 映射后地址)。receiveFunc/endpointForPacket:Direct 入站用学得的真实源地址;中继入站按 peerKey 查缓存。Send→directSendTo:经共享 socketWriteToUDP发往目标地址(源端口固定 listen_port, 满足对称直连)。移除旧的随机端口兜底 socket(fallbackSock/fallbackSend)。
- 新增
core/engine.go:新增directByBind标志(由NewEnhancedBind置位)。增强模式下NotifyPeerInfo不再对 Direct 层主动拨号(WG 启动后自动向 endpoint 发握手经Send承载), 避免DirectFactory.Dial再绑 listen_port 造成 UDP 端口冲突。
效果
- 双公网对称直连:
A:port↔B:port原生可通。 - NAT 后主动侧 ↔ 公网侧:公网侧收包学到对端真实地址,回包自然返回,无需对端预先公网可达、 无需路由器端口映射。core 成为地址权威,集成侧可从 core 取对端地址喂给 wg-go。
- TURN/WebRTC/WS 等中继/信令层不受影响,仍经 connMgr 承载(Direct 不可用时补充)。
- TCP 直连族(FakeTCP/RealTCP)为 TCP,与 UDP
directSock不冲突。
验证
go build ./... && go vet ./... && go test ./...→ 全部通过(3 个测试包 ok)。
2026-07-15 修复:直连传输 socket 未绑定本地端口,两台机器互传失败(核心 P0)
背景(用户排查「两台机器传不了数据」)
此前已修复「初始直连缺入站读取」,但端到端仍不通。进一步排查数据路径(Bind→Relay→
DirectFactory)发现更底层缺陷:增强模式下 WG 经 EnhancedBind 接管,并不在 listen_port
上监听真实 UDP(Open 仅从 inboundCh 取包,无 ListenUDP)。
根因
DirectFactory.Dial 用 dialer.DialContext(ctx,"udp",candidate),本地端口为随机临时端口:
- A 拨 B:51820 → A 的 socket 本地
A:临时、远端B:51820; - B 拨 A:51820 → B 的 socket 本地
B:临时、远端A:51820。
A 发往 B:51820 的包到达 B 的 OS 时,B 在 51820 无任何监听 socket(B 的传输 socket 本地是
临时端口),被 OS 丢弃;B→A 同理。即两端都出得去、进不来——这正是「frp 能传、meshray-core
不行」的本质:frp 是中心中继(两端都出向连 server),而本仓增强模式要求对称直连却没把
本地端口绑到 listen_port。
修复
让 Direct-UDP 传输 socket 把本地端口绑到 listen_port(WG listen_port),形成
A:port↔B:port 对称直连:
connect/direct.go:DirectFactory增加localPort与SetLocalPort;Dial时dialer.LocalAddr = &net.UDPAddr{Port: bindPort}(优先DialConfig.LocalPort,其次工厂值)。connect/strategy.go:DialConfig增加LocalPort int。core/engine.go:Engine持有listenPort与directFactory引用,新增SetListenPort回填工厂;initiateConnection把listenPort注入DialConfig.LocalPort。core/bind.go:NewEnhancedBind内调用eng.SetListenPort(listenPort), 使 standalone 与受管模式(均经此入口)都能拿到本端端口。
验证
- 临时单测模拟两机(回环两端口):A:15180↔B:15181 对称直连,双向 Write/Read 均成功 (修复前临时端口下对端收不到)。已删除该临时测试。
go build ./... && go vet ./...→ 通过。
注
NAT 后的机器仍依赖 TURN/WebRTC 中继/打洞层(需配置 ice 服务器),本修复解决的是
「直连(独立 IP / 公网 / 同局域网)对称互通」这一基础能力。
2026-07-15 独立 meshray-core 改用 TOML 配置文件
需求
独立运行希望使用 TOML 配置文件,而非 JSON。
改动
- 新增依赖
github.com/BurntSushi/toml。 core/cmd/meshray-core/config.go:Config各字段增加toml标签(键名与 JSON 一致,snake_case)。loadConfig按扩展名自动选解码器:.toml/.conf→ TOML;其它(含.json)→ JSON, 旧 JSON 配置继续可用。判断前先剥离.sample模板后缀,避免meshray-core.toml.sample被误判为 JSON。
main.go:默认配置路径由meshray-core.json改为meshray-core.toml;-c仍可指定任意路径。- 新增样例
core/cmd/meshray-core/meshray-core.toml.sample。
注意(TOML 语义坑)
TOML 中 [[peers]] 数组表之后、下一个表头之前的键会被归入该 peers 元素。
故 layer_strategy / layers 等顶层键必须置于 [[peers]] 之前,否则会被误塞进 peers 表。
样例已据此排布,并加注释提醒。
验证
- 临时单测加载
meshray-core.toml.sample,确认 network_id / interface / mode / peers / ice / layer_strategy / layers 全部正确映射(已删除该临时测试)。 go build ./... && go vet ./...→ 通过。
2026-07-15 新增:传输层级支持手动指定(auto / manual)
需求
除默认的 9 层自动降级外,需支持手动指定传输层级,便于在「已知网络环境」下锁定链路 (如强制走 TURN-TCP 中继、或强制 Direct-UDP 直连),也便于排障时固定单链路复现。
配置入口(独立 meshray-core)
core/cmd/meshray-core/config.go:Config新增两字段layer_strategy:"auto"(默认,9 层自动降级)或"manual"(仅用下方 layers)。layers:[]string,仅 manual 生效。单元素=强制固定层、禁用降级; 多元素=按列表顺序自定义降级链。
- 支持别名解析(
core/connect/strategy.go新增ParseLayer):如udp/tcp/wss/webrtc/turn等均可识别。 - 示例:
{ "layer_strategy":"manual", "layers":["TURN-TCP"] }强制 TURN-TCP 中继;{ "layer_strategy":"manual", "layers":["Direct-UDP","TURN-TCP"] }直连优先、不通再中继。
联动修复(降级顺序感知)
FallbackController原本按Layer枚举下标(currentIndex+1)降级/恢复,手动自定义顺序下 会越界到错误层级。改为按StrategyScheduler.layerOrder顺序索引取上一/下一层 (新增nextLayerInOrder/prevLayerInOrder)。- 由此,手动单层级场景下「无下一层」自然退化为不降级,语义正确。
ICE 配置补齐(手动 TURN/WebRTC 必需)
- 独立二进制此前从未调用
Engine.SetICEConfig,导致 TURN/WebRTC 工厂拿不到 STUN/TURN 地址。 main.go现已把配置里的ice.stun/ice.turn下达到引擎,手动 TURN-TCP/TURN-TLS/ WebRTC 层级方可真正建连。
管控面(ctr)
internal/ctr已通过update.EnabledLayers→SetLayerOrder支持自定义顺序, 等价「手动多层级」,本次无需改动;后续若需「auto/manual」语义可在此扩展。
构建验证
core独立:go build ./... && go vet ./...→ 通过。
2026-07-14 核心路基缺陷修复 (P0 + P1)
背景
此前验证集中在 HTTP/前端接口与页面,属表层验证。核心组网路基(WireGuard 增强模式 P2P 劫持转发链路)实际断裂。本次以单元测试钉死缺陷后,按 P0→P1 全修。
P0-1 Bind 空指针崩溃
- 文件:
core/transport/conn_manager.go - 问题:
Add/Remove/CloseAll在conn==nil时调用conn.Close()/RemoteAddr()空指针 panic。增强模式每次AddPeer都会直接崩。 - 修复: 三处全部对
nilconn 做安全处理(仅登记 peerKey,不调用 conn 方法)。
P0-2 建连永不触发
- 文件:
core/engine.go、core/connect/direct.go、internal/ctr/ctr.go、internal/ctr/wg.go - 问题:
ctr从不调用engine.NotifyPeerInfo注入对端候选,candidateStore恒空,initiateConnection直接 return,9 层策略拨号永不触发;DialConfig也无Candidates,DirectFactory拿不到对端地址。 - 修复:
DialConfig增加Candidates字段。DirectFactory.Dial优先使用信令下发的候选地址。engine.initiateConnection将candidateStore转为DialConfig.Candidates。WGManager.ListPeers从wgctrl读取真实 Endpoint 作为候选来源。ctr.SwitchMode切增强模式时注入对端真实 Endpoint 触发建连; 新增NotifyPeerCandidates供信令层注入。
P1-1 routeID 体系断裂(双向转发只通一半)
- 文件:
core/transport/relay.go、core/engine.go - 问题:
engine.Bind用extractRouteID(publicKey)(前4字符 ASCII 和)注册本地端口, 而relay.forwardIncoming用 WG 包receiver index查表,两套 routeID 无映射, 对端→本地转发全部 miss。 - 修复: 转发路由统一改为
peerKey。删除伪造的extractRouteID;RegisterLocalPort/UnregisterLocalPort/StartReadFromLocalPort/forwardIncoming全部以peerKey为索引。
P1-2 UpdateCoreConfig 桩
- 文件:
internal/ctr/ctr.go、core/engine.go、core/connect/strategy.go、core/connect/ice.go - 问题:
UpdateCoreConfig直接return "尚未实现";SetICEConfig仅打日志; WebRTC 工厂 ICE 无法更新。 - 修复:
- 新增
CoreConfigUpdate结构(STUN/TURN/启用层)。 UpdateCoreConfig真实生效:下发 ICE 配置 + 设置传输层优先级顺序。engine.SetICEConfig真正调用scheduler.UpdateWebRTCICE。scheduler新增UpdateWebRTCICE;WebRTCFactory新增UpdateICE;ICEClient新增UpdateConfig。
- 新增
P1-1 后续清理:routeIDStore 死状态
- 文件:
core/engine.go、internal/ctr/ctr.go、core/engine_test.go - 问题: 上轮删除了
extractRouteID并把转发路由统一为peerKey,但Engine仍保留routeIDStore map[string]uint32,NotifyPeerInfo仍接收并存储恒为 0 的routeID,initiateConnection仍查routeIDStore[peerKey]作为冗余闸门。该 map 实际只是 “是否收到过 NotifyPeerInfo”的布尔标记,存的 0 无任何用途,且对其他触发路径是陷阱。 - 修复: 彻底删除
routeIDStore字段、其初始化与清理、NotifyPeerInfo的routeID参数 及存储逻辑;initiateConnection删除 “获取 RouteID” 冗余检查。路由判定完全统一为candidateStore非空 +peerKey索引。同步清理ctr、engine_test的调用点。
测试
core/transport/relay_test.go: peerKey 路由匹配转发 / 未知丢弃。core/engine_test.go: Bind 不再崩溃、无候选不盲目建连、注入候选后链路接通。core/connect/scheduler_test.go: 策略层选择、DirectFactory 缺候选必败、 候选地址真实建连(本地 listener 验证)。- 验证说明: 沙箱 App Control 拦截
core包测试二进制的执行;core/connect、core/transport测试已成功运行通过;全量go build ./...与go vet ./core/...通过。
2026-07-15 架构重构:多仓解耦 + 编译期注入 + 闭源 core + conn.Bind 接管
背景
原架构把闭源增强逻辑(core)以本地子目录方式内嵌于开源管理器主仓,存在两点硬伤:
- 开源条约风险:开源主仓直接包含闭源
core源码,无法干净剥离。 - Endpoint 劫持原理缺陷:旧方案把 peer Endpoint 改写为回环端口,企图让 WG 入站 流量经本地端口旁路;但 WG 入站按真实源地址匹配 peer,改写后源地址与 peer 不一致, 入站接不通,9 层传输大部分是空壳,信令层缺失,零端到端验证。
经架构决策(用户确认):
- 底层用开源
wireguard-go,增强层meshray-core闭源,二者物理边界清晰、 可分开授权;管理器开源;无 meshray-core 时系统仍以标准 WG 直连运行。 - 代码物理边界 = 多仓解耦(独立 module)。
- 增强层接入方式 = 编译期注入(build tag 触发空白导入
init()注册)。
关键契约:开源契约层(叶子 module)
- 新增
pkg/contract(module git.zkcoi.com/zkcoi/meshray-contract,开源叶子模块), 作为主仓与 core 之间唯一的 module 边界契约,解决meshray ↔ meshray-core双向依赖导致的 module 级循环依赖:- 定义
Candidate、Layer(9 层常量 +String+DefaultLayerOrder)、ICEConfig、PeerStatus、EngineStatus、EngineController接口 (Start/Stop/NotifyPeerCandidates/SetICEConfig/SetLayerOrder/GetStatus)。 - 定义
BindFactory = func(listenPort int, networkID string, logger *zap.Logger) (conn.Bind, error); 注册表RegisterEnhancedBindFactory/GetBindFactory(enhanced bool): enhanced=false 返回 wireguard-go 默认 Bind(保证无 core 仍可直连); enhanced=true 未注册时返回errBindNotEnhanced。
- 定义
core 独立 module(闭源)
- 新增
core/go.mod(module git.zkcoi.com/zkcoi/meshray-core,go 1.26.0), require 主仓全部依赖 +meshray-contract,末尾replace git.zkcoi.com/zkcoi/meshray-contract => ../pkg/contract。 - 新增
core/bind.go:EnhancedBind实现conn.Bind,包裹conn.NewDefaultBind();Send当前透传并预留 9 层策略插入点;NewEnhancedBind调用getOrCreateEngine。 - 新增
core/registry.go:engines单例 +getOrCreateEngine;engineController适配contract.EngineController;init()注册RegisterEnhancedBindFactory+RegisterEngineProvider(仅编译进 core 时执行)。 - 新增
core/adapter.go:契约 ↔ core 类型转换(候选 / ICE / 层顺序 / 状态)。 core/engine.go:import 路径改.../meshray/core/→.../meshray-core/; 删除废弃的Bind(peerKey, localPort)/Unbind(peerKey)旁路方法(根治 Endpoint 旁路缺陷); 删除不再使用的"fmt"import。core/transport/plugin.go:删除死代码ExtractRouteID接口方法。core/plugins/wg/wgparse.go:删除ExtractRouteID实现及无用 import,仅保留IsControlPacket/IsDataPacket。core/engine_test.go:删除依赖已废弃engine.Bind/Unbind的用例。
管理器主仓(开源)
go.mod:删除旧replace .../core => ./core;新增replace .../meshray-contract => ./pkg/contract、replace .../meshray-core => ./core;require 两新 module(均v0.0.0)。- 新增
internal/ctr/core_enable.go://go:build meshray_core+_ "git.zkcoi.com/zkcoi/meshray-core"空白导入,触发 coreinit()注册。 internal/ctr/wg.go:CreateDevice增meshMode参数;startUserModeWGProcessWithRefs用contract.GetBindFactory(meshMode == "enhanced")替代硬编码conn.NewDefaultBind()(交由 core 经 conn.Bind 接管 WG 收发); 增强模式获取失败返回错误;UpdatePeerEndpoint注释强调增强模式不应改写回环端口。internal/ctr/ctr.go:去除core具名 import;coreInst *core.Core→engines map[string]contract.EngineController;CreateNetworkenhanced 模式走contract.NewEngineController;AddPeer/RemovePeer不再改写 Endpoint 为回环端口;SwitchMode经engineCtl.NotifyPeerCandidates注入候选;CoreConfigUpdate.TURNServers改[]string、EnabledLayers改[]contract.Layer;NetworkStatus.CoreStatus改*contract.EngineStatus。
构建验证
core独立:go build ./... && go vet ./...→ 通过。- 管理器默认(无 core):
go build ./...→ 通过(标准 WG 直连)。 - 管理器增强(编译期注入):
go build -tags meshray_core ./...→ 通过。
仍然待补(非本次范围)
EnhancedBind.Send实际插入 9 层策略(当前透传)。- 信令层真正驱动
NotifyPeerCandidates;P3 传输层空壳补全。 docs/下仍描述 “relay 按 route_id 查表转发” 的旧文档校正(用户规则禁止新增说明文档, 已通过代码注释反映新路由逻辑)。
2026-07-15 增强 Bind 虚拟桥接 + meshray-core 独立二进制
背景
用户希望直接运行 core 在两台机器上验证收发,而非必须经由管理器。
核查发现旧 EnhancedBind 仅是纯透传(Send 直接转交默认 UDP bind),而 9 层
relay 的 localPorts 在增强模式下从未被注册,导致 9 层传输逻辑根本没接到 WG 收发路径上。
本次把 EnhancedBind 重写为真正桥接 WG ↔ 引擎 9 层传输,并新增独立可运行二进制。
关键发现(wireguard-go 匹配机制)
阅读 device/receive.go 确认:传输包由包内 receiver index 匹配 peer,Endpoint 仅用于
握手更新对端出向地址与 MAC2 cookie 计算。因此回灌入站时只要携带对端真实 Endpoint,
即可正确接通,无需让 WG 监听真实 UDP 端口——这正是增强 Bind 完全接管收发的依据。
core/bind.go(重写)
EnhancedBind不再依赖默认 bind 的真实 UDP 监听:Open返回从inboundCh读取引擎 入站的接收函数;Send按ep反查 peerKey,经引擎 9 层传输连接发送,连接未就绪时 惰性触发拨号并走直连兜底。deliverInbound:引擎传输连接读到的入站密文投入inboundCh,由接收函数拷贝进 WG 缓冲并填入对端真实 Endpoint 回灌。RegisterPeer(addr, peerKey):维护 endpoint→peerKey 与 peerKey→真实 Endpoint 映射。- 接收函数严格遵守 WG 缓冲所有权(拷贝而非替换指针),
BatchSize/ParseEndpoint委托默认 bind。
core/transport/relay.go
- 新增
OnInbound func(peerKey string, packet []byte)钩子;StartReadFromRemoteConn设置后 入站改投该钩子(即 EnhancedBind),否则走原本地端口转发(向后兼容)。
core/engine.go
Engine增加bind *EnhancedBind字段(由NewEnhancedBind回填)。NotifyPeerInfo在收到候选时自动RegisterPeer,并新增EnsureDial(Send 惰性拨号用)。
core/cmd/meshray-core(新增独立二进制)
main.go+config.go:-c config.json启动、-genkey生成私钥。- 直接以
core.NewEnhancedBind接管 wireguard-go 用户态设备收发;按配置创建 TUN、 注入对端候选触发 9 层拨号、Up后分配隧道 IP(Linuxip/ Windowsnetsh)。 mode: enhanced走 9 层接管;mode: native退化为裸 WG 直连,便于对照测试。- 新增
meshray-core.example.json示例配置(占位符,用户替换公私钥/endpoint 即可两机对测)。 go.mod:go mod tidy引入wireguard/wgctrl与 Windowswintun依赖。
构建验证
core独立:go build ./... && go vet ./...→ 通过;go build -o meshray-core.exe ./cmd/meshray-core通过;./meshray-core.exe -genkey正常输出私钥。- 管理器默认:
go build ./...→ 通过;增强:go build -tags meshray_core ./...→ 通过。
两机对测步骤(摘要,详见二进制内注释与示例配置)
- 两台机器各自
meshray-core -genkey得私钥,用private_key对应公钥填对方public_key。 - A 的
interface=10.0.0.1/24、peers[0].endpoint=B公网IP:51820、allowed_ips=10.0.0.2/32;B 对称。 - 两台均
meshray-core -c meshray-core.json,随后ping 10.0.0.x验证隧道收发。 - Windows 需已安装 Wintun 驱动(随 WireGuard 客户端提供)。
2026-07-15 修复:初始直连缺少入站读取,公网/独立 IP 机器直连亦不通
背景(用户纠正)
用户指出 meshray-core 的核心机制必须同时满足「NAT 后的机器」与「有独立 IP 的机器」, 二者都要能直连(P2P)→ 失败再走中继/打洞兜底。这意味着即使两台公网机器直连, 当前实现也应能接通。实测与代码核查发现:初始直连路径本身就没接通,与是否 NAT 无关。
根因
StrategyScheduler.Dial(core/connect/strategy.go:284)成功后仅返回连接,从不调用OnConnectionUpdate。OnConnectionUpdate回调(core/engine.go:45)才是唯一会调用relay.StartReadFromRemoteConn启动入站读取的地方;而它只在reconnectToLayer(降级重连)与probeHigherLayers(恢复探测)内触发。Engine.initiateConnection拿到Dial返回的直连后只做了connMgr.Add,未启动入站读取协程。
断点后果
两侧都只发出握手、收不到对方回包 → 握手无法完成 → 隧道起不来。 该缺陷在「两台公网/独立 IP 机器直连」场景下同样存在,并非 NAT 专属问题。
修复
core/engine.go:initiateConnection在connMgr.Add之后显式调用e.relay.StartReadFromRemoteConn(context.Background(), peerKey, conn), 使初始直连的入站密文能经OnInbound回灌 WG(增强模式)或写入本地端口。- 降级重连 / 恢复探测仍走
OnConnectionUpdate原路径,无重复启动冲突 (旧连接被connMgr.Add关闭后其读取协程自然退出)。
构建验证
core独立:go build ./... && go vet ./...→ 通过。- 注:沙箱无法运行 TUN/Wintun,端到端需用户在两台真实机器(或同机双进程)验证
ping 10.0.0.x收发。修复后公网 IP / 同局域网直连场景预期可正常握手建隧。