# 变更日志 (CHANGELOG) ## 2026-07-15 仓库拆分:core 独立为 `meshray/core`,wg-go 集成迁移至 `internal/ctr/corebind` ### 背景(架构决策) core(转发引擎)本质是**去中心化 frp**——9 层传输只转发字节 A↔B,不关心载荷是应用流量 (形态 A:独立 frp 隧道)还是 WG 密文(形态 B:增强传输)。此前 core 直接 `import wg-go` 并内置 `EnhancedBind`,导致「集成干扰核心」:核心层被 wg-go / contract 绑架,难以独立演进, 也无法作为纯粹的去中心化 frp 基地。 故按用户决策: - 项目命名统一为 **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`:经共享 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 上监听真实 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 配置文件(类 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.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` 都会直接崩。 - 修复: 三处全部对 `nil` conn 做安全处理(仅登记 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`)以本地子目录方式内嵌于开源管理器主仓,存在两点硬伤: 1. **开源条约风险**:开源主仓直接包含闭源 `core` 源码,无法干净剥离。 2. **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"` 空白导入,触发 core `init()` 注册。 - `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`;`CreateNetwork` enhanced 模式走 `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 独立二进制(类 frp) ### 背景 用户希望像 frp 一样直接运行 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`:类 frp 风格,`-c config.json` 启动、`-genkey` 生成私钥。 - 直接以 `core.NewEnhancedBind` 接管 wireguard-go 用户态设备收发;按配置创建 TUN、 注入对端候选触发 9 层拨号、`Up` 后分配隧道 IP(Linux `ip` / Windows `netsh`)。 - `mode: enhanced` 走 9 层接管;`mode: native` 退化为裸 WG 直连,便于对照测试。 - 新增 `meshray-core.example.json` 示例配置(占位符,用户替换公私钥/endpoint 即可两机对测)。 - `go.mod`:`go 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/24`、`peers[0].endpoint=B公网IP:51820`、`allowed_ips=10.0.0.2/32`;B 对称。 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.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 / 同局域网直连场景预期可正常握手建隧。