docs(memory): 拆分部署手册到 DEPLOY.md,记录 Uptime Kuma 部署

This commit is contained in:
夏犀麟 2026-09-13 17:06:01 +08:00
parent 247afdd8ff
commit 3a5beaf963
17 changed files with 2236 additions and 64 deletions

10
.idea/go.imports.xml generated Normal file
View File

@ -0,0 +1,10 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="GoImports">
<option name="excludedPackages">
<array>
<option value="golang.org/x/net/context" />
</array>
</option>
</component>
</project>

View File

@ -1,5 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?>
<module type="WEB_MODULE" version="4">
<component name="Go" enabled="true" />
<component name="GoModuleSettings" enabled="true" />
<component name="NewModuleRootManager">
<content url="file://$MODULE_DIR$" />

View File

@ -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 7800X3D8C/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 90120fps @ 30Mbps 或 1080p 120144fps @ 2025Mbps不足以支撑 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:4014:55
### 三个决定性发现
1. **家里 IPv4 是运营商级 NATCGNAT**`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**RDNA3VCN 4.0**支持 AV1 硬解**/ 32GB / 954GB可安装软件。
- **画质定档 1080p120**H.265 22Mbps 起步 → 稳定后切 AV1 1618Mbps
### 交付物
- 方案文档升级至 **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:4115: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` APIcheck-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:4715: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/` → **打不开**
- **服务端日志无任何来自手机的记录**(只有本机自测那条)→ 请求**未到达**家里主机
- ⚠️ 但此结论有**前置条件未排除**:用户手机可能本身没有 IPv64G/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:0416:12
**类型**CHG
### 完成项
- 下载 **Sunshine v2026.906.222525** MSI33,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-bitAV1 的 YUV444 不支持属正常)
- 显示器识别2560×1440 @ 170.002HzHAGS 已启用
- 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 FamilyWeb 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:1016: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 路线作废
- 公司 CPURyzen 7 7840H + **Radeon 780M**;屏幕 **2560×1600@120Hz**(比假设的 60Hz 好一档)
- **IPv6本机无 v6 地址v6 出网两条 DNS 全不通 → 公司是纯 IPv4 网络** ⇒ **IPv6 直连彻底作废**
- 高位 UDP 可出网STUNmiwifi / 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`
- 本地实跑验证通过(家里侧:全部 OPENportquiz 全端口可达)
- 新增检查项:管理员权限自检 / 火绒·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"的白名单防火墙;若将来有公网 IPv4Sunshine 的 47989/47990/48010 大概率可直连,腾讯云 frp 中继(任意端口)也具备可行性
- 家里 ToDesk 在多个 IPv6 UDP 端点监听5706257068对应 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` 子域做 DDNSDNSPod 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:5619: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 中继 | 1080p605 Mbps | — |
| C | 网易UU远程 / ToDesk | 延迟 + 反作弊风险 | — |
**代价认知**1080p120 @ 20 Mbps ≈ **9 GB/小时**5G 延迟预计 **4070 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 防火墙(或光猫改桥接)

View File

@ -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`,内存限 512mhealthy
2026-09-13 | DOC | MEMORY.md 瘦身:生产部署/安全加固/工具链坑位拆到同目录 DEPLOY.md新增「站点与容器清单」表

View File

