# RemLink v1.0(Userspace Netstack 版)实施规格 > 权威设计来源:仓库根目录《RemLink_v1.0_技术设计与AI开发规格书_Netstack版.docx》(版本日期 2026-08-19)。本文件将其转化为 spec-driven 开发格式;若实现中发现本文件与 docx 原文有冲突,以 docx 为准并回改本文件。禁止为了让代码跑通而绕开核心约束。 ## 背景与目标 工业自动化远程调试中,工程师需要在异地直接访问现场设备 IP(如 192.168.13.10 的 PLC、HMI HTTP、RDP),而端口代理需逐个配置、协议适配无法泛化。RemLink 通过中心式 IPv4 L3 虚拟组网 + Engineer 动态下发远程 CIDR + Site 端 gVisor netstack userspace 子网网关,让 Windows 普通 IP 应用无需任何协议适配即可访问现场地址,且允许多个现场使用完全相同的网段。 ## 变更内容 - 全新构建 RemLink v1.0 三端产品(绿地项目,全部为新增能力): - **Server**:Ubuntu / Docker / Linux Kernel WireGuard + Go(wgctrl 编排)+ SQLite + Vue 3 Web UI - **Engineer**:Windows Wails GUI + wireguard-go(嵌入进程)+ 单一 RemLink Wintun + PacketMux - **Site**:Windows Console + wireguard-go(嵌入进程)+ 单一 RemLink Wintun + gVisor netstack SubnetGateway - 架构基线:**单 Wintun + wireguard-go + PacketMux + gVisor netstack**;不使用 WinNAT、Windows IP Forwarding、Transit CIDR、WireGuardNT - 按 Phase 0–10 分阶段实施,Gate A–D 为关键验收关卡(见 tasks.md) - 仅 IPv4;Remote Subnet v1 支持 TCP / 单播 UDP / ICMP Echo,不实现二层 ## 影响范围 - 受影响规格:无(首个 change) - 受影响代码:全新 Go monorepo `remlink/`(目录结构见 R15) - 固定版本外部依赖:Wintun DLL、wireguard-go(锁定 commit)、gVisor netstack(锁定 commit)、wgctrl、SQLite driver、Wails、Vue 3 + Vite、golang.org/x/net/icmp ## 新增需求 ### 需求: R1 总体架构与不可变约束 系统 必须 采用中心式 Hub-and-Spoke 架构:所有 Engineer/Site 节点只与 Server 建立 WireGuard 连接,数据始终经 Server Hub 中转;节点只配置 Server 公网 IP/端口,不配置彼此公网地址。 系统 必须 遵守以下不可变决策: | 决策项 | v1.0 最终选择 | |---|---| | 虚拟组网 | 中心式 Hub-and-Spoke,无 P2P | | Windows 虚拟网卡 | 每个 Engineer/Site 只有 1 块名为 RemLink 的 Wintun | | Windows WireGuard | wireguard-go 嵌入进程,不用 WireGuardNT | | Server WireGuard | Linux Kernel WireGuard | | 数据层 | IPv4 L3 捕获;Remote Subnet v1 = TCP/UDP/ICMP Echo | | 远程子网 | Engineer 建立 Session 时动态下发;Site 配置不保存现场 CIDR | | Site Subnet Gateway | gVisor netstack userspace relay,不依赖 WinNAT/IP Forwarding | | Engineer 并发 | 一个 Engineer 同时最多连接一个 Site | | Site 并发 | 底层允许多个 Engineer 同时连接同一个 Site | | 现场网段重复 | 允许不同 Site 使用完全相同的 192.168.x.0/24 | | 二层 | 明确不做 ARP/以太网帧/DCP/LLDP/TAP/Bridge | | 服务端网络地址 | Server Web UI 配置;Node IP 由 Server IPAM 自动分配 | 明确不在 v1 范围:二层协议、P2P/STUN/TURN/UDP 打洞、IPv6、IP 广播/组播、SCTP/GRE/ESP、用户注册/多租户/RBAC/OAuth、HA/Cluster/K8s、自研 TCP/IP 栈/VPN/网卡驱动/NAT、一个 Engineer 同时连多个 Site。 #### 场景: 两个 Site 使用相同现场网段 - **当** Site-A 与 Site-B 都使用 192.168.13.0/24 作为现场网段,且分别被 Engineer-A、Engineer-B 通过 Session 访问 - **则** 两条 Session 同时工作互不串流;Server WireGuard 不出现路由冲突(Server 只按 Overlay /32 选路,现场 CIDR 位于 Session UDP Payload 内) #### 场景: 尝试 P2P 或二层能力 - **当** 任何实现引入 P2P 直连、打洞、ARP/TAP/Bridge 代码 - **则** 违反不可变约束,不被接受 ### 需求: R2 Overlay 虚拟组网与 Server IPAM 系统 必须 提供 Overlay 虚拟组网与集中式 IPAM: - 默认 Overlay CIDR 为 10.88.0.0/16(仅初始值,Server Web UI 必须允许修改);Server 地址默认取该网段第一个可用地址(如 10.88.0.1);Engineer 与 Site 使用统一 Node Pool。 - 每个 Node 的地址在 Server WireGuard Peer 上以 /32 AllowedIPs 登记;客户端到 Server 的唯一 Peer 使用整个 Overlay CIDR 作为 AllowedIPs。 - Server 是 Overlay 地址唯一权威来源,客户端不得自行填写虚拟 IP。 - Node 首次注册时由 IPAM 分配地址,后续重启保持同一地址;地址不得为网络地址、广播地址、Server 地址或已占用地址。 - Web UI 可手动修改 Node IP;修改后触发该 Node 重新 Bootstrap/重建 WireGuard。 - 删除 Node 后撤销 WireGuard Peer、Node Token,并释放地址。 修改 Overlay CIDR 时系统 必须 按 7 步流程执行:Web UI 提交并校验新 CIDR → 所有 Active Session 进入 STOPPING/CLOSED → IPAM 为现有 Node 重新分配/迁移地址 → 重建 wg0 地址和 Peer AllowedIPs → 在线 Node 经旧 Control 通道收 REBOOTSTRAP_REQUIRED(通道断则自动走公网 Bootstrap API)→ Windows Node 更新 Adapter IP/路由并重启 wireguard-go → Site 重建 NetworkConfig 与 Session Gateway(不修改 Windows NAT/Forwarding)。 Node 必须 在应用新 Overlay CIDR 前检查其与本机现有直连网络是否重叠;发现冲突时不得强行启用,向 Server 报告 OVERLAY_LOCAL_CONFLICT。 #### 场景: Node 地址稳定 - **当** 已注册 Node 重启后再次 Bootstrap - **则** 获得与之前相同的 Overlay IP #### 场景: Node 本地网络与 Overlay 冲突 - **当** Node 本机直连网络与 Overlay CIDR 重叠 - **则** 拒绝启用新配置并上报 OVERLAY_LOCAL_CONFLICT ### 需求: R3 Windows 单虚拟网卡数据面(MuxTun / PacketMux) Engineer 与 Site 必须 都只创建一块名称固定为 RemLink 的 Wintun L3 Adapter(默认 MTU 1280),同时承载 Overlay 节点通信和 Engineer 的 Remote Subnet 路由。不存在第二块 Subnet Adapter,也不存在 Transit CIDR。 系统 必须 将真实 Wintun 包装为 MuxTun(实现 wireguard-go 的 tun.Device 接口,Read/Write 代理给真实 Wintun)后交给 wireguard-go Device,使 RemLink 能在"Windows 网络栈 ↔ WireGuard 引擎"边界做 L3 分流。MuxTun 必须尽量薄。 Engineer 出方向 PacketMux 必须 仅根据目标 IPv4 地址分类: - 目标属于 Overlay CIDR:原包不修改,直接交给 wireguard-go。 - 目标属于当前 Active Session 的任一 Remote CIDR:不交给 wireguard-go,将原始 IPv4 bytes 放入 SubnetSender 的有界队列。 - 其他目标:丢弃并做限速日志。 SubnetSender 必须 使用普通 Go UDP Socket(绑定 Engineer Overlay IP,目标为 Site OverlayIP:6200)发送 `RemLinkHeader + 原始 IPv4 Packet`,由 Windows TCP/IP 生成外层 IP/UDP Header;该外层 UDP Packet 因目标属于 Overlay CIDR 再次进入同一 Wintun,被 PacketMux 判定为 Overlay 流量交给 wireguard-go(防循环:外层目标为 Site Overlay IP,不属于 Remote CIDR,只进入一次 WireGuard)。 MuxTun 并发要求: - PacketMux Read 不得在发送 Remote Packet 时直接阻塞等待 UDP 网络 I/O;使用有界 channel + 独立 Sender goroutine。 - 当一次 Wintun batch 全部为 Remote Packet 时,Read 应继续读取,直到能向 wireguard-go 返回至少一个 Overlay Packet 或收到关闭/错误事件,避免返回 0,nil 造成 busy loop。 - Wintun Write 由 wireguard-go 入方向和 Site Session 注入共用时,通过统一 Writer/锁序列化。 - packet buffer 尽量复用池;先保证正确性,再做 batch 优化。 #### 场景: Remote 包外层再入 Wintun - **当** Engineer 应用访问 192.168.13.10,PacketMux 拦截后由 SubnetSender 以 UDP 发往 Site Overlay IP:6200 - **则** 外层 UDP 再次进入同一 Wintun 后被识别为 Overlay 流量交给 wireguard-go,无死锁、无无限循环 ### 需求: R4 Remote Subnet Session Session 业务规则: - Remote CIDR 由 Engineer 用户在连接 Site 时输入并下发;Site 本地配置不预存现场网段。 - 一个 Engineer 同时最多一个处于 CREATING/PREPARING_SITE/ACTIVE 的 Site Session。 - 一个 Session 可携带多个 IPv4 CIDR,数据结构从第一天就使用数组。 - Site 底层允许同时服务多个 Engineer;通过 SessionID + Engineer Overlay IP 区分。 - 两个不同 Site 的 Remote CIDR 可以完全相同。 - 目标 CIDR 不得与 RemLink Overlay CIDR 重叠,不得与 Engineer 本地非 RemLink 网络发生任何前缀重叠。 - v1 禁止 0.0.0.0/0 Exit Node 模式。 Session 状态机 必须 为:CREATING → PREPARING_SITE → READY → ACTIVE → STOPPING → CLOSED,以及 FAILED(任一关键检查失败)。 Session 建立顺序 必须 为:Engineer GUI 选择 Site 并输入 CIDR → Engineer 本地冲突检查(失败不请求 Server)→ CREATE_SESSION → Server 校验(已有 Session/Site 在线/CIDR 合法性)→ Server 生成随机 64-bit SessionID、状态 PREPARING_SITE、向 Site 下发 PREPARE_SESSION → Site 对每个 CIDR 做 Windows Route Lookup 并检查 SubnetGateway 能力与 flow capacity → Site 返回 PREPARE_RESULT(含 subnet_gateway=netstack)→ Server 向 Engineer 返回 SESSION_CONFIG(SessionID、Site Overlay IP、Remote CIDRs、MTU、Session UDP Port)→ Engineer 添加 Windows Remote Routes 并启动 SessionTransport → Engineer 发送 ROUTES_READY → Server 置 ACTIVE 并通知 Site。 Session 数据包格式(两个方向统一)SHALL 为: | 字段 | 长度 | 说明 | |---|---|---| | Magic | 4 bytes | ASCII: RMLK | | Version | 1 byte | v1 = 1 | | Type | 1 byte | v1 固定 0x01 = IPv4 | | Flags | 2 bytes | v1 置 0 | | SessionID | 8 bytes | Server 生成的 uint64 | | PayloadLen | 2 bytes | 原始 IPv4 Packet 长度 | | Reserved | 2 bytes | 置 0 | | Payload | N bytes | 完整原始 IPv4 Packet | 不加入 TCP 风格序号/ACK/重传/拥塞控制;不做 TCP-over-TCP。WireGuard 已提供外层完整性和加密,不再增加自研加密或可靠 UDP。 Session 双向收包校验 必须 满足: - Engineer 与 Site 都只在自己的 OverlayIP:6200 上监听 Session UDP,不监听公网地址或 0.0.0.0。 - 根据 SessionID 查找 Active Session。 - UDP 外层源 Overlay IP 必须等于该 Session 对端绑定的 Overlay IP(双向都校验)。 - Engineer→Site 方向内层 IPv4 Source 默认必须等于 Engineer Overlay IP(应用显式绑定其他物理源地址的流量不保证可用)。 - Engineer→Site:内层 Destination 必须属于 Session Remote CIDR;Site→Engineer:内层 Source 必须属于 Session Remote CIDR,Destination 必须等于 Engineer Overlay IP。 - IPv4 Total Length、版本和实际 Payload 长度必须一致。 - 校验失败直接丢弃并做限速安全日志。 #### 场景: Engineer 已有 Active Session 再连第二个 Site - **当** Engineer 在 ACTIVE Session 期间请求连接另一个 Site - **则** 拒绝并返回 ENGINEER_SESSION_EXISTS #### 场景: 目标 CIDR 与本地网络冲突 - **当** Engineer 本机存在 192.168.0.0/16,用户输入 192.168.13.0/24 - **则** 本地冲突检查失败(前缀重叠),不向 Server 发起请求 ### 需求: R5 Site Userspace Subnet Gateway(gVisor netstack) Site 收到 Session UDP 后 必须 取出原始 IPv4 Packet,直接交给 gVisor netstack SubnetGateway(通过 channel.Endpoint.InjectInbound),不注入 Windows Wintun 做内核转发。gVisor netstack 负责 TCP/UDP 连接状态和报文重建;RemLink 不实现 TCP 状态机。现场侧实际出站使用 Windows 普通 host socket(net.Dial / UDPConn),目标设备看到的源地址是 Site 的现场可达地址。 - v1 不创建 Transit CIDR,不创建 RemLinkNAT,不要求 PLC/网关认识 Overlay 网段。 - 能力边界:v1 明确支持 TCP、单播 UDP 和 ICMP Echo(Ping);IP 广播/组播、SCTP、GRE、ESP 及其他非 TCP/UDP IP 协议不在保证范围。 - SubnetGateway 接口: ```go type SubnetGateway interface { Prepare(ctx context.Context, cfg SessionConfig) error InjectIPv4(ctx context.Context, sessionID uint64, packet []byte) error CloseSession(ctx context.Context, sessionID uint64) error } // v1 default / only backend: GVisorNetstackBackend ``` - 优先直接依赖 upstream gVisor netstack 并固定 commit;可参考 Tailscale wgengine/netstack(BSD-3-Clause)实现模式,但不得引入整个 Tailscale 控制面。未来 Kernel/NAT Backend 必须保持接口不变。 - TCP Relay:gVisor netstack 接收 Engineer 的 SYN,创建 Engineer-facing TCP endpoint;RemLink 使用 host net.Dial("tcp", target) 建立 Site→PLC 连接,以 io.CopyBuffer 双向搬运字节;不做应用协议识别。 - UDP Relay:按 SessionID + Engineer 源/目标五元组维护轻量 flow mapping;每个 flow 使用 host UDPConn 与现场目标通信,收到回复后写回 gVisor UDP endpoint;flow 使用可配置 idle timeout 做 GC。 - 返回包必须使用 Session 封装(对称封装,v1 强制设计):gVisor netstack 生成面向 Engineer 的 IPv4 响应包(SRC=现场 IP → DST=Engineer Overlay IP)后,Site 必须将其再封装成 Session UDP(Outer SRC=Site Overlay IP,Outer DST=Engineer Overlay IP,Payload=SessionHeader+RawIPv4);不得把该 Raw Packet 直接交给 WireGuard(Server 对 Site Peer 的 AllowedIPs 只有 /32,源地址会被 cryptokey routing 拒绝)。Engineer SessionListener 校验后将 Raw IPv4 写入本机 Wintun 入站方向;Windows 最终看到 SRC=现场 IP、DST=Engineer Overlay IP。 - ICMP Echo Relay:v1 只支持 Echo Request/Reply;收到 Echo Request 后由 PingRelay 使用成熟 ICMP 库或受控系统 ping 从 Site 探测目标;目标响应后构造与原请求 ID/Sequence 对应的 Echo Reply Raw IPv4 经 SessionTransport 返回 Engineer;其他 ICMP 类型不支持。 - Site PREPARE 时 必须 对每个目标 CIDR 做 Windows Route Lookup:DIRECT(直连)允许;ROUTED(明确静态/动态路由)允许;DEFAULT_ONLY(仅默认路由)默认拒绝;NO_ROUTE 拒绝(SITE_NO_ROUTE);OVERLAY_CONFLICT 拒绝。 - 建议初始 flow 上限(均可配置):TCP 2048 flows、UDP 4096 flows、UDP idle timeout 60s。 #### 场景: 现场设备返回路径 - **当** PLC 192.168.13.10 响应 Site host socket 的连接 - **则** Site 经 gVisor netstack 生成面向 Engineer 的 Raw IPv4,再以 SessionHeader 封装经 UDP 发往 Engineer Overlay IP;PLC 无需任何 Overlay 返回路由 ### 需求: R6 控制面与节点协议 系统 必须 分离 Bootstrap 与正常 Control: - Bootstrap(公网 HTTP):`http://SERVER_IP:8080/api/v1/bootstrap/...` - WireGuard:`SERVER_IP:51820/udp` - Control(仅 Overlay):`ws://10.88.0.1:7001/control` 节点第一次启动无 WireGuard 时使用公网 Bootstrap API 获取 Overlay IP、Server WireGuard PublicKey 和 Endpoint;建立 WireGuard 后所有控制消息走 Overlay 内 Control WebSocket。 Node 身份 必须 包含:NodeID(UUID,首次运行生成并持久化)、NodeType(engineer/site)、NodeName、WireGuard PrivateKey(本地生成,仅本机保存)、WireGuard PublicKey(注册时提交 Server)、NodeToken(Server 注册成功后生成,用于后续身份验证;不是用户系统)。 Windows 本地敏感字段(WG PrivateKey、NodeToken)SHALL 优先使用 Windows DPAPI 保护后落盘,不以明文 YAML 保存私钥;为简化自用部署,首次注册 Join Token 可以明文保存在 Engineer/Site YAML 中。Server PrivateKey 保存到数据目录并设置严格文件权限。 Server 必须 维护可轮换 Join Token:新 Node 注册必须提供 Join Token;注册成功后改用 NodeToken。v1 不实现 HTTPS,公网 Bootstrap/管理 HTTP 的机密性不由 TLS 提供,作为部署边界在文档中说明。 Heartbeat 参数(默认值):interval 5s;ONLINE=最近心跳 ≤15s;UNSTABLE=15–30s;OFFLINE=>30s;Reconnect backoff=1s,2s,5s,10s,30s 上限。 ### 需求: R7 Server 设计 Server 必须 承担:Bootstrap API 和 Web UI、IPAM/Node Registry/Node Token、Linux Kernel WireGuard interface 和 Peer 编排(wgctrl)、Control WebSocket Hub、Session Manager、节点/Session 统计聚合、SQLite 持久化和日志查询;不逐包处理 WireGuard Overlay 数据。 Server wg0 必须 配置 Address 10.88.0.1/16(随 Overlay CIDR)、ListenPort 51820;每个 Node Peer 的 AllowedIPs 为其 Overlay /32。Linux 必须启用 IPv4 forwarding 并允许 wg0→wg0 转发;Server 不对 Overlay 做 SNAT。v1 不同时实现 kernel WireGuard 与 wireguard-go 两套 Server Backend。 Docker Compose 基线 必须 只授予 NET_ADMIN(不用 privileged: true),映射 /dev/net/tun、51820/udp、8080/tcp,挂载 ./data:/app/data,sysctls net.ipv4.ip_forward=1。镜像启动 Preflight 必须检查内核 WireGuard 能否创建接口;失败时明确报错并退出,不静默切换。 Web UI 必须 提供五个页面: | 页面 | 核心内容 | |---|---| | Dashboard | Uptime、Overlay CIDR、在线 Engineer/Site、Active Session、流量 | | Nodes | 名称、类型、Overlay IP、WG PublicKey 摘要、Handshake、App 状态、版本、LastSeen | | Sessions | Engineer、Site、Remote CIDRs、状态、上下行流量、持续时间、强制断开 | | Network | Overlay CIDR、Server IP、WG Port、Join Token 轮换 | | Logs | 按时间/级别/模块/Node/Session 过滤事件 | ### 需求: R8 Engineer 设计 Engineer 进程(RemLinkEngineer.exe)SHALL 包含:Wails GUI、Bootstrap/Control Client、Node Identity Store、Wintun Adapter Manager、MuxTun/PacketMux、wireguard-go Device、Session Manager、SessionTransport(UDP Sender + Listener)、RouteManager、Stats、Logging。 GUI 必须 提供:Server 公网地址/Overlay 状态/本机虚拟 IP/Control 状态/版本显示;全部 Site 列表(Name、Overlay IP、Online、RemoteSubnetCapability、LastSeen);选择 Site 后输入一个或多个 CIDR;连接前执行本地 CIDR 冲突检查;连接后显示 SessionID、目标 Site、CIDR、上传/下载、包数、时延和日志;Active Session 时禁止再选择第二个 Site。 Session READY 后 Engineer 必须 为每个 Remote CIDR 增加指向 RemLink Adapter 的 Windows 路由,由 RouteManager 统一创建并带 RemLink ownership metadata/本地状态记录以便异常恢复。不得通过降低 Metric 强抢冲突路由;发现 Remote CIDR 与本机现有非 RemLink 直连/静态/VPN 前缀重叠时直接拒绝建立 Session。 应用显式绑定网卡的情况:普通 Socket 由 Windows 根据 Remote Route 选择 RemLink Adapter;显式绑定物理网卡或固定源 IP 的工业软件不完全透明,应在其网络接口选择中选择 RemLink Adapter;v1 不增加源地址改写。 ### 需求: R9 Site 设计 Site 进程(RemLinkSite.exe,Console)SHALL 包含:Bootstrap/Control Client、Node Identity Store、Wintun Adapter Manager、MuxTun/PacketMux、wireguard-go Device、UDP Session Listener :6200(Overlay only)、gVisor Netstack SubnetGateway、TCP/UDP/Ping Relay、Route Inspector、Flow Manager/Stats、Logging。 Site 启动能力检测 必须 按 7 步执行:加载 Node Identity 并获取 Server NetworkConfig → 检查 Overlay CIDR 与本地直连网络冲突 → 创建/复用 RemLink Wintun 并配置 Overlay IP/Prefix/MTU → 启动 wireguard-go 并等待 Server Overlay 可达 → 初始化 gVisor netstack SubnetGateway(TCP/UDP forwarder 与 flow limits;无需检查或修改 WinNAT)→ 启动仅绑定 OverlayIP:6200 的 Session UDP Listener(同一端口承载双向)→ 连接 Control WebSocket 并报告能力、OS、版本、netstack 状态与 flow capacity。 Console 输出 必须 展示 Server、Node、Overlay IP、WireGuard/Control/Remote Subnet/SubnetGateway 状态及 SESSION/ROUTE 事件。 ### 需求: R10 数据库与数据模型 Server 必须 使用单 SQLite 数据库文件(如 /app/data/remlink.db),database/sql + 稳定 SQLite Driver,迁移采用成熟 migration 工具或嵌入 SQL migration 文件,不在代码中散落 CREATE TABLE。 核心表 必须 包含: | 表 | 关键字段 | |---|---| | settings | key, value, updated_at | | nodes | node_id, type, name, overlay_ip, wg_public_key, node_token_hash, status, version, os_version, last_seen | | sessions | session_id, engineer_node_id, site_node_id, status, created_at, active_at, closed_at, error_code | | session_cidrs | session_id, cidr | | session_stats | session_id, tx_bytes, rx_bytes, tx_packets, rx_packets, updated_at | | event_logs | time, level, module, node_id, session_id, message, fields_json | Windows Node 不使用 Server SQLite;Engineer 与 Site 必须作为完全独立的便携式包发布,持久状态放在各自 EXE 所在目录,且不得共用运行文件。敏感字段用 DPAPI,至少保存 NodeID、NodeToken、WireGuard PrivateKey、Server URL、NodeName、最后一次 NetworkConfig 版本,以及 RemLink 创建过的路由状态用于 Reconcile。 ### 需求: R11 API 与消息定义 Public Bootstrap API 必须 提供:GET /api/v1/server/info(Server ID、版本、WG Endpoint、Bootstrap 信息)、POST /api/v1/bootstrap/register(首次 Node 注册)、POST /api/v1/bootstrap/config(已注册 Node 用 NodeID+NodeToken 拉取最新 NetworkConfig)。 Admin Web API 必须 提供:GET /api/v1/admin/nodes;PATCH /api/v1/admin/nodes/{id}(改名/改 Overlay IP);DELETE /api/v1/admin/nodes/{id}(撤销 Node);GET /api/v1/admin/sessions;POST /api/v1/admin/sessions/{id}/disconnect(强制断开);GET /api/v1/admin/network;PUT /api/v1/admin/network;GET /api/v1/admin/logs。v1 不做用户系统;如需保护仅实现可选的单一 Admin Token,不设计账户/角色体系。 Control WebSocket 消息 必须 包含: | Type | 方向 | 关键字段 | |---|---|---| | HELLO | Node→Server | node_id, node_token, config_version, capabilities | | WELCOME | Server→Node | server_time, network_config_version | | NODE_LIST | Server→Engineer | sites[] | | CREATE_SESSION | Engineer→Server | site_node_id, target_cidrs[] | | PREPARE_SESSION | Server→Site | session_id, engineer_overlay_ip, target_cidrs[] | | PREPARE_RESULT | Site→Server | ok, route_results[], subnet_gateway_status, tcp_capacity, udp_capacity, error | | SESSION_CONFIG | Server→Engineer | session_id, peer_overlay_ip, cidrs[], mtu, udp_port | | ROUTES_READY | Engineer→Server | session_id | | SESSION_ACTIVE | Server→Engineer/Site | session_id | | STOP_SESSION | 任意→Server / Server→Node | session_id, reason | | SESSION_STATS | Engineer/Site→Server | cumulative counters | | HEARTBEAT | 双向 | timestamp/status | | REBOOTSTRAP_REQUIRED | Server→Node | config_version, reason | 错误码基线 必须 包含:SERVER_UNREACHABLE、JOIN_TOKEN_INVALID、NODE_AUTH_FAILED、OVERLAY_LOCAL_CONFLICT、ENGINEER_SESSION_EXISTS、SITE_OFFLINE、CIDR_INVALID、CIDR_LOCAL_CONFLICT、CIDR_OVERLAY_CONFLICT、SITE_NO_ROUTE、NETSTACK_UNAVAILABLE、FLOW_LIMIT_REACHED、SESSION_TIMEOUT、SESSION_INJECT_FAILED。 ### 需求: R12 路由冲突、异常与恢复 Engineer 本地 CIDR 冲突算法 必须 使用 net/netip 做真正的 Prefix overlap(不使用字符串比较):收集 Windows 有效路由和所有非 RemLink 直连前缀;默认路由 0.0.0.0/0 不作为冲突依据;任何更具体的现有本地/VPN 路由只要与 Remote CIDR 有重叠即拒绝。 Reconcile 规则: - Engineer 启动时检查 RemLink Adapter 上由自身记录创建但没有对应 Active Session 的 Remote Routes,删除残留。 - Site 启动时初始化/重建 userspace SubnetGateway;不检查、不创建、不删除任何 Windows NAT。 - Site 清理上次异常退出遗留的 userspace flow/session 状态;Windows 系统路由、NAT、Forwarding 不需要恢复。 - Wintun Adapter 可复用,不要求每次退出删除;卸载/显式清理时再删除。 Server 重启后所有 Remote Session 必须 直接标记 CLOSED,不实现透明 Session Resume;Node 的 wireguard-go/Control 自动重连,Engineer 用户重新点击连接,Site userspace flow 随 Session 关闭并清理。 网络切换(Wi-Fi→网线/热点)时由 WireGuard 正常 roaming/重新握手处理;客户端只配置 Server 固定 Endpoint,Server Peer Endpoint 由 WireGuard 根据握手学习;Control WebSocket 断线使用指数退避重连。 ### 需求: R13 日志、统计与可观测性 日志模块 必须 分为:CORE、BOOTSTRAP、WG、IPAM、CONTROL、SESSION、ROUTE、NETSTACK、TUN、SUBNET、SYSTEM。高频 packet 日志默认关闭;DEBUG 也禁止逐包打印 Payload,只允许限速采样(防止 PLC 下载/RDP 时写满磁盘)。 Session 流量统计 必须 以 Engineer 视角定义:Upload=Engineer→Site LAN,Download=Site LAN→Engineer;Engineer PacketMux 在拦截 Remote 出包时累计上传,Engineer SessionListener 在校验并注入 Remote Reply 前累计下载;每 5 秒向 Server 上报累计值。Server 不为了统计进入 WireGuard packet path;节点层 WireGuard 总流量可由 wgctrl 读取作为辅助指标。 ### 需求: R14 安全边界 - WireGuard 负责公网数据通道加密、认证和完整性;RemLink 不重复实现。 - 每个 Node 独立 WireGuard KeyPair;PrivateKey 不上传 Server。 - Server WireGuard Peer 只允许该 Node 的 /32 Overlay IP,防止节点伪造其他 Overlay Source。 - Engineer 与 Site 的 Session UDP Listener 都只绑定 Overlay 地址,并按方向校验 SessionID、Outer Source、Inner Source/Target CIDR、Inner Destination。 - Join Token 仅用于首次加入;NodeToken 用于应用层身份。 - v1 按需求不实现 HTTPS;公网暴露时可由外部反向代理/防火墙补充,但不是 v1 内核功能。 - 绝不自动关闭 Windows Firewall;如需要规则,只创建带 RemLink 前缀、可识别且可回滚的规则。 ### 需求: R15 项目结构、关键接口与依赖管理 Monorepo 结构 必须 为: ``` remlink/ ├─ cmd/ │ ├─ server/ │ ├─ engineer/ │ └─ site/ ├─ internal/ │ ├─ model/ │ ├─ protocol/ │ ├─ config/ │ ├─ identity/ │ ├─ ipam/ │ ├─ control/ │ ├─ session/ │ ├─ subnet/ │ │ ├─ header.go │ │ ├─ sender.go │ │ ├─ listener.go │ │ └─ validator.go │ ├─ overlay/ │ │ ├─ clientwg/ │ │ │ ├─ device.go │ │ │ ├─ muxtun.go │ │ │ └─ adapter.go │ │ └─ serverwg/ │ ├─ platform/ │ │ ├─ windows/ │ │ │ ├─ route/ │ │ │ ├─ netinfo/ │ │ │ ├─ socket/ │ │ │ └─ dpapi/ │ │ └─ linux/ │ │ └─ netlink/ │ ├─ subnetgateway/ │ │ ├─ netstack/ │ │ ├─ tcprelay/ │ │ ├─ udprelay/ │ │ └─ pingrelay/ │ ├─ database/ │ ├─ logging/ │ └─ stats/ ├─ frontend/ │ ├─ server/ │ └─ engineer/ ├─ deploy/docker/ └─ docs/ ``` 关键接口 必须 为: ```go type RouteManager interface { AddRemote(prefix netip.Prefix, ifIndex uint32) error RemoveRemote(prefix netip.Prefix) error Conflicts(prefix netip.Prefix) ([]RouteConflict, error) Lookup(dst netip.Addr) (RouteInfo, error) } type SubnetGateway interface { Prepare(ctx context.Context, cfg SessionConfig) error InjectIPv4(ctx context.Context, sessionID uint64, packet []byte) error CloseSession(ctx context.Context, sessionID uint64) error } type SessionTransport interface { SendIPv4(sessionID uint64, peer netip.Addr, packet []byte) error } ``` MuxTun 约束:必须实现所固定 wireguard-go 版本的 tun.Device 接口,go.mod 锁定 wireguard-go commit/version,升级前先跑 MuxTun Windows POC;base Device 的 Name/MTU/Events/BatchSize 按原语义代理;Read 方向负责 OS→WireGuard 分类;Write 方向负责 WireGuard→Windows 原样写入(Engineer SessionReceiver 的 Remote Reply 也通过统一 Wintun inbound writer 注入;Site netstack 数据不注入 Windows Wintun);Close 必须可幂等,并使所有 goroutine 退出。 依赖管理:所有 Go module、NPM package、Wintun DLL、gVisor commit 均固定版本/commit,禁止生产构建使用 latest 浮动依赖(gVisor netstack API 不保证稳定,升级必须经过 POC);Wintun DLL 使用 go:embed 打入 EXE,首次运行释放到当前角色 EXE 所在目录,不得通过 `%ProgramData%` 在 Engineer 与 Site 之间共享;发布包提供 THIRD_PARTY_NOTICES(WireGuard/Wintun/gVisor 及参考复用代码许可证,如直接采用 Tailscale 代码片段遵循其 BSD-3-Clause);Windows Engineer 与 Site 都要求管理员权限(创建 Wintun、配置接口/路由;Site v1 不修改 WinNAT 或 IP Forwarding)。 ### 需求: R16 阶段化开发与验收 开发 必须 按 Phase 0–10 顺序执行(详见 tasks.md),每阶段通过明确验收后才进入下一阶段;先完成 Phase 1–7 网络 Gate,再投入完整 GUI。Gate A–D 为关键关卡: - Gate A:wireguard-go 能通过 MuxTun 使用唯一 Wintun,正常 Overlay 通信。 - Gate B:Remote Packet 被 MuxTun 截获后,普通 UDP Socket 的外层 Overlay Packet 能通过同一 Wintun 再进入 WireGuard,不死锁、不无限循环。 - Gate C:Site 收到 Session Raw IPv4 后能注入 gVisor netstack;TCP/UDP Forwarder 能建立 host socket 到现场目标并完成双向数据搬运。 - Gate D:gVisor netstack 能把返回数据重新构造成 Engineer 看到的原始目标 IP 流,并通过 Session UDP 反向封装回 Engineer;Ping Relay 能正确返回 ICMP Echo Reply。 最终验收 必须 通过规格书第 19 章 T01–T18 全部场景(详见 checklist.md)。测试 TCP/102、TCP/502、HTTP、RDP、ICMP Echo、UDP 的目的是证明通用 TCP/UDP/Ping userspace gateway 成立;生产代码中禁止出现 S7Proxy、ModbusProxy、HTTPProxy 等应用协议专用模块;PingRelay 是唯一明确的 ICMP Echo 诊断模块。 ## 修改需求 无(绿地项目)。 ## 删除需求 无(绿地项目)。