# Gateway Keepalive:.NET 托盘版重写方案 日期:2026-10-23。本文是待实施方案。 源码基线:`main`,`a18914bd4e38feb2984398b7dc3619e794366697`(提交说明:重构网关切换逻辑,待测试)。已通过本机 `lion-desktop` SSH 密钥克隆。此次仅阅读源码、验证独立的参数绑定问题并编写方案,未运行原保活脚本,未修改系统网络设置。 ## 1. 目标与推荐选型 做成一个 Windows 常驻托盘工具:检测旁路由是否可用,故障时切回主路由,恢复稳定后切回旁路由;可以看状态、手动指定模式、暂停自动切换和恢复网络配置。 推荐 **C# + .NET 10 LTS + WinForms**,目标框架 `net10.0-windows`,第一版面向 x64,支持平台以 .NET 10 官方 Windows 支持矩阵为准。用 `ApplicationContext + NotifyIcon` 管理常驻生命周期,参考隔壁鼠标工具的深色弹出面板、自绘状态图标、悬停气泡和单实例唤醒。 隔壁工具使用 .NET Framework 4.8 和系统 csc,优点是产物小、无需另装运行时。本项目优先使用现代 .NET 的异步网络、取消与测试支持;不以它的 82 KB 作为体积目标。发布提供两种包:本机已有 Desktop Runtime 时使用较小的依赖运行时版本,其他机器使用包含运行时的独立版本。单 exe 的原生依赖打包与解包行为需在发布阶段验证,第一版不启用裁剪或 Native AOT。 规划时本机已有 .NET 10 Desktop Runtime,尚未安装 SDK。实施阶段已在用户目录准备并校验 .NET 10 SDK;实现与验证结果见 `docs/validation.md`。 ## 2. 原项目的实际行为 | 文件 | 作用 | | --- | --- | | `GateWay-KeepAlive.ps1` | 提权、选择网卡、读取和切换默认路由、设置 DNS、运行健康检查循环 | | `proxy-test.ps1` | SOCKS5 无认证握手与目标 CONNECT 探测 | | `config.ps1` | 网关、网卡关键字、探测与防抖参数 | | `logger.ps1` | 调试控制台输出与 Windows 事件日志 | 现有配置:主路由 `192.168.6.1`,旁路由 `192.168.6.2`,SOCKS5 `192.168.6.2:1070`;旁路由 DNS 也使用 `.2`;目标 `www.google.com:443`;网卡名称包含 `Ethernet`;每轮检查结束后等待 5 秒;连续成功/失败 3 次,切换冷却 30 秒。直连 DNS 配置为空时重置为自动获取。 当前属于通过更换默认网关接入旁路由的方案,不是设置 Windows 系统代理。SOCKS5 只是旁路由能力探针;SOCKS 可用不能单独证明经旁路由转发的默认路径可用。实际故障反应时间还要加上探测耗时和冷却剩余时间,不能简单承诺为 15 秒。 ## 3. 重写时必须处理的问题 | 源码现状 | 新版处理 | | --- | --- | | `Test-HostReachable` 使用参数 `$Host`,与 PowerShell 只读自动变量冲突 | 改成 C# 探针;已用独立参数绑定示例确认会报只读变量错误,无需执行原脚本 | | 先删除目标网卡所有 IPv4 默认路由,再添加新网关 | 只操作明确归属的路由,分阶段切换、读回验证、失败补偿,保留原路径到切换成功 | | DNS 与路由分开写入,出错没有回滚 | 保存切换前快照与操作日志;任何一步失败都进入补偿流程,恢复失败单独告警 | | 直连 DNS 恢复默认按 DHCP,未保存原始静态设置;`originalDns`、`TestIP` 没进入实际检查链 | 区分自动 DNS 与有序静态 DNS 列表;恢复真实基线;合并配置定义与实际使用 | | 根据是否等于旁路由网关判为代理/直连,第三方网关也会算直连 | 识别主路由、旁路由、外部配置、无路由和网卡断开,不擅自覆盖外部配置 | | 仅按网卡名称取第一个 Up 网卡,循环固定旧索引 | 首次显式选择并保存网卡 GUID;索引随枚举更新;网络环境变化重新校验 | | SOCKS 响应假设一次 Read 足够,连接回复固定读 10 字节 | 精确读取、检查版本/方法/返回码,按 IPv4、域名、IPv6 地址类型消费完整回复 | | 依赖 ICMP 且部分探测缺少统一总超时 | ICMP 默认只做诊断;网络操作均有超时、整轮预算与取消 | | DNS/SOCKS 成功后还可能默认路径不通,探测也可能经另一张网卡出网 | 分开显示各层结果;校验目标网卡与有效路由,必要时绑定探测源地址,切换后验证实际转发路径 | ## 4. 托盘交互 左键托盘图标开关面板;点外部或按 Esc 收起。首次启动打开配置向导,后续启动只驻留托盘。关闭面板继续工作;“退出程序”才结束常驻。 主面板约 400 × 430 逻辑像素,最终尺寸以中文排版实测为准: ```text 网关保活 自动 · 旁路由 当前网关 192.168.6.2 检测正常 Ethernet · 当前 DNS 192.168.6.2 旁路由连接 正常 2 ms 旁路由 DNS 正常 12 ms SOCKS5 正常 180 ms 当前出口 正常 220 ms [ 自动 ] [ 固定直连 ] [ 固定旁路由 ] 连续失败 0/3 · 距上次切换 2 分钟 最近事件:旁路由已恢复,自动切回 [立即检测] [暂停自动切换] [设置] [日志] ``` 状态图标同时使用颜色和形状/标记:绿色表示旁路由工作,蓝色表示主路由直连,黄色表示降级或切换中,红色表示网络故障,灰色表示暂停或目标网卡断开。悬停只显示当前模式、网关及最近异常,不启动额外网络检测。 右键菜单:打开面板、立即检测、模式选择、暂停/继续自动切换、恢复接管前配置、登录后启动、查看日志、退出。设置使用独立窗口,包含网卡、主/旁路由、DNS 策略、探测目标与端口、阈值/间隔/冷却、提醒和退出行为。 “暂停”仅停止自动改配置,仍展示网络状态;固定模式完成一次指定切换后持续监测与告警,不被自动恢复逻辑覆盖。恢复接管前配置后进入暂停,防止马上又被自动切走。默认退出停止监测并保留当前网络,提供可选的“退出时恢复接管前配置”,界面明确显示该行为。 通知只在模式真正变化、故障持续、回滚失败等事件发生时提示,相同状态不每 5 秒弹一次。 ## 5. 后台状态机与健康检查 把“用户策略”和“实际网络状态”分开保存。 - 用户策略:`Auto`、`ForceDirect`、`ForceProxy`、`Paused`。 - 网络状态:`Initializing`、`Direct`、`Proxy`、`Switching`、`Degraded`、`Disconnected`、`ExternalConfiguration`、`RecoveryRequired`。 使用一个异步控制循环串行处理定时检查、立即检测、手动切换、配置更新和网卡变化事件。探针可在预算内并行,但路由/DNS 写入始终串行;立即检测请求合并,避免重复点击产生多个检查循环。通过状态快照推送到 UI 线程,窗体事件不直接改网络。 自动模式保留默认的 5 秒间隔、成功/失败 3 次和 30 秒冷却,均可配置。成功与失败计数分别要求连续;进入新环境或切换完成后重置;冷却用单调时间计算,系统时间校正不影响它。失败回退和恢复旁路由可以配置不同冷却,避免恢复后的长冷却拖延紧急回退。网卡断开不是旁路由故障,断开期间停止累计与切换。 健康检查分层记录耗时与失败原因:旁路由 TCP 可达、指定旁路由 DNS 查询、可选 SOCKS5 CONNECT、当前默认路径 HTTPS/TLS。DNS 查询明确指定 DNS 服务器,不能用只走系统 DNS 的 `Dns.GetHostAddressesAsync` 冒充指定服务器检测。优先封装 Windows DNS API;SOCKS5 使用 `TcpClient`/`NetworkStream` 自行实现精确帧读取。 旁路由恢复候选检查即使处于直连也可以运行。切回旁路由后必须验证实际出口;未通过就回退并进入恢复退避,避免“SOCKS 好用、默认转发坏了”造成循环切换。支持多个可配置探测目标及成功策略,减少单站点故障误判;第一版默认沿用现有目标,同时在配置中明确展示它。 初始每探针超时建议 1.5~3 秒,整轮预算建议 4 秒,以实测调整。超过预算记一次失败并释放 socket;下轮不与本轮重叠,不在恢复后补跑积压轮次。休眠唤醒、DHCP 更新、网络改变时清零旧计数、等待环境稳定后重新枚举。 ## 6. 网络接管、回滚与权限 通过 Windows IP Helper API 操作 IPv4 默认路由:`GetIpForwardTable2`、`GetIpInterfaceEntry`、`CreateIpForwardEntry2`、`SetIpForwardEntry2`、`DeleteIpForwardEntry2`,用 `GetBestRoute2` 验证有效选路;通过 `GetInterfaceDnsSettings` / `SetInterfaceDnsSettings` 读取和设置 DNS。所有结构布局、字符串生命周期、返回码与原生内存释放封装在 Windows 层。DNS API 的最低系统版本为 Windows 10 Build 19041,程序同时检查运行时平台要求。 切换流程: 1. 验证目标网卡 GUID、当前网络指纹、网关可达范围、当前路由/DNS 与上次快照是否一致。 2. 原子保存本次操作前快照与切换日志,包含路由 metric、接口 metric、DNS 自动/静态来源与有序列表。 3. 准备目标路由,优先保留原路由,通过精确路由项与 metric 控制优先级,考虑全机其他接口的有效路由。 4. 按目标模式应用 DNS 与路由优先级,读回验证两者,执行出口检查;系统没有跨路由和 DNS 的原子事务,界面显示切换中。 5. 成功才发布新状态和记录冷却;失败按操作日志恢复刚才改过的项,读回验证;补偿失败停止自动切换并展示恢复操作。 第 3 步的 metric 策略需先做原生 API 原型验证:DHCP 路由可能被系统重建,不能靠假设永久提高一次 metric 就一直有效;不能影响 VPN、其他网卡、特定前缀路由或 IPv6。第一版只管理选定网卡的 IPv4 默认路径,面板明确标注这个范围,不能把 IPv6 绕行误报为 IPv4 代理成功。 “切换前快照”与“接管前直连基线”分开保存。若启动时已处于旁路由,不能把当前旁路由误存成直连基线;优先使用同一网络且验证过的历史直连基线,或要求配置主路由与 DNS 策略。不同网络环境不重放旧快照。发现程序外修改时进入外部配置状态,暂停写入并允许重新接管。 路由写入需要管理员权限。第一版采用一个管理员权限托盘进程,启动时提权,拒绝提权则明确退出,不启动半工作的自动切换。登录自启使用任务计划程序,以当前用户、交互式登录、最高权限运行;首次创建/修改该计划在已提权进程中完成。不能直接套用鼠标工具的 HKCU Run 启动方式,也不使用无交互 Session 0 运行托盘。 不在第一版额外引入 Windows 服务。以后若需要未登录也保活,再拆成低权限托盘 + 管理员服务;现有 Core/Windows 边界便于迁移。崩溃后程序本身无法立即回滚,第一版靠持久化日志在下次启动时识别未完成切换,验证现场后进入恢复流程,不能承诺进程退出后仍有实时恢复能力。 ## 7. 工程划分与数据 ```text GatewayKeepalive.sln src/ GatewayKeepalive.Core/ 配置模型、状态机、切换计划、探针/时钟/系统接口 GatewayKeepalive.Windows/ 网卡枚举、路由/DNS API、权限、任务计划 GatewayKeepalive.Tray/ Program、ApplicationContext、托盘、面板、设置、日志 tests/ GatewayKeepalive.Tests/ 状态机、故障注入、SOCKS5 假服务、配置迁移 docs/ network-behavior.md 网络修改范围、恢复语义、手动恢复步骤 ``` Core 不依赖 WinForms。关键接口包括 `INetworkConfiguration`、`IHealthProbe`、`ISnapshotStore` 和可替换时钟;用假实现验证切换,不依赖真实网关。 配置放 `%LocalAppData%\GatewayKeepalive\settings.json`,保留 schemaVersion、网卡 GUID、网关、DNS 策略、探测与 UI 设置;快照和未完成切换日志独立保存。所有持久化采用临时文件写入再替换、校验与必要备份。按天滚动文件日志记录探测摘要、切换原因、系统错误码和恢复结果,设置保留期限;Windows 事件日志作为可选集成。 原 `config.ps1` 保留为历史参考。迁移只映射明确支持的赋值项,遇到表达式则让用户手动填写;不通过执行 PowerShell 配置来导入。首次展示原配置中的 `.1`、`.2`、1070 和阈值供确认,不能仅凭名称猜中网卡就开始改网络。 参考隔壁的 `TrayApp.cs`、`PanelForm.cs`、`Theme.cs`、`DarkForm.cs`、`TextTip.cs`、`TrayIconRenderer.cs` 和单实例交互;保留体验,按现代 WinForms 生命周期重构。使用 PerMonitorV2 和随目标显示器 DPI 更新布局,避免直接照搬隔壁的系统 DPI 固定缩放;悬停定时器仅气泡显示时运行。 ## 8. 实施顺序与验收 | 阶段 | 交付与验收 | | --- | --- | | 1:只读诊断 | SDK/解决方案、网卡和路由/DNS快照、分层探针、`--diagnose`。不写系统设置,展示实际选路与失败原因 | | 2:可恢复切换 | 原生 API 原型、手动切换、操作日志与补偿、`--dry-run`。故障注入验证任一步失败均不冒报成功;真实双网关环境验证读回和回退 | | 3:自动保活 | 状态机、防抖、冷却、恢复退避、网络变更/唤醒处理。验证 2 次失败不切、3 次达阈值才切、冷却阻止抖动、固定/暂停策略不被覆盖 | | 4:托盘成品 | 面板、设置、单实例、图标/气泡、滚动日志、登录自启与发布。验证 100%/150%/200% 缩放、混合 DPI 多屏、Explorer 重启、退出语义与注销行为 | 有价值的自动测试:用假时钟检验计数与冷却;在路由成功/DNS失败、DNS成功/出口失败、补偿失败等边界注入故障;本地 SOCKS5 假服务器分片、截断、延迟、返回不同地址类型;验证旧快照不会跨网卡/跨网络恢复。测试不修改开发机真实网络。 真机集成覆盖旁路由关闭、DNS服务停止、SOCKS端口关闭、默认转发失效但SOCKS正常、目标站点单独故障、拔网线、DHCP续租、VPN共存、双网卡、重启程序时仍在旁路由模式。第一阶段完成后再进入真实网络切换验收;当前没有实际测量性能、执行故障演练或构建新版。 第一版完成标准:平时托盘常驻;自动回退/恢复原因清楚;手动模式稳定;任何写入失败都有可验证的恢复结果;未知网络不自动套用旧配置;能够从日志解释每一次切换。 ## 9. 官方技术依据 - [.NET 支持策略](https://dotnet.microsoft.com/en-us/platform/support/policy):.NET 10 为 LTS,支持至 2028 年 11 月。 - [WinForms NotifyIcon](https://learn.microsoft.com/en-us/dotnet/desktop/winforms/controls/notifyicon-component-windows-forms):托盘图标、菜单与后台进程交互。 - [SetIpForwardEntry2](https://learn.microsoft.com/en-us/windows/win32/api/netioapi/nf-netioapi-setipforwardentry2):管理员要求、路由项键与路由/接口 metric 的关系。 - [GetInterfaceDnsSettings](https://learn.microsoft.com/en-us/windows/win32/api/netioapi/nf-netioapi-getinterfacednssettings) / [SetInterfaceDnsSettings](https://learn.microsoft.com/en-us/windows/win32/api/netioapi/nf-netioapi-setinterfacednssettings):接口 DNS 读写与最低系统版本。 - [任务计划的安全上下文](https://learn.microsoft.com/en-us/windows/win32/taskschd/security-contexts-for-running-tasks):交互式登录与最高权限注册要求。 - [.NET 单文件部署](https://learn.microsoft.com/en-us/dotnet/core/deploying/single-file/overview):依赖运行时/独立部署、原生依赖解包与体积取舍。