# SDD-003:随客户端分发 WireGuard 隧道组件 | 字段 | 内容 | |---|---| | 版本 / 变更级别 | 0.3 / S3(内置 Go 系统隧道、路由、DNS 与特权执行) | | 设计状态 | Draft,已核对本地实现与 opsxdb / opsxapi 的桌面打包方式 | | 实现状态 | Go 探针、SOCKS5 与 macOS 原生 `utun` 数据面、Rust 受限子进程桥接代码已实现;系统工具调用路径已移除,内置直连尚不可用;拨号用户可自动登记至代理启动;macOS 系统 SOCKS 开关已实现,真机验收待办;系统接口地址/路由/DNS 和恢复尚未完成 | | 负责人 / 日期 | opsx-vpn / 2026-10-21 | | 关联需求 | 企业员工安装 OpsX VPN 即可拨号,无需 Homebrew 或手动安装 wireguard-tools | | 批准状态 | 用户已要求先评估设计后开发;具体平台发布签名尚待配置 | ## 1. 背景、目标与范围 - **当前行为与证据**:`client/src-tauri/src/tunnel.rs` 只管理本机密钥与设备记录;此前的 `wg-quick` 执行和 PATH 检测已按用户要求移除。`system.rs` 在特权网络服务完成前始终报告直连未就绪;Go 探针及应用代理仍可用。`client/src-tauri/tauri.conf.json` 打包 Go sidecar;`../opsxdb/src-tauri/tauri.conf.json` 使用 `externalBin` 打包 Go sidecar。现有 `docs/specs/SDD-002-desktop-wireguard-client.md` 中普通拨号用户和设备 API 已进入第二阶段,目标 OS 的真实系统隧道仍待验。 - **目标**:Rust 保持唯一的客户端控制面及本机私钥拥有者;按 OS 引入可随应用签名分发的隧道执行层。macOS 优先;用户不必安装 Homebrew 或 WireGuard 命令。Linux 复用内核 WireGuard,但打包必要工具与权限策略;Windows 使用专门的 Windows WireGuard 驱动/服务方案,不把 macOS 的 Go TUN 实现当通用驱动。 - **本轮实施边界**:保留可打包的 Go netstack 探针与本机 SOCKS5 应用代理;客户端不再执行系统 WireGuard 命令。完成特权 helper、路由/DNS 回滚和目标 OS 握手验收之前,UI 只报告内置直连未就绪。 - **保留行为**:普通用户认证、设备公钥注册、配置领取及服务端 WireGuard UDP 协议不改;前端不接收私钥,服务端不接收客户端私钥。 ## 2. 需求与约束 - FR-01:启用免安装分发通道的发布版仅在安装包内存在**正确目标架构、可校验版本**的隧道组件且系统适配就绪时报告“内置引擎就绪”;缺失时应明确报错,绝不运行 PATH 中同名程序。 - FR-02:所有模式均不调用本机 `wg` / `wg-quick`;内置网络服务未就绪时直连明确不可用,应用代理单独可用。 - FR-03:随包组件只接受固定动作及受约束的本机配置路径,不接受来自 WebView 的任意命令或路径;日志不可含私钥、令牌、完整配置。 - FR-04:macOS 正式接入必须完成受控提权、TUN、路由与 DNS 原值保存和恢复;授权取消、进程崩溃、升级或断开均须保持可恢复性。系统辅助程序的签名与安装按目标发行证书验收。 - FR-05:Windows/Linux 按各自系统 API 和权限交付;未验收的平台应明确“尚不支持内置隧道”,不能用模拟连接替代。 - FR-06:无系统工具的用户登录后可从概览启动 Go 应用代理:先校验最新线路授权、自动登记/刷新本机设备、使用实际 WireGuard 参数启动本机 SOCKS5。UI 必须将代理状态与整机 VPN 状态分开;退出、撤销设备、进程停止后不可仍报代理在线。 - NFR-01:正式发布不得从远程地址动态下载可执行文件、不得通过 shell 插入远端配置、不得使用旧飞连魔改 WireGuard 协议。 - NFR-02:非管理员安装包、系统提权、代码签名、升级与卸载必须分别测试;不能将可执行文件放进用户可写目录再让 root 执行。 ## 3. 架构与方案 ```text React → Tauri invoke → Rust(登录、设备私钥、连接状态) ├─ 当前:Go 应用代理;直连未就绪 └─ 目标:签名的特权隧道组件 ├─ macOS:WireGuard Go 引擎 + 受控网络配置/恢复 ├─ Linux:内核 WireGuard + 受控配置服务 └─ Windows:WireGuardNT/官方隧道服务适配 ``` - 参考 opsxdb 的按 triple 构建与 Tauri `externalBin`,**不复用**它开放 HTTP 端口的后端模型;隧道组件无需监听 TCP 端口,特权边界必须固定动作、受限 IPC 和签名校验。opsxapi 证明纯 Rust 控制面足够,不必把登录 API 搬进 Go。 - 采用标准上游 WireGuard 协议。`wireguard-go` 是用户空间引擎,不能单独完成 route/DNS/提权,必须由系统适配器完成,且需单独审查依赖版本与许可证。严禁直接移植 `PinkD/wireguard-go` 的私有扩展。 - `client/tunnel-go` 当前通过 `netstack.CreateNetTUN` 实现数据面探针,Rust `client_probe` 只对已登录用户的本机设备记录发起固定公共 TCP 目标 `1.1.1.1:443` 的连接。此模式能在无需管理员权限时检查端到端握手与网关出口;不提供系统 VPN 路由,不能作为“已连接”状态依据,也不能静默读取记录自动运行。 - 测试专线 `opsx-client-pilot` 当前给该设备下发的 `AllowedIPs` 为 `10.88.0.0/24`;服务器 `net.ipv4.ip_forward=1`,公网验证使用的临时 NAT 规则已清理。正常公网使用前需要由管理员明确更新该专线的客户端授权路由并持久化对应网段及出口网卡的 NAT。 - 本机代理固定监听 `127.0.0.1:18743`,只接受 SOCKS5 CONNECT;由 VPN 网络栈解析域名,并按服务器下发的 AllowedIPs 逐目的地址校验。前端明确显示只有配置了此代理的应用才经过 VPN;退出登录或退出应用时停止进程。此代理在本机回环端口没有额外认证,设备上的其他本地进程也可使用它,正式发行前应评估本机进程间隔离要求。 - **顺序**:先实现选择器和构建/验证契约,区分内置和系统工具;再建立 macOS 受限 helper 与网络快照/恢复;真机完成全隧道及分流、DNS、睡眠/唤醒、异常退出与真实握手;随后分别实现 Linux/Windows。未通过门槛前保持现有模式并显示准确的依赖信息。 ## 4. 接口、数据与配置契约 ### 无 `wg` / `wg-quick` 的 macOS 直连实现 - Go 子进程在用户权限下使用上游 `tun.CreateTUN("utun", mtu)` 创建接口、启动 WireGuard,并通过一次性 stdout 消息把系统分配的 `utunN` 回传 Rust;本机配置保持 0600。Go 子进程不能仅因为接口创建成功就报告已联网;Rust 完成 IP、路由、DNS 设置后才允许控制面查询真实握手。 - 对照 `../demo/clash-verge-rev`:它的 `config/clash.rs` 为 Go 核心 Mihomo 配置 `tun.auto-route/auto-detect-interface/dns-hijack`,`core/service.rs` 由经管理员确认的安装器将核心发布到服务拥有的路径,再用受限 IPC 启动;`feat/tun.rs` 在服务未就绪时停用 TUN。其“rule/global”是 Mihomo 的流量策略,不等于 WireGuard 的 AllowedIPs。OpsX 不直接复用 Mihomo 的代理规则或服务 IPC 协议。 - 其他参考边界:corplink-rs 的飞连私有协议字段不适用于本项目标准 WireGuard;Tailscale/NetBird 可以参考服务化与网络生命周期,但不能据此假设它们的路由/DNS 实现已经被本项目核对或移植;AmneziaWG-go 的混淆需服务端同时支持,当前服务端未采用该协议。本机只核对过 Clash Verge Rev 的源码,其他项目目前属于候选参考。 - 已核对本地 `../demo/vpn/wireguard-go`(提交 `2631ce9`),与客户端 `go.mod` 锁定的上游模块版本一致。其 `tun/tun_darwin.go` 中 `CreateTUN("utun", mtu)` 让内核分配真实接口名,且可选环境变量 `WG_TUN_NAME_FILE` 会写文件;本项目禁用这一额外写入,仅通过 JSON 回传真实名称。其 `main.go` 用 `device.Wait`、终止信号和 `device.Close` 管理生命周期,但没有替应用设置地址、路由或 DNS;`device/uapi.go` 的 `IpcGet` 会包含私钥,必须在 Go 进程内过滤后才返回 Rust。 - Go `tun` 命令仅在 macOS 运行,读取 0600 本机记录,创建接口后只输出 `interface_created` 和系统分配的 `utunN`;其标准输入仅接受 `status`、`stop`,关闭输入、父进程退出或接口关闭时终止。`status` 仅输出握手秒数与收发字节数,不透传包含私钥的 WireGuard UAPI 原文;首次接口事件**不是**握手或路由就绪。普通登录进程不得因为收到这个消息就更新 UI 为已连接。 - `interface_created` 同时携带不含密钥的 IPv4 路由计划:先在原物理出口固定网关 endpoint `/32`(如果授权路由覆盖它),再按授权网段安装 TUN 路由;`0.0.0.0/0` 展开为两个 `/1`。域名 endpoint 在动路由前解析成一个 IP,WireGuard Device 使用同一个解析结果,防止多个 DNS 地址导致绕行规则与实际发送目标不一致。若需网关绕行,Go 在启动前只读调用 `/sbin/route -n get` 并提取物理接口和网关;已走 `utun`、无接口或格式非法则拒绝。此结果是快照,特权服务实际安装路由前仍须复核物理出口,防止检查后网络切换。当前只规划 IPv4,出现 IPv6 授权立即拒绝,不把未接管的 IPv6 流量误称为全局模式。仍需特权服务安全执行和恢复,此计划本身不会改变系统路由。 - `client/src-tauri/src/native_tunnel.rs` 已能在 macOS 启动 Go 子进程、限制启动与查询等待时长、解析非机密计划与握手计数,断开或 Rust 对象销毁时终止子进程;字段在 Rust 侧再次校验。该模块先保持脱离 UI 的直连路径,待特权服务完成网络事务与恢复并通过握手/流量验收才启用,防止接口创建事件被 UI 当作可联网状态。 - Rust 只允许经校验的 IPv4、合法 `utunN`、服务端 `AllowedIPs` 及固定动作传给受控系统服务;服务安装到管理员拥有的固定位置,验签/校验所有权,使用受限本地 IPC 更新路由和 DNS,不能从用户可写路径提权运行 Go 核心,也不通过临时拼接的 AppleScript/shell 命令充当发布版特权服务。专线 endpoint 需为物理网络出口保留路由,防止全局路由自吞。 - 系统配置事务顺序:解析 endpoint 的实际地址并记录原物理出口与 DNS 状态;先持久化带有接口名和拥有者标识的恢复记录,再设置接口地址和路由、受控 DNS;核对网络栈实际状态、服务端新握手和授权目标连通性后才发布连接状态。全局路由应避开 VPN endpoint,网络切换时重新计算出口。若启动中途失败,按逆序恢复,只撤回自身仍持有的设置;恢复失败保留记录并提示人工修复,不覆盖后续第三方改动。服务重启时扫描自己的未完成事务并恢复。 - 路由/DNS 变更前写入只属于本应用的恢复记录。部分失败立即回滚;断开、注销、设备撤销、Go 进程意外退出、应用退出或下次启动重试恢复,只清理与当前接口/目标一致的配置。恢复失败时明确报告,并阻止把连接显示为成功。网络切换、休眠唤醒、DNS 原值和抢占冲突需要 macOS 真机验证。 - 本轮优先 macOS;Linux/Windows 需要独立系统权限与适配,未实现时不得显示内置直连可用。现有 SOCKS5 应用代理保留;任何平台均不回退到系统 WireGuard 工具。 - `get_system_readiness` 报告平台、Go 代理组件、`engine_mode` 和可读 `engine_detail`;不再检测或展示系统 WireGuard 命令。`bundled` 仅可在实际引擎、权限和系统配置均通过验收后返回。 - 内置组件的目标 triple 路径和版本由构建产物显式声明;发布流程核对哈希、签名和可执行权限。不能从用户可写数据目录或 PATH 寻找发布 helper。 - 服务端 API、数据库及客户端设备密钥数据模型不变;未来 helper 通过最小化本地 IPC 收到连接参数,不接触用户口令或管理令牌。 - 本轮 Go 代理继续显式开启,不自动修改系统代理、路由或 DNS。Rust 将设备准备与整机拨号分离;代理路径复用同一设备授权流程,启动时重新查授权,失败时不遗留假在线状态。试验用户的 `AllowedIPs=10.88.0.0/24` 只可代理该网段,不能宣称可经 VPN 浏览公网。 ### macOS 系统代理扩展(S3,2026-10-21) - 用户显式启动应用代理后可单独启用系统 SOCKS 代理。仅当设备配置实际包含 `0.0.0.0/0` 时允许开启;当前私网试验专线应拒绝,避免把普通网页发送到不允许公网访问的隧道。系统代理只影响遵循 macOS 代理设置的应用,不等同于整机 TUN,也不能保证所有 UDP 或 DNS 走隧道。 - 启用前枚举网络服务、检查 HTTP/HTTPS/SOCKS/PAC/自动发现是否已有代理;已有代理时拒绝接管。持久记录将要更改的服务与原 SOCKS 配置;启用失败逐项回滚。关闭时只恢复仍指向自身监听端口的服务,绝不覆盖其他程序随后修改的代理。进程意外退出或下次启动应尝试清理残留;无法安全恢复时给出错误并保留恢复记录。网络服务新增或切换不能自动把未授权服务纳入代理范围。 - 启用、关闭、注销、退出与隧道切换均需串行操作。恢复失败不得悄悄关闭仍被系统代理引用的本地监听;手动改动代理后状态显示为冲突。验收包括私网路由拒绝、已有代理拒绝、启停还原、部分失败回滚、异常退出后重启恢复及更换网络服务。正式真机验收前不把此能力描述为跨平台整机 VPN。 ## 5. 风险与验收 | 风险或失败场景 | 防护 / 降级 / 恢复 | 可观察信号 | |---|---|---| | Go 引擎已启动但没有路由/DNS | 连接前检查系统适配就绪;握手只说明对端通信,不代表全隧道可用 | 界面报告未完成的阶段,路由/DNS 实测 | | 提权执行用户替换的二进制 | 验证签名,固定安装路径与所有权,不在用户数据目录执行 | 签名/权限校验失败,拒绝连接 | | 崩溃残留默认路由或 DNS | 启动前快照、事务回滚、下次启动修复 | 异常结束后系统路由/DNS 与原值一致 | | 三平台能力误报 | 平台分别发布和验收;未支持者显示真实原因 | 每平台独立安装和断开报告 | | AC | 对应需求 | 前置条件与操作 | 预期结果 | 验证方式 | 结果 | |---|---|---|---|---|---| | AC-01 | FR-01, FR-02 | macOS/Linux 上有或没有系统 WireGuard 命令时打开客户端 | 两种环境均报告内置直连未就绪;客户端不运行系统命令,Go 代理独立可用 | Rust 单测、构建、静态调用核查、真机 | Rust 单测与静态核查通过;真机 Not Run | | AC-01a | FR-01, NFR-01 | 构建固定版本 Go 组件,输入授权路由内/外目标 | 仅支持授权内目标,不输出密钥,打包客户端可定位正确架构组件 | Go 单测、构建和实际出口探测 | Go 单测、macOS arm64 构建、临时授权公网出口与 SOCKS5h 网页请求通过;正式安装包签名 Not Run | | AC-02 | FR-03, NFR-01/02 | 尝试替换组件、传非法路径、控制字符 | 拒绝执行,机密不记录 | 隔离测试、签名检查 | Not Run | | AC-03 | FR-04 | macOS 连接、断开、撤销授权、强制退出及重启 | 真实握手;路由/DNS 保持或恢复 | 两台目标机人工/自动验收 | Not Run | | AC-03a | FR-03/04 | 数据面收到格式错误、过大、符号链接设备记录;模拟全局/分流路由、物理出口与 `utun` 出口;父进程退出或接口意外关闭 | 拒绝错误输入与非物理出口;进程退出并释放接口;接口创建事件不冒充联网完成 | Go 单测、跨平台构建、macOS 故障注入 | 输入与物理出口解析单测、构建通过;macOS 故障注入 Not Run | | AC-04 | FR-05 | Windows/Linux 干净系统安装、拨号、卸载 | 无独立工具安装步骤,未支持平台不假装就绪 | 各平台测试 | Not Run | | AC-05 | FR-06 | 无 `wg-quick`、有拨号用户授权时点击“应用代理” | 自动登记设备并启动 Go 代理;界面展示应用代理地址且整机 VPN 仍未连接;撤销/退出后代理停止 | Rust/Go 单测、macOS 实机检查 | Rust 模拟服务端首次登记/刷新测试通过;Go 私网探测成功,本机代理监听/私网 SOCKS 握手通过;GUI 拨号用户与撤销验收待运行 | | AC-06 | macOS 系统代理扩展 | 私网路由/已有代理拒绝;全局路由下启停、断网、异常退出 | 已占用设置不覆盖;仅恢复自身代理;不把 SOCKS 宣称为所有流量经过 VPN | macOS 真机逐项验收 | Rust 构建/静态状态解析通过;系统设置与公网网页真机验收 Not Run | ## 6. 发布与恢复 - 第一阶段关闭系统工具直连,仅交付隔离执行层和 Go 代理;特权网络服务与恢复通过验收后才启用内置直连。切换正式引擎前收集目标架构及代码签名资料。 - 2026-10-21 用户明确授权后,使用 0600 临时设备配置将 AllowedIPs 扩为全路由,在服务器仅为 `10.88.0.0/24` 添加临时出口 NAT;Go 数据面实际连接 `223.5.5.5:443` 成功,通过 SOCKS5h 获取 `http://www.baidu.com` 返回 HTTP 200。原始设备记录仍只允许 `10.88.0.0/24`;验证后已删除唯一带 `opsx-vpn-wg4-pilot` 标记的临时 NAT 规则。服务器自身访问 `1.1.1.1:443` 也超时,客户端探测目标改为可达的 `223.5.5.5:443`。这些验证不证明系统 VPN 或路由/DNS 恢复完成。 - 2026-10-21 只读复核运行中的 wg4、IPv4 转发与 NAT:IPv4 转发已开启,POSTROUTING 仅有 OpenVPN `10.10.0.0/24` 规则,WireGuard `10.88.0.0/24` 没有持久出口 NAT。全局线路上线需先通过管理后台启用该专线的出口 NAT 并下发经授权的全局 IPv4 路由,再实测浏览器访问;当前不能标为公网验收通过。 - 先在独立试验机测试安装与签名,再验证路由/DNS 回滚与握手,然后分批发版;不能仅靠 `cargo check` 或浏览器预览宣称可分发。 - 出现网络配置残留时用保存的系统快照恢复并停用有问题的组件;保留旧安装包与开发模式供排障。服务端不变,无数据库回滚。 ## 7. 待决问题 | 问题 | 是否阻塞 | 推荐方案 | |---|---|---| | macOS Developer ID、Network Extension entitlement 与签名/公证资源 | 阻塞 macOS 正式分发 | 评估上游官方 WireGuardKit/Network Extension 与 Go helper 两条路线;以能稳定签名、恢复网络为准 | | 目标平台与架构矩阵 | 阻塞跨平台正式验收 | 先 macOS arm64,再 macOS x86_64、Linux、Windows 分别验收 | | Go helper 许可证、依赖锁定及安全更新 | 阻塞打包发布 | 固定上游 tag/模块校验和、生成第三方许可清单、随应用更新 |