Files
Meshray-Manager/CHANGELOG.md
T
zkcoi ccae5e2d42 docs: drop frp self-comparison in core references
- go.mod/main.go/config.go/sample/CHANGELOG: 移除 类 frp/去中心化 frp 本体/像 frp 类比
- 保留 ExternalService 中真实的 frp_server 外部穿透服务(客观功能,非自比)
2026-07-15 16:40:07 +08:00

449 lines
29 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 变更日志 (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 ./...` → 0manager 仓库编译通过,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 只接收所连对端的包:
- NASNAT 后)拨 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 建共享 socketlisten_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 无任何监听 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.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` 都会直接崩。
- 修复: 三处全部对 `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 独立二进制
### 背景
用户希望直接运行 core 在两台机器上验证收发,而非必须经由管理器。
核查发现旧 `EnhancedBind` 仅是**纯透传**Send 直接转交默认 UDP bind),而 9 层
`relay``localPorts` 在增强模式下从未被注册,导致 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` 读取引擎
入站的接收函数;`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` 后分配隧道 IPLinux `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 / 同局域网直连场景预期可正常握手建隧。