diff --git a/.idea/go.imports.xml b/.idea/go.imports.xml
new file mode 100644
index 0000000..644cdf0
--- /dev/null
+++ b/.idea/go.imports.xml
@@ -0,0 +1,10 @@
+
+
+
+
+
+
\ No newline at end of file
diff --git a/.idea/workbuddy.xpcool.com.iml b/.idea/workbuddy.xpcool.com.iml
index 7c421e3..9c408cc 100644
--- a/.idea/workbuddy.xpcool.com.iml
+++ b/.idea/workbuddy.xpcool.com.iml
@@ -1,5 +1,6 @@
+
diff --git a/.workbuddy/memory/2026-09-10.md b/.workbuddy/memory/2026-09-10.md
new file mode 100644
index 0000000..cb31c8c
--- /dev/null
+++ b/.workbuddy/memory/2026-09-10.md
@@ -0,0 +1,352 @@
+# 2026-09-10 工作日志
+
+## 家庭主机远程串流方案设计(公司电脑 → 家里主机)
+
+**类型**:REQ(方案设计,待确认后实施)
+
+### 触发需求
+在公司电脑远程控制家里这台主机,要求速度快、画质好,能打《英雄联盟》快节奏娱乐模式(海克斯大乱斗类)。
+
+### 本次实测底账(家里主机 = 本开发机)
+- **GPU**:NVIDIA RTX 4070 SUPER(驱动 32.0.15.9186),支持 NVENC H.264/H.265/**AV1** 硬编;另有 7800X3D 核显 AMD Radeon Graphics
+- **CPU/内存**:Ryzen 7 7800X3D(8C/16T) / 31.2 GB
+- **系统**:Windows 11 专业版 build 26200
+- **显示器**:AOC 2560×1440 @ **170Hz**
+- **网络**:Realtek 2.5GbE,链路 1Gbps,内网 IP `192.168.1.30`,网关 `192.168.1.1`(HTTP 200 可登录)
+- **宽带(Ookla CLI,电信上海节点 id 3633)**:下行 **943.82 Mbps** / 上行 **46.36 Mbps** / 空闲延迟 33.91ms / **丢包 0.0%**
+- **出口 IP**:`1.204.100.17`(中国电信广东,与历史记录的 1.204.105.116 同段,动态)
+
+### 关键发现
+1. **上行 46M 是整个方案的唯一硬瓶颈**,可支撑 2K 90–120fps @ 30Mbps 或 1080p 120–144fps @ 20–25Mbps;不足以支撑 2K 170Hz 满刷。
+2. **本机已存在远控残留驱动**:`Todesk Virtual Display Adapter`、`GameViewer Virtual Display Adapter`(网易UU远程组件)、`MuMu Virtual Display Adapter`。这些虚拟显示/虚拟输入驱动是腾讯 ACE/TP 反作弊重点扫描特征。
+3. **Moonlight 官方提供 Windows Portable 便携版**(官方原话:for work/public PCs without the ability to install new programs)→ 完美解决公司电脑无管理员权限问题。
+4. **反作弊风险是真实存在的**:社区有大量因远控软件在后台被判定"第三方软件"而十年封禁的案例,"远程操控着玩游戏"是最高风险场景。已写入方案第五节并给出降低措施。
+
+### 产出
+- 方案文档:`E:\Project\workbuddy.xpcool.com\远程游戏串流方案_家到公司.md`(v1 草案,待确认)
+- 结论:**主方案 = Sunshine(家)+ Moonlight 便携版(公司)+ 公网 UDP 直连**;网易UU远程作为应急通道;Windows RDP / TeamViewer 明确不适用;硬件 KVM 延迟不达标。
+
+### 待用户确认(阻塞下一步)
+1. 路由器 WAN 口 IP 是否等于 `1.204.100.17`(判断有无公网 IPv4,决定走端口转发还是组网)
+2. 公司电脑:能否装软件 / 显卡型号 / 屏幕分辨率与刷新率
+3. 公司网络是否限制 UDP、是否有代理或行为审计
+4. 是否接受反作弊风险(若账号价值高,建议只作办公/挂机通道)
+
+### 顺手记录
+- PowerShell 工具在本机**不回显 stdout**(exit code 正常但无输出),需 `Out-File` 落盘后用 Read 读取;Bash 内调用 `powershell.exe` 被安全策略拒绝。
+- Cloudflare 测速端点在本地几乎不可用(上传 121KB/s、下载 0),国内测速改用 **Ookla 官方 CLI**(`install.speedtest.net` 可直连下载)。
+
+---
+
+## 追加:Phase 0 前置验证执行结果(同日 14:40–14:55)
+
+### 三个决定性发现
+1. **家里 IPv4 是运营商级 NAT(CGNAT)**:`tracert` 第 2 跳 = `100.64.0.1`(RFC 6598 运营商专用段)→ **v1 的"公网 IPv4 + 端口转发"方案彻底作废**。
+2. **家里有公网 IPv6**:`240e:338:263:3600::/64`(RA 下发,电信)。可用地址 `240e:338:263:3600:e591:cb62:3503:8a23`(Preferred)。→ **IPv6 直连成为首选路径**(IPv6 无 NAT,只需网关防火墙放行入站)。
+3. **IPv4 侧为对称 NAT**:STUN 实测映射端口每次不同(1986 / 1990)→ IPv4 打洞成功率低,但 IPv6 侧打洞前景好。
+
+### 其他确认
+- **网关 = 中国电信智能网关(天翼网关)**,LuCI 界面 `/cgi-bin/luci`,MAC `88-C7-8F-A6-BC-43`;密码找回阶梯已写入方案文档(贴纸 useradmin → telecomadmin/`nE7jA%5m` → 小翼管家 App → 10000 号 → 恢复出厂;且**方案本身不依赖路由器后台**)。
+- 本机 Windows 防火墙:Domain=开 / Private=**关** / **Public=开**,以太网被判定为 Public;**当前会话不是管理员**,后续装 Sunshine 需用户点 UAC。
+- 本机装有 **Clash Verge**(系统代理 127.0.0.1:7897 开关=0)与**火绒**(HipsDaemon/HRWSCCtrl)。
+- **公司电脑(用户截图)**:Ryzen 7 7840H + **Radeon 780M**(RDNA3,VCN 4.0,**支持 AV1 硬解**)/ 32GB / 954GB,可安装软件。
+- **画质定档 1080p120**(H.265 22Mbps 起步 → 稳定后切 AV1 16–18Mbps)。
+
+### 交付物
+- 方案文档升级至 **v2**:`远程游戏串流方案_家到公司.md`(架构重构为 IPv6 直连优先)
+- 新增 `公司电脑网络自测.ps1`(UTF-8 with BOM,已在临时目录实跑验证通过):检测设备/显示、IPv6 出网、代理与审计软件、TCP 端口自由度、UDP 出网 + STUN NAT 类型、到家里的链路。
+
+### 待用户执行(阻塞下一步)
+1. 手机 4G/5G 打开 `http://[240e:338:263:3600:e591:cb62:3503:8a23]:38443/` 看是否返回 `IPV6-INBOUND-OK`(家里临时监听已起,端口 38443)
+2. 公司电脑跑一次 `公司电脑网络自测.ps1`
+
+### 本次踩坑(已沉淀到 MEMORY.md)
+- PowerShell 工具 stdout 被吞、`[byte] -shl` 溢出归零、`Get-Content -Raw`+`Out-File` 加 BOM 导致 UTF-8 中文被按 GBK 重写而永久损坏、PATH 被裁剪导致 ping/netsh 找不到、后台监听进程杀不掉、防火墙规则指向已失效的 node 路径。
+
+---
+
+## 追加:临时监听重启 + 引入「日志验证法」(同日 15:41–15:50)
+
+**类型**:CHG
+
+### 做了什么
+1. **清理旧监听**:上次会话留下的两个后台监听(38443 / 38080)本次全部 `Stop-Process` 成功,端口已释放(上次杀不掉的 38080 残留也清掉了)。
+2. **重起带访问日志的监听**(端口 38443):新版 `server.js` 把每个到达请求(来源 IPv6、UA、时间)追加写入 `%TEMP%\wb_ipv6test\inbound.log`,本地自测已命中日志。
+3. **本机 IPv6 地址复查**:`240e:338:263:3600:e591:cb62:3503:8a23` 仍为 **Preferred(未变)**;另有 `...:29f1:e63f:d476:597e` 也是 Preferred。
+4. **防火墙规则复核**:`C:\Users\xxl\AppData\Roaming\nvm\v23.0.0\node.exe` 有 **Public 档 TCP+UDP、任意端口** 的入站放行规则(因此该 node 起监听不会被本机防火墙吞掉,测试结果有效)。
+
+### 关键结论:「从外部自动验证 IPv6 入站」没有可用手段
+逐一实测失败:
+| 手段 | 结果 |
+|---|---|
+| `check-host.net` API(check-tcp) | 对 IPv6 一律 `{"error":"invalid_url"}`(方括号/无方括号/http:// 前缀均试过) |
+| `ping.pe` / `tcp6.ping.pe` | **接受 IPv6 目标**,但结果为 JS 异步渲染,静态抓取只有 `Connecting to ...` + `waiting_for_results` |
+| 公共 CORS 代理 codetabs | 522 |
+| allorigins | `Network connection lost`(不足以作为证据) |
+| corsproxy.io | 需 API key |
+| thingproxy / Jina Reader | 空响应(被拦) |
+| hackertarget nmap API | 已需 API key |
+
+→ 定案:**采用「手机 4G TCP 测试 + 服务端访问日志」双证据判定**,即使手机页面渲染异常,只要日志有 `REQ from=` 记录即可确证入站通。
+
+### 交付物更新
+- `远程游戏串流方案_家到公司.md`:第七节补充「日志验证法」判定表与日志路径;附 B 补充上述"外部探测不可用"的完整踩坑清单。
+- `%TEMP%\wb_ipv6test\server.js` 重写为带日志版本(并新增 `ext_probe.py`,check-host.net 探测脚本,结论:该服务不支持 IPv6)。
+
+---
+
+## 追加:脚本跨设备损坏诊断 + 纯 ASCII 版交付(同日 15:47–15:55)
+
+**类型**:FIX
+
+### 现象
+用户把脚本弄到公司电脑(`E:\xxcool\project\workbuddy.xpcool.com\网络自测.ps1`)执行后报错:
+1. `意外的标记"InterNetworkV6"` —— 文件里 `AddressFamily]::InterNetworkV6` **丢了一个 `::`**
+2. `IPv6 鍑虹綉 ... 涓嶹€?` —— **中文乱码**(UTF-8 字节被按 GBK 解码的典型样子)
+
+### 诊断
+- 本机 `公司电脑网络自测.ps1` 第 35 行**是正确的**(带 `::`),文件 BOM 完好(`ef bb bf`)
+- → 根因是**走了"文本复制粘贴"而不是"传文件"**,粘贴链路丢了 BOM 与个别字符
+- 乱码呈 `鍑虹綉` 形态 = 字节仍是 UTF-8,**只是缺 BOM** → 加 BOM 即可救(非双重编码,可逆)
+
+### 处置
+1. **新增纯 ASCII 版脚本 `net-test.ps1`**(输出全英文、文件内无非 ASCII 字符)→ 免疫一切编码问题;已在本机实跑通过,自检行输出 `Script encoding : no BOM (file is pure ASCII - harmless)`
+2. 给了用户「一键修复已损坏文件」的命令(显式按 UTF-8 读 → 补 `::` → 以带 BOM 的 UTF-8 写回)
+3. 修复经验沉淀进 skill `windows-host-probe`(新增"跨设备交付会静默损坏文件"一节)
+
+### 手机 IPv6 入站测试结果(用户 15:47 执行)
+- 用户手机访问 `http://[240e:338:263:3600:e591:cb62:3503:8a23]:38443/` → **打不开**
+- **服务端日志无任何来自手机的记录**(只有本机自测那条)→ 请求**未到达**家里主机
+- ⚠️ 但此结论有**前置条件未排除**:用户手机可能本身没有 IPv6(4G/5G 未开 IPv6、或未关 WiFi),需先做对照测试(手机访问 `ipv6.icanhazip.com` / `tcp6.ping.pe`)才能定论是"网关拦入站"
+
+### 其他
+- 想借本机 **Clash Verge** 代理(127.0.0.1:7897)从境外节点验证入站 → **Clash 未运行(7897 未监听)**,此路不通
+- ⚠️ **腾讯云服务器 SSH 公钥认证被拒**:`ssh -i CentOS.pem -p 22025 xxcool@193.112.118.168` 报 `Permission denied (publickey)`(网络可达、走到认证阶段)。后续所有服务器部署动作会受阻,需与用户确认密钥/密码。
+
+---
+
+## Phase 1 启动:Sunshine 已安装(16:04–16:12)
+
+**类型**:CHG
+
+### 完成项
+- 下载 **Sunshine v2026.906.222525** MSI(33,595,392 B,与官方发布字节数完全一致)+ **Moonlight Portable x64 6.1.0**(29,502,440 B)
+ → 存于 `C:\Users\xxl\Downloads\wb-stream\`
+ → GitHub CDN 直连被打断(exit 56),改用镜像 **`https://ghfast.top/`** 成功,约 700KB/s(`ghproxy.net`、`gh-proxy.com` 也返回 206 可用)
+- 提权安装成功(`Start-Process msiexec -Verb RunAs` + 用户点 UAC),退出码 0
+- 服务 `SunshineService`(`sunshinesvc.exe` / LocalSystem / Auto / **Running**)
+- 编码器探测:**h264_nvenc、hevc_nvenc、av1_nvenc 全部可用**(含 10-bit;AV1 的 YUV444 不支持属正常)
+- 显示器识别:2560×1440 @ 170.002Hz,HAGS 已启用
+- Web UI 凭据已创建:**admin / Sunshine2026**(`POST /api/password`,字段 `newUsername/newPassword/confirmNewPassword`)
+
+### ⚠️ 关键阻塞:Sunshine 默认只监听 IPv4
+- 实测 `TCP 0.0.0.0:47984/47989/47990/48010`;`::1:47990` **连接被积极拒绝** → 完全无 IPv6 绑定
+- 官方文档(docs.lizardbyte.dev v0.23.1 advanced_usage)确认配置项:
+ - **`address_family = both`**(默认 `ipv4`)→ 双栈
+ - `upnp = on` → Sunshine 的 UPnP 实现**含 IPv6 防火墙控制**,可能自动放行网关入站
+- 改法:编辑 `C:\Program Files\Sunshine\config\sunshine.conf` 后重启服务(需管理员);
+ **或走 Web UI**(Configuration → Advanced → Address Family),Web UI 以服务权限运行,**免管理员**
+
+### 工具与安全边界踩坑(重要)
+- `Start-Process powershell.exe -Verb RunAs` 被安全策略拦截("spawns a child process that bypasses PowerShell command validation")
+ → **无法用提权 shell 跑脚本**;提权启动非 shell 的 exe(如 msiexec)可行
+- `curl -u user:pass`(HTTP Basic 凭据)触发**敏感内容审批并超时**;按规则**不重试、不绕过** → 该步交用户手动操作
+- PowerShell 工具默认是**非管理员上下文**:写 `C:\Program Files\` 被拒("访问被拒绝")、`Restart-Service` 报"无法打开服务"
+
+### 入站判定方法论修正
+- 临时监听用 `http.createServer`,**只在收到完整 HTTP 请求时写日志**;ping.pe 的纯 TCP 探测成功**不会留日志**
+- → "ping.pe 显示 Connected" 与 "日志无记录" **可并存、不矛盾**(TCP 层 vs HTTP 层)
+- 用户 16:00 反馈"通了"按 **TCP 层可达**理解
+
+### 用户手机 IPv6 前置条件已排除
+- 手机(关 WiFi / 4G-5G)可访问 `https://ipv6.icanhazip.com/` → 手机确有 IPv6,此前"打不开"的测试**有效**
+- 但 HTTP 请求仍未到达家里 → 差异可能来自手机侧(运营商 IPv6 路由 / 浏览器拦非标端口 http)
+
+### 下一步
+1. 用户在 Web UI 改 `address_family`(+ UPnP),重启服务
+2. 验证出现 `TCP [::]:47989` 等 IPv6 监听
+3. 手机 Moonlight(局域网)先跑通配对,再走外网
+
+---
+
+## 16:10–16:30 公司端自测结果回收 → 路线二次重构(v3)
+
+### ✅ 用户已完成 Sunshine 配置(已验证生效)
+- `C:\Program Files\Sunshine\config\sunshine.conf` = `address_family = both` + `upnp = enabled`
+- 日志确认解析成功:`config: 'address_family' = both` / `config: 'upnp' = enabled`
+- 监听已变双栈:`TCP [::]:47984 / 47989 / 47990 / 48010`(**v2 的"IPv6 阻塞项"彻底解决**)
+- `sunshine_state.json` 已存在(凭据已建)
+- 无害报错:`NvEnc: gpu doesn't support YUV444 encode`(日志自称可忽略)
+- ⚠️ 虚拟手柄不可用:`gamepads.virtualhid-not-available`(需 ViGEmBus 内核驱动,**建议不装**,抬高反作弊风险)
+
+### 🔴 公司端实测(`网络自测结果_20260910_160152.txt`)→ IPv6 路线作废
+- 公司 CPU:Ryzen 7 7840H + **Radeon 780M**;屏幕 **2560×1600@120Hz**(比假设的 60Hz 好一档)
+- **IPv6:本机无 v6 地址,v6 出网两条 DNS 全不通 → 公司是纯 IPv4 网络** ⇒ **IPv6 直连彻底作废**
+- 高位 UDP 可出网:STUN(miwifi / hitv)均有回包,出口 `117.188.24.89`
+- 系统代理开关=0 但服务器填了 `127.0.0.1:7892`(疑似装过 Clash 类客户端,待查)
+- 安全软件:`HipsDaemon`(火绒 HIPS) + `HRWSCCtrl`(火绒安全中心) + `edr_monitor` + `savsvc`
+
+### 🎯 重大纠正:家里 IPv4 是「锥形 NAT」,不是对称 NAT
+- v2 测试方法**错误**:每个 STUN 用新 socket(本地端口变了)→ 映射端口必然不同 → 误得"对称 NAT"
+- **正确方法**(同一 socket 连问多个 STUN):本地 55001 → 恒 `1.204.100.17:3051`;55002 → 恒 `:3063`
+- ⇒ **端点无关映射(Cone/EIM),打洞可行** —— 这是本次最有价值的修正
+- 已写 `%TEMP%\wb_nat\nat_test.py` 作为可复用工具
+
+### 🎯 第二个纠正:v1 脚本的"端口白名单"结论也是误判
+- 靶子无效:`114.114.114.114:8080`(该主机根本没开 8080)、`github.com:443`(国内 DNS 污染)
+- 已重写 `net-test-v2.ps1`(**纯 ASCII、无 BOM、免疫复制粘贴**),靶子换成真在监听的:
+ `www.baidu.com:443`、`www.qq.com:443`、`www.aliyun.com:80`、`193.112.118.168:80/443/22025`、`portquiz.net:443/8080/8443`
+- 本地实跑验证通过(家里侧:全部 OPEN,portquiz 全端口可达)
+- 新增检查项:管理员权限自检 / 火绒·EDR 清单 / 本地代理端口监听 / 已装 VPN·远控软件
+
+### ToDesk 现成可用(重要旁证)
+- **用户此刻正在公司用 ToDesk 控家里**(说明公司↔家里的中转链路已验证可用)
+- 家里这侧日志分析:ToDesk **文件传输**会话为 `planb:1 ... p2p:0`(**走中转,非 P2P**)
+- 控制会话链路类型需看用户侧界面(`.xlog` 是压缩格式,挖不出)→ **已请用户查看界面显示"P2P直连"还是"中转"**
+- ToDesk 的 RTC/QUIC 配置:`default_hard_codec: h265`,`br_limit 50000/100000`
+
+### 技术方案已重排(详见方案文档 v3)
+- 路径 A(首选):**申请公网 IPv4** + 端口转发/UPnP → 公司端**零安装**
+- 路径 B(次选):**Tailscale** 打洞(家里锥形 NAT 是好基础)→ 公司端需管理员+TUN 驱动,可能触发火绒/EDR
+- 路径 C(兜底):ToDesk / UU远程(已验证可连通,但延迟与反作弊风险)
+- 路径 D(保底):腾讯云中继(仅 5 Mbps)
+
+### 下一步(已交用户)
+1. 看 ToDesk 界面显示 P2P直连 还是 中转(5 秒,信息量最大)
+2. 公司电脑跑 `net-test-v2.ps1`,回传 `net-test-v2_*.txt`
+3. 打 10000 申请公网 IPv4
+4. 家里改电源设置(关屏=从不、睡眠=从不)
+
+### 交付物
+- `net-test-v2.ps1`(新增,纯 ASCII)
+- `远程游戏串流方案_家到公司.md` 升级到 **v3**(路线重构 + 三处测试陷阱记录)
+
+---
+
+## 16:30 更新 · 公司端网络身份确认 + ToDesk 反查(本轮)
+
+### 用户提供(ipconfig)
+- 公司笔记本走 **WiFi** 接入企业网,网段 `192.168.250.0/24`,网关 `192.168.250.1`
+- **IPv6 只有 `fe80::` 链路本地地址 → 无 RA 全局地址,纯 IPv4 网络(硬结论,IPv6 路线彻底作废)**
+- 出口 IP `117.188.24.89`,全公司共享同一出口
+
+### AI 从被控端反查 ToDesk(用户无需操作,用户反馈"找不到状态栏")
+- ToDesk 只与自家服务器保持 TCP 长连接:`219.151.138.225:8080`、`219.151.138.225:443`、`118.24.225.33:443`
+- **与公司出口 `117.188.24.89` 无任何 TCP 直连记录**;文件传输会话为 `planb:1 ... p2p:0`(中转)
+- ⭐ **关键利好:公司允许出站到 8080 非标端口** → 公司不是"只放行 80/443"的白名单防火墙;若将来有公网 IPv4,Sunshine 的 47989/47990/48010 大概率可直连,腾讯云 frp 中继(任意端口)也具备可行性
+- 家里 ToDesk 在多个 IPv6 UDP 端点监听(57062–57068,对应 3 个轮换的隐私扩展地址)+ IPv4 `192.168.1.30:57065/57069`;UDP 无连接、端点列表看不到对端,**不能据此断定打洞失败,但打洞信心已下调**
+- 附带结论:家里 IPv6 有多个轮换隐私地址 → IPv6 直连必须配 DDNS(该路线已作废,仅留档)
+
+### 交付 / 变更
+- `net-test-v2.ps1` **已复制到家里主机桌面** `C:\Users\xxl\Desktop\net-test-v2.ps1`(13,690 B),便于用户用 **ToDesk 文件传输**拉到公司电脑,避免复制粘贴损坏
+- 方案文档 §1.6 已补入公司接入方式与 ToDesk 反查结论;第七节 ① 改为"已完成,无需再找状态栏",② 增加 ToDesk 文件传输操作步骤
+- 确认 v2 脚本**已内置** `193.112.118.168:80/443/22025` 的 TCP 连通性测试(可验证公司能否访问腾讯云,为 frp 中继铺路)
+
+### 下一步(用户侧)
+1. 用 ToDesk 文件传输把桌面 `net-test-v2.ps1` 拉到公司电脑并运行 → 回传 `net-test-v2_*.txt`
+2. 若 UDP 打洞数据不乐观 → **优先打 10000 申请公网 IPv4**(路径 A,公司端零安装)
+3. 家里改电源设置(关屏=从不、睡眠=从不)
+
+---
+
+## 16:35 更新 · 公司端 v2 实测结果回执(**路线定案**)
+
+来源:`net-test-v2_20260910_163314.txt`(公司电脑运行 v2 脚本,用户经 ToDesk 文件传输送达)
+
+### 三条硬结论
+
+1. ❌ **公司是对称 NAT**:同一本地 UDP 端口 `46001` 问多个 STUN → 映射端口 `16993/16994/17011/43681` **各不相同**;且**公网出口有多个**(`117.188.24.89` 与 `117.188.118.254` 按目的地分流)→ **P2P 打洞不可行**(v3 曾把 Tailscale 打洞列为次选,本轮判死)
+2. ❌ **公司无 IPv6**(复核 `IPv6 local : NONE`)→ IPv6 直连彻底作废
+3. ✅ **公司 TCP 出站完全自由**:`portquiz.net` 的 `443/8080/8443` 全 OPEN,**连腾讯云 `193.112.118.168:22025` 也 OPEN** → 任意端口的直连/中继"通道"毫无障碍
+
+### 其他实测事实
+
+- **管理员**:当前会话非管理员,但账号 `ybtdevxxl` **在 Administrators 组内** → 可提权
+- **已装软件**:`Clash Verge`、`WireGuard`、`ToDesk`、火绒安全软件
+- **安全软件**:**火绒 `HipsDaemon` / `HRWSCCtrl` 运行中**;`aTrust` / `edr_monitor` / `savsvc` / Defender 全系 **Stopped** → 无强制 EDR 管控
+- **屏幕**:主屏 2560×1600@120(脚本报 `1707×1067` = 150% 缩放后的逻辑值),副屏 1920×1080
+- UDP 出网可用,但 `stun.qq.com:3478` 超时(该服务器自身问题,非本地限制)
+
+### 路线定案(v4)
+
+| 优先 | 路线 | 画质上限 | 公司端代价 |
+|---|---|---|---|
+| **A(主攻)** | **申请公网 IPv4** + 端口转发 / UPnP | **1080p120 满血** | 零安装 |
+| B | 腾讯云 frp 中继(通道已验证可行,卡在 5 Mbps) | 1080p60 | 零安装 |
+| C | 网易UU远程 / ToDesk(兜底) | 延迟 + 反作弊风险 | 零安装 |
+
+**核心洞察**:唯一瓶颈是"**家里没有公网入口**";公司侧出站通道意外宽松。
+
+### 交付
+
+- 方案文档升级到 **v4**:新增 `v3 → v4` 修订摘要、第二章「方案选型(v4 定案)」重排(Tailscale 打洞移入已排除)、第七节改为"主攻公网 IP"并附话术与三种客服应对
+- 待用户执行:**打 10000 申请公网 IPv4**
+
+---
+
+## 16:41 用户提问 · 申请到的会是动态公网 IPv4 吗?
+
+**类型**:REQ(咨询,未实施)
+
+### 答复要点(已交付用户)
+1. **必然是动态的**:家宽走 DHCP 动态分配,运营商不会给家宽静态 IP(静态属企业专线产品,需签约加钱)。10000 能做的只是把会话地址从 CGNAT 池(100.64.x.x)换到真公网池,本质仍是动态。
+2. **动态不影响方案**:电信动态 IP 只要光猫不断线就长期不变(本机出口 `1.204.100.17` 与历史 `1.204.105.116` 同属 1.204 段);再用 `xpcool.com` 子域做 DDNS(DNSPod API + 定时上报脚本,约 5 分钟粒度),Moonlight 端只填域名 → 图示"家里 WAN → DNSPod → 公司 Moonlight"链路。
+3. **比"动态"更该防的三个坑**:
+ - 拿到的仍是 `100.64.x.x` / 私网段 → 挂电话前必须确认"能否被外网直接访问"
+ - 光猫路由模式造成**双层 NAT** → 需光猫改桥接,或光猫内给路由器 WAN 做 DMZ
+ - 家宽普遍封 80/443 → Sunshine 用 47989/47984-48010,**不受影响**
+4. **唯一可靠的验证法**:手机流量查出口 IP,**必须完全等于**路由器 WAN 口 IP(且 WAN IP 非私网段)。
+5. **时机建议**:10000 是 7×24,上班摸鱼时即可打电话登记;但**改光猫/重启必须等回家做** —— 当前唯一远程通道是 ToDesk,一旦改崩断网就连带失去操作通道。
+
+---
+
+## 18:56–19:05 · 公网 IPv4 被拒 → IPv6 路线重启评估(v5)
+
+**类型**:CHG
+
+### 用户输入
+"公网 ipv4 申请不到,ipv6 可以吗" —— 10000 申请公网 IPv4 被拒。
+
+### 本轮实测(家里侧,19:00 复查)
+- **IPv6 出网全通**:`ipv6.icanhazip.com` / `www.taobao.com` / `www.qq.com` 的 TCP 443 全部 OK
+- **IPv6 地址三枚**:
+
+| 地址 | 后缀类型 | 状态 | 用途 |
+|---|---|---|---|
+| `240e:338:263:3600:29f1:e63f:d476:597e` | **Suffix=Link** | Preferred | ✅ **首选服务地址 —— 后缀永不轮换** |
+| `240e:338:263:3600:e591:cb62:3503:8a23` | Suffix=Random | Preferred | ⚠️ 隐私扩展,会轮换 |
+| `240e:338:263:3600:51f3:a026:4256:a337` | Suffix=Random | Deprecated | ❌ 废弃 |
+
+- 默认路由 → `fe80::1`;Public 档防火墙开启,`node.exe` 有 Public/Private 入站 Allow 规则 → 本机侧不拦
+- **监听已重起**:`:::38443 LISTENING`(PID 27508),日志 `%TEMP%\wb_ipv6test\inbound.log` 正常写入
+
+### 关键论证(本轮最重要的一条推理)
+v4 说"IPv6 判死",精确说法应是"**公司那条有线/WiFi 出口没有 IPv6**",而非"公司场景不可用"。
+→ 公司笔记本还有**第二条出口从未被用上:手机 5G 热点**(用户手机实测有 IPv6)。
+→ v5 主攻 = **手机热点补 IPv6 + 家里 IPv6 直连**,公司端仍零安装、画质上限仍是 1080p120。
+
+### v5 路线定案
+
+| 优先 | 路线 | 画质 | 卡点 |
+|---|---|---|---|
+| A(主攻) | 手机热点 + 家里 IPv6 直连 | **1080p120 满血** | ①热点下游能否拿到 IPv6 ②天翼网关是否放行 IPv6 入站 |
+| A′(长期最优,当前失败) | 公网 IPv4 + 端口转发 | 1080p120 | 10000 申请被拒 |
+| B | 腾讯云 frp 中继 | 1080p60(5 Mbps) | — |
+| C | 网易UU远程 / ToDesk | 延迟 + 反作弊风险 | — |
+
+**代价认知**:1080p120 @ 20 Mbps ≈ **9 GB/小时**;5G 延迟预计 **40–70 ms**。
+
+### 交付物
+- `远程游戏串流方案_家到公司.md` → **v5**:新增 v4→v5 摘要、§1.3 重写(IPv6 地址稳定性分析)、§2 选型重排(路径 A 改为手机热点,原 A 降为 A′)、已排除表补入"IPv6 隧道类(6in4/Teredo/WARP)"、§7 重写为两步验证法
+- `hotspot-ipv6-test.ps1`(新增,**纯 ASCII / 无 BOM**,已试跑通过,并复制到家里桌面 `C:\Users\xxl\Desktop\` 供 ToDesk 传输)—— 一次测完"热点下游有无 IPv6 + 能否连到家里"
+- `ipv6-home-qrcode.png`(新增,扫码目标 `http://[240e:338:263:3600:29f1:e63f:d476:597e]:38443/`)
+
+### 本轮踩坑(重要,已沉淀)
+1. **`& script.ps1` 调用脚本文件时,脚本内的写盘会静默失败** —— probe.ps1 / probe2.ps1 都返回 exit 0 却完全没生成结果文件;改**内联命令 + `Set-Content` 落盘**立刻成功。→ 后续所有探测一律内联,不落脚本文件。
+2. **`Test-NetConnection` 对不可达的 IPv6 目标会长时间阻塞**,把整个脚本拖到工具超时(第一次探测就是这样废掉的)→ 改用 `TcpClient.BeginConnect` + `AsyncWaitHandle.WaitOne(ms)` 实现硬超时。
+3. `netsh`/`netstat` 的文本过滤会假阴性(`netstat | ? { $_ -match ':38443' }` 报 NONE,实际 `Get-NetTCPConnection -LocalPort 38443` 显示 `:::38443 Listen`)→ **查监听一律用 `Get-NetTCPConnection`**。
+
+### 待用户执行(决定走哪条路)
+1. **手机 5G(关 WiFi)扫码**访问家里 `http://[240e:338:263:3600:29f1:e63f:d476:597e]:38443/` → 验证家里 IPv6 入站是否放行(此前失败,疑天翼网关拦入站)
+2. **手机开热点 + 公司笔记本连上** → 跑 `hotspot-ipv6-test.ps1` → 验证热点下游能否拿到 IPv6 并连通家里
+3. 若步骤 1 失败 → 按 §7 ② 放行天翼网关 IPv6 防火墙(或光猫改桥接)
diff --git a/.workbuddy/memory/2026-09-13.md b/.workbuddy/memory/2026-09-13.md
new file mode 100644
index 0000000..9d3bd51
--- /dev/null
+++ b/.workbuddy/memory/2026-09-13.md
@@ -0,0 +1,5 @@
+# 2026-09-13
+
+2026-09-13 | CFG | status.xpcool.com:新增 nginx 反代配置(先 HTTP,含 WebSocket 透传),nginx -t 通过并已 reload
+2026-09-13 | DEP | 部署 Uptime Kuma 2.5.3 监控面板:容器 `uptime-kuma`,127.0.0.1:3001,数据 `/data/uptime-kuma`,内存限 512m,healthy
+2026-09-13 | DOC | MEMORY.md 瘦身:生产部署/安全加固/工具链坑位拆到同目录 DEPLOY.md;新增「站点与容器清单」表
diff --git a/.workbuddy/memory/DEPLOY.md b/.workbuddy/memory/DEPLOY.md
new file mode 100644
index 0000000..0361c9f
--- /dev/null
+++ b/.workbuddy/memory/DEPLOY.md
@@ -0,0 +1,55 @@
+# xpcool.com 生产环境 · 部署与运维手册
+
+> 从 MEMORY.md 拆出(2026-09-13),存放服务器、容器、站点、安全加固与本机工具链坑位等细节。
+> MEMORY.md 只保留规则与索引。
+
+## 生产部署(腾讯云 193.112.118.168,SSH 22025/xxcool,Docker+宿主机 Nginx)
+- 站点落点:前端静态 → `/data/www/<域名>/`;nginx 配置 → `/data/nginx/conf.d/<域名>.conf`(80→301→443 + 反代/静态);证书 → `/data/nginx/ssl/<域名>/<域名>_bundle.crt|.key`(本地源:`E:\xxcool\ssl\<域名>_nginx/`)。
+- service 后端容器:`service.xpcool.com:new`,`127.0.0.1:10100:10100`,网络 `xpcool-net`,env:`GF_GCFG_ENV=prod` + `DB_DSN=mysql:service:xxx@tcp(xpcool-mysql:3306)/service?...`(**必须带 mysql: 类型前缀**)+ `JWT_SECRET`;容器内必须有 `manifest/config/config.yaml`(基础配置),否则 GF 启动报「找不到 config」;构建目录 `/data/deploy/projects/service.xpcool.com/`(main + manifest/config + resource,Dockerfile 用 COPY manifest/config 整体拷贝)。
+- DB:`xpcool-mysql` 容器(root 密码见该容器 env),库名 `service`,种子执行顺序 003→004→004b→007(服务器表结构旧,必须先 003 加列;导入用 `sudo sh -c 'docker exec -i xpcool-mysql mysql ... < file'` 宿主机重定向)。
+- 权限映射:admin_menu type=2 行的 path 必须匹配实际路由(现为 `POST /api/service/admin/...`,见 manifest/sql/007_menu_permissions_v3.sql)。
+- 文件下载站(2026-09-03 上线,当晚迁移):`file.xpcool.com` = **FileBrowser Quantum** 容器 `fbq`(`gtstef/filebrowser:stable` v1.5.5,`127.0.0.1:10881→80`,restart unless-stopped,`/data/share:/srv:ro` **只读挂载**,数据卷 `/data/filebrowser-q`:config.yaml+database.db+tmp,属主=镜像 uid1000)+ nginx 反代。**要点**:初始凭据 **admin/admin**(首登后立即改密);配置走 `config.yaml`(sources/cacheDir),管理走网页 UI;API 与原版不兼容(需用户建 long-live token,/swagger 亦需);镜像默认非 root(uid1000);原版 filebrowser(容器 `filebrowser`/10880/db `/data/filebrowser/database`)已于 2026-09-01 归档、现已停用**保留回滚**;Quantum v2.0 beta 权限模型改 per-source(view/download/modify/create/delete)。改密/建只读账号/临时分享(过期+密码+匿名)均在网页 UI。
+- SSH 凭据/密码不入库,需要时询问用户或查对话记忆;远程批量命令用 paramiko 脚本(`%TEMP%\wb_deploy\ssh_exec.py`,支持第 2 参数超时秒数),docker build 用服务器端 nohup+日志轮询防超时。**(2026-09-11 密钥轮换后更新)**:当前登录 = 本地 `~/.ssh/xpcool_ed25519`(ed25519,注释 xpcool-server-20260904)→ `xxcool@193.112.118.168:22025`(root 被拒;密码登录已禁);旧 `CentOS.pem`(RSA,对应服务器公钥 `skey-q6pq6jb3`)**已作废并从 authorized_keys 移除**(因私钥误提交 Gitea 事件而轮换),服务器 authorized_keys 另存 2 把钥(`mcp-server-manager@local`、`xxcoolwork@gmail.com`)为其他用途、保留不动;安全组按出口 IP 限放 22025(本机出口 1.204.105.116 曾因未放行连不上,80/443 可达);本机旧记录 `~/.ssh/id_ed25519` 与 wb_deploy 脚本**在当前设备不存在**。
+- **服务器安全加固(2026-08-27 完成)**:firewalld 已启用(放行 22025/80/443/3000/2222;cockpit/rpcbind 已停);fail2ban 保护 sshd(port=22025, maxretry5/bantime1h);监控脚本 `/usr/local/bin/server-monitor.sh` + cron 每 10 分钟(调度在 **xxcool 的 crontab**:`*/10 * * * * sudo /usr/local/bin/server-monitor.sh`;日志 `/var/log/server-monitor.log`,Bark 推送配 `/etc/server-monitor.conf` 的 `BARK_URL`);**告警去重终版(2026-09-11)**:状态目录 `/var/lib/server-monitor/` —— `banned-ips.state`(已提醒过的封禁 IP,**只增不减 → 同一 IP 永不重复提醒**)、`ssh-src-ips.state`(已提醒过的登录来源 IP,**仅新 IP 首次登录**才提醒)、`ports.state`(当前已告警的未登记端口,消失后清除)、`ssh-brute.state`(爆破告警状态机:`attacking`=本轮已提醒 / `idle`=已复位;攻击持续不刷屏,攻击停止静默复位,**再犯视为新一波才重新提醒**);完整审计仍在 fail2ban.log + journald 且已上报 admin 后台(脚本备份 `server-monitor.sh.bak.20260911`);另有安全日志上报器 `/usr/local/bin/server-security-collector.sh`(cron **每 5 分钟**,xxcool crontab)→ POST 到 `127.0.0.1:10100/api/service/open/security/log/report`,配置 `/etc/server-security.conf`(INTERNAL_TOKEN)、状态 `/var/lib/server-security/last.ts`;dnf-automatic 每天 06:00 自动安全补丁;SSH 密钥登录已部署(现用 `~/.ssh/xpcool_ed25519`)、密码登录已禁。**2026-09-11 加固**:已关闭 `X11Forwarding`(**生效位置是 drop-in `/etc/ssh/sshd_config.d/50-redhat.conf:17`,主配置里那行是注释、改了无效**;改前备份 `.bak.20260911`,`sshd -t` 预检 + `sshd -T` 核对 + `systemctl reload sshd` 不断连);nginx 遗留垃圾已清理(`.code.xpcool.com.conf.swo`、`tool.xpcool.com.conf.download.0` → 移入 `/data/nginx/_junk_backup_20260911/`,未直接删、可回滚)。**nginx 配置目录约定**:`include /data/nginx/conf.d/*.conf;`,故 conf.d 下非 `.conf` 后缀文件不会被加载。**遗留待办**:`lighthouse` 账号(腾讯云自带 uid1000,NOPASSWD ALL)是否锁定待用户决策。**坑**:① 腾讯云安全组未放行 3000/2222/8081,Gitea 走 nginx 反代 80/443;② 改 sshd 前必须先 `sshd -t` 预检+备份(曾因脏行导致 SSH 断开);③ EPEL 源需腾讯云镜像(`mirrors.cloud.tencent.com/epel`)且 baseurl 必须手动写全;④ CentOS9 的 fail2ban 在 EPEL。**⑤(2026-09-04 新坑)腾讯云改带宽若切计费模式会触发实例冷重启,重启后 firewalld 自定义端口规则曾丢失致 SSH 22025 失联(80/443 正常)——firewall-cmd 放行务必带 `--permanent`,且实例冷重启后要复查 22025 放行**。另:带宽 3M→5M 已升级(2026-09-04,实测 619KB/s 满速)。**⑥(2026-09-11 溯源新坑)本机出口 IP `1.204.105.116` 曾于 09-03 23:00 被自己的 fail2ban 封禁、09-04 23:00 才解封——这正是当时「SSH 22025 超时」的真因(当时误判为安全组未放行,绕了远路)。SSH 突然不通时,排查顺序应为:先 `fail2ban-client status sshd` + `grep Ban /var/log/fail2ban.log`,再怀疑安全组**。**⑦(2026-09-11 体检结论)服务器未发现被入侵迹象**:UID0 仅 root、无空密码账号、无异常 SUID/后门/劫持、无特权容器、系统目录近 30 天无改动;外网暴露面实测仅 **22025/80/443**(3000/2222/8081 等均被安全组挡住,firewalld 虽放行 3000/2222 但安全组是第二道闸)。
+- 本地 pnpm 修复(2026-08-27 升级为 wrapper 方案):根因=本环境 MSYS 路径转换故障,把 `/c/...` 参数转成 `E:\c\...`(多 `E:\c\` 前缀)致 corepack shim 与 node 都找不到 pnpm.js。**已建持久 wrapper**:`~/.workbuddy/binaries/node/bin/pnpm`(内容:`export NODE_OPTIONS=--experimental-sqlite; exec "<托管node.exe>" "C:/Users/ybtdevxxl/.workbuddy/binaries/node/workspace/node_modules/pnpm/bin/pnpm.cjs" "$@"`,关键=给 node 传 Windows 风格路径 `C:/...` 规避 MSYS 转换)。**提交/构建前需 `PATH="~/.workbuddy/binaries/node/bin:$PATH"` 前置**,pre-commit 钩子(lefthook oxlint/oxfmt/eslint/stylelint/checkType)即可正常运行;否则钩子崩 → 只能 --no-verify。已验证 pnpm 11.16.0 + `pnpm exec oxlint` 1.79.0。
+
+## 日常同步习惯(建议)
+- 每次工作结束:在各改动项目 `git add + commit`,本工作空间同样提交(规则/日志变化时)。**只 commit、不要 push**(2026-09-01 用户明确:推送由用户自行完成,AI 无需代为 push,避免凭据交互失败)。
+- **验证分工(2026-09-01 用户明确)**:AI 能做的验证(类型检查、构建、单元/二进制测试、自动化冒烟)照做;**AI 不方便验证的部分(真实浏览器交互体验、视觉效果、下载弹窗等)直接交给用户手动验证**,不要反复折腾自动化工具。
+- 换设备开工前:先 `git pull` 各仓库,保证规则与记录最新。
+
+## 本机 Windows/PowerShell 工具链坑位(2026-09-10 实测,写脚本必看)
+- **PowerShell 工具不回显 stdout**:exit code 正常但看不到输出;必须把结果 `Out-File` 落盘,再用 Read 读取。Bash 里调 `powershell.exe` 会被安全策略拒绝。
+- **`[byte] -shl 8` 会溢出归零**(结果仍是 byte 宽度)。字节拼 16 位整数必须先 `[int]` 转换,否则高位丢失(剪贴板/协议解析类代码的高频坑)。
+- **给 .ps1 加 BOM 绝不能用 `Get-Content -Raw` + `Out-File`**:本机默认按 GBK 解码 UTF-8 文件,中文会被永久破坏且不可逆。正确做法是字节级前置 `EF BB BF`(`[System.IO.File]::WriteAllBytes`)。
+- **PATH 被裁剪**:脚本里直接写 `ping` / `netsh` 会报"无法识别",须用 `$env:SystemRoot\System32\xxx.exe` 绝对路径;且不要写 `& $exe args 2>$null | Out-String`(会报"无法在管道中间运行文档"),改用 `$out = & $exe args` 再 `@($out) -join ' '`。
+- **PowerShell 变量名不区分大小写**:`$R` 与循环变量 `$r` 是同一个变量,会导致集合被静默覆盖。
+- **后台监听的进程可能杀不掉**(Stop-Process / taskkill 均无效),换端口重新起更省事。
+- **防火墙规则按可执行文件路径匹配**:一批 node.exe 规则指向已不存在的路径(失效);目前 `C:\Users\xxl\AppData\Roaming\nvm\v23.0.0\node.exe` 有有效的 Public 档「TCP+UDP 任意端口」入站放行规则——需要做**免提权的入站可达性测试**时就用它监听。
+- 测入站**不要用 ping**:Windows 默认拦 ICMPv6/ICMP 入站,必须用 TCP 端口测试。
+- 国内测速:Cloudflare 端点不可用;用 **Ookla 官方 CLI**(`install.speedtest.net` 可直连下载)。
+- 本机网络备注:家里的宽带是**运营商级 NAT(CGNAT,第 2 跳 100.64.0.1)**,无公网 IPv4;但有**公网 IPv6**(`240e:338:263:3600::/64`)。以太网被判定为 Public 网络档。
+
+## 站点与容器清单(2026-09-13 核对)
+> 服务器 193.112.118.168,容器均为 `restart unless-stopped`;除特别标注外只监听 `127.0.0.1`,由宿主机 nginx 反代。
+
+| 域名 | 容器 | 监听 | 说明 |
+|------|------|------|------|
+| admin.xpcool.com | —(静态 `/data/www/`) | — | 管理后台前端 |
+| service.xpcool.com | `service.xpcool.com:new` | 127.0.0.1:10100 | Go 后端(xpcool-net,连 xpcool-mysql) |
+| tool.xpcool.com | —(静态) | — | 前端工具站 |
+| file.xpcool.com | `fbq` | 127.0.0.1:10881 | FileBrowser Quantum 只读下载 |
+| read.xpcool.com | `qread` | 127.0.0.1:8082 | 阅读 legado 自托管 |
+| git.xpcool.com | `gitea` + `gitea-runner` | 0.0.0.0:3000 / 0.0.0.0:2222 | 代码托管与 CI |
+| sub2api.xpcool.com | `sub2api` + postgres + redis | 127.0.0.1:18080 | — |
+| code.xpcool.com | `code-server` | 127.0.0.1:8080 | 网页版 VS Code |
+| **status.xpcool.com** | **`uptime-kuma`** | **127.0.0.1:3001** | **服务监控面板(2026-09-13 新增)** |
+| —(暂无域名) | `bark` | 0.0.0.0:8081 | Bark 推送服务端 |
+| — | `xpcool-mysql` | 127.0.0.1:3306 | MySQL 8.0(库 `service`) |
+
+### Uptime Kuma(status.xpcool.com,2026-09-13 部署)
+- 镜像 `louislam/uptime-kuma:2.5.3`(2.x 稳定线;1.x 已停维护,v2 有破坏性变更,跨大版本需迁移数据库)。
+- 启动参数:`--restart unless-stopped -p 127.0.0.1:3001:3001 -e TZ=Asia/Shanghai -v /data/uptime-kuma:/app/data --memory 512m`。服务器内存仅 3.7G 且 **swap=0**,必须限内存,避免 OOM 波及同机其他容器。
+- **v2 首次启动是「选择数据库」引导页**(逻辑在 `/app/server/setup-database.js`,读 `data/db-config.json`),选 SQLite 即可,之后进入创建管理员账号页。Uptime Kuma **没有找回密码入口**,密码丢失只能改数据库/清 2FA,务必记牢。
+- nginx 反代**必须透传 WebSocket**(`proxy_http_version 1.1` + `Upgrade`/`Connection` 头),否则面板无限重连、状态刷不出来。
+- nginx 配置模板见 `.workbuddy/tmp/status.xpcool.com.conf`。
diff --git a/.workbuddy/memory/MEMORY.md b/.workbuddy/memory/MEMORY.md
index 0a553c5..8daee8f 100644
--- a/.workbuddy/memory/MEMORY.md
+++ b/.workbuddy/memory/MEMORY.md
@@ -1,78 +1,43 @@
# xpcool.com 工作空间 · 统一规则与项目索引
-> 本文件随会话自动加载。保持精简:只放规则与索引,详细历史在各项目 CHANGELOG。
+> 随会话自动加载。本文件只放规则与索引;**生产部署与运维细节见同目录 `DEPLOY.md`**,历史在各项目 CHANGELOG。
## 三条铁律(全项目通用)
-1. 中文注释:写/改代码时,在应有处(函数、复杂逻辑、配置项、非显然分支)加中文注释。
-2. 做记录:每次请求/变更/修复/文档动作,都在对应项目 CHANGELOG.md 顶部追加一条。
-3. 中文优先:与用户的思考、输出、交流,能中文尽量中文。
+1. **中文注释**:写/改代码时,在应有处(函数、复杂逻辑、配置项、非显然分支)加中文注释。
+2. **做记录**:每次请求/变更/修复/文档动作,在对应项目 `CHANGELOG.md` 顶部追加一条;跨项目规则与索引更新写回本目录。
+3. **中文优先**:思考、输出、交流能中文就中文。
-## 接口设计规范(2026-08-27 起 · 全项目强制)
-> 用户拍板:新建 HTTP 接口**全部采用 POST**;**URL 中不得带任何参数**(查询参数与路径参数 `{id}` 都禁止);**所有入参一律通过请求体 body(JSON)传递**。
-- 路由用静态 path(如 `/recruitment/info/update`),`id` 等也放 body,不用 `/update/{id}`。
-- 既适用于 service 后端(g.Meta `method:"post"`),也适用于 admin 前端(`requestClient.post(url, data)`,url 无参)。
-- **首个落地模块**:service.xpcool.com 的 `recruitment`(招聘考试聚合),作为规范范本;后续新接口(含 house 之外的模块)一律遵守,存量接口逐步迁移。
+## 接口设计规范(2026-08-27 起 · 强制)
+- 新建 HTTP 接口**一律 POST**;**URL 不带任何参数**(查询参数、路径参数 `{id}` 都禁止);**入参全部走 body(JSON)**。
+- 路由用静态 path(如 `/recruitment/info/update`),`id` 等也放 body。
+- 同时约束 service 后端(g.Meta `method:"post"`)与 admin 前端(`requestClient.post(url, data)`,url 无参)。
+- 范本模块:service.xpcool.com `recruitment`;存量接口逐步迁移。
## 界面开发约定(2026-08-26 起)
-- **admin.xpcool.com 后台界面开发优先使用 TDesign MCP**(`tdesign-mcp-server`,已配置于 `~/.workbuddy/mcp.json`,`npx -y tdesign-mcp-server@latest`;tools:get-component-docs / get-component-list / get-component-changelog / get-component-dom / search-icons)。
-- 写/改 tdesign 组件代码前,先通过 MCP 查官方文档,用官方封装属性与推荐用法,避免踩自造轮子/已知坑。
-- 树形表格必须用 `t-enhanced-table`(t-table/PrimaryTable 不支持 tree);异步数据后需 `await nextTick()` + `tableRef.value.expandAll()`(defaultExpandAll 只在首次渲染生效)。
+- admin.xpcool.com 写 TDesign 组件前**先用 TDesign MCP 查官方文档**(`tdesign-mcp-server`,配在 `~/.workbuddy/mcp.json`),用官方封装属性,避免自造轮子。
+- 树形表格必须用 `t-enhanced-table`;异步数据后需 `await nextTick()` + `tableRef.value.expandAll()`(`defaultExpandAll` 只在首次渲染生效)。
-## 工作空间说明(git 管理 · 跨设备同步)
-- 本工作空间仓库:`workbuddy.xpcool.com`(含本规则、每日日志、README),远程:`https://git.xpcool.com/xpcool/workbuddy.xpcool.com.git`
-- **布局约定(跨设备必须一致)**:所有项目与工作空间仓库位于同一父目录下:
- ```
- <父目录>/
- ├── workbuddy.xpcool.com/ ← 本工作空间(规则+索引+日志)
- ├── admin.xpcool.com/
- ├── service.xpcool.com/
- └── tool.xpcool.com/
- ```
- 换设备时按此布局 clone,索引即全部生效,无需改任何路径。
+## 工作空间与项目索引
+- 本仓库 `workbuddy.xpcool.com`(规则+索引+每日日志),远程 `https://git.xpcool.com/xpcool/workbuddy.xpcool.com.git`。
+- **布局约定**:所有项目与工作空间位于同一父目录下(`<父目录>/workbuddy.xpcool.com`、`admin.xpcool.com`、`service.xpcool.com`、`tool.xpcool.com`),换设备按此布局 clone 即可,索引全部生效。
-## 项目索引(变更记录在各项目 .workbuddy/memory/CHANGELOG.md)
-| 项目 | 类型 | 相对路径(相对本工作空间) | 远程仓库 |
-|------|------|--------------------------|---------|
-| admin.xpcool.com | Vue 管理后台前端 | ../admin.xpcool.com/.workbuddy/memory/CHANGELOG.md | https://git.xpcool.com/xpcool/admin.xpcool.com.git |
-| service.xpcool.com | Go 后端服务 | ../service.xpcool.com/.workbuddy/memory/CHANGELOG.md | https://git.xpcool.com/xpcool/service.xpcool.com.git |
-| tool.xpcool.com | 前端工具站 | ../tool.xpcool.com/.workbuddy/memory/CHANGELOG.md | https://git.xpcool.com/xpcool/tool.xpcool.com.git |
-(新增项目:在此加一行 + 在其 .workbuddy/memory/ 建 CHANGELOG.md + git.xpcool.com 建仓库)
+| 项目 | 类型 | 变更记录 | 远程仓库 |
+|------|------|---------|---------|
+| admin.xpcool.com | Vue 管理后台前端 | `../admin.xpcool.com/.workbuddy/memory/CHANGELOG.md` | git.xpcool.com/xpcool/admin.xpcool.com.git |
+| service.xpcool.com | Go 后端服务 | `../service.xpcool.com/.workbuddy/memory/CHANGELOG.md` | git.xpcool.com/xpcool/service.xpcool.com.git |
+| tool.xpcool.com | 前端工具站 | `../tool.xpcool.com/.workbuddy/memory/CHANGELOG.md` | git.xpcool.com/xpcool/tool.xpcool.com.git |
-## 记录格式(省 token · 快速命中)
-- 倒序:最新在上,一行一条
-- 模板:`YYYY-MM-DD | 类型 | 一句话摘要`
-- 类型:REQ 请求 / CHG 变更 / FIX 修复 / DOC 文档 / CFG 配置 / DEP 依赖
-- 示例:`2026-08-26 | CHG | 新增用户列表分页接口并补中文注释`
+## 记录格式
+- 倒序、一行一条:`YYYY-MM-DD | 类型 | 一句话摘要`;类型:REQ / CHG / FIX / DOC / CFG / DEP。
## 命中流程
-1. 判断是否涉及某项目(看项目名/路径)。
-2. 读该项目 CHANGELOG.md 头部(最新 10–15 条)了解近况。
-3. 处理后于 CHANGELOG 顶部追加本次记录。
-4. 新增项目时同步更新上方索引表。
+判断涉及哪个项目 → 读该项目 CHANGELOG 头部 10–15 条 → 处理后于顶部追加本次记录 → 新增项目时同步更新上方索引表。
-## 生产部署(腾讯云 193.112.118.168,SSH 22025/xxcool,Docker+宿主机 Nginx)
-- 站点落点:前端静态 → `/data/www/<域名>/`;nginx 配置 → `/data/nginx/conf.d/<域名>.conf`(80→301→443 + 反代/静态);证书 → `/data/nginx/ssl/<域名>/<域名>_bundle.crt|.key`(本地源:`E:\xxcool\ssl\<域名>_nginx/`)。
-- service 后端容器:`service.xpcool.com:new`,`127.0.0.1:10100:10100`,网络 `xpcool-net`,env:`GF_GCFG_ENV=prod` + `DB_DSN=mysql:service:xxx@tcp(xpcool-mysql:3306)/service?...`(**必须带 mysql: 类型前缀**)+ `JWT_SECRET`;容器内必须有 `manifest/config/config.yaml`(基础配置),否则 GF 启动报「找不到 config」;构建目录 `/data/deploy/projects/service.xpcool.com/`(main + manifest/config + resource,Dockerfile 用 COPY manifest/config 整体拷贝)。
-- DB:`xpcool-mysql` 容器(root 密码见该容器 env),库名 `service`,种子执行顺序 003→004→004b→007(服务器表结构旧,必须先 003 加列;导入用 `sudo sh -c 'docker exec -i xpcool-mysql mysql ... < file'` 宿主机重定向)。
-- 权限映射:admin_menu type=2 行的 path 必须匹配实际路由(现为 `POST /api/service/admin/...`,见 manifest/sql/007_menu_permissions_v3.sql)。
-- 文件下载站(2026-09-03 上线,当晚迁移):`file.xpcool.com` = **FileBrowser Quantum** 容器 `fbq`(`gtstef/filebrowser:stable` v1.5.5,`127.0.0.1:10881→80`,restart unless-stopped,`/data/share:/srv:ro` **只读挂载**,数据卷 `/data/filebrowser-q`:config.yaml+database.db+tmp,属主=镜像 uid1000)+ nginx 反代。**要点**:初始凭据 **admin/admin**(首登后立即改密);配置走 `config.yaml`(sources/cacheDir),管理走网页 UI;API 与原版不兼容(需用户建 long-live token,/swagger 亦需);镜像默认非 root(uid1000);原版 filebrowser(容器 `filebrowser`/10880/db `/data/filebrowser/database`)已于 2026-09-01 归档、现已停用**保留回滚**;Quantum v2.0 beta 权限模型改 per-source(view/download/modify/create/delete)。改密/建只读账号/临时分享(过期+密码+匿名)均在网页 UI。
-- SSH 凭据/密码不入库,需要时询问用户或查对话记忆;远程批量命令用 paramiko 脚本(`%TEMP%\wb_deploy\ssh_exec.py`,支持第 2 参数超时秒数),docker build 用服务器端 nohup+日志轮询防超时。**(2026-09-11 密钥轮换后更新)**:当前登录 = 本地 `~/.ssh/xpcool_ed25519`(ed25519,注释 xpcool-server-20260904)→ `xxcool@193.112.118.168:22025`(root 被拒;密码登录已禁);旧 `CentOS.pem`(RSA,对应服务器公钥 `skey-q6pq6jb3`)**已作废并从 authorized_keys 移除**(因私钥误提交 Gitea 事件而轮换),服务器 authorized_keys 另存 2 把钥(`mcp-server-manager@local`、`xxcoolwork@gmail.com`)为其他用途、保留不动;安全组按出口 IP 限放 22025(本机出口 1.204.105.116 曾因未放行连不上,80/443 可达);本机旧记录 `~/.ssh/id_ed25519` 与 wb_deploy 脚本**在当前设备不存在**。
-- **服务器安全加固(2026-08-27 完成)**:firewalld 已启用(放行 22025/80/443/3000/2222;cockpit/rpcbind 已停);fail2ban 保护 sshd(port=22025, maxretry5/bantime1h);监控脚本 `/usr/local/bin/server-monitor.sh` + cron 每 10 分钟(调度在 **xxcool 的 crontab**:`*/10 * * * * sudo /usr/local/bin/server-monitor.sh`;日志 `/var/log/server-monitor.log`,Bark 推送配 `/etc/server-monitor.conf` 的 `BARK_URL`);**告警去重终版(2026-09-11)**:状态目录 `/var/lib/server-monitor/` —— `banned-ips.state`(已提醒过的封禁 IP,**只增不减 → 同一 IP 永不重复提醒**)、`ssh-src-ips.state`(已提醒过的登录来源 IP,**仅新 IP 首次登录**才提醒)、`ports.state`(当前已告警的未登记端口,消失后清除)、`ssh-brute.state`(爆破告警状态机:`attacking`=本轮已提醒 / `idle`=已复位;攻击持续不刷屏,攻击停止静默复位,**再犯视为新一波才重新提醒**);完整审计仍在 fail2ban.log + journald 且已上报 admin 后台(脚本备份 `server-monitor.sh.bak.20260911`);另有安全日志上报器 `/usr/local/bin/server-security-collector.sh`(cron **每 5 分钟**,xxcool crontab)→ POST 到 `127.0.0.1:10100/api/service/open/security/log/report`,配置 `/etc/server-security.conf`(INTERNAL_TOKEN)、状态 `/var/lib/server-security/last.ts`;dnf-automatic 每天 06:00 自动安全补丁;SSH 密钥登录已部署(现用 `~/.ssh/xpcool_ed25519`)、密码登录已禁。**2026-09-11 加固**:已关闭 `X11Forwarding`(**生效位置是 drop-in `/etc/ssh/sshd_config.d/50-redhat.conf:17`,主配置里那行是注释、改了无效**;改前备份 `.bak.20260911`,`sshd -t` 预检 + `sshd -T` 核对 + `systemctl reload sshd` 不断连);nginx 遗留垃圾已清理(`.code.xpcool.com.conf.swo`、`tool.xpcool.com.conf.download.0` → 移入 `/data/nginx/_junk_backup_20260911/`,未直接删、可回滚)。**nginx 配置目录约定**:`include /data/nginx/conf.d/*.conf;`,故 conf.d 下非 `.conf` 后缀文件不会被加载。**遗留待办**:`lighthouse` 账号(腾讯云自带 uid1000,NOPASSWD ALL)是否锁定待用户决策。**坑**:① 腾讯云安全组未放行 3000/2222/8081,Gitea 走 nginx 反代 80/443;② 改 sshd 前必须先 `sshd -t` 预检+备份(曾因脏行导致 SSH 断开);③ EPEL 源需腾讯云镜像(`mirrors.cloud.tencent.com/epel`)且 baseurl 必须手动写全;④ CentOS9 的 fail2ban 在 EPEL。**⑤(2026-09-04 新坑)腾讯云改带宽若切计费模式会触发实例冷重启,重启后 firewalld 自定义端口规则曾丢失致 SSH 22025 失联(80/443 正常)——firewall-cmd 放行务必带 `--permanent`,且实例冷重启后要复查 22025 放行**。另:带宽 3M→5M 已升级(2026-09-04,实测 619KB/s 满速)。**⑥(2026-09-11 溯源新坑)本机出口 IP `1.204.105.116` 曾于 09-03 23:00 被自己的 fail2ban 封禁、09-04 23:00 才解封——这正是当时「SSH 22025 超时」的真因(当时误判为安全组未放行,绕了远路)。SSH 突然不通时,排查顺序应为:先 `fail2ban-client status sshd` + `grep Ban /var/log/fail2ban.log`,再怀疑安全组**。**⑦(2026-09-11 体检结论)服务器未发现被入侵迹象**:UID0 仅 root、无空密码账号、无异常 SUID/后门/劫持、无特权容器、系统目录近 30 天无改动;外网暴露面实测仅 **22025/80/443**(3000/2222/8081 等均被安全组挡住,firewalld 虽放行 3000/2222 但安全组是第二道闸)。
-- 本地 pnpm 修复(2026-08-27 升级为 wrapper 方案):根因=本环境 MSYS 路径转换故障,把 `/c/...` 参数转成 `E:\c\...`(多 `E:\c\` 前缀)致 corepack shim 与 node 都找不到 pnpm.js。**已建持久 wrapper**:`~/.workbuddy/binaries/node/bin/pnpm`(内容:`export NODE_OPTIONS=--experimental-sqlite; exec "<托管node.exe>" "C:/Users/ybtdevxxl/.workbuddy/binaries/node/workspace/node_modules/pnpm/bin/pnpm.cjs" "$@"`,关键=给 node 传 Windows 风格路径 `C:/...` 规避 MSYS 转换)。**提交/构建前需 `PATH="~/.workbuddy/binaries/node/bin:$PATH"` 前置**,pre-commit 钩子(lefthook oxlint/oxfmt/eslint/stylelint/checkType)即可正常运行;否则钩子崩 → 只能 --no-verify。已验证 pnpm 11.16.0 + `pnpm exec oxlint` 1.79.0。
+## 协作约定
+- **只 commit、不要 push**(推送由用户自行完成,避免凭据交互失败)。
+- **验证分工**:AI 能做的(类型检查、构建、冒烟测试、容器健康检查)照做;浏览器交互体验、视觉效果等交给用户手动验证,不反复折腾自动化。
+- 换设备开工前先 `git pull` 各仓库。
+- 服务器操作走 SSH:`193.112.118.168:22025`,用户 `xxcool`,密钥 `~/.ssh/xpcool_ed25519`。**尽量把多个操作合并成一次 SSH 调用**,减少权限弹窗打扰用户。
-## 日常同步习惯(建议)
-- 每次工作结束:在各改动项目 `git add + commit`,本工作空间同样提交(规则/日志变化时)。**只 commit、不要 push**(2026-09-01 用户明确:推送由用户自行完成,AI 无需代为 push,避免凭据交互失败)。
-- **验证分工(2026-09-01 用户明确)**:AI 能做的验证(类型检查、构建、单元/二进制测试、自动化冒烟)照做;**AI 不方便验证的部分(真实浏览器交互体验、视觉效果、下载弹窗等)直接交给用户手动验证**,不要反复折腾自动化工具。
-- 换设备开工前:先 `git pull` 各仓库,保证规则与记录最新。
-
-## 本机 Windows/PowerShell 工具链坑位(2026-09-10 实测,写脚本必看)
-- **PowerShell 工具不回显 stdout**:exit code 正常但看不到输出;必须把结果 `Out-File` 落盘,再用 Read 读取。Bash 里调 `powershell.exe` 会被安全策略拒绝。
-- **`[byte] -shl 8` 会溢出归零**(结果仍是 byte 宽度)。字节拼 16 位整数必须先 `[int]` 转换,否则高位丢失(剪贴板/协议解析类代码的高频坑)。
-- **给 .ps1 加 BOM 绝不能用 `Get-Content -Raw` + `Out-File`**:本机默认按 GBK 解码 UTF-8 文件,中文会被永久破坏且不可逆。正确做法是字节级前置 `EF BB BF`(`[System.IO.File]::WriteAllBytes`)。
-- **PATH 被裁剪**:脚本里直接写 `ping` / `netsh` 会报"无法识别",须用 `$env:SystemRoot\System32\xxx.exe` 绝对路径;且不要写 `& $exe args 2>$null | Out-String`(会报"无法在管道中间运行文档"),改用 `$out = & $exe args` 再 `@($out) -join ' '`。
-- **PowerShell 变量名不区分大小写**:`$R` 与循环变量 `$r` 是同一个变量,会导致集合被静默覆盖。
-- **后台监听的进程可能杀不掉**(Stop-Process / taskkill 均无效),换端口重新起更省事。
-- **防火墙规则按可执行文件路径匹配**:一批 node.exe 规则指向已不存在的路径(失效);目前 `C:\Users\xxl\AppData\Roaming\nvm\v23.0.0\node.exe` 有有效的 Public 档「TCP+UDP 任意端口」入站放行规则——需要做**免提权的入站可达性测试**时就用它监听。
-- 测入站**不要用 ping**:Windows 默认拦 ICMPv6/ICMP 入站,必须用 TCP 端口测试。
-- 国内测速:Cloudflare 端点不可用;用 **Ookla 官方 CLI**(`install.speedtest.net` 可直连下载)。
-- 本机网络备注:家里的宽带是**运营商级 NAT(CGNAT,第 2 跳 100.64.0.1)**,无公网 IPv4;但有**公网 IPv6**(`240e:338:263:3600::/64`)。以太网被判定为 Public 网络档。
+## 生产环境与本机工具链
+→ 见同目录 **`DEPLOY.md`**:服务器/容器/站点与域名清单、nginx 与证书约定、安全加固与告警去重、本机 Windows/PowerShell 工具链坑位。
diff --git a/.workbuddy/tmp/status.xpcool.com.conf b/.workbuddy/tmp/status.xpcool.com.conf
new file mode 100644
index 0000000..da3f587
--- /dev/null
+++ b/.workbuddy/tmp/status.xpcool.com.conf
@@ -0,0 +1,24 @@
+# status.xpcool.com 服务监控面板(Uptime Kuma 2.5.3)
+# 反代后端容器 uptime-kuma:127.0.0.1:3001
+# 2026-09-13 首次部署:先以 HTTP 提供(便于完成初始化与验证)
+# 待腾讯云免费证书到位后,改为「80 -> 301 -> 443」标准形态
+
+server {
+ listen 80;
+ server_name status.xpcool.com;
+
+ # 面板依赖 WebSocket 实时推送状态,必须透传 Upgrade 头,否则页面无限重连
+ location / {
+ proxy_pass http://127.0.0.1:3001;
+ proxy_http_version 1.1;
+ proxy_set_header Upgrade $http_upgrade;
+ proxy_set_header Connection "upgrade";
+ proxy_set_header Host $host;
+ proxy_set_header X-Real-IP $remote_addr;
+ proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
+ proxy_set_header X-Forwarded-Proto $scheme;
+ # 心跳/长轮询连接保持时间,避免面板偶发掉线
+ proxy_read_timeout 300s;
+ proxy_send_timeout 300s;
+ }
+}
diff --git a/hotspot-ipv6-test.ps1 b/hotspot-ipv6-test.ps1
new file mode 100644
index 0000000..e7f72dc
--- /dev/null
+++ b/hotspot-ipv6-test.ps1
@@ -0,0 +1,104 @@
+# ============================================================
+# Hotspot IPv6 readiness test (pure ASCII -- safe to copy/paste)
+#
+# Purpose:
+# 1) After connecting this laptop to a PHONE HOTSPOT,
+# check whether the laptop receives a global IPv6 address.
+# 2) Check whether the HOME host can be reached directly over IPv6.
+#
+# Output: Desktop\hotspot-ipv6-result.txt
+# ============================================================
+
+# --- change these two lines if the home IPv6 address changes ---
+$HOME_V6 = '240e:338:263:3600:29f1:e63f:d476:597e'
+$HOME_PORT = 38443
+# ---------------------------------------------------------------
+
+$desk = [Environment]::GetFolderPath('Desktop')
+if (-not $desk) { $desk = $env:TEMP }
+$o = Join-Path $desk 'hotspot-ipv6-result.txt'
+$L = New-Object System.Collections.Generic.List[string]
+function A($s) { $L.Add([string]$s) }
+function Flush() { $L -join "`r`n" | Set-Content -Path $o -Encoding UTF8 }
+
+$okOut = $false
+$okHome = $false
+
+A ('TIME ' + (Get-Date -Format 'yyyy-MM-dd HH:mm:ss'))
+A ('HOST ' + [System.Net.Dns]::GetHostName())
+A ('RESULT FILE: ' + $o)
+A ''
+
+A '[1] Global IPv6 addresses on this machine'
+$ga = @(Get-NetIPAddress -AddressFamily IPv6 -ErrorAction SilentlyContinue |
+ Where-Object { $_.IPAddress -notlike 'fe80*' -and $_.IPAddress -ne '::1' })
+if ($ga.Count -eq 0) {
+ A ' NONE <-- this network gives NO global IPv6'
+} else {
+ foreach ($a in $ga) {
+ A (" {0} :: {1}/{2} :: Pref={3} Suffix={4} State={5}" -f `
+ $a.InterfaceAlias, $a.IPAddress, $a.PrefixLength, `
+ $a.PrefixOrigin, $a.SuffixOrigin, $a.AddressState)
+ }
+}
+Flush
+
+A ''
+A '[2] IPv6 default route'
+$r = @(Get-NetRoute -AddressFamily IPv6 -DestinationPrefix '::/0' -ErrorAction SilentlyContinue)
+if ($r.Count -eq 0) { A ' NONE' } else {
+ foreach ($x in $r) { A (" {0} -> {1}" -f $x.InterfaceAlias, $x.NextHop) }
+}
+Flush
+
+function Tcp6([string]$h, [int]$port, [int]$ms) {
+ try {
+ $a = @([System.Net.Dns]::GetHostAddresses($h) |
+ Where-Object { $_.AddressFamily -eq [System.Net.Sockets.AddressFamily]::InterNetworkV6 })
+ if ($a.Count -eq 0) { return 'NO-AAAA' }
+ $ad = $a[0]
+ $c = New-Object System.Net.Sockets.TcpClient($ad.AddressFamily)
+ $iar = $c.BeginConnect($ad, $port, $null, $null)
+ if (-not $iar.AsyncWaitHandle.WaitOne($ms, $false)) { $c.Close(); return 'TIMEOUT' }
+ try { $c.EndConnect($iar); $c.Close(); return ('OK ' + $ad.IPAddressToString) }
+ catch { $c.Close(); return ('FAIL ' + $_.Exception.Message) }
+ } catch { return ('ERR ' + $_.Exception.Message) }
+}
+
+A ''
+A '[3] Outbound IPv6 test (decisive for the whole route)'
+foreach ($h in @('ipv6.icanhazip.com', 'www.taobao.com')) {
+ $res = Tcp6 $h 443 4000
+ A (" {0}:443 -> {1}" -f $h, $res)
+ if ($res -like 'OK*') { $okOut = $true }
+}
+Flush
+
+A ''
+A '[4] Direct reach to HOME host over IPv6 (the real goal)'
+$u = 'http://[' + $HOME_V6 + ']:' + $HOME_PORT + '/'
+A (' target = ' + $u)
+try {
+ $r2 = Invoke-WebRequest -Uri $u -UseBasicParsing -TimeoutSec 6
+ A (' HTTP status = ' + $r2.StatusCode)
+ A (' body line 1 = ' + (($r2.Content -split '\r?\n')[0]))
+ if ($r2.StatusCode -eq 200) { $okHome = $true }
+} catch {
+ A (' FAILED: ' + $_.Exception.Message)
+}
+Flush
+
+A ''
+A '===== SUMMARY ====='
+if ($okOut -and $okHome) {
+ A ' PASS: hotspot gives IPv6 AND home host is reachable.'
+ A ' -> The IPv6 direct route is viable from this laptop.'
+} elseif ($okOut -and -not $okHome) {
+ A ' PARTIAL: IPv6 outbound OK, but home host unreachable.'
+ A ' -> Most likely the home gateway blocks inbound IPv6,'
+ A ' or the home IPv6 address/prefix has changed.'
+} else {
+ A ' FAIL: no usable IPv6 outbound on this network.'
+ A ' -> The hotspot handed out no IPv6 (IPv6 sharing may be off).'
+}
+Flush
diff --git a/ipv6-home-qrcode.png b/ipv6-home-qrcode.png
new file mode 100644
index 0000000..3a5fec2
Binary files /dev/null and b/ipv6-home-qrcode.png differ
diff --git a/ipv6-inbound-qrcode.png b/ipv6-inbound-qrcode.png
new file mode 100644
index 0000000..a316954
Binary files /dev/null and b/ipv6-inbound-qrcode.png differ
diff --git a/net-test-v2.ps1 b/net-test-v2.ps1
new file mode 100644
index 0000000..d4ad42b
--- /dev/null
+++ b/net-test-v2.ps1
@@ -0,0 +1,294 @@
+# =====================================================================
+# net-test-v2.ps1 - Company-side network readiness probe (v2)
+# ---------------------------------------------------------------------
+# WHY v2: the v1 test reported "symmetric NAT" and "port whitelist",
+# but BOTH conclusions were wrong, because:
+# * NAT test used a NEW socket (new local port) per STUN
+# server -> mapped ports differ for ANY nat type.
+# * port-whitelist test used targets where the port is
+# simply not listening (114.114.114.114:8080,
+# github.com:443 is DNS-poisoned in CN).
+# This version uses the CORRECT methods.
+# Output is 100% ASCII and carries NO BOM -> immune to copy/paste.
+# =====================================================================
+
+$ErrorActionPreference = 'Continue'
+$Out = New-Object System.Collections.ArrayList
+
+function W {
+ param([string]$T)
+ [void]$Out.Add($T)
+ Write-Host $T
+}
+function Sec {
+ param([string]$T)
+ W ''
+ W ('===== ' + $T + ' =====')
+}
+
+$stamp = Get-Date -Format 'yyyyMMdd_HHmmss'
+$resultFile = Join-Path (Get-Location) ('net-test-v2_' + $stamp + '.txt')
+
+# ---------------------------------------------------------------------
+# Chinese vendor names, built from Unicode code points so that this file
+# stays PURE ASCII (copy/paste-safe) while still matching Chinese names.
+$cnSun = [char]0x5411 + [char]0x65E5 + [char]0x8475 # "sunflower" remote control
+$cnHr = [char]0x706B + [char]0x7ED2 # Huorong
+$cnSang = [char]0x6DF1 + [char]0x4FE1 + [char]0x670D # Sangfor
+$cnQax = [char]0x5947 + [char]0x5B89 + [char]0x4FE1 # QiAnXin
+$cnTq = [char]0x5929 + [char]0x64CE # TianQing
+$cnAsi = [char]0x4E9A + [char]0x4FE1 # AsiaInfo
+$cnLr = [char]0x8054 + [char]0x8F6F # Leagsoft
+$cnUu = 'UU' + [char]0x8FDC + [char]0x7A0B # NetEase UU Remote
+
+W ('net-test-v2 company-side network probe ' + (Get-Date -Format 'yyyy-MM-dd HH:mm:ss'))
+
+# ---------------------------------------------------------------------
+Sec '0. self check'
+$selfBytes = [System.IO.File]::ReadAllBytes($PSCommandPath)
+$hasBom = ($selfBytes.Length -ge 3 -and $selfBytes[0] -eq 0xEF -and $selfBytes[1] -eq 0xBB -and $selfBytes[2] -eq 0xBF)
+W ('script file : ' + $PSCommandPath)
+W ('script has BOM : ' + $hasBom + ' (pure ASCII script -> BOM irrelevant)')
+
+# ---------------------------------------------------------------------
+Sec '1. privileges (need admin to install a VPN driver)'
+$isAdmin = ([Security.Principal.WindowsPrincipal][Security.Principal.WindowsIdentity]::GetCurrent()).IsInRole([Security.Principal.WindowsBuiltInRole]::Administrator)
+W ('running as Administrator : ' + $isAdmin)
+try {
+ $grp = (net localgroup Administrators) 2>$null
+ W ('local Administrators group members:')
+ foreach ($g in $grp) { if ($g.Trim() -ne '') { W (' ' + $g.Trim()) } }
+} catch { W ' (could not query localgroup)' }
+W 'note: if you can install software normally, you usually have admin'
+
+# ---------------------------------------------------------------------
+Sec '2. host info'
+try {
+ $os = Get-CimInstance Win32_OperatingSystem
+ W ('OS : ' + $os.Caption + ' build ' + $os.BuildNumber)
+ W ('RAM : ' + [math]::Round((Get-CimInstance Win32_ComputerSystem).TotalPhysicalMemory / 1GB, 1) + ' GB')
+} catch { W 'OS info failed' }
+try {
+ Get-CimInstance Win32_VideoController | ForEach-Object {
+ W ('GPU : ' + $_.Name + ' | drv ' + $_.DriverVersion)
+ }
+} catch { W 'GPU info failed' }
+try {
+ Add-Type -AssemblyName System.Windows.Forms -ErrorAction SilentlyContinue
+ $scr = [System.Windows.Forms.Screen]::AllScreens
+ foreach ($s in $scr) {
+ W ('SCREEN : ' + $s.Bounds.Width + 'x' + $s.Bounds.Height + ' primary=' + $s.Primary)
+ }
+} catch { W 'screen info failed' }
+
+# ---------------------------------------------------------------------
+Sec '3. IPv6 capability (re-check)'
+$v6 = Get-NetIPAddress -AddressFamily IPv6 -ErrorAction SilentlyContinue |
+ Where-Object { $_.IPAddress -notlike 'fe80*' -and $_.IPAddress -ne '::1' }
+if ($v6) {
+ foreach ($a in $v6) { W ('IPv6 local : ' + $a.IPAddress + ' (' + $a.AddressState + ')') }
+} else {
+ W 'IPv6 local : NONE -> company network is IPv4 only (IPv6 direct path is dead)'
+}
+
+# ---------------------------------------------------------------------
+Sec '4. TCP egress freedom (targets that REALLY listen)'
+function Test-Tcp {
+ param([string]$H, [int]$P, [int]$TimeoutMs = 4000)
+ $res = 'UNKNOWN'
+ try {
+ $ips = [System.Net.Dns]::GetHostAddresses($H) | Where-Object { $_.AddressFamily.ToString() -eq 'InterNetwork' }
+ if (-not $ips) { return 'DNS_FAIL' }
+ $sock = New-Object System.Net.Sockets.Socket('InterNetwork', 'Stream', 'Tcp')
+ $ar = $sock.BeginConnect((New-Object System.Net.IPEndPoint($ips[0], $P)), $null, $null)
+ if ($ar.AsyncWaitHandle.WaitOne($TimeoutMs, $false)) {
+ try { $sock.EndConnect($ar); $res = 'OPEN' }
+ catch { $res = 'REFUSED (path OK, port closed)' }
+ } else {
+ $res = 'TIMEOUT (blocked / dropped)'
+ }
+ $sock.Close()
+ } catch { $res = ('ERR ' + $_.Exception.Message) }
+ return $res
+}
+
+$tcpTargets = @(
+ @('www.baidu.com', 443, 'domestic HTTPS - baseline'),
+ @('www.qq.com', 443, 'domestic HTTPS - baseline'),
+ @('www.aliyun.com', 80, 'domestic HTTP - baseline'),
+ @('193.112.118.168', 80, 'your own server :80'),
+ @('193.112.118.168', 443, 'your own server :443'),
+ @('193.112.118.168', 22025, 'your own server :22025 (non-standard)'),
+ @('portquiz.net', 443, 'portquiz :443 (all-port listener)'),
+ @('portquiz.net', 8080, 'portquiz :8080 (all-port listener)'),
+ @('portquiz.net', 8443, 'portquiz :8443 (all-port listener)')
+)
+foreach ($t in $tcpTargets) {
+ $r = Test-Tcp -H $t[0] -P $t[1]
+ W ('TCP {0,-22} {1,-6} -> {2} [{3}]' -f ($t[0] + ':' + $t[1]), $t[1], $r, $t[2])
+}
+W 'read: if baidu:443 is OPEN then 443 is NOT whitelisted-blocked.'
+W 'read: portquiz.net listens on EVERY port -> OPEN there proves arbitrary ports allowed.'
+
+# ---------------------------------------------------------------------
+Sec '5. UDP egress + CORRECT NAT mapping test'
+function New-StunReq {
+ $b = New-Object byte[] 20
+ $b[0] = 0; $b[1] = 1; $b[2] = 0; $b[3] = 0
+ $b[4] = 0x21; $b[5] = 0x12; $b[6] = 0xA4; $b[7] = 0x42
+ $r = New-Object byte[] 12
+ (New-Object System.Random).NextBytes($r)
+ [Array]::Copy($r, 0, $b, 8, 12)
+ return , $b
+}
+function Invoke-Stun {
+ param($Udp, [string]$H, [int]$P, [int]$TimeoutMs = 3000)
+ try {
+ $ips = [System.Net.Dns]::GetHostAddresses($H) | Where-Object { $_.AddressFamily.ToString() -eq 'InterNetwork' }
+ if (-not $ips) { return @{ S = 'DNS_FAIL' } }
+ $req = New-StunReq
+ [void]$Udp.Send($req, $req.Length, $ips[0].ToString(), $P)
+ $Udp.Client.ReceiveTimeout = $TimeoutMs
+ $remote = New-Object System.Net.IPEndPoint([System.Net.IPAddress]'0.0.0.0', 0)
+ try { $resp = $Udp.Receive([ref]$remote) } catch { return @{ S = 'TIMEOUT' } }
+ if ($resp.Length -lt 20) { return @{ S = 'SHORT' } }
+ for ($i = 0; $i -lt 12; $i++) {
+ if ($resp[8 + $i] -ne $req[8 + $i]) { return @{ S = 'TXID_MISMATCH' } }
+ }
+ $mtype = ([int]$resp[0] * 256) + [int]$resp[1]
+ if ($mtype -ne 0x0101) { return @{ S = ('TYPE_' + $mtype) } }
+ $mlen = ([int]$resp[2] * 256) + [int]$resp[3]
+ $pos = 20
+ $end = 20 + $mlen
+ while ($pos + 4 -le $end) {
+ $atype = ([int]$resp[$pos] * 256) + [int]$resp[$pos + 1]
+ $alen = ([int]$resp[$pos + 2] * 256) + [int]$resp[$pos + 3]
+ if ($atype -eq 0x0020 -and $alen -ge 8 -and $resp[$pos + 5] -eq 1) {
+ $p = (([int]$resp[$pos + 6] * 256) + [int]$resp[$pos + 7]) -bxor 0x2112
+ $b0 = [int]$resp[$pos + 8] -bxor 0x21
+ $b1 = [int]$resp[$pos + 9] -bxor 0x12
+ $b2 = [int]$resp[$pos + 10] -bxor 0xA4
+ $b3 = [int]$resp[$pos + 11] -bxor 0x42
+ return @{ S = 'OK'; IP = ('{0}.{1}.{2}.{3}' -f $b0, $b1, $b2, $b3); Port = $p }
+ }
+ if ($atype -eq 0x0001 -and $alen -ge 8 -and $resp[$pos + 5] -eq 1) {
+ $p = ([int]$resp[$pos + 6] * 256) + [int]$resp[$pos + 7]
+ return @{ S = 'OK'; IP = ('{0}.{1}.{2}.{3}' -f $resp[$pos + 8], $resp[$pos + 9], $resp[$pos + 10], $resp[$pos + 11]); Port = $p }
+ }
+ $pos += 4 + $alen + ((4 - ($alen % 4)) % 4)
+ }
+ return @{ S = 'NO_ATTR' }
+ } catch { return @{ S = ('ERR ' + $_.Exception.Message) } }
+}
+
+$stunList = @(
+ @('stun.miwifi.com', 3478),
+ @('stun.hitv.com', 3478),
+ @('stun.qq.com', 3478),
+ @('stun.l.google.com', 19302),
+ @('stun.nextcloud.com', 443)
+)
+
+$rounds = @()
+foreach ($lp in @(46001, 46002)) {
+ W ('--- round : fixed local UDP port ' + $lp + ' ---')
+ try {
+ $udp = New-Object System.Net.Sockets.UdpClient($lp)
+ } catch {
+ W (' bind local port failed: ' + $_.Exception.Message)
+ continue
+ }
+ $maps = @()
+ foreach ($sv in $stunList) {
+ $r = Invoke-Stun -Udp $udp -H $sv[0] -P $sv[1]
+ if ($r.S -eq 'OK') {
+ W (' {0,-24} -> mapped {1}:{2}' -f ($sv[0] + ':' + $sv[1]), $r.IP, $r.Port)
+ $maps += $r
+ } else {
+ W (' {0,-24} -> {1}' -f ($sv[0] + ':' + $sv[1]), $r.S)
+ }
+ }
+ $udp.Close()
+ $rounds += , @{ LP = $lp; Maps = $maps }
+ W ' => STUN replies received means UDP egress AND UDP return path work.'
+}
+
+W ''
+W '--- NAT verdict ---'
+$verdict = 'UNKNOWN (not enough STUN samples)'
+foreach ($rd in $rounds) {
+ $ports = ($rd.Maps | ForEach-Object { $_.Port } | Sort-Object -Unique)
+ if ($ports.Count -eq 0) { continue }
+ W ('local port ' + $rd.LP + ' -> public ip ' + (($rd.Maps | ForEach-Object { $_.IP } | Sort-Object -Unique) -join ',') + ' mapped ports: ' + ($ports -join ','))
+ if ($ports.Count -eq 1 -and $rd.Maps.Count -ge 2) {
+ $verdict = 'ENDPOINT-INDEPENDENT MAPPING (cone NAT) -> UDP hole punching is FEASIBLE'
+ } elseif ($ports.Count -gt 1) {
+ $verdict = 'SYMMETRIC NAT (mapping changes per destination) -> hole punching unreliable, relay needed'
+ }
+}
+W ('NAT verdict : ' + $verdict)
+
+# ---------------------------------------------------------------------
+Sec '6. local proxy / tunnel clients already present'
+W 'listening local TCP ports (common proxy ports):'
+$common = @(1080, 1081, 1087, 2080, 2081, 7890, 7891, 7892, 7893, 7897, 8080, 8889, 10808, 10809, 10810, 20171)
+$listening = Get-NetTCPConnection -State Listen -ErrorAction SilentlyContinue
+foreach ($p in $common) {
+ $hit = $listening | Where-Object { $_.LocalPort -eq $p }
+ if ($hit) {
+ $procName = ''
+ try { $procName = (Get-Process -Id $hit[0].OwningProcess -ErrorAction SilentlyContinue).ProcessName } catch { }
+ W (' LISTEN ' + $p + ' pid=' + $hit[0].OwningProcess + ' proc=' + $procName)
+ }
+}
+W '(if 7890/7897/7892 etc are LISTENING, a Clash-style proxy client is running)'
+
+W ''
+W 'installed software matching VPN / proxy / remote-control / streaming keywords:'
+$keys = @(
+ 'HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall\*',
+ 'HKLM:\SOFTWARE\WOW6432Node\Microsoft\Windows\CurrentVersion\Uninstall\*',
+ 'HKCU:\SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall\*'
+)
+$pat = 'tailscale|zerotier|wireguard|netbird|clash|v2ray|xray|shadowsocks|trojan|sing-box|mihomo|todesk|sunlogin|anydesk|teamviewer|uu|gameviewer|parsec|moonlight|sunshine|radmin|splashtop|rustdesk|netease'
+$pat = $pat + '|' + $cnSun + '|' + $cnHr + '|' + $cnSang + '|' + $cnQax + '|' + $cnTq + '|' + $cnAsi + '|' + $cnLr + '|' + $cnUu
+$found = @()
+foreach ($k in $keys) {
+ try {
+ Get-ItemProperty $k -ErrorAction SilentlyContinue | ForEach-Object {
+ $n = $_.DisplayName
+ if ($n -and ($n -match $pat)) { $found += $n }
+ }
+ } catch { }
+}
+if ($found.Count -gt 0) {
+ $found | Sort-Object -Unique | ForEach-Object { W (' ' + $_) }
+} else { W ' (none matched)' }
+
+# ---------------------------------------------------------------------
+Sec '7. security / endpoint-management agents (may block drivers or audit traffic)'
+$secPat = 'hipsdaemon|hrwsc|edr|savsvc|sangfor|huorong|qianxin|tianqing|asiainfo|symantec|mcafee|sophos|kaspersky|trend|eset|bitdefender|crowdstrike|sentinel|defender|acmp|leagsoft|atrust|anysec|zhudongfangyu|sysdiag|360'
+$secPat = $secPat + '|' + $cnHr + '|' + $cnSang + '|' + $cnQax + '|' + $cnTq + '|' + $cnAsi + '|' + $cnLr
+$svcs = Get-Service -ErrorAction SilentlyContinue
+foreach ($s in $svcs) {
+ $text = ($s.Name + ' ' + $s.DisplayName)
+ if ($text -match $secPat) {
+ W (' ' + $s.Name + ' | ' + $s.DisplayName + ' | ' + $s.Status)
+ }
+}
+
+# ---------------------------------------------------------------------
+Sec '8. summary'
+W ''
+W ('IPv6 available : ' + ($(if ($v6) { 'YES' } else { 'NO' })))
+W ('Admin rights : ' + $isAdmin)
+W ('NAT verdict : ' + $verdict)
+W ''
+W 'How to read the TCP section:'
+W ' * www.baidu.com:443 OPEN -> 443 egress is fine (v1 result was false)'
+W ' * portquiz.net:* OPEN -> arbitrary TCP ports allowed'
+W ' * everything TIMEOUT -> real port whitelist, relay over 443 only'
+W ''
+
+$Out | Out-File -FilePath $resultFile -Encoding UTF8
+W ('result saved to: ' + $resultFile)
diff --git a/net-test-v2_20260910_163314.txt b/net-test-v2_20260910_163314.txt
new file mode 100644
index 0000000..7707f84
--- /dev/null
+++ b/net-test-v2_20260910_163314.txt
@@ -0,0 +1,97 @@
+net-test-v2 company-side network probe 2026-09-10 16:33:15
+
+===== 0. self check =====
+script file : E:\xxcool\project\workbuddy.xpcool.com\net-test-v2.ps1
+script has BOM : False (pure ASCII script -> BOM irrelevant)
+
+===== 1. privileges (need admin to install a VPN driver) =====
+running as Administrator : False
+local Administrators group members:
+ 别名 Administrators
+ 注释 管理员对计算机/域有不受限制的完全访问权
+ 成员
+ -------------------------------------------------------------------------------
+ Administrator
+ ybtdevxxl
+ 命令成功完成。
+note: if you can install software normally, you usually have admin
+
+===== 2. host info =====
+OS : Microsoft Windows 11 专业版 build 26200
+RAM : 31.2 GB
+GPU : Sharing Monitor | drv 17.1.40.460
+GPU : AMD Radeon 780M Graphics | drv 31.0.14003.38003
+SCREEN : 1707x1067 primary=True
+SCREEN : 1920x1080 primary=False
+
+===== 3. IPv6 capability (re-check) =====
+IPv6 local : NONE -> company network is IPv4 only (IPv6 direct path is dead)
+
+===== 4. TCP egress freedom (targets that REALLY listen) =====
+TCP www.baidu.com:443 443 -> OPEN [domestic HTTPS - baseline]
+TCP www.qq.com:443 443 -> OPEN [domestic HTTPS - baseline]
+TCP www.aliyun.com:80 80 -> OPEN [domestic HTTP - baseline]
+TCP 193.112.118.168:80 80 -> OPEN [your own server :80]
+TCP 193.112.118.168:443 443 -> OPEN [your own server :443]
+TCP 193.112.118.168:22025 22025 -> OPEN [your own server :22025 (non-standard)]
+TCP portquiz.net:443 443 -> OPEN [portquiz :443 (all-port listener)]
+TCP portquiz.net:8080 8080 -> OPEN [portquiz :8080 (all-port listener)]
+TCP portquiz.net:8443 8443 -> OPEN [portquiz :8443 (all-port listener)]
+read: if baidu:443 is OPEN then 443 is NOT whitelisted-blocked.
+read: portquiz.net listens on EVERY port -> OPEN there proves arbitrary ports allowed.
+
+===== 5. UDP egress + CORRECT NAT mapping test =====
+--- round : fixed local UDP port 46001 ---
+ stun.miwifi.com:3478 -> mapped 117.188.24.89:16993
+ stun.hitv.com:3478 -> mapped 117.188.24.89:16994
+ stun.qq.com:3478 -> TIMEOUT
+ stun.l.google.com:19302 -> mapped 117.188.24.89:17011
+ stun.nextcloud.com:443 -> mapped 117.188.118.254:43681
+ => STUN replies received means UDP egress AND UDP return path work.
+--- round : fixed local UDP port 46002 ---
+ stun.miwifi.com:3478 -> mapped 117.188.24.89:17013
+ stun.hitv.com:3478 -> mapped 117.188.24.89:17014
+ stun.qq.com:3478 -> TIMEOUT
+ stun.l.google.com:19302 -> mapped 117.188.24.89:17041
+ stun.nextcloud.com:443 -> mapped 117.188.118.254:43705
+ => STUN replies received means UDP egress AND UDP return path work.
+
+--- NAT verdict ---
+local port 46001 -> public ip 117.188.118.254,117.188.24.89 mapped ports: 16993,16994,17011,43681
+local port 46002 -> public ip 117.188.118.254,117.188.24.89 mapped ports: 17013,17014,17041,43705
+NAT verdict : SYMMETRIC NAT (mapping changes per destination) -> hole punching unreliable, relay needed
+
+===== 6. local proxy / tunnel clients already present =====
+listening local TCP ports (common proxy ports):
+(if 7890/7897/7892 etc are LISTENING, a Clash-style proxy client is running)
+
+installed software matching VPN / proxy / remote-control / streaming keywords:
+ Clash Verge
+ ToDesk
+ WireGuard
+ 火绒安全软件
+
+===== 7. security / endpoint-management agents (may block drivers or audit traffic) =====
+ AMD Crash Defender Service | AMD Crash Defender Service | Running
+ aTrustService | aTrustService | Stopped
+ edr_monitor | edr_monitor | Stopped
+ HipsDaemon | Huorong Internet Security Daemon | Running
+ HRWSCCtrl | Huorong Windows Security Center | Running
+ MDCoreSvc | Microsoft Defender Core 服务 | Stopped
+ mpssvc | Windows Defender Firewall | Running
+ savsvc | EDR Antivirus Service | Stopped
+ Sense | Windows Defender Advanced Threat Protection Service | Stopped
+ WdNisSvc | Microsoft Defender 防病毒网络检查服务 | Stopped
+ WinDefend | Microsoft Defender 防病毒服务 | Stopped
+
+===== 8. summary =====
+
+IPv6 available : NO
+Admin rights : False
+NAT verdict : SYMMETRIC NAT (mapping changes per destination) -> hole punching unreliable, relay needed
+
+How to read the TCP section:
+ * www.baidu.com:443 OPEN -> 443 egress is fine (v1 result was false)
+ * portquiz.net:* OPEN -> arbitrary TCP ports allowed
+ * everything TIMEOUT -> real port whitelist, relay over 443 only
+
diff --git a/net-test.ps1 b/net-test.ps1
new file mode 100644
index 0000000..8d94b78
--- /dev/null
+++ b/net-test.ps1
@@ -0,0 +1,245 @@
+# ============================================================================
+# Network readiness self-test (run on the CLIENT / office PC)
+#
+# Purpose: decide whether this machine can act as a Sunshine + Moonlight
+# streaming client back to the home PC over public IPv6.
+#
+# This file is intentionally PURE ASCII on purpose, so that it survives being
+# copy-pasted between machines. It does NOT require a UTF-8 BOM.
+# All output is plain English.
+#
+# Run:
+# powershell -ExecutionPolicy Bypass -File net-test.ps1
+#
+# A result file is written next to the script:
+# net-test-result_YYYYMMDD_HHmmss.txt
+# ============================================================================
+
+$ErrorActionPreference = 'SilentlyContinue'
+$Sys32 = Join-Path $env:SystemRoot 'System32'
+$netshExe = Join-Path $Sys32 'netsh.exe'
+
+$Report = New-Object System.Collections.ArrayList
+
+function W([string]$Text) {
+ [void]$Report.Add($Text)
+ Write-Host $Text
+}
+function SEC([string]$Title) {
+ W ''
+ W ('===== ' + $Title + ' =====')
+}
+
+# ---- EDIT ME if the home address changes ----------------------------------
+$HomeIPv6 = '240e:338:263:3600:e591:cb62:3503:8a23'
+$HomePort = 38443
+# ---------------------------------------------------------------------------
+
+W '=========================================================='
+W ' Network self-test for remote game streaming (client side)'
+W (' Time: ' + (Get-Date).ToString('yyyy-MM-dd HH:mm:ss'))
+W '=========================================================='
+
+# Self encoding check: proves that a missing BOM cannot break this file.
+try {
+ $sb = [System.IO.File]::ReadAllBytes($MyInvocation.MyCommand.Path)
+ $hasBom = ($sb.Length -ge 3 -and $sb[0] -eq 0xEF -and $sb[1] -eq 0xBB -and $sb[2] -eq 0xBF)
+ if ($hasBom) { W 'Script encoding : UTF-8 with BOM' }
+ else { W 'Script encoding : no BOM (file is pure ASCII - harmless)' }
+} catch { }
+
+# ===================== 1. DEVICE & DISPLAY =====================
+SEC '1. DEVICE & DISPLAY'
+$os = Get-CimInstance Win32_OperatingSystem
+if ($os) { W ('OS : ' + $os.Caption + ' build ' + $os.BuildNumber + ' ' + $os.OSArchitecture) }
+$cpu = Get-CimInstance Win32_Processor | Select-Object -First 1
+if ($cpu) { W ('CPU : ' + ([string]$cpu.Name).Trim() + ' (' + $cpu.NumberOfCores + 'C/' + $cpu.NumberOfLogicalProcessors + 'T)') }
+$cs = Get-CimInstance Win32_ComputerSystem
+if ($cs) { W ('RAM : ' + [math]::Round($cs.TotalPhysicalMemory / 1GB, 1) + ' GB') }
+
+foreach ($g in (Get-CimInstance Win32_VideoController)) {
+ $hz = $g.CurrentRefreshRate
+ if (-not $hz -or $hz -le 0) { $hz = 'unknown' } else { $hz = ($hz.ToString() + 'Hz') }
+ W ('GPU : ' + $g.Name + ' drv ' + $g.DriverVersion + ' ' + $hz)
+}
+
+try {
+ Add-Type -AssemblyName System.Windows.Forms
+ foreach ($s in [System.Windows.Forms.Screen]::AllScreens) {
+ $prim = ''
+ if ($s.Primary) { $prim = ' (primary)' }
+ W ('Display : ' + $s.DeviceName + ' ' + $s.Bounds.Width + 'x' + $s.Bounds.Height + $prim)
+ }
+} catch { }
+
+W 'HINT : a 60Hz panel costs ~17ms per frame. If your panel is 120/144Hz,'
+W ' go to Settings > System > Display > Advanced and set it to max.'
+
+# ===================== 2. IPv6 =====================
+SEC '2. IPv6'
+$v6 = Get-NetIPAddress -AddressFamily IPv6 -ErrorAction SilentlyContinue |
+ Where-Object { $_.IPAddress -notlike 'fe80*' -and $_.IPAddress -ne '::1' -and $_.PrefixOrigin -ne 'WellKnown' }
+if ($v6) {
+ foreach ($a in $v6) { W ('Local IPv6: ' + $a.IPAddress + ' via ' + $a.InterfaceAlias) }
+} else {
+ W 'Local IPv6: NONE -> IPv6 direct connection is NOT possible from this PC'
+}
+
+$egress = ''
+foreach ($u in @('https://api6.ipify.org', 'https://ipv6.icanhazip.com')) {
+ try {
+ $r = Invoke-WebRequest -Uri $u -TimeoutSec 10 -UseBasicParsing
+ if ($r.Content) { $egress = ([string]$r.Content).Trim(); break }
+ } catch { }
+}
+if ($egress) { W ('IPv6 egress: OK your public IPv6 = ' + $egress) }
+else { W 'IPv6 egress: FAIL (no IPv6 route to the internet, or blocked)' }
+
+# ===================== 3. PROXY & SECURITY SOFTWARE =====================
+SEC '3. PROXY & SECURITY SOFTWARE'
+$wOut = & $netshExe winhttp show proxy
+W ('WinHTTP : ' + ((@($wOut) -join ' ') -replace '\s+', ' ').Trim())
+
+$ie = Get-ItemProperty 'HKCU:\Software\Microsoft\Windows\CurrentVersion\Internet Settings' -ErrorAction SilentlyContinue
+if ($ie) { W ('IE proxy : enable=' + $ie.ProxyEnable + ' server=' + $ie.ProxyServer + ' pac=' + $ie.AutoConfigURL) }
+
+$watch = 'zhudongfangyu|HipsDaemon|HipsTray|usysdiag|360|qqpcmgr|QQPCTray|Symantec|McAfee|CrowdStrike|SentinelAgent|Sophos|ekrn|avp|Kaspersky|TmListen|SangFor|EDRAgent|AnyDesk|ToDesk|sunlogin|oray'
+$hits = Get-Process -ErrorAction SilentlyContinue |
+ Where-Object { $_.ProcessName -match $watch } |
+ Select-Object -ExpandProperty ProcessName -Unique
+if ($hits) { foreach ($h in $hits) { W ('Running : ' + $h) } }
+else { W 'Running : no known security / audit / remote-control tool detected' }
+
+# ===================== 4. UDP & STUN (NAT type) =====================
+SEC '4. UDP & STUN (NAT type)'
+try {
+ $udp = New-Object System.Net.Sockets.UdpClient 0
+ $udp.Client.ReceiveTimeout = 3000
+ $dns = [System.Net.IPAddress]::Parse('114.114.114.114')
+ $q = [byte[]](0x12,0x34,0x01,0x00,0x00,0x01,0x00,0x00,0x00,0x00,0x00,0x00,
+ 0x03,0x77,0x77,0x77,0x03,0x63,0x6f,0x6d,0x00,0x00,0x01,0x00,0x01)
+ $ep = New-Object System.Net.IPEndPoint ($dns, 53)
+ [void]$udp.Send($q, $q.Length, $ep)
+ $rm = New-Object System.Net.IPEndPoint ([System.Net.IPAddress]::Any, 0)
+ $resp = $udp.Receive([ref]$rm)
+ $udp.Close()
+ W ('UDP/53 : OK (' + $resp.Length + ' bytes from 114.114.114.114)')
+} catch {
+ W ('UDP/53 : FAILED - high-port UDP may be blocked (' + $_.Exception.Message + ')')
+}
+
+function Invoke-Stun {
+ param([string]$Server, [int]$Port = 3478, [int]$TimeoutMs = 3000)
+ $o = New-Object psobject -Property @{ Server = ($Server + ':' + $Port); Ok = $false; Mapped = ''; Local = 0; Err = '' }
+ try {
+ $ip = ([System.Net.Dns]::GetHostAddresses($Server) |
+ Where-Object { $_.AddressFamily -eq [System.Net.Sockets.AddressFamily]::InterNetwork } |
+ Select-Object -First 1)
+ if (-not $ip) { throw 'dns resolution failed' }
+ $u = New-Object System.Net.Sockets.UdpClient 0
+ $u.Client.ReceiveTimeout = $TimeoutMs
+ $o.Local = $u.Client.LocalEndPoint.Port
+ $ep = New-Object System.Net.IPEndPoint ($ip, $Port)
+ $pkt = New-Object byte[] 20
+ $pkt[0] = 0x00; $pkt[1] = 0x01
+ $pkt[4] = 0x21; $pkt[5] = 0x12; $pkt[6] = 0xA4; $pkt[7] = 0x42
+ $rnd = New-Object System.Random
+ for ($i = 8; $i -lt 20; $i++) { $pkt[$i] = [byte]$rnd.Next(0, 256) }
+ [void]$u.Send($pkt, 20, $ep)
+ $rm2 = New-Object System.Net.IPEndPoint ([System.Net.IPAddress]::Any, 0)
+ $resp = $u.Receive([ref]$rm2)
+ $u.Close()
+ $pos = 20
+ while ($pos + 4 -le $resp.Length) {
+ # NOTE: cast to [int] before shifting - [byte] -shl 8 overflows to 0
+ $atype = ([int]$resp[$pos] -shl 8) -bor [int]$resp[$pos + 1]
+ $alen = ([int]$resp[$pos + 2] -shl 8) -bor [int]$resp[$pos + 3]
+ if ($atype -eq 0x0020 -and $alen -ge 8) {
+ $b = [byte[]]$resp[($pos + 8)..($pos + 11)]
+ $b[0] = [byte]($b[0] -bxor 0x21); $b[1] = [byte]($b[1] -bxor 0x12)
+ $b[2] = [byte]($b[2] -bxor 0xA4); $b[3] = [byte]($b[3] -bxor 0x42)
+ $xp = ((([int]$resp[$pos + 6] -shl 8) -bor [int]$resp[$pos + 7]) -bxor 0x2112)
+ $o.Mapped = (New-Object System.Net.IPAddress (, $b)).ToString() + ':' + $xp
+ break
+ }
+ elseif ($atype -eq 0x0001 -and $alen -ge 8) {
+ $b = [byte[]]$resp[($pos + 8)..($pos + 11)]
+ $mp = (([int]$resp[$pos + 6] -shl 8) -bor [int]$resp[$pos + 7])
+ $o.Mapped = (New-Object System.Net.IPAddress (, $b)).ToString() + ':' + $mp
+ break
+ }
+ $pos += 4 + $alen + ((4 - ($alen % 4)) % 4)
+ }
+ $o.Ok = $true
+ } catch { $o.Err = $_.Exception.Message }
+ return $o
+}
+
+$mappedPorts = @()
+foreach ($srv in @('stun.miwifi.com', 'stun.qq.com', 'stun.hitv.com')) {
+ $r = Invoke-Stun -Server $srv
+ if ($r.Ok -and $r.Mapped -ne '') {
+ W ('STUN ' + $r.Server + ' -> mapped ' + $r.Mapped + ' (local port ' + $r.Local + ')')
+ $mappedPorts += ($r.Mapped.Split(':')[-1])
+ } elseif ($r.Err -ne '') {
+ W ('STUN ' + $r.Server + ' -> failed: ' + $r.Err)
+ } else {
+ W ('STUN ' + $r.Server + ' -> no response')
+ }
+}
+if ($mappedPorts.Count -ge 2) {
+ $uniq = ($mappedPorts | Sort-Object -Unique).Count
+ if ($uniq -eq 1) { W ('NAT type : CONE (identical mapped port) -> UDP hole punching likely OK') }
+ else { W ('NAT type : SYMMETRIC (mapped ports ' + ($mappedPorts -join ', ') + ') -> UDP hole punching difficult') }
+} else {
+ W 'NAT type : UNKNOWN (not enough STUN replies)'
+}
+
+# ===================== 5. HOME REACHABILITY (TCP) =====================
+SEC '5. HOME REACHABILITY (TCP)'
+function Test-Tcp6 {
+ param([string]$Address, [int]$Port, [int]$TimeoutMs = 5000)
+ $c = $null
+ try {
+ $c = New-Object System.Net.Sockets.TcpClient ([System.Net.Sockets.AddressFamily]::InterNetworkV6)
+ $iar = $c.BeginConnect($Address, $Port, $null, $null)
+ $ok = $iar.AsyncWaitHandle.WaitOne($TimeoutMs, $false)
+ if ($ok -and $c.Connected) { return $true }
+ return $false
+ } catch { return $false }
+ finally { if ($c) { $c.Close() } }
+}
+
+if ($HomeIPv6 -ne '') {
+ if (Test-Tcp6 -Address $HomeIPv6 -Port $HomePort) {
+ W ('TCP [' + $HomeIPv6 + ']:' + $HomePort + ' -> OK *** IPv6 INBOUND WORKS ***')
+ W ' => Path 1 (Sunshine + Moonlight over IPv6 direct) is VIABLE.'
+ } else {
+ W ('TCP [' + $HomeIPv6 + ']:' + $HomePort + ' -> FAILED')
+ W ' => Either the home listener is down, or the home gateway blocks IPv6 inbound.'
+ W ' => Fall back to hole punching (Tailscale) or a relay.'
+ }
+} else { W 'Home IPv6 not configured - skipped.' }
+
+# ===================== 6. SUMMARY =====================
+SEC 'SUMMARY'
+$v6ok = ($v6 -ne $null)
+$inOk = $false
+if ($HomeIPv6 -ne '') { $inOk = Test-Tcp6 -Address $HomeIPv6 -Port $HomePort }
+W ('IPv6 address present : ' + $v6ok)
+W ('IPv6 internet egress : ' + $(if ($egress) { 'OK' } else { 'FAIL' }))
+W ('Home IPv6 reachable : ' + $inOk)
+if ($v6ok -and $egress -and $inOk) { W 'VERDICT: good to go - use Moonlight over IPv6 direct.' }
+elseif ($v6ok -and $egress) { W 'VERDICT: IPv6 works but home is not reachable - check home listener/gateway.' }
+else { W 'VERDICT: no usable IPv6 here - the office network likely blocks it.' }
+
+# ===================== WRITE RESULT FILE =====================
+$outFile = Join-Path (Get-Location).Path ('net-test-result_' + (Get-Date).ToString('yyyyMMdd_HHmmss') + '.txt')
+try {
+ $Report | Out-File -FilePath $outFile -Encoding UTF8
+ Write-Host ''
+ Write-Host ('Result saved to: ' + $outFile)
+} catch {
+ Write-Host ''
+ Write-Host ('Could not write result file: ' + $_.Exception.Message)
+}
diff --git a/pingpe-test-qrcode.png b/pingpe-test-qrcode.png
new file mode 100644
index 0000000..fd9c357
Binary files /dev/null and b/pingpe-test-qrcode.png differ
diff --git a/公司电脑网络自测.ps1 b/公司电脑网络自测.ps1
new file mode 100644
index 0000000..b418685
--- /dev/null
+++ b/公司电脑网络自测.ps1
@@ -0,0 +1,259 @@
+# ============================================================
+# 公司电脑网络与设备自测脚本
+# 用途:验证公司电脑能否走通「远程串流家里的电脑」所需条件
+# 用法:先在开始菜单打开 Windows PowerShell,再执行:
+# powershell -ExecutionPolicy Bypass -File "公司电脑网络自测.ps1"
+# 结果:屏幕输出 + 同目录下「网络自测结果_时间戳.txt」
+# ============================================================
+
+$ErrorActionPreference = 'Continue'
+$resultFile = Join-Path $PSScriptRoot ('网络自测结果_' + (Get-Date -Format 'yyyyMMdd_HHmmss') + '.txt')
+# 注意:PowerShell 变量名不区分大小写,这里刻意避开 $r / $R 这类易冲突的名字
+$Lines = New-Object System.Collections.ArrayList
+
+function Add-Line {
+ param([string]$Text)
+ [void]$Lines.Add($Text)
+ Write-Host $Text
+}
+function Add-Section {
+ param([string]$Title)
+ Add-Line ''
+ Add-Line ('===== ' + $Title + ' =====')
+}
+
+# 用绝对路径调用系统命令:某些受管控环境的 PATH 会被裁剪,直接写 ping / netsh 会找不到
+$Sys32 = Join-Path $env:SystemRoot 'System32'
+$pingExe = Join-Path $Sys32 'ping.exe'
+$netshExe = Join-Path $Sys32 'netsh.exe'
+
+# 强制走 IPv6 的 TCP 连通性测试(ICMPv6 常被系统/网关默认拦截,所以必须用 TCP 判断)
+function Test-TcpIPv6 {
+ param([string]$Address, [int]$Port, [int]$TimeoutMs = 5000)
+ $c = $null
+ try {
+ $c = New-Object System.Net.Sockets.TcpClient ([System.Net.Sockets.AddressFamily]::InterNetworkV6)
+ $iar = $c.BeginConnect($Address, $Port, $null, $null)
+ $ok = $iar.AsyncWaitHandle.WaitOne($TimeoutMs, $false)
+ if ($ok -and $c.Connected) { return $true }
+ return $false
+ } catch { return $false }
+ finally { if ($c) { $c.Close() } }
+}
+
+# 把外部命令输出安全地拼成一行文本(命令无输出时返回空串,避免 Join 抛异常)
+function Get-NativeText {
+ param($Output)
+ return (@($Output) -join ' ')
+}
+
+Add-Line ('公司电脑网络自测 ' + (Get-Date -Format 'yyyy-MM-dd HH:mm:ss'))
+
+# ---------- 1. 设备与显示(决定解码能力与串流分辨率) ----------
+Add-Section '1. 设备与显示'
+try {
+ $cpu = Get-CimInstance Win32_Processor | Select-Object -First 1
+ Add-Line ('CPU : ' + $cpu.Name.Trim())
+ $cs = Get-CimInstance Win32_ComputerSystem
+ Add-Line ('内存 : ' + [math]::Round($cs.TotalPhysicalMemory / 1GB, 1) + ' GB')
+ $os = Get-CimInstance Win32_OperatingSystem
+ Add-Line ('系统 : ' + $os.Caption + ' build ' + $os.BuildNumber)
+ foreach ($g in (Get-CimInstance Win32_VideoController)) {
+ Add-Line ('显卡 : ' + $g.Name + ' 驱动 ' + $g.DriverVersion +
+ ' 当前 ' + $g.CurrentHorizontalResolution + 'x' + $g.CurrentVerticalResolution + '@' + $g.CurrentRefreshRate + 'Hz')
+ }
+ Add-Line '提示 : 若屏幕是 120/144Hz,请到「设置-系统-屏幕-高级显示」把刷新率手动调到最高'
+}
+catch { Add-Line ('读取设备信息失败: ' + $_.Exception.Message) }
+
+# ---------- 2. IPv6 能力(最关键的一项:家里只有公网 IPv6 可直连) ----------
+Add-Section '2. IPv6 能力(关键)'
+try {
+ $v6 = Get-NetIPAddress -AddressFamily IPv6 -ErrorAction SilentlyContinue |
+ Where-Object { $_.IPAddress -notlike 'fe80*' -and $_.IPAddress -ne '::1' -and $_.PrefixOrigin -ne 'WellKnown' }
+ if ($v6) {
+ foreach ($a in $v6) { Add-Line ('本机 IPv6 : ' + $a.IPAddress + ' (' + $a.InterfaceAlias + ')') }
+ } else {
+ Add-Line '本机 IPv6 : 无 -> 公司是纯 IPv4 网络,家里 IPv6 直连走不通,只能靠打洞或中转'
+ }
+ # 用 TCP 而不是 ping 来判断:ICMPv6 默认会被 Windows 防火墙拦掉,ping 不通不代表 IPv6 不可用
+ $v6Targets = @(
+ @{ Name = '阿里公共DNS v6 (2400:3200::1:53)'; Addr = '2400:3200::1'; Port = 53 },
+ @{ Name = '电信DNS v6 (240e:4c:4008::1:53)'; Addr = '240e:4c:4008::1'; Port = 53 }
+ )
+ foreach ($t in $v6Targets) {
+ if (Test-TcpIPv6 -Address $t.Addr -Port $t.Port) {
+ Add-Line ('IPv6 出网 : ' + $t.Name + ' -> 通(公司有可用的 IPv6)')
+ } else {
+ Add-Line ('IPv6 出网 : ' + $t.Name + ' -> 不通')
+ }
+ }
+ Add-Line '说明 : 只要有任意一条通,说明公司网络能跑 IPv6,家里的 IPv6 直连方案就有戏'
+}
+catch { Add-Line ('IPv6 检测失败: ' + $_.Exception.Message) }
+
+# ---------- 3. 代理与审计软件初筛(决定会不会被拦) ----------
+Add-Section '3. 代理与审计软件初筛'
+try {
+ $wOut = & $netshExe winhttp show proxy
+ $w = (Get-NativeText $wOut) -replace '\s+', ' '
+ Add-Line ('WinHTTP 代理 : ' + $w.Trim())
+ $envProxy = @()
+ if ($env:HTTP_PROXY) { $envProxy += ('HTTP_PROXY=' + $env:HTTP_PROXY) }
+ if ($env:HTTPS_PROXY) { $envProxy += ('HTTPS_PROXY=' + $env:HTTPS_PROXY) }
+ if ($envProxy.Count -gt 0) { Add-Line ('环境变量代理 : ' + ($envProxy -join '; ')) } else { Add-Line '环境变量代理 : 无' }
+ $ieProxy = Get-ItemProperty 'HKCU:\Software\Microsoft\Windows\CurrentVersion\Internet Settings' -ErrorAction SilentlyContinue
+ if ($ieProxy) {
+ Add-Line ('系统代理 : 开关=' + $ieProxy.ProxyEnable + ' 服务器=' + $ieProxy.ProxyServer + ' PAC=' + $ieProxy.AutoConfigURL)
+ }
+ $keywords = 'Sangfor|深信服|QiAnXin|奇安信|360|TianQing|天擎|NSFOCUS|绿盟|EDR|Sentinel|CrowdStrike|Symantec|McAfee|Sophos|ESET|Kaspersky|卡巴|Huorong|火绒|IP-guard|亿赛通|天锐|网神|亚信|安恒|Forcepoint|Zscaler|Netskope|Umbrella|AnyConnect'
+ $hit = @()
+ $hit += Get-Process -ErrorAction SilentlyContinue | Where-Object { $_.Name -match $keywords }
+ $hit += Get-Service -ErrorAction SilentlyContinue | Where-Object { $_.DisplayName -match $keywords }
+ if ($hit.Count -gt 0) {
+ Add-Line '命中管控/安全类软件(可能拦截远控或加密流量):'
+ $hit | Select-Object -First 12 | ForEach-Object { Add-Line (' - ' + $_.Name) }
+ } else {
+ Add-Line '未发现明显的安全审计类软件'
+ }
+}
+catch { Add-Line ('代理/审计检测失败: ' + $_.Exception.Message) }
+
+# ---------- 4. 出站 TCP 端口自由度(是否只放行 80/443) ----------
+Add-Section '4. 出站 TCP 端口自由度'
+$tcpTargets = @(
+ @{ Name = 'github.com:22 (SSH)'; Host = 'github.com'; Port = 22 },
+ @{ Name = 'github.com:443 (HTTPS)'; Host = 'github.com'; Port = 443 },
+ @{ Name = '114.114.114.114:53 (TCP DNS)'; Host = '114.114.114.114'; Port = 53 },
+ @{ Name = '114.114.114.114:8080'; Host = '114.114.114.114'; Port = 8080 }
+)
+foreach ($t in $tcpTargets) {
+ try {
+ $c = Test-NetConnection -ComputerName $t.Host -Port $t.Port -InformationLevel Quiet -WarningAction SilentlyContinue
+ if ($c) { Add-Line ($t.Name.PadRight(28) + ' -> 通') } else { Add-Line ($t.Name.PadRight(28) + ' -> 不通或被拦') }
+ } catch { Add-Line ($t.Name.PadRight(28) + ' -> 检测异常') }
+}
+Add-Line '说明 : 若只有 443 通、非标端口不通,说明公司做了端口白名单,串流要依赖打洞或中转'
+
+# ---------- 5. UDP 出站能力(串流视频走 UDP,最关键的一测) ----------
+Add-Section '5. UDP 出站能力(关键)'
+
+# 5.1 DNS over UDP 53:最基础的 UDP 通断验证
+try {
+ $udp = New-Object System.Net.Sockets.UdpClient
+ $udp.Client.ReceiveTimeout = 3000
+ $srv = New-Object System.Net.IPEndPoint ([System.Net.IPAddress]::Parse('114.114.114.114'), 53)
+ $q = [byte[]](0xAB,0xCD, 0x01,0x00, 0x00,0x01, 0x00,0x00, 0x00,0x00, 0x00,0x00,
+ 0x02,0x71,0x71, 0x03,0x63,0x6F,0x6D, 0x00, 0x00,0x01, 0x00,0x01)
+ [void]$udp.Send($q, $q.Length, $srv)
+ $remote = New-Object System.Net.IPEndPoint ([System.Net.IPAddress]::Any, 0)
+ $resp = $udp.Receive([ref]$remote)
+ $udp.Close()
+ Add-Line ('UDP/53 (114.114.114.114) -> 通,收到 ' + $resp.Length + ' 字节')
+}
+catch { Add-Line ('UDP/53 -> 不通或被拦 : ' + $_.Exception.Message) }
+
+# 5.2 STUN:验证任意高位 UDP 端口能否出去,并取出 NAT 映射出的公网地址
+function Invoke-StunProbe {
+ param([string]$Server, [int]$Port = 3478, [int]$TimeoutMs = 3000)
+ $res = New-Object psobject -Property @{ Server = ($Server + ':' + $Port); Ok = $false; Mapped = ''; LocalPort = 0; Err = '' }
+ try {
+ $ip = ([System.Net.Dns]::GetHostAddresses($Server) |
+ Where-Object { $_.AddressFamily -eq [System.Net.Sockets.AddressFamily]::InterNetwork } |
+ Select-Object -First 1)
+ if (-not $ip) { throw 'DNS 解析失败' }
+ $udp = New-Object System.Net.Sockets.UdpClient 0
+ $udp.Client.ReceiveTimeout = $TimeoutMs
+ $res.LocalPort = $udp.Client.LocalEndPoint.Port
+ $ep = New-Object System.Net.IPEndPoint ($ip, $Port)
+ $pkt = New-Object byte[] 20
+ $pkt[0] = 0x00; $pkt[1] = 0x01
+ $pkt[4] = 0x21; $pkt[5] = 0x12; $pkt[6] = 0xA4; $pkt[7] = 0x42
+ $rnd = New-Object System.Random
+ for ($i = 8; $i -lt 20; $i++) { $pkt[$i] = [byte]$rnd.Next(0, 256) }
+ [void]$udp.Send($pkt, 20, $ep)
+ $remote = New-Object System.Net.IPEndPoint ([System.Net.IPAddress]::Any, 0)
+ $resp = $udp.Receive([ref]$remote)
+ $udp.Close()
+ $pos = 20
+ while ($pos + 4 -le $resp.Length) {
+ # 注意:必须先把 byte 转成 int 再位移,否则 [byte] -shl 8 会溢出归零
+ $atype = ([int]$resp[$pos] -shl 8) -bor [int]$resp[$pos + 1]
+ $alen = ([int]$resp[$pos + 2] -shl 8) -bor [int]$resp[$pos + 3]
+ if ($atype -eq 0x0020 -and $alen -ge 8) {
+ $b = [byte[]]$resp[($pos + 8)..($pos + 11)]
+ $b[0] = [byte]($b[0] -bxor 0x21)
+ $b[1] = [byte]($b[1] -bxor 0x12)
+ $b[2] = [byte]($b[2] -bxor 0xA4)
+ $b[3] = [byte]($b[3] -bxor 0x42)
+ $xp = ((([int]$resp[$pos + 6] -shl 8) -bor [int]$resp[$pos + 7]) -bxor 0x2112)
+ $res.Mapped = (New-Object System.Net.IPAddress (, $b)).ToString() + ':' + $xp
+ break
+ }
+ elseif ($atype -eq 0x0001 -and $alen -ge 8) {
+ $b = [byte[]]$resp[($pos + 8)..($pos + 11)]
+ $mp = (([int]$resp[$pos + 6] -shl 8) -bor [int]$resp[$pos + 7])
+ $res.Mapped = (New-Object System.Net.IPAddress (, $b)).ToString() + ':' + $mp
+ break
+ }
+ $pos += 4 + $alen + ((4 - ($alen % 4)) % 4)
+ }
+ $res.Ok = $true
+ } catch { $res.Err = $_.Exception.Message }
+ return $res
+}
+
+$stunServers = @('stun.miwifi.com', 'stun.qq.com', 'stun.hitv.com')
+$mappedPorts = @()
+foreach ($s in $stunServers) {
+ $r = Invoke-StunProbe -Server $s
+ if ($r.Ok) {
+ Add-Line ('STUN ' + $r.Server.PadRight(24) + ' -> 通 本地端口 ' + $r.LocalPort + ' 映射为 ' + $r.Mapped)
+ if ($r.Mapped) { $mappedPorts += ($r.Mapped -split ':')[-1] }
+ } else {
+ Add-Line ('STUN ' + $r.Server.PadRight(24) + ' -> 不通 : ' + $r.Err)
+ }
+}
+if ($mappedPorts.Count -ge 2) {
+ $uniq = ($mappedPorts | Sort-Object -Unique).Count
+ if ($uniq -eq 1) {
+ Add-Line 'NAT 判断 : 多次映射端口一致 -> 锥形 NAT,P2P 打洞成功率高(好消息)'
+ } else {
+ Add-Line 'NAT 判断 : 每次映射端口都不同 -> 对称 NAT,P2P 打洞较难,需要中继兜底'
+ }
+}
+Add-Line '说明 : STUN 能通 = 高位 UDP 端口可出网,这是 Sunshine/Moonlight 直连的前提'
+
+# ---------- 6. 到家里的链路(核心结论所在) ----------
+Add-Section '6. 到家里的链路(核心)'
+# 家里主机的公网 IPv6(若网关重新拨号 / 前缀变化,需要重新获取后替换)
+$homeV6 = '240e:338:263:3600:e591:cb62:3503:8a23'
+# 家里的运营商出口 IPv4(100.64 开头 = 运营商级 NAT,无法直连,这是正常现象)
+$homeV4 = ''
+
+if ($homeV6 -ne '') {
+ # 先 TCP 再 ping:TCP 通即可确认入站放行;ping 不通多半只是 ICMPv6 被拦,不必惊慌
+ # 端口 38443 对应家里临时监听;正式启用 Sunshine 后应改成 47989 / 47990
+ if (Test-TcpIPv6 -Address $homeV6 -Port 38443) {
+ Add-Line '家里 IPv6:38443 -> 通 ★ IPv6 入站放行,可以走「最优路径:IPv6 直连」'
+ } else {
+ Add-Line '家里 IPv6:38443 -> 不通(可能网关 IPv6 防火墙拦了入站,或家里测试监听没开)'
+ }
+ $pOut = & $pingExe -6 -n 2 -w 2000 $homeV6
+ $p = Get-NativeText $pOut
+ if ($p -match 'Reply from|来自') { Add-Line 'ping6 家里 -> 通' } else { Add-Line 'ping6 家里 -> 不通(ICMPv6 常被默认拦截,仅供参考)' }
+} else { Add-Line '未填写家里 IPv6,跳过' }
+
+if ($homeV4 -ne '') {
+ $pOut = & $pingExe -n 2 -w 2000 $homeV4
+ $p = Get-NativeText $pOut
+ if ($p -match 'Reply from|来自') { Add-Line 'ping4 家里出口 -> 通' } else { Add-Line 'ping4 家里出口 -> 不通(被运营商 NAT 拦,属正常)' }
+} else { Add-Line '未填写家里 IPv4,跳过' }
+
+# ---------- 收尾 ----------
+Add-Section '自测完成'
+Add-Line '把结果文件发给 AI 助手,据此决定走 IPv6 直连 / 打洞 / 中转。'
+Add-Line ('结果已保存: ' + $resultFile)
+try { $Lines | Out-File -FilePath $resultFile -Encoding UTF8 } catch { Write-Host ('写文件失败: ' + $_.Exception.Message) }
+Write-Host ''
+Write-Host ('结果文件:' + $resultFile)
diff --git a/网络自测结果_20260910_160152.txt b/网络自测结果_20260910_160152.txt
new file mode 100644
index 0000000..41e165c
--- /dev/null
+++ b/网络自测结果_20260910_160152.txt
@@ -0,0 +1,51 @@
+公司电脑网络自测 2026-09-10 16:01:52
+
+===== 1. 设备与显示 =====
+CPU : AMD Ryzen 7 7840H w/ Radeon 780M Graphics
+内存 : 31.2 GB
+系统 : Microsoft Windows 11 专业版 build 26200
+显卡 : Sharing Monitor 驱动 17.1.40.460 当前 x@Hz
+显卡 : AMD Radeon 780M Graphics 驱动 31.0.14003.38003 当前 2560x1600@120Hz
+提示 : 若屏幕是 120/144Hz,请到「设置-系统-屏幕-高级显示」把刷新率手动调到最高
+
+===== 2. IPv6 能力(关键) =====
+本机 IPv6 : 无 -> 公司是纯 IPv4 网络,家里 IPv6 直连走不通,只能靠打洞或中转
+IPv6 出网 : 阿里公共DNS v6 (2400:3200::1:53) -> 不通
+IPv6 出网 : 电信DNS v6 (240e:4c:4008::1:53) -> 不通
+说明 : 只要有任意一条通,说明公司网络能跑 IPv6,家里的 IPv6 直连方案就有戏
+
+===== 3. 代理与审计软件初筛 =====
+WinHTTP 代理 : 褰撳墠鐨?WinHTTP 浠g悊鏈嶅姟鍣ㄨ缃? 鐩存帴璁块棶(娌℃湁浠g悊鏈嶅姟鍣?銆?
+环境变量代理 : 无
+系统代理 : 开关=0 服务器=127.0.0.1:7892 PAC=
+命中管控/安全类软件(可能拦截远控或加密流量):
+ - OneDrive
+ - OneDrive.Sync.Service
+ - edr_monitor
+ - HipsDaemon
+ - HRWSCCtrl
+ - savsvc
+
+===== 4. 出站 TCP 端口自由度 =====
+github.com:22 (SSH) -> 通
+github.com:443 (HTTPS) -> 不通或被拦
+114.114.114.114:53 (TCP DNS) -> 通
+114.114.114.114:8080 -> 不通或被拦
+说明 : 若只有 443 通、非标端口不通,说明公司做了端口白名单,串流要依赖打洞或中转
+
+===== 5. UDP 出站能力(关键) =====
+UDP/53 -> 不通或被拦 : 使用“1”个参数调用“Receive”时发生异常:“由于连接方在一段时间后没有正确答复或连接的主机没有反应,连接尝试失败。”
+STUN stun.miwifi.com:3478 -> 通 本地端口 52662 映射为 117.188.24.89:17002
+STUN stun.qq.com:3478 -> 不通 : 使用“1”个参数调用“Receive”时发生异常:“由于连接方在一段时间后没有正确答复或连接的主机没有反应,连接尝试失败。”
+STUN stun.hitv.com:3478 -> 通 本地端口 55902 映射为 117.188.24.89:17017
+NAT 判断 : 每次映射端口都不同 -> 对称 NAT,P2P 打洞较难,需要中继兜底
+说明 : STUN 能通 = 高位 UDP 端口可出网,这是 Sunshine/Moonlight 直连的前提
+
+===== 6. 到家里的链路(核心) =====
+家里 IPv6:38443 -> 不通(可能网关 IPv6 防火墙拦了入站,或家里测试监听没开)
+ping6 家里 -> 不通(ICMPv6 常被默认拦截,仅供参考)
+未填写家里 IPv4,跳过
+
+===== 自测完成 =====
+把结果文件发给 AI 助手,据此决定走 IPv6 直连 / 打洞 / 中转。
+结果已保存: E:\xxcool\project\workbuddy.xpcool.com\网络自测结果_20260910_160152.txt
diff --git a/远程游戏串流方案_家到公司.md b/远程游戏串流方案_家到公司.md
new file mode 100644
index 0000000..17c7db2
--- /dev/null
+++ b/远程游戏串流方案_家到公司.md
@@ -0,0 +1,710 @@
+# 家庭主机远程串流方案(公司电脑 → 家里主机)
+
+> **目标**:在公司电脑上远程控制家里主机,能正常游玩《英雄联盟》快节奏娱乐模式(海克斯大乱斗类,团战密集、吃操作延迟)。
+> **版本**:v5 | 2026-09-10 | **状态:公网 IPv4 申请被拒 → IPv6 路线重启评估。家里入口已就绪,瓶颈转移到「公司端没有 IPv6 出口」**
+
+---
+
+## v4 → v5 修订摘要(**IPv4 申请失败 → IPv6 路线重启**)
+
+用户反馈:**打 10000 申请公网 IPv4 未成功**(运营商不给家宽公网 IPv4)。于是把 v4 判死的 IPv6 路线重新翻出来,逐项核对前置条件。
+
+| 项 | v4 状态 | v5 实测/修订 |
+|---|---|---|
+| 公网 IPv4 | ✅ 主攻路线 | ❌ **申请被拒** —— 这条路暂时封死 |
+| IPv6 直连 | ❌ "公司无 IPv6,判死" | ⚠️ **重启评估** —— 家里侧条件经复查**全部达标**,卡点收敛为一个非常具体的点:**公司端缺 IPv6 出口** |
+| 家里 IPv6 出网 | 未单独测 | ✅ **19:00 复查全通**:`ipv6.icanhazip.com` / `www.taobao.com` / `www.qq.com` 的 TCP 443 全部 OK |
+| 家里 IPv6 地址 | 只知道有两个随机地址 | ✅ **发现固定后缀地址** `...:29f1:e63f:d476:597e`(`Suffix=Link`,后缀**不轮换**)→ 适合做长期服务地址 + DDNS 目标 |
+| 家里 IPv6 入站 | 未证明(手机测过一次失败) | ⏳ **待复测** —— 最大嫌疑是天翼网关**默认拦截 IPv6 入站**(IPv6 是"无 NAT 直通",放行全靠网关防火墙) |
+| 公司端 IPv6 出口 | 无(硬结论) | 🔑 **新增解法:手机 5G 热点** —— 用户手机实测**有 IPv6**(能开 `ipv6.icanhazip.com`),等于公司笔记本可以"借"一条 IPv6 出口 |
+
+**v5 路线重排:**
+
+| 优先 | 路线 | 画质上限 | 公司端代价 | 卡点 |
+|---|---|---|---|---|
+| **A(主攻)** | **手机 5G 热点 → 公司笔记本获得 IPv6 → 直连家里 IPv6** | **1080p120 满血** | 零安装 | ① 热点下游能否拿到 IPv6 ② 家里网关是否放行入站 |
+| B | 腾讯云 frp 中继(公司 IPv4 → 服务器 → 家里) | 1080p60(受 5 Mbps 限) | 零安装 | 服务器 IPv6 支持 |
+| C | 网易UU远程 / ToDesk | 中继延迟 + 反作弊风险 | 零安装 | 无 |
+
+> **核心洞察**:v4 之后瓶颈从「**家里没有入口**」变成「**公司没有 IPv6 出口**」。
+> 家里其实**有一条干净的入口**(公网 IPv6,无 NAT、只需网关放行),只是公司这侧够不着 —— 而"够不着"这件事,一部手机就能补上。
+
+**方案 A 的两条硬约束(必须提前认清)**
+
+| 约束 | 数值 | 说明 |
+|---|---|---|
+| **流量成本** | 1080p120 @ 20 Mbps ≈ **9 GB/小时** | 这才是手机热点的真正代价。打 3 小时 ≈ 27 GB,必须确认套餐余量或办一张大流量副卡 |
+| **延迟代价** | 预计 **40–70 ms** | 5G 空口 + 跨网(手机运营商 → 电信)。LOL 可接受(普通玩家本身 30–50 ms),但**打不满 120fps 的体感优势** |
+
+---
+
+## v3 → v4 修订摘要(**路线第三次修正,这次是定案**)
+
+公司端 `net-test-v2.ps1` 实测结果回来了(`net-test-v2_20260910_163314.txt`),**三条硬结论**:
+
+| 项 | v3 假设 | v4 实测结论 |
+|---|---|---|
+| 公司出站端口 | 未知,担心是白名单 | ✅ **完全自由**:`portquiz.net` 的 `443/8080/8443` 全 OPEN,连你腾讯云的 **`22025`** 也 OPEN → **任意 TCP 端口出站都能用** |
+| **公司 NAT 类型** | 未知 | ❌ **对称 NAT**(同一本地端口问不同 STUN → 映射端口 `16993/16994/17011` 各不相同)+ **多公网出口**(`117.188.24.89` 与 `117.188.118.254` 同时出现、按目的地分流)→ **P2P 打洞基本不可行** |
+| 公司 IPv6 | v3 已判死 | ❌ 复核确认 `IPv6 local : NONE` |
+| 公司管理员权限 | 未知 | ⚠️ 当前会话**非管理员**,但账号 `ybtdevxxl` **在 Administrators 组内** → 可提权(只需点一次 UAC) |
+| 公司已装软件 | 未知 | 🎁 **已装 `Clash Verge`、`WireGuard`、`ToDesk`、火绒** —— 若将来走隧道,WireGuard 现成、无需再装驱动 |
+| 公司安全软件 | 火绒在跑 | ⚠️ **火绒 `HipsDaemon` / `HRWSCCtrl` 运行中**;其余 EDR(`aTrust` / `edr_monitor` / `savsvc` / Defender 全系)**均为 Stopped** → 没有强制管控在跑,这是好消息 |
+| 公司主屏 | 2560×1600@120 | ✅ 复核一致(脚本报 `1707×1067` 是 **150% 缩放后的逻辑值**,2560÷1.5 = 1707) |
+
+**路线定案:**
+
+| 路线 | 判决 | 依据 |
+|---|---|---|
+| IPv6 直连 | ❌ **死** | 公司无 IPv6 |
+| Tailscale / ZeroTier **打洞** | ❌ **基本死** | 公司**对称 NAT + 多出口 IP**,映射端口不可预测 |
+| **A. 申请公网 IPv4 + Sunshine 直连** | ✅ **主攻** | 公司出站任意端口均可用 → 拿到公网 IP 即可 **1080p120 满血** |
+| B. 腾讯云 frp 中继 | ⚠️ 可立即启用,画质受限 | 公司能连腾讯云任意端口 → 技术上完全通;**但服务器仅 5 Mbps**,只够 1080p60 @ 4~5 Mbps |
+| C. 网易UU远程 | ⚠️ 兜底 | 厂商大带宽中继、不受 NAT 影响、专为游戏优化;代价是反作弊风险 |
+| D. ToDesk | ⚠️ 兜底 | 已实测可连通(走中转),办公向延迟 |
+
+> **一句话总结**:唯一的瓶颈是**家里没有公网 IP**。解决它,一切迎刃而解;不解决,就只能接受「中继 + 降码率」。
+
+---
+
+## v2 → v3 修订摘要(**重要,路线变了**)
+
+公司端自测结果回来后,发现 v2 的核心前提不成立,路线必须再改一次:
+
+| 项 | v2 结论 | v3 修订 |
+|---|---|---|
+| 打通方式 | 公网 **IPv6 直连** | ❌ **作废**:公司网络**完全没有 IPv6**(本机无 IPv6 地址、IPv6 出网两条 DNS 全不通)→ IPv6 直连从"最优解"变成"走不通" |
+| 家里 NAT 类型 | "对称 NAT,打洞困难" | ❌ **v2 测错了**:v2 每个 STUN 用新 socket(新本地端口),映射端口当然不同。**正确测法**(同一 socket 问多个 STUN)结论是 —— **端点无关映射(锥形/Cone NAT),打洞可行** ✅ |
+| 公司端口白名单 | v1 报"443 不通、疑似白名单" | ❌ **v1 测错了**:拿 `114.114.114.114:8080`(该主机根本没开 8080)和 `github.com:443`(国内 DNS 被污染)当靶子。已重做为**有效靶子**测法(见 `net-test-v2.ps1`) |
+| 首选路线 | IPv6 直连 | ✅ **改为二选一**:**A. 申请公网 IPv4 + 端口转发**(若电信批)/ **B. Tailscale 打洞**(家里是锥形 NAT,基础不错) |
+| 公司屏幕 | 待补测 | ✅ **2560×1600 @ 120Hz** → 120Hz 屏是意外之喜,手感延迟少 8ms 左右 |
+| 公司安全软件 | 未知 | ⚠️ 发现企业级管控:**火绒 HipsDaemon / HRWSCCtrl** + `edr_monitor` + `savsvc`(详见 1.6) |
+| 画质定档 | 1080p120 | ✅ 维持 **1920×1080 @ 120fps**(公司屏 16:10,串流按 1920×1080 等比即可) |
+
+---
+
+## 一、实测底账(2026-09-10)
+
+### 1.1 被控端(家 · 本机)硬件
+
+| 项目 | 实测值 | 对串流的意义 |
+|---|---|---|
+| 显卡 | **NVIDIA RTX 4070 SUPER**(驱动 32.0.15.9186) | Ada 架构,NVENC 支持 **H.264 / H.265 / AV1** 硬编,画质天花板很高 |
+| CPU | Ryzen 7 7800X3D(8C/16T) | 游戏性能强,编码走 NVENC 不占 CPU |
+| 内存 | 31.2 GB | 充足 |
+| 系统 | Windows 11 专业版 build 26200 | Sunshine / Moonlight 官方良好支持 |
+| 显示器 | AOC **2560×1440 @ 170Hz** | 可输出 2K 高刷;但按 1080p120 定档更稳 |
+| 网卡 | Realtek 2.5GbE,链路 1Gbps,IP `192.168.1.30` | 有线千兆,本地无瓶颈 |
+| 网关 | `192.168.1.1`(可访问,LuCI 界面) | 见 1.4 |
+
+### 1.2 网络实测 —— 三个决定性发现
+
+| 指标 | 实测值 | 结论 |
+|---|---|---|
+| 下行 | 943.82 Mbps | 千兆宽带(Ookla,电信上海节点 id 3633) |
+| **上行** | **46.36 Mbps** | **画质天花板;可撑 1080p120 甚至 2K120** |
+| 丢包 | **0.0%** | 线路质量好 |
+| 出口 IPv4 | `1.204.100.17`(电信,广东,动态) | ⚠️ 这是**运营商 NAT 的公网出口**,不是你家地址 |
+| **tracert 第 2 跳** | **`100.64.0.1`** | ⚠️⚠️ **运营商级 NAT(CGNAT)实锤** |
+| 出口 IPv6 | **`240e:338:263:3600::/64`** | ✅ **公网 IPv6,可被外部直达(待验证入站放行)** |
+| STUN 映射端口 | ❗v2 报"每次不同"→ **测法有误**;**正确复测(同一 socket 打多个 STUN)**:本地端口 55001 → 恒为 `:3051`;55002 → 恒为 `:3063` | ✅ **端点无关映射(Cone/EIM)**,IPv4 侧打洞**可行**,不是对称 NAT |
+
+**修正后的三条推论**
+
+1. ❌ **IPv4 端口转发仍不可行** —— 你确实没有公网 IPv4(`100.64.0.1` 是运营商大内网,RFC 6598 专用段)
+2. ✅ **IPv4 侧 P2P 打洞是可行的** —— 家里是**锥形 NAT(映射端点无关)**,这是打洞最有利的一类。v2 里"对称 NAT 打洞困难"的结论是**因为测试方法错误**(每问一个 STUN 就换一个新 socket → 本地端口变了,映射端口当然跟着变)。**已用正确方法复测推翻。**
+3. ⚠️ **IPv6 虽然最优,但公司没有** —— 家里有公网 IPv6 这点仍然成立,只是**公司那条网线/WiFi 完全跑不了 IPv6**。
+ 🔄 **v5 修订(措辞必须精确)**:这不等于"IPv6 在公司场景作废",而只是"**公司的那一条网络出口**没有 IPv6"。公司笔记本还有**第二条出口**从未被用上 —— **手机 5G 热点**(用户手机实测有 IPv6)。所以这条路在 v5 复活,详见第二章 A 案。
+
+### 1.3 家里可直接对外服务的 IPv6 地址(**v5 · 19:00 复查更新**)
+
+```
+240e:338:263:3600:29f1:e63f:d476:597e ← ✅ 首选:Suffix=Link,后缀固定不轮换,生命周期 1.21 天(自动续期)
+240e:338:263:3600:e591:cb62:3503:8a23 ← ⚠️ Suffix=Random(隐私扩展地址),会轮换,当前 17.7 小时后过期
+240e:338:263:3600:51f3:a026:4256:a337 ← ❌ Deprecated(已废弃,忽略)
+```
+
+**这次复查最大的收获:找到了一个"后缀永不变化"的地址。**
+
+| 地址组成 | 是否变化 | 说明 |
+|---|---|---|
+| 前缀 `240e:338:263:3600::/64` | ⚠️ **可能变** | 天翼网关 PPPoE 拨号时下发,重拨或运营商侧调整时会变 |
+| 后缀 `29f1:e63f:d476:597e` | ✅ **不变** | 基于网卡标识派生(`Suffix=Link`),网卡不换它就永远不变 |
+
+**这带来一个很实际的好处**:DDNS 只需要盯住**前缀那 4 段**是否变化,Windows 侧什么都不用改(不必关隐私扩展),就能拿到一个长期可寻址的目标地址。
+
+其他实测(19:00):
+- **IPv6 出网正常**:`ipv6.icanhazip.com` / `www.taobao.com` / `www.qq.com` 的 TCP 443 **全部 OK**
+- IPv6 默认路由 → `fe80::1`(网关)
+- Windows 防火墙:Public 档开启(家里以太网属 Public),且 `node.exe` 有 Public/Private **入站 Allow** 规则 → 本机侧不拦
+
+> **v5 补充(路线重启)**:这组地址**重新变成核心资产**。v4 时因为"公司无 IPv6"把它判死,但公司那台笔记本还有一条**从未用上**的出口 —— **手机热点**(见第二章 A 案)。用户手机实测有 IPv6,等于随时能给公司笔记本接上一条 IPv6 通道。
+
+⚠️ 前缀由网关 RA 下发,重拨可能变 → 长期使用必须配 DDNS(见第七章)。
+
+> **⚠️ 仍待证明的一件事**:家里能**出** IPv6(已验证),但**外部能不能进得来**还没证明。IPv6 没有 NAT,"放行全靠网关防火墙"——天翼网关默认**拦截入站**,这是此前手机测试失败的最大嫌疑。验证方法见第七章。
+
+---
+
+### 1.4 网关(路由器)与密码找回
+
+**判定结果**:**中国电信智能网关(天翼网关)**,管理界面为 LuCI 风格(首页 302 到 `/cgi-bin/luci`),网关 MAC `88-C7-8F-A6-BC-43`。
+
+忘记后台密码的**阶梯式解决办法**(从省事到暴力):
+
+1. **看机身贴纸**:找 `useradmin` 和随机的 8 位密码。普通账号能看状态页与 WAN 信息,部分型号也能改端口映射 / IPv6 防火墙
+2. **试超级管理员默认密码**:账号 `telecomadmin`,经典默认 `nE7jA%5m`(华为系天翼网关最常见),其次 `admintelecom`。部分地区出厂就是随机密码(贴在机身或在电信 App 里)
+3. **手机 App**:`小翼管家` / `天翼看家`,可查看网关信息、部分设置,也常能重置管理密码
+4. **打 10000 号**:报宽带账号,让客服重置网关管理密码
+5. **恢复出厂**(reset 孔长按 10 秒):**最后一招**。天翼网关重置后会经 TR-069 自动重新下发业务配置(含宽带账号),多数能自动恢复上网,但有失败风险,需客服兜底
+6. ✅ **其实可以完全绕开路由器后台**:
+ - **IPv6 直连不需要端口转发**(IPv6 无 NAT,只需网关防火墙放行入站)
+ - **组网打洞不需要后台**(Tailscale / ZeroTier)
+ - **商业远控不需要后台**(UU远程 / ToDesk 走它们自己的服务器)
+
+> 结论:**拿不到路由器密码不会阻塞方案**。只有"网关把 IPv6 入站也拦了,且你不同意用打洞/中转"这一种情况才需要密码去放行。
+
+### 1.5 本机环境的重要细节
+
+- Windows 防火墙:Domain=开启 / **Private=关闭** / Public=开启;**以太网被判定为 Public 档**
+- **当前会话不是管理员** → 装 Sunshine、加防火墙规则、改电源计划都需要你点一次 UAC 确认
+- 已存在的远控残留驱动:`Todesk Virtual Display Adapter`、`GameViewer Virtual Display Adapter`(网易UU远程)、`MuMu Virtual Display Adapter`
+- 本机装有 Clash Verge(系统代理 127.0.0.1:7897,当前开关=0)与火绒(HipsDaemon / HRWSCCtrl)
+
+### 1.6 公司端(**v3 已拿到实测结果**)
+
+来源:`网络自测结果_20260910_160152.txt`(公司电脑本地运行 v1 脚本产出)
+
+| 项目 | 实测值 | 结论 |
+|---|---|---|
+| CPU | **AMD Ryzen 7 7840H**(8C/16T,Zen4) | 性能远超解码需求 |
+| 核显 | **AMD Radeon 780M**(RDNA3 / Phoenix,VCN 4.0) | ✅ **支持 AV1 硬解**,也支持 H.265 |
+| 内存 | 31.2 GB | 充足 |
+| 系统 | Windows 11 专业版 build 26200 | 良好 |
+| 屏幕 | **共享显示器 + 2560×1600@120Hz**(另有 `Sharing Monitor` 虚拟项) | ✅ **120Hz**!比 v2 假设的 60Hz 屏好一档,少约 8ms |
+| **IPv6** | **本机无 IPv6 地址;IPv6 出网(阿里 v6 DNS / 电信 v6 DNS)全部不通** | ❌ **企业网是纯 IPv4** → 有线/WiFi 这条出口没有 IPv6(🔄 v5:可改用**手机热点**作为 IPv6 出口) |
+| UDP 出网 | STUN 可用(`stun.miwifi.com`、`stun.hitv.com` 均有响应,本地高位端口 → 公网 `117.188.24.89`) | ✅ **高位 UDP 可出网且能收到回包**,这是打洞/串流的基础 |
+| 系统代理 | 开关=0,但服务器填了 `127.0.0.1:7892` | ⚠️ 公司电脑上可能装过 Clash 类代理客户端(**待查是否在跑、有无可用节点**) |
+| 安全/管控软件 | **`HipsDaemon`(火绒 HIPS 主服务)**、**`HRWSCCtrl`(火绒安全中心)**、`edr_monitor`、`savsvc`、`OneDrive×2` | ⚠️ **有企业级 HIPS/EDR**,装 VPN 驱动(TUN/虚拟网卡)大概率会弹窗告警甚至被拦;也可能记录外联行为 |
+| 出站 TCP 端口自由度 | v1 结论**不可信**(靶子无效,见 v3 摘要) | 🔄 需用 `net-test-v2.ps1` 复测 |
+| 管理员权限 | **待确认** | 决定能不能装 Tailscale(要装 TUN 驱动);Moonlight 便携版不需要管理员 |
+
+**补充实测(2026-09-10 16:28,用户提供 `ipconfig`)—— 接入方式与网络身份已确认**
+
+```
+无线局域网适配器 WLAN:
+ IPv6 地址 . . . . . : fe80::ecdb:9605:2cf8:9a3d%11 <- 仅链路本地,无全局 IPv6
+ IPv4 地址 . . . . . : 192.168.250.84
+ 子网掩码 . . . . . : 255.255.255.0
+ 默认网关. . . . . . : 192.168.250.1
+```
+
+| 项 | 结论 |
+|---|---|
+| 接入方式 | **笔记本 + WiFi 接入企业网**(不是有线) |
+| 内网网段 | `192.168.250.0/24` —— 企业统一网段(家用极少见),网关 `192.168.250.1` |
+| IPv6 | **只有 `fe80::` 链路本地地址,无 RA 全局地址** → 企业网本身不发 IPv6 | 🔄 **v5 精确化**:死的是"**这条企业网出口**",不是"公司场景"。笔记本可另接**手机热点**获得 IPv6(见第二章 A 案) |
+| 出口 IP | `117.188.24.89`(全公司共享同一出口,非公司专属) |
+| 内网 IP 归属 | 由企业网 DHCP 下发,**随时可能变**,不能用作稳定地址 |
+
+> **公司端唯一还缺两块拼图**:① 出站端口是否真被白名单限制;② 是否有管理员权限。两者都能用 `net-test-v2.ps1` 一次性拿到。
+
+**从被控端(家里)反查 ToDesk —— 意外挖到一个好消息(2026-09-10 16:29)**
+
+用户在公司用 ToDesk 控制家里主机,我直接在家里这侧抓了 ToDesk 的实时连接:
+
+```
+# ToDesk 全部对外 TCP(非局域网)
+TCP Established 59695 -> 219.151.138.225:8080 <- 非标端口!
+TCP Established 59665 -> 219.151.138.225:443
+TCP Established 57577 -> 118.24.225.33:443
+
+# 到公司出口 117.188.24.89 的直连:无记录
+# ToDesk UDP 端点(家里侧,多个 IPv6 地址 + IPv4)
+UDP 240e:338:263:3600:e591:cb62:3503:8a23:57068
+UDP 240e:338:263:3600:51f3:a026:4256:a337:57067
+UDP 240e:338:263:3600:29f1:e63f:d476:597e:57066
+UDP 192.168.1.30:57069
+```
+
+两条关键推论:
+
+1. ✅ **公司网络允许出站到非标端口(至少 8080 可用)** —— 这是**分量很重的利好**:说明公司不是"只放行 80/443"的白名单防火墙。若家里将来有公网 IPv4,Sunshine 的 `47989/47990/48010` 这些端口公司大概率能直连;腾讯云 frp 中继(任意端口)也具备可行性。
+2. ⚠️ ToDesk **TCP 层没有与公司出口 IP 的直连**,且文件传输会话为 `planb:1 ... p2p:0`(走中转)。UDP 是无连接的、从端点列表看不到对端,所以**不能据此断定打洞失败**,但至少没有直连证据 → 打洞方案(路径 B)的信心下调。
+
+> 附带发现:家里这台主机的 IPv6 地址有**多个轮换的隐私扩展地址**(同一前缀下 3 条)。这印证了"IPv6 直连必须配 DDNS"的判断(不过该路线已被公司端否决,仅留档)。
+
+---
+
+## 二、方案选型(v5 定案)
+
+**先看硬约束 —— 它们决定一切:**
+
+| 约束 | 事实 | 影响 |
+|---|---|---|
+| 家里无公网 IPv4 | CGNAT(tracert 第 2 跳 `100.64.0.1`),且 **10000 申请被拒** | ❌ 端口转发 / 直连彻底没戏(v5 确认这条路已走不通) |
+| 家里 IPv4 NAT | **锥形 / 端点无关映射(EIM)** | 单看家里是打洞友好,但被下一条堵死 |
+| **公司 NAT 类型** | ❌ **对称 NAT + 多公网出口**(同一本地端口映射到 `16993/16994/17011` 各不相同;出口 IP 出现 `117.188.24.89` 与 `117.188.118.254` 两个) | **对称 + 锥形 组合 → 打洞不可行** |
+| **家里有公网 IPv6** | `240e:338:263:3600::/64`,**19:00 实测出网全通**,且找到**固定后缀地址** | ✅ **v5 主攻:这是家里唯一干净的入口** |
+| 公司(有线/WiFi)无 IPv6 | `IPv6 local : NONE`(企业网不发 RA) | ⚠️ **但不是死路** —— 可借手机热点补出口 |
+| **手机 5G 有 IPv6** | 用户手机实测可打开 `ipv6.icanhazip.com` | ✅ 给公司笔记本补 IPv6 出口的现实手段 |
+| **公司 TCP 出站全自由** | `portquiz.net` 的 `443/8080/8443` 全 OPEN,且能连你腾讯云的 `22025` | ✅ **中继 / 直连方案的"通道"完全没问题** |
+| 公司有 HIPS + EDR | 火绒 `HipsDaemon` / `HRWSCCtrl` **运行中** | ⚠️ 装内核驱动类方案有告警 / 被拦风险 |
+| 家里上行 46 Mbps | Ookla 实测 | 画质天花板由它决定,与走哪条路无关 |
+
+> **核心洞察(v5 更新)**:v4 时瓶颈是「**家里没有入口**」;现在家里**明明有一条干净的入口**(公网 IPv6,无 NAT、只需网关放行),瓶颈转移到了「**公司这侧够不着**」。
+> 而"够不着"这件事 —— **一部手机就能补上**。
+
+### 路径 A(**v5 主攻**):手机热点补 IPv6 + 家里 IPv6 直连
+
+**思路**:公司这条有线/WiFi 出口没有 IPv6,但公司笔记本还有一个网络接口从没用上 —— **WiFi 连自己的手机热点**。手机 5G 实测有 IPv6,热点下游设备通常也能分到 IPv6 前缀。
+
+```
+[家里主机] --公网 IPv6--> 中国电信 IPv6 骨干 <--IPv6-- [手机 5G] --热点--> [公司笔记本 · Moonlight]
+ ▲
+ 唯一门槛:天翼网关放行 IPv6 入站
+```
+
+- **优点**
+ - **公司端零安装**:Moonlight 便携版即可 —— 不装驱动、不碰火绒、不要管理员
+ - **画质上限最高**:家里 46 Mbps 上行全用得上 → **1080p120 满血**
+ - **零月费**(只有流量开销),不依赖任何第三方服务、不需要备案域名、不受反作弊厂商策略影响
+- **代价(必须提前认清)**
+ - ⚠️ **流量**:1080p120 @ 20 Mbps ≈ **9 GB/小时**;打 3 小时 ≈ 27 GB。**建议单独办一张大流量副卡**
+ - ⚠️ **延迟**:5G 空口 + 跨网(手机运营商 → 电信),预计 **40–70 ms**。LOL 可接受(普通玩家本身 30–50 ms),但吃不满 120fps 的体感优势
+ - ⚠️ **稳定性**:5G 信号波动会直接反映到画面码率上
+- **两个前置条件(必须先验证,步骤见第七章)**
+ 1. 手机热点**下游设备**能否拿到 IPv6 全局地址(部分手机/ROM 默认只共享 IPv4)
+ 2. 天翼网关是否**放行 IPv6 入站**(默认拦截,这是此前手机 4G 测试失败的最大嫌疑)
+
+### 路径 A′(**已确认失败,但保留为长期最优解**):申请公网 IPv4 + 端口转发
+
+- 原为 v4 主攻路线,**用户打 10000 申请未成功** —— 运营商不给家宽公网 IPv4
+- 若日后政策松动、或换运营商/换套餐,可立刻复活:拿到公网 IPv4 → 网关端口转发(Sunshine 的 `upnp = enabled` 已开,甚至不用登网关后台)
+- **它是延迟最低、画质最好、对反作弊最友好的方案**,只是当前拿不到
+
+### 路径 B(**可立即启用**):腾讯云 frp 中继
+
+- 公司端实测**能连 `193.112.118.168:22025`** → **中继通道完全可行**(frp 走任意端口都行)
+- ⚠️ **5 Mbps 是硬伤**:只够 1080p60 @ 4~5 Mbps,画质明显缩水,达不到"画质好"
+- 升级选项与代价:按带宽升级(如 20 Mbps)月费上升;**按流量计费更不可取** —— 1080p120 @ 20 Mbps ≈ **9 GB/小时**,长期串流成本无法承受
+- 定位:**先跑起来、验证链路与手感**的过渡方案,同时等公网 IP
+
+### 路径 C(**兜底**):网易UU远程 / ToDesk(两端都已有痕迹)
+
+- 10 分钟可用、**不受 CGNAT / 对称 NAT / 端口限制**(走厂商国内中转,且厂商带宽远大于自建中继)
+- 网易UU远程是**专为游戏**设计的远控,比 ToDesk 更贴合"打游戏"场景
+- 延迟约 30–60ms → 办公够用,**打大乱斗团战会有可感知的跟手差异**
+- ⚠️ 反作弊风险最高档(见第五节)
+- 定位:**A 未获批、B 画质不够时的现实选择**,或临时救急
+
+### ❌ 已排除
+
+| 方案 | 排除原因 |
+|---|---|
+| **Tailscale / ZeroTier 打洞** | ❌ **公司是对称 NAT + 多出口** → 打洞不可行(v3 曾列为次选,v4 判死);退化走境外 DERP 中继延迟极差 |
+| IPv6 直连(**公司有线/WiFi 这条出口**) | 企业网不发 IPv6 —— 🔄 v5 精确化:可用**手机热点**绕过,见路径 A |
+| IPv4 端口转发(**当前状态**) | 家里是 CGNAT,且 **10000 申请公网 IPv4 被拒** → 当前不可用(保留为路径 A′) |
+| **IPv6 隧道类**(HE 6in4 / Teredo / Cloudflare WARP) | 6in4 需放行 **IP 协议 41**(企业防火墙几乎不可能放);Teredo / WARP 回程绕行,延迟 150 ms+ → 办公凑合、**打游戏不可用** |
+| Windows 远程桌面(RDP) | 无 GPU 画面、刷新率锁 30、输入延迟高,不能打游戏 |
+| TeamViewer / AnyDesk | 办公向设计,帧率与延迟都不达标 |
+| 大带宽按流量中继 | 1080p120 约 9 GB/小时,流量成本不可持续 |
+
+---
+
+## 三、目标参数(**1080p120 定档**)
+
+| 用途 | 分辨率 | 帧率 | 编码 | 码率 | 上行占用 |
+|---|---|---|---|---|---|
+| **首发默认** | 1920×1080 | **120** | **H.265** | **22 Mbps** | 47% |
+| 优化后(首选) | 1920×1080 | 120 | **AV1** | 16–18 Mbps | 35–39% |
+| 弱网兜底 | 1920×1080 | 60 | H.265 | 10 Mbps | 22% |
+| 有余力再上 | 2560×1440 | 120 | AV1 | 30 Mbps | 65% |
+| ❌ 不推荐 | 2560×1440 | 170 | AV1 | 45+ Mbps | ~100% |
+
+**编码策略**:先用 **H.265** 跑通(兼容性最稳)→ 确认稳定后切 **AV1**(公司端 780M 支持 AV1 硬解,同画质可省约 20–30% 码率,画面更干净)。
+
+---
+
+## 四、延迟预算(目标 < 35ms)
+
+| 环节 | 耗时 | 说明 |
+|---|---|---|
+| 家:采集 + NVENC 编码 | 5–10 ms | 1080p120 H.265/AV1 |
+| 家:上行排队 | 1–3 ms | 码率留 50% 余量时 |
+| 公网往返(家 ↔ 公司) | 8–25 ms | 同城 8–15ms,跨省 20–35ms |
+| 公司:解码 + 渲染 | 3–6 ms | 780M 硬解 |
+| 公司:显示器一帧 | **0–17 ms** | **60Hz 屏 = 16.7ms 硬成本**;144Hz 屏仅 ~7ms |
+| **合计** | **20–45 ms** | 打大乱斗的临界体验线约在 40ms |
+
+> 📌 **公司屏刷新率是硬成本**。若是 120/144Hz 屏,务必到「设置-系统-屏幕-高级显示」把刷新率手动调到最高。
+
+---
+
+## 五、反作弊风险(简版,细节你说稍后再展开)
+
+**事实**:腾讯从未公布"用远控会封号"的条款,但社区有**大量十年封禁案例**,理由是"检测到第三方软件",与远控软件在后台高度相关;**"远程操控着玩游戏"被反复指为最高风险场景**。
+
+**本机现状不干净**:已存在 ToDesk / 网易UU远程 / MuMu 的虚拟显示驱动,而这些虚拟显示与虚拟输入设备正是 ACE/TP 反作弊的重点扫描特征。
+
+**可用的降低手段**(无法归零):
+1. 优先用 **Sunshine**(用户态进程 + 系统 API 注入输入,不装虚拟显示器、不装内核驱动)
+2. 公司端用 **Moonlight 便携版**(不写注册表、不留服务)
+3. 游戏前停用国产远控服务与虚拟显示设备
+4. 绝不用任何宏 / 连点 / 自动化映射
+5. 一个天然优势:游戏看到的仍是**家里的 IP**,不会触发异地登录风控
+
+---
+
+## 六、实施计划(v3)
+
+### Phase 0 · 前置验证(**基本完成**)
+- [x] 家侧硬件与带宽实测(4070S / 7800X3D / 上行 46.36M / 丢包 0%)
+- [x] 判定 IPv4 为 CGNAT
+- [x] **家里 IPv4 NAT 类型复测 → 锥形(端点无关映射 / EIM),打洞可行** ✅(推翻 v2 的"对称 NAT"误判)
+- [x] 确认家侧有公网 IPv6
+- [x] 公司端自测结果回收:**纯 IPv4 网络、无 IPv6**;屏幕 2560×1600@**120Hz**;发现火绒 HIPS + EDR
+- [x] Sunshine 已安装、服务运行、编码器三件套可用、凭据已建、双栈配置已生效
+- [ ] **公司端跑 `net-test-v2.ps1`** ← 唯一还缺的技术验证(见第七节)
+- [ ] **打 10000 号申请公网 IPv4** ← 与上一条并行;零成本,但可能一步解决问题
+
+### Phase 1 · 家里端(**只剩电源设置没做**)
+
+| 项 | 状态 |
+|---|---|
+| Sunshine v2026.906.222525 | ✅ `C:\Program Files\Sunshine\` |
+| 服务 | ✅ `SunshineService` / LocalSystem / 开机自启 / Running |
+| 编码器 | ✅ `h264_nvenc` / `hevc_nvenc` / **`av1_nvenc`**(含 10-bit) |
+| 管理凭据 | ✅ `admin` / `Sunshine2026` |
+| 双栈监听 | ✅ `address_family = both`,已验证 `[::]:47984 / 47989 / 47990 / 48010` |
+| UPnP | ✅ `upnp = enabled`(**等路径 A 拿到公网 IP 后才有意义**) |
+| 电源与显示器 | [ ] 关闭显示器 = 从不、睡眠 = 从不(否则采集黑屏)← **待你点** |
+
+#### ✅ 曾经的阻塞项:Sunshine 默认只监听 IPv4 —— **已解决**
+
+安装后实测(2026-09-10):全部监听都是 `0.0.0.0:47984/47989/47990/48010`,IPv6 回环 `::1:47990` 直接被"积极拒绝"(根本没绑定)。
+
+**你已在 Web UI 里改好了**,当前 `C:\Program Files\Sunshine\config\sunshine.conf` 内容为:
+
+```ini
+address_family = both
+upnp = enabled
+```
+
+重启后日志确认两行配置都被正确解析(`config: 'address_family' = both` / `config: 'upnp' = enabled`),监听已变成双栈:
+
+```
+TCP [::]:47984 TCP [::]:47989 TCP [::]:47990 TCP [::]:48010
+```
+
+> 虽然公司没有 IPv6 让这条路暂时用不上,但**开着双栈没有副作用**,而且以后万一公司网络升级 IPv6,改都不用改。
+
+#### Phase 1 已确认的环境事实
+
+| 项 | 实测值 |
+|---|---|
+| 安装目录 | `C:\Program Files\Sunshine\` |
+| 配置文件 | `config\sunshine.conf`(安装时为空,0 字节) |
+| 凭据文件 | `config\sunshine_state.json`(创建凭据后生成) |
+| 日志 | `config\sunshine.log`(**排查问题第一现场**) |
+| 显示器 | 2560×1440 @ 170.002Hz,HDR 容器,HAGS 已启用 |
+| Web UI | `https://localhost:47990`(自签名证书,浏览器需点"继续访问") |
+| Web UI API | **HTTP Basic 认证**(`WWW-Authenticate: Basic realm="Sunshine Gamestream Host"`),非 cookie 会话 |
+| 首次设置 API | `POST /api/password`,JSON: `{newUsername, newPassword, confirmNewPassword}` |
+
+
+### Phase 2 · 打通链路(按路径 A / B 二选一)
+
+**路径 A —— 只适用"申请到公网 IPv4"的情况(公司端零安装,最优先)**
+- [ ] 家里:确认网关端口转发生效,或让 Sunshine 的 UPnP 自动映射
+- [ ] 手机 4G 直连验证:`http://公网IP:47990` 能打开 Web UI 即成功
+- [ ] 公司:Moonlight 便携版 → 填 `公网IP:47989` 配对(一次性 PIN)
+
+**路径 B —— Tailscale(不依赖电信)**
+- [ ] 家里:装 Tailscale 并登录(需 UAC)
+- [ ] 公司:装 Tailscale 并登录(需管理员;**留意火绒/EDR 弹窗**)
+- [ ] 公司:Moonlight 便携版 → 填**家里的 Tailscale IP(100.x.x.x)**配对
+- [ ] 首测参数 **1920×1080 @ 120 / H.265 / 22 Mbps**,开性能浮层看 encode / network / decode 分项
+
+### Phase 3 · 调优
+- [ ] Windows 鼠标加速关闭("提高指针精确度"),缩放 100%
+- [ ] 游戏内:**无边框窗口** + 分辨率与串流一致 + 刷新率"不封顶" + 关垂直同步
+- [ ] Moonlight 关 V-Sync(换更低延迟)
+- [ ] 跑顺后切 AV1,码率降到 16–18 Mbps
+- [ ] 公司屏确认跑在 **120Hz**(实测已是 120Hz,串流时再核一次)
+- [ ] 若走路径 A 且公网 IP 会变,配 DDNS
+
+### Phase 4 · 兜底与卫生
+- [ ] 保留 ToDesk(**当前已在用,已验证可用**)与网易UU远程作为应急通道
+- [ ] 需要时在腾讯云部署 WireGuard / DERP 兜底(注意 5 Mbps 限制)
+- [ ] **游戏前停用国产远控服务与虚拟显示设备**(ToDesk / GameViewer / MuMu 的 Virtual Display)
+
+---
+
+## 七、现在需要你做的事(v5)
+
+### ⚠️ 前提:公网 IPv4 申请被拒 → 主攻方向改为「手机热点 + 家里 IPv6 直连」
+
+| 路线 | v4 判决 | v5 修订 |
+|---|---|---|
+| IPv6 直连 | ❌ 死(公司无 IPv6) | 🔄 **复活** —— 家里侧条件已全部核实合格,公司侧可借**手机热点**补 IPv6 |
+| P2P 打洞(Tailscale / ZeroTier) | ❌ 死 | ✅ 维持死判(公司对称 NAT + 多出口) |
+| 申请公网 IPv4 | ✅ 主攻 | ❌ **申请被拒** → 降级为"长期最优解"(路径 A′) |
+| 出站端口自由度 | ✅ 意外地好 | ✅ 维持(支撑路径 B 中继可行) |
+
+**v5 路线表:**
+
+| 优先 | 路线 | 公司端代价 | 能达到的画质 | 卡点 |
+|---|---|---|---|---|
+| **A** | **手机热点补 IPv6 + 家里 IPv6 直连** | **零安装**(只要 Moonlight 便携版) | ✅ **1080p120 满血** | ① 热点下游有无 IPv6 ② 家里网关是否放行入站 |
+| B | 腾讯云 frp 中继(现在就能做) | 零安装 | ⚠️ 仅够 1080p60 | 服务器 5 Mbps |
+| C | 网易UU远程 / ToDesk(兜底) | 零安装 | ⚠️ 延迟 + 反作弊风险 | 无 |
+
+---
+
+### ① ⭐ 当前最重要:验证 IPv6 路线能否成立(两步,约 10 分钟)
+
+家里**已重新起好带日志的监听**(端口 38443,`:::38443 LISTENING` 已确认),下面两步直接决定走哪条路。
+
+#### 步骤 1 —— 手机 5G 验证「家里 IPv6 入站是否放行」
+
+用**手机(关 WiFi、走 4G/5G 流量)**扫下面这个二维码,或直接在系统浏览器里打开:
+
+```
+http://[240e:338:263:3600:29f1:e63f:d476:597e]:38443/
+```
+
+> 二维码文件:`ipv6-home-qrcode.png`(我已生成在项目目录,**用系统浏览器扫,别用微信扫一扫**——微信会拦非标端口的 http)
+
+| 看到什么 | 含义 |
+|---|---|
+| 页面显示 `IPV6-INBOUND-OK` | ✅ **家里 IPv6 入站是通的** —— 好消息,路子活了 |
+| 一直转圈 / 打不开 | ❌ 大概率是**天翼网关拦了 IPv6 入站** → 看下面 ② |
+
+> 打不开也没关系:**家里服务端有访问日志**,我能判断请求到底有没有到达 —— 只要你告诉我"试过了"即可。
+
+#### 步骤 2 —— 手机热点验证「公司笔记本能否拿到 IPv6」
+
+这一步决定这条路的上限(能不能真的在公司上网):
+
+1. 手机**开热点**(进热点设置确认 **IPv6 / 流量共享** 是开的;部分国产 ROM 默认只共享 IPv4)
+2. **公司笔记本连上这个热点**(先断开公司 WiFi 或让热点优先)
+3. 跑我准备好的脚本 `hotspot-ipv6-test.ps1`(**已放在家里桌面** `C:\Users\xxl\Desktop\`,用 ToDesk 文件传输拉到公司电脑 —— 别复制粘贴文本)
+4. 结果会自动写到公司电脑桌面 `hotspot-ipv6-result.txt`,发我
+
+> 脚本是**纯 ASCII、无 BOM**,免疫复制粘贴损坏;它一次就把"有没有 IPv6 + 能不能连到家里"两件事都测了。
+
+#### 两步的四种组合 → 直接决定走哪条路
+
+| 步骤1(手机直连家里) | 步骤2(笔记本有 IPv6) | 结论与下一步 |
+|---|---|---|
+| ✅ | ✅ | 🎉 **全通** → 直接进 Phase 2 配对,**1080p120 满血**,成本只有流量 |
+| ✅ | ❌ | 手机热点不给下游 IPv6 → 改用 **USB 共享 / 蓝牙共享**,或退路径 B 中继 |
+| ❌ | ✅ | 家里网关拦入站 → 按 ② 放行网关 IPv6 防火墙 |
+| ❌ | ❌ | 两条都堵 → 走路径 B(腾讯云中继);或再争取公网 IPv4 / 换运营商 |
+
+---
+
+### ② 若卡在「家里网关拦 IPv6 入站」:三种放行办法
+
+天翼网关**默认拦截 IPv6 入站**(IPv6 无 NAT,安全性全靠网关防火墙),这是此前手机测试失败的最大嫌疑。
+
+| 办法 | 操作 | 可行性 |
+|---|---|---|
+| **1. 网关里关 IPv6 防火墙** | 登录 `192.168.1.1` → 安全 / 防火墙 → **IPv6 防火墙** → 关闭或调成"低" | 天翼网关多数型号有此开关;**需要后台密码**(找回阶梯见 §1.4) |
+| **2. 光猫改桥接 + 自备路由器** | 让天翼网关只做光猫,用你自己的路由器 PPPoE 拨号 → 完全掌控 IPv6 防火墙与端口放行 | 最彻底,但**改错会全屋断网**,务必在家时操作 |
+| **3. 退路径 B** | 不做入站放行,改用腾讯云中继 | 一定能通,但画质降到 1080p60 |
+
+> ⚠️ **安全提醒**:IPv6 是"无 NAT 直通" —— 放行意味着那个地址**所有端口都对公网可见**。建议只放行串流必要端口(`47984-48010` / `47989` / `48010`),用完可关。别把 Windows 的 `445` / `3389` 之类暴露出去。
+
+---
+
+### ③ 提前准备:流量(步骤 2 通过的话)
+
+- 1080p120 @ 20 Mbps ≈ **9 GB/小时**;打 3 小时 ≈ 27 GB
+- **建议单独办一张大流量副卡**(或用现有套餐先试一晚测准真实消耗)
+- 流量吃紧时的降档方案:1080p60 @ 8 Mbps ≈ 3.6 GB/小时(画质换成本)
+
+---
+
+### ④ 顺手把家里电源设置改掉(1 分钟)
+
+`设置 → 系统 → 电源和电池 → 屏幕和睡眠`:**关闭屏幕 = 从不**、**睡眠 = 从不**。否则人不在家时显示器关闭会导致采集黑屏。
+
+---
+
+### ① ToDesk 连接方式 —— ✅ **已由 AI 从被控端反查完成,你不用再找状态栏**
+
+你反馈"找不到状态栏"(新版 ToDesk 把 P2P / 中转标识收进了设备详情面板,确实不好找)。我改用更硬的办法:**直接在家里主机的进程层面抓 ToDesk 的实时连接**,结论见 §1.6。摘要:
+
+| 抓到什么 | 说明 |
+|---|---|
+| 只与 ToDesk 自家服务器 (`219.151.138.225:443/8080`、`118.24.225.33:443`) 保持 TCP 长连接 | 走厂商侧 |
+| 与公司出口 `117.188.24.89` **无任何 TCP 直连记录** | 至少 TCP 层没有直连 |
+| 文件传输会话 `planb:1 ... p2p:0` | 该会话明确走**中转** |
+| ✅ **但公司能出站到 8080 非标端口** | **重要利好**(说明不是 80/443 白名单) |
+
+**这一项已完成,不用再花时间找界面标识。** 真正还缺的是 UDP 侧的硬数据 → 见 ②。
+
+### ② 公司网络实测 —— ✅ **已完成**(`net-test-v2.ps1` 结果已回)
+
+脚本跑通,原始结果见 `net-test-v2_20260910_163314.txt`。核心数据:
+
+| 测试项 | 结果 | 判决 |
+|---|---|---|
+| IPv6 | `IPv6 local : NONE` | ❌ 纯 IPv4 |
+| **TCP 出站** | `baidu:443` / `qq:443` / `aliyun:80` / `portquiz:443,8080,8443` **全 OPEN**;**你腾讯云的 `80/443/22025` 也全 OPEN** | ✅ **任意端口出站自由** |
+| UDP 出站 | 多台 STUN 均收到回包(`stun.qq.com` 超时是它自身问题) | ✅ UDP 能出能回 |
+| **NAT 类型** | 同一本地端口 `46001` 问不同 STUN → 映射端口 `16993 / 16994 / 17011 / 43681` **各不相同**;出口 IP 出现 `117.188.24.89` 与 `117.188.118.254` **两个** | ❌ **对称 NAT + 多出口 → 打洞不可行** |
+| 管理员权限 | 当前会话非管理员,但账号 `ybtdevxxl` **在 Administrators 组内** | ⚠️ 可提权(点 UAC 即可) |
+| 已装软件 | `Clash Verge` / `WireGuard` / `ToDesk` / 火绒 | 🎁 WireGuard 现成(但打洞已判死,用处有限) |
+| 安全软件 | **火绒 `HipsDaemon` + `HRWSCCtrl` 运行中**;`aTrust` / `edr_monitor` / `savsvc` / Defender 全系 **Stopped** | ⚠️ 火绒会拦驱动;但**无强制 EDR 管控**(好消息) |
+
+> 这轮也验证了 v2 脚本的价值:v1 报的"端口白名单"确是误判,v2 用真实靶子测出**出站完全自由** —— 这条结论直接支撑了路径 B(frp 中继)的可行性判断。
+
+### ⑤(可选,长期)公网 IPv4 仍值得换个渠道再试一次
+
+被拒不代表永久关闭。它是**延迟最低、画质最好、不依赖任何第三方**的方案,值得换个说法/渠道再争取:
+
+| 渠道 | 话术 / 要点 |
+|---|---|
+| 再打 10000 | 换时段、换客服;说法改成:"现在分到 `100.64` 开头的内网地址,**公司 VPN 连不回家里的 NAS**,需要公网 IP 才能访问" |
+| 小翼管家 App | 部分地区有自助申请入口,比电话更快 |
+| 线下营业厅 | 当面申请,权限通常比电话客服大 |
+| 换运营商 / 换套餐 | 移动、联通在部分地区对公网 IP 更宽松;也可直接问"企业宽带"报价 |
+| 工信部申诉 | **最后手段**(CGNAT 用户申请公网 IP 属合理诉求),耗时长,前三招先走完 |
+
+> 一旦拿到公网 IPv4,**立刻切回路径 A′** —— 那才是这个方案的最优形态(零第三方、延迟最低、公司端零安装)。
+
+### 附:原 v4 的申请话术(留档)
+
+> "我家里需要在外网访问自己的监控和 NAS,现在分到的是 100.64 开头的内网地址,能不能帮我换成公网 IP?"
+
+> 申请时**强调"不用开网站、不需要备案域名",纯自用监控 / NAS**,通过率更高。
+
+---
+
+## 附 C:v2 阶段的 IPv6 入站验证过程(已完成,留存备查)
+
+### ① 手机测 IPv6 入站(1 分钟,决定性)
+家里当前**已开着临时监听**(端口 38443,**带访问日志版**)。请:
+
+1. 手机关掉 WiFi,**用 4G/5G 流量**
+2. 浏览器打开:
+ ```
+ http://[240e:338:263:3600:e591:cb62:3503:8a23]:38443/
+ ```
+3. 看到 `IPV6-INBOUND-OK` → ✅ **网关放行 IPv6 入站,走路径 1(最优)**
+ 打不开 / 超时 → 网关拦了入站,走路径 2(打洞),或登录网关放行
+
+> 注意:**不要用 ping 判断**。Windows 默认拦掉 ICMPv6 入站,ping 不通不代表 IPv6 不可用,必须用这个 TCP 测试。
+
+**日志验证法(本次新增,用于排除误判)**
+
+监听会把每个到达的请求写入日志,因此判定不依赖手机浏览器是否正确渲染:
+
+| 现象 | 日志 | 判定 |
+|---|---|---|
+| 手机看到 `IPV6-INBOUND-OK` | 有记录 | ✅ 入站通 |
+| 手机页面打不开 | **有记录** | ✅ 入站通(只是浏览器/网速问题) |
+| 手机页面打不开 | **无记录** | ❌ 入站被网关拦截 |
+
+日志文件:`%TEMP%\wb_ipv6test\inbound.log`(形如 `[2026-09-10T07:42:50Z] REQ from=<手机地址> ...`)
+
+> 监听进程随会话结束可能失效。**测试前若间隔较久,让 AI 重新起一次**(几十秒即可)。
+> 另:之前那个残留在 38080 端口的旧监听本次已成功清理,端口已释放。
+
+#### ⚠️ 日志法的一个重要局限(本次踩坑,2026-09-10 修正)
+
+临时监听是用 `http.createServer` 实现的,**只在收到完整 HTTP 请求时才写日志**。
+因此:**纯 TCP 探测(如 ping.pe 的端口检查)只做三次握手、不发 HTTP 请求,连接成功也不会留下日志。**
+
+结论:**"ping.pe 显示 Connected" 与 "日志无记录" 可以同时成立,二者不矛盾**。
+- 判断"TCP 层是否可达" → 看 ping.pe / itdog 等外部探测结果
+- 判断"HTTP 层是否可达" → 看日志 + 手机浏览器实际渲染
+
+#### 本次实测记录(2026-09-10)
+
+| 时间 | 测试方式 | 结果 | 有效? |
+|---|---|---|---|
+| 15:42 | 本机 PowerShell 访问公网地址(内网回环) | HTTP 200,日志有记录 | 仅证明本机自通 |
+| ~15:50 | 手机 **连 WiFi** 访问 | 打不开 | ❌ 无效(手机此时无 IPv6,且走回环易误判) |
+| ~15:55 | 手机 **4G/5G** 访问 | 页面无响应、不报错;**日志无记录** | ✅ 有效,HTTP 层未到达 |
+| ~16:00 | 手机扫码 **tcp6.ping.pe** 探测 | 用户反馈"通了" | ✅ TCP 层可达(待复核细节) |
+
+**综合判定**:**外网 → 家里的 TCP 层大概率可达**(ping.pe 探测),
+但手机浏览器的 HTTP 请求未到达 —— 二者差异可能来自手机侧(运营商 IPv6 路由 / 浏览器限制非标端口 http)。
+**下一步用真实业务端口复核**:Sunshine 装好后,用 Moonlight 直接从公司连,成不成是最终标准。
+(Sunshine 用 47989/47984/48010 等端口,与 38443 不同,网关若做端口白名单需另放行。)
+
+### ② 把自测脚本带到公司电脑跑一遍(**旧版,已跑完,已被 `net-test-v2.ps1` 取代**)
+文件:`公司电脑网络自测.ps1`(同目录)
+
+```
+先在开始菜单打开 Windows PowerShell,然后:
+powershell -ExecutionPolicy Bypass -File "公司电脑网络自测.ps1"
+```
+
+它会输出一台设备「能不能支撑远程串流」的完整画像,重点看三行:
+- `IPv6 出网 : ...` → 决定能否走 IPv6 直连
+- `STUN ... -> 通` 与 `NAT 判断 : ...` → 决定打洞可行性
+- `家里 IPv6:38443 -> ...` → 若这行是"通",说明**从公司到家里已经打通了**
+
+---
+
+## 附 A:端口与命令
+
+```powershell
+# Sunshine 需要的入站规则(安装时一般会自动创建,失败时手动补)
+New-NetFirewallRule -DisplayName "Sunshine TCP" -Direction Inbound -Protocol TCP -LocalPort 47984,47989,47990,48010 -Action Allow
+New-NetFirewallRule -DisplayName "Sunshine UDP" -Direction Inbound -Protocol UDP -LocalPort 47998-48000,48002 -Action Allow
+
+# Moonlight 命令行示例(1080p120 / 22Mbps)
+moonlight stream <家里IPv6> "Desktop" -1080 -120fps -bitrate 22000
+
+# Moonlight 快捷键
+# Ctrl+Alt+Shift+S 性能浮层 Ctrl+Alt+Shift+Z 切换鼠标捕获 Ctrl+Alt+Shift+Q 退出串流
+```
+
+**已知坑**
+- 显示器进入睡眠 → Sunshine 抓不到画面、串流黑屏,务必关闭显示器休眠
+- 多显示器时需指定 `output_name`,指错会串到错误屏幕或全黑
+- 独占全屏可能因分辨率切换导致采集中断 → 统一用"无边框窗口"
+- 若走端口转发(本方案用不到),务必改非默认端口 + Sunshine Web UI 用强密码
+
+## 附 B:工程备注(本机探测踩过的坑)
+
+- 本机 PowerShell 工具的 stdout 会被吞掉 → 需要把结果 `Out-File` 落盘再用 Read 读
+- PowerShell 里 `[byte] -shl 8` 会溢出归零,字节拼 16 位整数必须先 `[int]` 转换
+- 用 `Get-Content -Raw` + `Out-File` 加 BOM 会把 UTF-8 中文按 GBK 解码后重写而**永久损坏文件**;加 BOM 应使用字节级操作 `WriteAllBytes` 前置 `EF BB BF`
+- 本机 PATH 被裁剪,脚本里调用 `ping` / `netsh` 会找不到 → 用 `$env:SystemRoot\System32\` 绝对路径
+- 一批防火墙入站规则指向的 node.exe 路径已不存在(失效规则),`C:\Users\xxl\AppData\Roaming\nvm\v23.0.0\node.exe` 才有有效的 Public 档全端口放行规则
+- **"从外部验证 IPv6 入站"没有可用的自动化手段**(2026-09-10 实测全部失败),必须用真实外部设备(手机 4G)验证:
+ - `check-host.net` API:`check-tcp` 对 IPv6 一律返回 `{"error":"invalid_url"}`(带/不带方括号、带 http:// 前缀都试过)
+ - `ping.pe` / `tcp6.ping.pe`:接受 IPv6 目标,但结果是 **JS 异步渲染**,静态抓取只能拿到 `Connecting to ...` 与 `Snapshot readiness: waiting_for_results`
+ - 公共 CORS 代理(codetabs / allorigins / corsproxy / thingproxy / Jina Reader):要么 522、要么需要 API key、要么直接被 GFW 拦截
+ - `hackertarget.com/nmap`:已改为需要 API key
+- 因此本项目采用**「手机 TCP 测试 + 服务端访问日志」双证据**方式判定入站可达性
+
+### ⚠️ 附 B 补充(v3,2026-09-10):两个"看起来专业、其实是错的"测试陷阱
+
+这两条是本项目**已经踩过并已修正**的坑,以后再写网络探测脚本必须避开:
+
+**陷阱 1:NAT 类型判定 —— 每个 STUN 换一个新 socket**
+- ❌ 错误做法:`for each stun: 新建 socket → sendto → 看映射端口`。因为**本地端口每次都变了**,运营商/网关必然分配不同的公网映射端口 —— 这是任何 NAT 类型都会出现的结果,**不能推出"对称 NAT"**
+- ✅ 正确做法:**同一个 socket(同一个本地端口)连续问多个 STUN 服务器**,比较映射端口是否一致:
+ - 一致 → 端点无关映射(Cone / EIM)→ 打洞可行
+ - 不一致 → 对称 NAT → 打洞难
+- 本项目实测(正确方法):本地 55001 → 恒为 `1.204.100.17:3051`;55002 → 恒为 `:3063` → **结论是锥形 NAT,v2 的"对称 NAT"是误判**
+
+**陷阱 2:判断"端口白名单"时用了根本没在监听的靶子**
+- ❌ 错误做法:连 `114.114.114.114:8080` 失败 → 就断定"公司封了非标端口"。实际上**那个 DNS 服务器压根没开 8080**,失败是必然的,跟防火墙无关。同理 `github.com:443` 在国内常被 DNS 污染/阻断,也**不能**作为"443 被封"的证据
+- ✅ 正确做法:用**确定在监听该端口**的靶子,且最好有"全端口监听"型服务:
+ - `www.baidu.com:443`、`www.qq.com:443`(国内基线,验证 443 是否真的可用)
+ - `portquiz.net`(**监听所有 TCP 端口**,在它上面测 8080/8443 才有意义)
+ - 自有服务器 `193.112.118.168:80/443`(nginx 确实在监听)
+- 判别逻辑:`REFUSED`(立即拒绝)= **路径是通的**,只是对端没开这个端口;`TIMEOUT`(一直等)= **真的被拦或丢包**
+
+**陷阱 3(方法论):日志监听只能证明 HTTP 层,不能否定 TCP 层**
+- `http.createServer` 只在收到完整 HTTP 请求时写日志。纯 TCP 端口探测(三次握手即断)**成功也不会留痕**
+- 所以"探测显示 Connected"与"日志空白"可以同时为真,**不可据此判定入站被拦**