Files
Meshray-Manager/CHANGELOG.md
T
zkcoi e9ca2f7d70 fix: rename manager module to git.zkcoi.com/zkcoi/Meshray-Manager
- go.mod module path matches repo zkcoi/Meshray-Manager (case-distinct from zkcoi/Meshray/core)
- rewrite internal imports meshray/{internal,web,pkg} -> Meshray-Manager/... (core refs kept)
- sync README.md / install.sh repo URLs; add CHANGELOG entry
2026-07-15 16:25:46 +08:00

28 KiB
Raw Blame History

变更日志 (CHANGELOG)

2026-07-15 修正 manager 模块路径为 git.zkcoi.com/zkcoi/Meshray-Manager

背景

Gitea 仓库名大小写不敏感:zkcoi/meshrayzkcoi/Meshray(core 仓库)会被视为 同一仓库,导致原模块路径 git.zkcoi.com/zkcoi/meshraygo get 会解析到 core 仓库 (其根模块为 git.zkcoi.com/zkcoi/meshray/core),而非本管理器。为使模块路径与仓库名 zkcoi/Meshray-Manager 一致、可从网络正确拉取,特此更名。

改动

  • go.modmodule git.zkcoi.com/zkcoi/meshraymodule 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.modrequire / replace .../meshray/core => ../Meshray 不变。
  • README.md / deploy/scripts/install.sh 的仓库地址同步改为 zkcoi/Meshray-Manager

验证

  • go build ./... → 0(全仓编译通过,core 引用未被误改)。

2026-07-15 仓库拆分:core 独立为 meshray/corewg-go 集成迁移至 internal/ctr/corebind

背景(架构决策)

core(转发引擎)本质是去中心化 frp——9 层传输只转发字节 A↔B,不关心载荷是应用流量 (形态 A:独立 frp 隧道)还是 WG 密文(形态 B:增强传输)。此前 core 直接 import wg-go 并内置 EnhancedBind,导致「集成干扰核心」:核心层被 wg-go / contract 绑架,难以独立演进, 也无法作为纯粹的去中心化 frp 基地。

故按用户决策:

  • 项目命名统一为 meshraywg-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/corecore/ 目录重命名为 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:移植 EnhancedBindNewEnhancedBind 改为通过 core 钩子接线 SetPeerRegistrar / SetRelayInboundHandler / SetDirectByBind(true) / SetListenPort), 实现 conn.BindOpen/Send/Close/ParseEndpoint/BatchSize/SetMark 等)。
    • registry.go:移植注册逻辑 + getOrCreateEngine(用 core.NewEngine(logger, core.NewMetrics())), init() 注册 contract.RegisterEnhancedBindFactory / contract.RegisterEngineProvider
    • adapter.gomeshray-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/coreinternal/ctr/corebind,使用 corebind.NewEnhancedBind、候选类型 meshraycore.Candidate
  • go.mod
    • 删除旧 replace git.zkcoi.com/zkcoi/meshray-core => ./corerequire meshray-core
    • 新增 require git.zkcoi.com/zkcoi/meshray/core v0.0.0 + replace git.zkcoi.com/zkcoi/meshray/core => ../Meshray(本地兄弟模块)。
  • internal/ctr/wg.goctr.gointerface.go:仅注释/字符串提及 meshray-core,无 import 依赖,保持不变。

验证

  • Meshraygo build ./... → 0go test ./transport/... ./connect/... → ok(零 wg / contract 依赖)。
  • Meshray-Managergo build ./... → 0go 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.Dialnet.DialContext)。connected socket 只接收所连对端的包:

  • NASNAT 后)拨 VPS:51820 成功,但其源地址经 NAT 改写为 WAN:随机端口
  • VPS 的 connected socket 连的是 NAS内网:51820,收到的 WAN:随机端口 源地址不匹配 → 丢弃。

即公网侧「收得到、回不去」。这不是 NAT 的固有限制,而是 connected socket 语义所致。

修复(回归标准 WireGuard Bind 语义)

Direct-UDP 改由 EnhancedBind共享未连接 UDP socketdirectSock,绑定 listen_port)承载:

  • core/bind.go
    • 新增 directSock *net.UDPConnlistenPortinboundPacket 增加 ep conn.Endpoint
    • Opennet.ListenUDP 在 listen_port 建共享 socketlisten_port=0 时由 OS 分配并回填 引擎实际端口),启动 readDirectLoop
    • readDirectLoop 从任意来源收包,把真实源地址作为 Endpoint 投入 inboundCh (NAT 穿透学址核心:公网侧据此学到对端 NAT 映射后地址)。
    • receiveFunc/endpointForPacket:Direct 入站用学得的真实源地址;中继入站按 peerKey 查缓存。
    • SenddirectSendTo:经共享 socket WriteToUDP 发往目标地址(源端口固定 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 上监听真实 UDPOpen 仅从 inboundCh 取包,无 ListenUDP)。

