PROJECT · 2026
DSH 手机访问:反向隧道 + 一键开关插件
家里 CGNAT 没有公网 IPv4,也能用手机随时随地打开 DSH Web UI:反向隧道 + 服务器 nginx 反代 + 输入框下方两个一键开关。
要解决的问题
家宽 WAN IP 是 100.64.x.x(RFC 6598 CGNAT),没有公网 IPv4;路由器里翻遍了也没找到 IPv6 入站放行在哪;腾讯云安全组也不想再开新端口。但我想在地铁上、在公司、在任何网络下,都能用手机打开家里的 DeepSeek Harness。
最后做成的结构:
手机(4G / 任意 WiFi)
│ https://dsh.example.com ← 全链路 TLS
▼
云服务器 :443
│ nginx proxy_pass → 127.0.0.1:18081(只绑 loopback)
▼
SSH 反向隧道(家里主动拨出,断线 10s 重连,登录自启)
▼
家里的 DSH Pocket 代理 :3081(PIN 验证)
▼
dsh web :3080
核心思路一句话:既然外面进不来,就让家里主动连出去,服务器只做一层 nginx 反代。腾讯云安全组一条规则都不用改(只用已经开着的 22/80/443)。
三条被否掉的路线
| 想法 | 为什么不行 |
|---|---|
| 路由器端口映射到公网 IPv4 | CGNAT 段内的地址互联网不可路由,映射了也白映射 |
| 直接用原生公网 IPv6 | 机器上确实有 240e: 地址,但 dsh-pocket 的代理 server.listen(port,'0.0.0.0') 是纯 IPv4,v6 连不上;还得在路由器(可能还有光猫)两层放行入站 |
| DDNS 更新 AAAA | Windows 同时有两条 IPv6:SuffixOrigin=Link 稳定地址与 SuffixOrigin=Random 临时地址(约一天轮换、出站优先用它)。「查我的 IP」问回来的是临时地址,写进 DNS 几小时就失效 |
还有个隐藏前提:DSH 前端多处调用 crypto.randomUUID(),该 API 只在 secure context(https 或 localhost)存在,所以用 http://192.168.x.x 裸开是会白屏的——必须走 HTTPS,或者用插件注入 polyfill。
做出来的三块
1. DSH 插件 @local/dsh-net-toggle —— 聊天输入框下面多一行:
手机访问 ● 局域网 已开 ● 互联网 已开 [复制链接]
- 宿主侧注册
/dsh-net前缀路由,认证直接复用 DSH 自己的围栏ctx.connection.requestRejection(req),没带浏览器 cookie 一律 401 - 局域网开关改 dsh-pocket 的
settings.json(它的代理每次请求重读配置,即时生效、不用重启) - 互联网开关调隧道脚本
-InstallTask/-Remove,不自己实现隧道,保证隧道只有一个 owner - 浏览器侧用
ctx.slots.inject('conversation.composer.dock', …)注册组件,5 秒轮询状态
2. dsh-tunnel.ps1 —— Windows 隧道守护:ssh -N -R 循环重连、启动文件夹 VBS 自启、pid 文件供插件读状态。用 WMI Win32_Process.Create 启动,让守护的父进程变成 WmiPrvSE.exe,脱离 DSH 进程树。
3. 服务器 nginx + certbot —— 443 反代到隧道端口,必须透传 Upgrade(DSH 的 RPC 是 WebSocket),proxy_read_timeout 3600s;80 只做 ACME 校验和跳转。
踩过的坑(都修了)
- VBS 的引号规则和命令行不一样:
Run ""C:\app.exe" -arg"在 VBScript 里是语法错误(""是空字符串)。正确做法是整条命令做成一个字符串字面量,路径不含空格时干脆不加引号。另外我用-Encoding ASCII写 VBS,中文目录名被写成????,启动项静默失效——开机自启从来没成功过,之前的隧道全是我手动起的。 Copy-Item自我复制:安装逻辑「把脚本复制到稳定路径」,从稳定路径运行时源=目标,直接IOException,守护还没启动就中止。taskkill死 PID +ErrorActionPreference='Stop':pid 里的进程早死了,taskkill往 stderr 写一行,被当成终止错误,后果同上。- 测的路径 ≠ 用户走的路径:我一直用工作区那份脚本测试(源≠目标),而按钮和启动项走
%LOCALAPPDATA%那份(源=目标),所以 2、3 两个 bug 一直没暴露。教训:按用户实际触发的入口测试。 - 进程树:
Start-Process起的守护挂在 dsh 进程树里,dsh 一重启就被带走 → 公网 502。改用 WMI 创建。
安全上做的几件事
- 只用到已经开放的 22/80/443,安全组零新增规则
- 服务器上的隧道端口只绑
127.0.0.1,外网碰不到 - 入口 PIN(8–64 位字母数字)+ 同 IP 5 次/60 秒限速
- 实测「关掉局域网不会把自己锁在门外」:局域网关闭后公网入口仍可访问
已知限制
- 家里电脑要开着、
dsh web在跑、隧道守护在跑;休眠会断,唤醒后约 10 秒自动重连 dsh web重启后手机要重新输一次 PIN(cookie 绑定进程 sessionKey)- 开机自启是「登录 Windows 后」触发,停登录界面不会起
附件是脱敏后的完整工程(域名、IP、密码都换成了占位符):插件源码、隧道脚本、nginx 配置、签发脚本和一份 README。