rebuild: split core into separate repo; move wg-go integration to internal/ctr/corebind

- go.mod: require git.zkcoi.com/zkcoi/meshray/core, replace => ../Meshray
- internal/ctr/corebind: EnhancedBind + registry + adapter (wg-go integration isolated)
- cmd/mr-wg migrated from old core/cmd/meshray-core
- core/ removed; CHANGELOG updated; fix checkdb vet warning
This commit is contained in:
2026-07-15 16:08:13 +08:00
parent 15dab96872
commit 59e3059246
39 changed files with 2350 additions and 4361 deletions
+375 -127
View File
@@ -1,163 +1,411 @@
# 更新日志
# 变更日志 (CHANGELOG)
## [2.0.2] - 2026-03-20
## 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 基地。
#### DDNS 完整功能(P0 优先级)
- ✅ DNS Provider 抽象层,支持多云服务商
- Cloudflare Provider(真实 API 集成)
- 腾讯云 DNSPod Provider(真实 API 集成)
- 阿里云 Provider(占位实现)
- ✅ IP 自动检测服务(公网/本地 IPv4/IPv6
- ✅ 后台任务调度器(每 5 分钟自动检测 IP 变化)
- ✅ Dashboard DDNS 监控卡片
- ✅ 前端 IP 自动检测按钮
- ✅ 防抖动设计 + 事务处理
故按用户决策:
- 项目命名统一为 **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 / 管控面。
#### P1 管理功能
- ✅ 修改密码功能(bcrypt 加密)
- ✅ 重启核心服务功能
### 改动(核心仓库 `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**。
#### P2 系统功能
### 改动(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 依赖,保持不变。
**备份恢复功能**:
- ✅ 创建备份 API
- ✅ 列出备份 API
- ✅ 恢复备份 API
- ✅ 删除备份 API
- ✅ 下载备份 API
### 验证
- `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。
**WebSocket 实时通知推送系统**:
- ✅ Notification 数据模型(SQLite 持久化)
- ✅ 6 个完整的 RESTful API
- ✅ 单播/广播双模式
- ✅ 前端通知中心组件(铃铛图标 + 红色角标)
- ✅ 下拉通知列表(滚动条 + 空状态)
- ✅ 一键全部已读
- ✅ 删除单条通知
- ✅ 自动刷新未读数(每 30 秒)
- ✅ 布局集成到顶部栏
## 2026-07-15 重构:Direct-UDP 改为共享监听 socket + WG 学址,原生支持 NAT 穿透(核心)
#### P3 增强功能
- ✅ 系统更新检查(GitHub Releases API + SemVer 比较)
- ✅ 版本对比对话框
- ✅ 更新日志展示
- ✅ 下载链接跳转
### 背景(用户诉求)
用户指出: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:随机端口` 源地址不匹配 → 丢弃。
#### 后端架构
- ✅ 完善 Service 层数据库访问封装(GetDB 方法)
- ✅ 统一 Handler 层构造函数设计
- ✅ 优化中间件注册流程
- ✅ 改进错误处理和日志记录
即公网侧「收得到、回不去」。这不是 NAT 的固有限制,而是 connected socket 语义所致。
#### 前端架构
- ✅ 创建独立的 notifications API 模块
- ✅ 开发可复用的 NotificationCenter 组件
- ✅ 集成到 MainLayout 布局
- ✅ 实现响应式通知列表 UI
### 修复(回归标准 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 端口冲突。
#### 编译与部署
- ✅ 创建 Windows 一键启动脚本(start.bat
- ✅ 创建 Linux/Mac启动脚本(start.sh
- ✅ 完善 .gitignore 配置
- ✅ 优化前端编译配置
### 效果
- 双公网对称直连:`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)。
#### 新增文档
- ✅ README.md - 项目主文档
- ✅ QUICKSTART.md - 快速入门指南
- ✅ README_开发完成总览.md - 开发完成总览
- ✅ 功能验证与测试报告.md - 测试验证文档
- ✅ 交付清单.md - 最终交付清单
## 2026-07-15 修复:直连传输 socket 未绑定本地端口,两台机器互传失败(核心 P0)
#### 实现报告
- ✅ 完整功能开发总结报告.md
- ✅ WebSocket 实时通知推送功能实现报告.md
- ✅ P3_系统更新检查功能实现报告.md
- ✅ 完整功能开发 - 最终完成报告.md
### 背景(用户排查「两台机器传不了数据」)
此前已修复「初始直连缺入站读取」,但端到端仍不通。进一步排查数据路径(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`
- **新增文件**: 22 个
- **代码行数**: ~6,100 行
- **API 接口**: 20 个(100% 实现)
- **文档**: 8 份
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 与受管模式(均经此入口)都能拿到本端端口。
- ✅ bcrypt 密码加密(DefaultCost 强度)
- ✅ JWT 身份验证
- ✅ CORS 跨域控制
- ✅ SQL 参数化查询(防注入)
- ✅ 权限隔离
- ✅ 操作日志记录(AuditLog)
### 验证
- 临时单测模拟两机(回环两端口):A:15180↔B:15181 对称直连,双向 Write/Read 均成功
(修复前临时端口下对端收不到)。已删除该临时测试。
- `go build ./... && go vet ./...` → 通过。
### ⚠️ 已知问题
###
NAT 后的机器仍依赖 TURN/WebRTC 中继/打洞层(需配置 `ice` 服务器),本修复解决的是
「直连(独立 IP / 公网 / 同局域网)对称互通」这一基础能力。
#### 待完善功能
## 2026-07-15 独立 meshray-core 改用 TOML 配置文件(类 frp)
1. **阿里云 DNS Provider**
- 原因:网络问题导致无法下载 libdns/aliyun
- 计划:网络恢复后安装并完成实现
### 需求
独立运行(类 frp 风格)希望像 frp 一样使用 TOML 配置文件,而非 JSON。
2. **真实备份逻辑**
- 原因:优先级较低,先完成框架
- 计划:实现数据库导出、配置文件备份等逻辑
### 改动
- 新增依赖 `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`
3. **WebSocket 中间件**
- 原因:已有轮询机制(每 30 秒),非必需
- 计划:可选优化,实现实时推送
### 注意(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 ./...` → 通过。
## [2.0.1] - 之前的版本
## 2026-07-15 新增:传输层级支持手动指定(auto / manual
### 基础功能
- ✅ WireGuard 组网核心功能
- ✅ 用户管理系统
- ✅ 设备管理
- ✅ 策略管理
- ✅ MeshSeed 凭证生成
- ✅ 待审核加入机制
- ✅ Dashboard 基础监控
- ✅ 实时监控面板
- ✅ 日志查看
- ✅ 系统设置
### 需求
除默认的 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`)。
- 由此,手动单层级场景下「无下一层」自然退化为不降级,语义正确。
### v2.1.0(计划中
- [ ] 阿里云 DNS Provider 实现
- [ ] 真实的备份/恢复逻辑
- [ ] WebSocket 实时推送中间件
- [ ] 告警规则管理
- [ ] 资源监控图表优化
### ICE 配置补齐(手动 TURN/WebRTC 必需
- 独立二进制此前从未调用 `Engine.SetICEConfig`,导致 TURN/WebRTC 工厂拿不到 STUN/TURN 地址。
- `main.go` 现已把配置里的 `ice.stun` / `ice.turn` 下达到引擎,手动 TURN-TCP/TURN-TLS/
WebRTC 层级方可真正建连。
### v2.2.0(规划中
- [ ] 多语言国际化
- [ ] 主题切换功能
- [ ] 移动端适配优化
- [ ] 性能监控和告警
- [ ] CI/CD 集成
### 管控面(ctr
- `internal/ctr` 已通过 `update.EnabledLayers``SetLayerOrder` 支持自定义顺序,
等价「手动多层级」,本次无需改动;后续若需「auto/manual」语义可在此扩展。
---
### 构建验证
- `core` 独立:`go build ./... && go vet ./...` → 通过。
## 📝 说明
## 2026-07-14 核心路基缺陷修复 (P0 + P1)
- 版本号格式:主版本号。次版本号。修订号
- 优先级说明:
- P0: 核心功能
- P1: 重要功能
- P2: 次要功能
- P3: 增强功能
### 背景
此前验证集中在 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 方法)。
**最后更新**: 2026-03-20
**维护人员**: MeshRay Team
### 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** 匹配 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`:类 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.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 / 同局域网直连场景预期可正常握手建隧。