根因

DirectFactory.Dialdialer.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 无任何监听 socketB 的传输 socket 本地是 临时端口),被 OS 丢弃;B→A 同理。即两端都出得去、进不来——这正是「frp 能传、meshray-core 不行」的本质:frp 是中心中继(两端都出向连 server),而本仓增强模式要求对称直连却没把 本地端口绑到 listen_port。

修复

让 Direct-UDP 传输 socket 把本地端口绑到 listen_portWG listen_port,形成 A:port↔B:port 对称直连:

  • connect/direct.goDirectFactory 增加 localPortSetLocalPortDialdialer.LocalAddr = &net.UDPAddr{Port: bindPort}(优先 DialConfig.LocalPort,其次工厂值)。
  • connect/strategy.goDialConfig 增加 LocalPort int
  • core/engine.goEngine 持有 listenPortdirectFactory 引用,新增 SetListenPort 回填工厂;initiateConnectionlistenPort 注入 DialConfig.LocalPort
  • core/bind.goNewEnhancedBind 内调用 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 配置文件(类 frp)

需求

独立运行(类 frp 风格)希望像 frp 一样使用 TOML 配置文件,而非 JSON。

改动

  • 新增依赖 github.com/BurntSushi/toml(与 frp 同款)。
  • 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.goConfig 新增两字段
    • 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.EnabledLayersSetLayerOrder 支持自定义顺序, 等价「手动多层级」,本次无需改动;后续若需「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/CloseAllconn==nil 时调用 conn.Close()/RemoteAddr() 空指针 panic。增强模式每次 AddPeer 都会直接崩。
  • 修复: 三处全部对 nil conn 做安全处理(仅登记 peerKey,不调用 conn 方法)。

P0-2 建连永不触发

  • 文件: core/engine.gocore/connect/direct.gointernal/ctr/ctr.gointernal/ctr/wg.go
  • 问题: ctr 从不调用 engine.NotifyPeerInfo 注入对端候选,candidateStore 恒空, initiateConnection 直接 return,9 层策略拨号永不触发;DialConfig 也无 Candidates DirectFactory 拿不到对端地址。
  • 修复:
    • DialConfig 增加 Candidates 字段。
    • DirectFactory.Dial 优先使用信令下发的候选地址。
    • engine.initiateConnectioncandidateStore 转为 DialConfig.Candidates
    • WGManager.ListPeerswgctrl 读取真实 Endpoint 作为候选来源。
    • ctr.SwitchMode 切增强模式时注入对端真实 Endpoint 触发建连; 新增 NotifyPeerCandidates 供信令层注入。

P1-1 routeID 体系断裂(双向转发只通一半)

  • 文件: core/transport/relay.gocore/engine.go
  • 问题: engine.BindextractRouteID(publicKey)(前4字符 ASCII 和)注册本地端口, 而 relay.forwardIncoming 用 WG 包 receiver index 查表,两套 routeID 无映射, 对端→本地转发全部 miss。
  • 修复: 转发路由统一改为 peerKey。删除伪造的 extractRouteID RegisterLocalPort/UnregisterLocalPort/StartReadFromLocalPort/forwardIncoming 全部以 peerKey 为索引。

P1-2 UpdateCoreConfig 桩

  • 文件: internal/ctr/ctr.gocore/engine.gocore/connect/strategy.gocore/connect/ice.go
  • 问题: UpdateCoreConfig 直接 return "尚未实现"SetICEConfig 仅打日志; WebRTC 工厂 ICE 无法更新。
  • 修复:
    • 新增 CoreConfigUpdate 结构(STUN/TURN/启用层)。
    • UpdateCoreConfig 真实生效:下发 ICE 配置 + 设置传输层优先级顺序。
    • engine.SetICEConfig 真正调用 scheduler.UpdateWebRTCICE
    • scheduler 新增 UpdateWebRTCICEWebRTCFactory 新增 UpdateICE ICEClient 新增 UpdateConfig