@ -0,0 +1,55 @@
# xpcool.com 生产环境 · 部署与运维手册
> 从 MEMORY.md 拆出2026-09-13存放服务器、容器、站点、安全加固与本机工具链坑位等细节。
> MEMORY.md 只保留规则与索引。
## 生产部署(腾讯云 193.112.118.168SSH 22025/xxcoolDocker+宿主机 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 + resourceDockerfile 用 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管理走网页 UIAPI 与原版不兼容(需用户建 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/2222cockpit/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` 账号(腾讯云自带 uid1000NOPASSWD ALL是否锁定待用户决策。**坑**:① 腾讯云安全组未放行 3000/2222/8081Gitea 走 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` 可直连下载)。
- 本机网络备注:家里的宽带是**运营商级 NATCGNAT第 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 Kumastatus.xpcool.com2026-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`

View File

@ -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}` 都禁止);**所有入参一律通过请求体 bodyJSON传递**。
- 路由用静态 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`toolsget-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 头部(最新 1015 条)了解近况。
3. 处理后于 CHANGELOG 顶部追加本次记录。
4. 新增项目时同步更新上方索引表。
判断涉及哪个项目 → 读该项目 CHANGELOG 头部 1015 条 → 处理后于顶部追加本次记录 → 新增项目时同步更新上方索引表。
## 生产部署(腾讯云 193.112.118.168SSH 22025/xxcoolDocker+宿主机 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 + resourceDockerfile 用 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管理走网页 UIAPI 与原版不兼容(需用户建 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/2222cockpit/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` 账号(腾讯云自带 uid1000NOPASSWD ALL是否锁定待用户决策。**坑**:① 腾讯云安全组未放行 3000/2222/8081Gitea 走 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` 可直连下载)。
- 本机网络备注:家里的宽带是**运营商级 NATCGNAT第 2 跳 100.64.0.1**,无公网 IPv4但有**公网 IPv6**`240e:338:263:3600::/64`)。以太网被判定为 Public 网络档。
## 生产环境与本机工具链
→ 见同目录 **`DEPLOY.md`**:服务器/容器/站点与域名清单、nginx 与证书约定、安全加固与告警去重、本机 Windows/PowerShell 工具链坑位。

View File

@ -0,0 +1,24 @@
# status.xpcool.com 服务监控面板Uptime Kuma 2.5.3
# 反代后端容器 uptime-kuma127.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;
}
}

104
hotspot-ipv6-test.ps1 Normal file
View File

@ -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

BIN
ipv6-home-qrcode.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 556 B

BIN
ipv6-inbound-qrcode.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 639 B

294
net-test-v2.ps1 Normal file
View File

@ -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)

View File

@ -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

245
net-test.ps1 Normal file
View File

@ -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)
}

BIN
pingpe-test-qrcode.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 657 B

View File

@ -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 判断 : 多次映射端口一致 -> 锥形 NATP2P 打洞成功率高(好消息)'
} else {
Add-Line 'NAT 判断 : 每次映射端口都不同 -> 对称 NATP2P 打洞较难,需要中继兜底'
}
}
Add-Line '说明 : STUN 能通 = 高位 UDP 端口可出网,这是 Sunshine/Moonlight 直连的前提'
# ---------- 6. 到家里的链路(核心结论所在) ----------
Add-Section '6. 到家里的链路(核心)'
# 家里主机的公网 IPv6若网关重新拨号 / 前缀变化,需要重新获取后替换)
$homeV6 = '240e:338:263:3600:e591:cb62:3503:8a23'
# 家里的运营商出口 IPv4100.64 开头 = 运营商级 NAT无法直连这是正常现象
$homeV4 = ''
if ($homeV6 -ne '') {
# 先 TCP 再 pingTCP 通即可确认入站放行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)

View File

@ -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 判断 : 每次映射端口都不同 -> 对称 NATP2P 打洞较难,需要中继兜底
说明 : STUN 能通 = 高位 UDP 端口可出网,这是 Sunshine/Moonlight 直连的前提
===== 6. 到家里的链路(核心) =====
家里 IPv6:38443 -> 不通(可能网关 IPv6 防火墙拦了入站,或家里测试监听没开)
ping6 家里 -> 不通ICMPv6 常被默认拦截,仅供参考)
未填写家里 IPv4跳过
===== 自测完成 =====
把结果文件发给 AI 助手,据此决定走 IPv6 直连 / 打洞 / 中转。
结果已保存: E:\xxcool\project\workbuddy.xpcool.com\网络自测结果_20260910_160152.txt

View File

@ -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必须确认套餐余量或办一张大流量副卡 |
| **延迟代价** | 预计 **4070 ms** | 5G 空口 + 跨网(手机运营商 → 电信。LOL 可接受(普通玩家本身 3050 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 7800X3D8C/16T | 游戏性能强,编码走 NVENC 不占 CPU |
| 内存 | 31.2 GB | 充足 |
| 系统 | Windows 11 专业版 build 26200 | Sunshine / Moonlight 官方良好支持 |
| 显示器 | AOC **2560×1440 @ 170Hz** | 可输出 2K 高刷;但按 1080p120 定档更稳 |
| 网卡 | Realtek 2.5GbE,链路 1GbpsIP `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`** | ⚠️⚠️ **运营商级 NATCGNAT实锤** |
| 出口 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/16TZen4 | 性能远超解码需求 |
| 核显 | **AMD Radeon 780M**RDNA3 / PhoenixVCN 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"的白名单防火墙。若家里将来有公网 IPv4Sunshine 的 `47989/47990/48010` 这些端口公司大概率能直连;腾讯云 frp 中继(任意端口)也具备可行性。
2. ⚠️ ToDesk **TCP 层没有与公司出口 IP 的直连**,且文件传输会话为 `planb:1 ... p2p:0`走中转。UDP 是无连接的、从端点列表看不到对端,所以**不能据此断定打洞失败**,但至少没有直连证据 → 打洞方案(路径 B的信心下调。
> 附带发现:家里这台主机的 IPv6 地址有**多个轮换的隐私扩展地址**(同一前缀下 3 条)。这印证了"IPv6 直连必须配 DDNS"的判断(不过该路线已被公司端否决,仅留档)。
---
## 二、方案选型v5 定案)
**先看硬约束 —— 它们决定一切:**
| 约束 | 事实 | 影响 |
|---|---|---|
| 家里无公网 IPv4 | CGNATtracert 第 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 空口 + 跨网(手机运营商 → 电信),预计 **4070 ms**。LOL 可接受(普通玩家本身 3050 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 更贴合"打游戏"场景
- 延迟约 3060ms → 办公够用,**打大乱斗团战会有可感知的跟手差异**
- ⚠️ 反作弊风险最高档(见第五节)
- 定位:**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** | 1618 Mbps | 3539% |
| 弱网兜底 | 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 硬解,同画质可省约 2030% 码率,画面更干净)。
---
## 四、延迟预算(目标 < 35ms
| 环节 | 耗时 | 说明 |
|---|---|---|
| 家:采集 + NVENC 编码 | 510 ms | 1080p120 H.265/AV1 |
| 家:上行排队 | 13 ms | 码率留 50% 余量时 |
| 公网往返(家 ↔ 公司) | 825 ms | 同城 815ms跨省 2035ms |
| 公司:解码 + 渲染 | 36 ms | 780M 硬解 |
| 公司:显示器一帧 | **017 ms** | **60Hz 屏 = 16.7ms 硬成本**144Hz 屏仅 ~7ms |
| **合计** | **2045 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.002HzHDR 容器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 IP100.x.x.x**配对
- [ ] 首测参数 **1920×1080 @ 120 / H.265 / 22 Mbps**,开性能浮层看 encode / network / decode 分项
### Phase 3 · 调优
- [ ] Windows 鼠标加速关闭("提高指针精确度"),缩放 100%
- [ ] 游戏内:**无边框窗口** + 分辨率与串流一致 + 刷新率"不封顶" + 关垂直同步
- [ ] Moonlight 关 V-Sync换更低延迟
- [ ] 跑顺后切 AV1码率降到 1618 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 用真实靶子测出**出站完全自由** —— 这条结论直接支撑了路径 Bfrp 中继)的可行性判断。
### ⑤(可选,长期)公网 IPv4 仍值得换个渠道再试一次
被拒不代表永久关闭。它是**延迟最低、画质最好、不依赖任何第三方**的方案,值得换个说法/渠道再争取:
| 渠道 | 话术 / 要点 |
|---|---|
| 再打 10000 | 换时段、换客服;说法改成:"现在分到 `100.64` 开头的内网地址,**公司 VPN 连不回家里的 NAS**,需要公网 IP 才能访问" |
| 小翼管家 App | 部分地区有自助申请入口,比电话更快 |
| 线下营业厅 | 当面申请,权限通常比电话客服大 |
| 换运营商 / 换套餐 | 移动、联通在部分地区对公网 IP 更宽松;也可直接问"企业宽带"报价 |
| 工信部申诉 | **最后手段**CGNAT 用户申请公网 IP 属合理诉求),耗时长,前三招先走完 |
> 一旦拿到公网 IPv4**立刻切回路径 A** —— 那才是这个方案的最优形态(零第三方、延迟最低、公司端零安装)。
### 附:原 v4 的申请话术(留档)
> "我家里需要在外网访问自己的监控和 NAS现在分到的是 100.64 开头的内网地址,能不能帮我换成公网 IP"
> 申请时**强调"不用开网站、不需要备案域名",纯自用监控 / NAS**,通过率更高。
---
## 附 Cv2 阶段的 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 补充v32026-09-10两个"看起来专业、其实是错的"测试陷阱
这两条是本项目**已经踩过并已修正**的坑,以后再写网络探测脚本必须避开:
**陷阱 1NAT 类型判定 —— 每个 STUN 换一个新 socket**
- ❌ 错误做法:`for each stun: 新建 socket → sendto → 看映射端口`。因为**本地端口每次都变了**,运营商/网关必然分配不同的公网映射端口 —— 这是任何 NAT 类型都会出现的结果,**不能推出"对称 NAT"**
- ✅ 正确做法:**同一个 socket同一个本地端口连续问多个 STUN 服务器**,比较映射端口是否一致:
- 一致 → 端点无关映射Cone / EIM→ 打洞可行
- 不一致 → 对称 NAT → 打洞难
- 本项目实测(正确方法):本地 55001 → 恒为 `1.204.100.17:3051`55002 → 恒为 `:3063` → **结论是锥形 NATv2 的"对称 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"与"日志空白"可以同时为真,**不可据此判定入站被拦**