一、远程连接:SSH 会话保活与断线重连
会话为什么会掉。最常见的原因不是网络断了,而是中间设备静默回收空闲连接——防火墙、NAT 网关为每条 TCP 连接维护状态表,超过 idle timeout(常见 5 分钟)没有报文经过就直接删表项,且不发 RST。此时双方协议栈都认为连接还在,形成半开连接。服务端 sshd 侧也有自己的空闲踢人策略,安全设备尤其积极。
心跳:用 SSH 协议层,不用 TCP keepalive。ssh2提供的是协议层心跳:
它周期性发送keepalive@openssh.com全局请求(OpenSSH 扩展),要求服务端 sshd 真正处理并应答。相比之下 TCP keepalive 默认周期是 2 小时量级,只能证明"对端内核还在回 ACK",证明不了 sshd 活着——遇到过设备内存耗尽、内核还在回 ACK 但 sshd 已僵死的情况。15 秒 × 8 次 = 120 秒容忍窗口,倒推自常见的 5 分钟 idle timeout。
顺便说,心跳的意义不只是"发现断线",更重要的是"防止被断"——每 15 秒有报文经过,防火墙就不会回收这条连接。
重连状态机。四个关键决策:
- 只在"曾经连上过"时才重试(
everConnected)。首次连接失败(密码错、端口不通)立即报错,不让人干等着——按退避策略走完 5 次要 59 秒,等完才告诉他"密码错了"是极差的体验。
- 指数退避
[2s, 4s, 8s, 15s, 30s],5 次。设备重启后 sshd 可能要几十秒才起来,固定短间隔会把机会全消耗在设备没起来的时候。
- 防重入:断线时
error和close都可能触发,用reconnecting标志位挡住,否则一次断线会安排两次重连、产生两条连接。
- 复用同一个 state 对象重连,终端缓冲区、窗口尺寸、日志句柄自然保留,用户看到的是"卡了一下又能用了"。
一个刻意不做的事:重连成功后的 shell 是全新的(工作目录、环境变量、sudo 状态全没了),我们不做命令自动重放。因为cd xxx && rm yyy在新会话里自动重放可能作用到完全不同的上下文。宁可明确告诉用户"这是新会话"。
老设备兼容:放开diffie-hellman-group1-sha1、3des-cbc、ssh-rsa、hmac-md5等已被现代客户端默认禁用的算法,握手超时给到 20 秒。这是运维工具的现实妥协——连不上等于零,所以做成按会话可配,而不是全局默认。
二、文件传输与远程桌面
SFTP 复用同一条 SSH 连接。点击终端标签栏的文件夹图标,会在这条已建立的 SSH 连接上再开一个 SFTP 通道,不需要二次认证。技术上就是ssh2的client.sftp(),一条 TCP 连接上跑多个 channel。代价是单条连接断了,文件面板也跟着断(跟着 SSH 重连一起恢复)。
远程桌面内嵌是这个工具里最"脏"的一块。Windows 上没有官方的 RDP 嵌入 API,主流做法是:
- 默认引擎:在独立的 PowerShell 宿主进程里,用 .NET WinForms 承载 RDP ActiveX 控件(
mstscax.dll),然后把那个窗口用 Win32SetParent挂进应用窗口。这样全程不出现mstsc.exe,登录界面(含密码错误的重登录)都画在嵌入区域里。
- 兜底引擎:精简系统上创建不了 RDP 控件时,退回"拉起
mstsc.exe再停靠其窗口"。
两个引擎共用同一套停靠管线(ps-dock.ps1助手进程):MoveWindow摆位、DPI 按物理像素换算(125%/150% 缩放下不偏移)、支持解除停靠与重新嵌入。
三个配套机制:
- 凭据预置:用
cmdkey把凭据写进 Windows 凭据管理器(TERMSRV/主机名),会话关闭时自动删除
- 看门狗:停靠后拉起独立监视进程,目标窗口持续无响应时自动解除停靠,避免 mstsc 卡死拖死整个工具
- 错误可见:账户被禁用、密码错误这类只出现在 mstsc 自己弹窗里的错误,读取弹窗文字并在状态栏给出中文解读
已知限制:跨进程SetParent可能被 EDR 拦截,此时只能降级为独立窗口。
三、串口 Console 口
串口这块的取舍在于避开原生模块。node-serialport是 Native 模块,换 Electron 版本就要为对应 ABI 重新编译(需要 VS 工具链),对分发很不友好。
替代方案:拉起一个ps-serial.ps1常驻进程(stdin 收指令 / stdout 推事件),串口收发由该进程内的 C# 桥完成——Windows 的 .NET Framework 自带串口 API,零原生依赖。
其他细节:每 3 秒枚举一次本机串口(含设备描述,如USB-SERIAL CH340),插上自动出现,拔出后保留选项并标注"已拔出";同一时间只允许一个串口会话(Console 口物理上独占)。
四、批量运维
定位是Ansible ad-hoc 的轻量替代:不需要 Linux 控制节点,客户端直连。
核心实现是一个受控并发池:
- 并发上限默认 8。一口气连 100 台会把设备(很多安全设备并发 SSH 能力很弱)和客户端一起压死
- 判定标准:退出码非 0 即失败;单台超时(默认 60 秒)不影响其他主机
- sudo 支持:勾选后用
sudo -S -p ''把登录密码从 stdin 喂进去,免交互跑需要 root 的命令
(-p ''是为了关掉 sudo 的密码提示符,避免它混进输出里破坏结果解析。)
五、内置文件分发服务
场景:安全设备(邮件网关、防毒墙)升级时只支持 HTTP 或 FTP 拉取固件,临时在笔记本上起一个分发端点最快。
一键把目录发布为HTTP + FTP双端点:
HTTP 侧:随机端口 + URL 内一次性 token(/d/<token>/),支持目录浏览、Range 断点续传(设备拉几百 MB 固件中断后可续)、/__sha256?f=文件名校验接口。
FTP 侧:最小命令集(LIST/RETR/STOR/SIZE/MDTM/REST),被动模式 PASV/EPSV(不支持主动模式 PORT)。
安全设计上有几个值得说的点:
- 随机端口 + 一次性 token + IP 白名单(支持 CIDR)+ TTL 自动关服:组合起来保证这个服务"用完就没了",不留下长期开放的端口
- 上传先写临时文件再原子改名:杜绝设备拉到半个文件。一次原子
rename操作,读的人要么看不到文件,要么看到完整文件
- FTP 数据连接强制与控制连接同源 IP:防止数据连接被劫持
- 全量访问审计日志,含文件 SHA256,便于事后追溯谁拉的什么
六、更新代理
场景不同但相关:设备不能直连外网时要拉特征库,而运维笔记本能上外网。于是笔记本临时充当代理。
协议上支持两种,因为设备固件五花八门:
- HTTP 代理(默认 44331 端口):明文 HTTP 请求走标准正向代理(请求行是绝对 URL,可解析、可缓存);HTTPS 必须走CONNECT 隧道——代理建立到目标的 TCP 连接,回
200 Connection Established,之后原样双向转发,什么都看不到也缓存不了 - SOCKS5:按 RFC 1928 三段式实现(greeting → 认证 → CONNECT 请求),只支持
0x01CONNECT,BIND/UDP ASSOCIATE直接拒绝。三种地址类型(IPv4/域名/IPv6)都要处理,其中 IPv4-mapped 的 IPv6 地址(::ffff:10.0.0.1)要还原成 IPv4 再做访问控制,否则 SSRF 检查认不出它是内网地址。
三个实现要点:
- 隧道建好后必须停止解析。SOCKS5 是字节流状态机,如果隧道数据继续往命令缓冲区里流,会协议错乱 + 内存无限增长(转 500MB 固件就攒 500MB)。用
stage = 'done'让状态机对后续字节视而不见,只由pipe搬运。
- 缓存的原子性与一致性。缓存键是
sha1(method + url),正文写.part-<时间戳>临时文件,finish后rename落盘。命中返回时必须删掉content-encoding和transfer-encoding——Node 的 http 客户端对 gzip 会自动解压,缓存的是解压后的原始字节,把原头返回出去会让设备拿明文当 gzip 解压而报错。这个 bug 的表现很有迷惑性:第一次升级正常,第二次开始就失败。
- 并发回源去重。十台设备同时请求同一个 URL 时,用
pending表让后来的请求等第一个请求的写盘 Promise(最多 3 秒),写完后直接命中缓存,外网只走一份流量。
安全上,代理天生是 SSRF 靶子,几道防线:
- 只按字符串判 IP 不够,攻击者把自己的域名 A 记录指向
127.0.0.1就能绕过(DNS rebinding),必须dns.lookup解析成 IP 后再判
- 拦截范围:回环、
0.0.0.0、链路本地(含云元数据169.254.169.254)、RFC1918、CGNAT100.64/10、IPv6 ULAfc00::/7与链路本地fe80::/10——IPv6 一定要一起判,只顾 IPv4 的实现等于没有防护
- 按"意图是否明确"区分策略:管理员显式填了上游更新地址时才放行私有网段;纯代理模式(客户端给什么 URL 就转什么)一律拦内网
- 认证失败返回407(不是 401),并清理
proxy-authorization等 hop-by-hop 头
七、网络抓包
对标 Wireshark,但不装 Npcap / WinPcap。
原理是 Windows 内核自带的raw socket +SIO_RCVALL:
SIO_RCVALL让网卡进入混杂镜像模式,收到所有方向的 IP 数据包。
抓包内核是一段 C#(packet-sniffer.cs),首次使用时用 .NET Framework 自带的csc.exe就地编译并缓存,产物放 userData。注意这个csc.exe只支持到C# 5语法,不能用?.和字符串插值——第一次写就踩了这个。
三个技术点:
- 方向判断:
SIO_RCVALL抓到的包里,目的地址等于本机 IP 的视为"接收"
- 导出 PCAP 用
LINKTYPE_RAW(101):因为 raw socket 已经剥掉了链路层头,包体直接就是 IP 包,导出的文件 Wireshark / tcpdump 可直接打开
- 协议识别是"端口线索 + 内容启发式"双通道。端口会骗人(8080 上跑的不一定是 HTTP),所以 30+ 协议的识别都靠内容特征兜底,比如 TLS 靠握手特征认出 SNI/ALPN,QUIC 靠首包特征,MySQL/Redis 靠响应报文结构
自研工具:
GitHub - Yingaogui/GGYDESK