P1-1 后续清理:routeIDStore 死状态

  • 文件: core/engine.gointernal/ctr/ctr.gocore/engine_test.go
  • 问题: 上轮删除了 extractRouteID 并把转发路由统一为 peerKey,但 Engine 仍保留 routeIDStore map[string]uint32NotifyPeerInfo 仍接收并存储恒为 0 的 routeID initiateConnection 仍查 routeIDStore[peerKey] 作为冗余闸门。该 map 实际只是 “是否收到过 NotifyPeerInfo”的布尔标记,存的 0 无任何用途,且对其他触发路径是陷阱。
  • 修复: 彻底删除 routeIDStore 字段、其初始化与清理、NotifyPeerInforouteID 参数 及存储逻辑;initiateConnection 删除 “获取 RouteID” 冗余检查。路由判定完全统一为 candidateStore 非空 + peerKey 索引。同步清理 ctrengine_test 的调用点。

测试

  • core/transport/relay_test.go: peerKey 路由匹配转发 / 未知丢弃。
  • core/engine_test.go: Bind 不再崩溃、无候选不盲目建连、注入候选后链路接通。
  • core/connect/scheduler_test.go: 策略层选择、DirectFactory 缺候选必败、 候选地址真实建连(本地 listener 验证)。
  • 验证说明: 沙箱 App Control 拦截 core 包测试二进制的执行;core/connectcore/transport 测试已成功运行通过;全量 go build ./...go vet ./core/... 通过。

2026-07-15 架构重构:多仓解耦 + 编译期注入 + 闭源 core + conn.Bind 接管

背景

原架构把闭源增强逻辑(core)以本地子目录方式内嵌于开源管理器主仓,存在两点硬伤:

  1. 开源条约风险:开源主仓直接包含闭源 core 源码,无法干净剥离。
  2. Endpoint 劫持原理缺陷:旧方案把 peer Endpoint 改写为回环端口,企图让 WG 入站 流量经本地端口旁路;但 WG 入站按真实源地址匹配 peer,改写后源地址与 peer 不一致, 入站接不通,9 层传输大部分是空壳,信令层缺失,零端到端验证。

经架构决策(用户确认):

  • 底层用开源 wireguard-go,增强层 meshray-core 闭源,二者物理边界清晰、 可分开授权;管理器开源;无 meshray-core 时系统仍以标准 WG 直连运行
  • 代码物理边界 = 多仓解耦(独立 module)。
  • 增强层接入方式 = 编译期注入build tag 触发空白导入 init() 注册)。

关键契约:开源契约层(叶子 module)

  • 新增 pkg/contractmodule git.zkcoi.com/zkcoi/meshray-contract,开源叶子模块), 作为主仓与 core 之间唯一的 module 边界契约,解决 meshray ↔ meshray-core 双向依赖导致的 module 级循环依赖:
    • 定义 CandidateLayer9 层常量 + String + DefaultLayerOrder)、ICEConfigPeerStatusEngineStatusEngineController 接口 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.modmodule git.zkcoi.com/zkcoi/meshray-corego 1.26.0), require 主仓全部依赖 + meshray-contract,末尾 replace git.zkcoi.com/zkcoi/meshray-contract => ../pkg/contract
  • 新增 core/bind.goEnhancedBind 实现 conn.Bind,包裹 conn.NewDefaultBind() Send 当前透传并预留 9 层策略插入点;NewEnhancedBind 调用 getOrCreateEngine
  • 新增 core/registry.goengines 单例 + getOrCreateEngineengineController 适配 contract.EngineControllerinit() 注册 RegisterEnhancedBindFactory + RegisterEngineProvider仅编译进 core 时执行)。
  • 新增 core/adapter.go:契约 ↔ core 类型转换(候选 / ICE / 层顺序 / 状态)。
  • core/engine.goimport 路径改 .../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/contractreplace .../meshray-core => ./corerequire 两新 module(均 v0.0.0)。
  • 新增 internal/ctr/core_enable.go//go:build meshray_core + _ "git.zkcoi.com/zkcoi/meshray-core" 空白导入,触发 core init() 注册。
  • internal/ctr/wg.goCreateDevicemeshMode 参数; startUserModeWGProcessWithRefscontract.GetBindFactory(meshMode == "enhanced") 替代硬编码 conn.NewDefaultBind()交由 core 经 conn.Bind 接管 WG 收发); 增强模式获取失败返回错误;UpdatePeerEndpoint 注释强调增强模式不应改写回环端口。
  • internal/ctr/ctr.go:去除 core 具名 importcoreInst *core.Coreengines map[string]contract.EngineControllerCreateNetwork enhanced 模式走 contract.NewEngineControllerAddPeer/RemovePeer 不再改写 Endpoint 为回环端口; SwitchModeengineCtl.NotifyPeerCandidates 注入候选; CoreConfigUpdate.TURNServers[]stringEnabledLayers[]contract.Layer NetworkStatus.CoreStatus*contract.EngineStatus

构建验证

  • core 独立:go build ./... && go vet ./... → 通过。
  • 管理器默认(无 core):go build ./... → 通过(标准 WG 直连)。
  • 管理器增强(编译期注入):go build -tags meshray_core ./... → 通过。

仍然待补(非本次范围)

  • EnhancedBind.Send 实际插入 9 层策略(当前透传)。
  • 信令层真正驱动 NotifyPeerCandidatesP3 传输层空壳补全。
  • docs/ 下仍描述 “relay 按 route_id 查表转发” 的旧文档校正(用户规则禁止新增说明文档, 已通过代码注释反映新路由逻辑)。

2026-07-15 增强 Bind 虚拟桥接 + meshray-core 独立二进制(类 frp

背景

用户希望像 frp 一样直接运行 core 在两台机器上验证收发,而非必须经由管理器。 核查发现旧 EnhancedBind 仅是纯透传Send 直接转交默认 UDP bind),而 9 层 relaylocalPorts 在增强模式下从未被注册,导致 9 层传输逻辑根本没接到 WG 收发路径上。 本次把 EnhancedBind 重写为真正桥接 WG ↔ 引擎 9 层传输,并新增独立可运行二进制。

关键发现(wireguard-go 匹配机制)

阅读 device/receive.go 确认:传输包由包内 receiver index 匹配 peerEndpoint 仅用于 握手更新对端出向地址与 MAC2 cookie 计算。因此回灌入站时只要携带对端真实 Endpoint, 即可正确接通,无需让 WG 监听真实 UDP 端口——这正是增强 Bind 完全接管收发的依据。

core/bind.go(重写)

  • EnhancedBind 不再依赖默认 bind 的真实 UDP 监听:Open 返回从 inboundCh 读取引擎 入站的接收函数;Sendep 反查 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,并新增 EnsureDialSend 惰性拨号用)。

core/cmd/meshray-core(新增独立二进制)

  • main.go + config.go:类 frp 风格,-c config.json 启动、-genkey 生成私钥。
  • 直接以 core.NewEnhancedBind 接管 wireguard-go 用户态设备收发;按配置创建 TUN、 注入对端候选触发 9 层拨号、Up 后分配隧道 IPLinux ip / Windows netsh)。
  • mode: enhanced 走 9 层接管;mode: native 退化为裸 WG 直连,便于对照测试。
  • 新增 meshray-core.example.json 示例配置(占位符,用户替换公私钥/endpoint 即可两机对测)。
  • go.modgo mod tidy 引入 wireguard/wgctrl 与 Windows wintun 依赖。

构建验证

  • 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 ./... → 通过。

两机对测步骤(摘要,详见二进制内注释与示例配置)

  1. 两台机器各自 meshray-core -genkey 得私钥,用 private_key 对应公钥填对方 public_key
  2. A 的 interface=10.0.0.1/24peers[0].endpoint=B公网IP:51820allowed_ips=10.0.0.2/32B 对称。
  3. 两台均 meshray-core -c meshray-core.json,随后 ping 10.0.0.x 验证隧道收发。
  4. Windows 需已安装 Wintun 驱动(随 WireGuard 客户端提供)。

2026-07-15 修复:初始直连缺少入站读取,公网/独立 IP 机器直连亦不通

背景(用户纠正)

用户指出 meshray-core 的核心机制必须同时满足「NAT 后的机器」与「有独立 IP 的机器」, 二者都要能直连(P2P)→ 失败再走中继/打洞兜底。这意味着即使两台公网机器直连, 当前实现也应能接通。实测与代码核查发现:初始直连路径本身就没接通,与是否 NAT 无关。

根因

  • StrategyScheduler.Dialcore/connect/strategy.go:284)成功后仅返回连接,从不调用 OnConnectionUpdate
  • OnConnectionUpdate 回调(core/engine.go:45)才是唯一会调用 relay.StartReadFromRemoteConn 启动入站读取的地方;而它只在 reconnectToLayer (降级重连)与 probeHigherLayers(恢复探测)内触发。
  • Engine.initiateConnection 拿到 Dial 返回的直连后只做了 connMgr.Add未启动入站读取协程

断点后果

两侧都只发出握手、收不到对方回包 → 握手无法完成 → 隧道起不来。 该缺陷在「两台公网/独立 IP 机器直连」场景下同样存在,并非 NAT 专属问题。

修复

  • core/engine.goinitiateConnectionconnMgr.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 / 同局域网直连场景预期可正常握手建隧。