604 lines
27 KiB
Markdown
604 lines
27 KiB
Markdown
# SimpleRemoter Go TCP Server Framework
|
||
|
||
基于 Go 语言实现的高性能 TCP 服务端框架,用于替代原有的 C++ IOCP 服务端。
|
||
|
||
## 项目结构
|
||
|
||
```
|
||
server/go/
|
||
├── go.mod # Go 模块定义
|
||
├── auth/
|
||
│ └── auth.go # 授权验证模块 (TOKEN_AUTH + Heartbeat HMAC)
|
||
├── buffer/
|
||
│ └── buffer.go # 线程安全的动态缓冲区
|
||
├── connection/
|
||
│ ├── context.go # 连接上下文
|
||
│ ├── errors.go # 错误定义
|
||
│ └── manager.go # 连接管理器
|
||
├── protocol/
|
||
│ ├── parser.go # 协议解析器
|
||
│ ├── codec.go # 编解码和压缩 (ZSTD)
|
||
│ ├── header.go # 协议头解密 (8种加密方式)
|
||
│ └── commands.go # 命令常量和LOGIN_INFOR解析
|
||
├── server/
|
||
│ ├── server.go # TCP 服务器核心
|
||
│ └── pool.go # Goroutine 工作池
|
||
├── logger/
|
||
│ └── logger.go # 日志模块 (基于 zerolog)
|
||
├── hub/
|
||
│ └── hub.go # 在线设备注册表 + 事件订阅
|
||
├── wsauth/
|
||
│ └── wsauth.go # Web 鉴权 (challenge-response + 不透明 token)
|
||
├── web/
|
||
│ ├── embed.go # //go:embed 嵌入 HTML/xterm.js 等 web 资源
|
||
│ ├── server.go # HTTP server (静态页面 + REST + WS 路由)
|
||
│ ├── ws.go # WebSocket 连接生命周期
|
||
│ ├── ws_handlers.go # WS 消息分发与处理
|
||
│ └── assets/
|
||
│ ├── index.html # 从 ../../web/index.html sync 而来 (gitignored)
|
||
│ └── static/ # 第三方 xterm.js 资源 (checked in)
|
||
├── licensing/
|
||
│ ├── signer.go # Signer interface (Sign / Mode / Close)
|
||
│ ├── local.go # LocalSigner — operator 部署,HMAC 直连
|
||
│ ├── remote.go # RemoteSigner — 客户部署,HTTPS + singleflight + 24h cache + heartbeat ticker
|
||
│ ├── noop.go # NoOpSigner — free tier,返回空签名
|
||
│ ├── token.go # JWT RS256 校验 + RSA 公钥加载
|
||
│ ├── quota.go # 配额追踪 (Reserve / RefreshExisting / 5min eviction)
|
||
│ ├── server.go # License Server HTTP handlers (/license/sign, /license/heartbeat) + Issue()
|
||
│ ├── factory.go # NewFromEnv / LicenseServerFromEnv + 模式选择
|
||
│ └── licensing_test.go # 17 个单元测试
|
||
└── cmd/
|
||
└── main.go # 程序入口
|
||
```
|
||
|
||
## 核心特性
|
||
|
||
底层基础设施:
|
||
|
||
- **高并发**: 基于 Goroutine 池管理并发连接
|
||
- **协议兼容**: 支持原有客户端的多种协议标识 (Hell/Hello/Shine/Fuck)
|
||
- **协议头解密**: 支持 8 种协议头加密方式 (V0-V6 + Default)
|
||
- **授权验证**: TOKEN_AUTH 和 Heartbeat HMAC-SHA256 双重授权
|
||
- **XOR 编码 / ZSTD 压缩**: 与客户端完全兼容
|
||
- **字符编码自适应**: 根据客户端能力位选择 UTF-8 直通或 GBK→UTF-8 转换
|
||
- **线程安全 / 优雅关闭 / 多端口监听 / 结构化日志**
|
||
|
||
Web 应用能力 (Phase 3-7):
|
||
|
||
- **Web 鉴权**: challenge-response 登录 + 不透明 token,与 users.json schema 互通
|
||
- **登录加固**: 双维度速率限制(10 次/分钟·IP + 5 次/15 分钟·用户名)+ 失败固定延迟,防口令枚举;`/get_salt` 用确定性假盐响应未知用户,杜绝用户名探测;WebSocket Origin 同源校验 + 显式白名单;`/api/devices` Bearer Token 鉴权
|
||
- **设备列表与监控**: 在线设备 / RTT / 活动窗口 / 分辨率 实时下发
|
||
- **Web 远程桌面**: 浏览器 WebCodecs 解码 H.264,二进制 WS 帧低延迟中继;late-join 自动重发最近 IDR;优雅 BYE 关闭防止客户端无意义重连
|
||
- **鼠标 / 键盘输入**: Win32 消息映射 (`WM_*` / `VK_*` / `MK_*`),MSG64 48 字节布局直传客户端
|
||
- **Web 终端**: xterm.js + Windows ConPTY / 旧 cmd 管道双模式;二进制 "TRM1" 帧分流;尺寸自适应;单设备单 viewer
|
||
- **用户与分组**: admin 可创建/删除 viewer 账号、配置 allowed_groups,users.json 原子写入
|
||
|
||
## 支持的命令
|
||
|
||
### 客户端 → 服务端
|
||
|
||
| Token | 值 | 用途 |
|
||
| ---- | ---- | ---- |
|
||
| `TOKEN_AUTH` | 100 | 授权请求(SN + Passcode + HMAC) |
|
||
| `TOKEN_HEARTBEAT` | 101 | 心跳包(携带 ActiveWnd / Ping / SN) |
|
||
| `TOKEN_LOGIN` | 102 | 主连接登录 |
|
||
| `TOKEN_BITMAPINFO` | 115 | 屏幕子连接首包,含分辨率 + clientID |
|
||
| `TOKEN_FIRSTSCREEN` | 116 | 原始 BGRA 首帧(Go 侧丢弃) |
|
||
| `TOKEN_NEXTSCREEN` | 117 | H.264 屏幕帧 |
|
||
| `TOKEN_SHELL_START` | 128 | 旧 cmd-pipe 终端子连接首包 |
|
||
| `TOKEN_KEYFRAME` | 134 | GOP 关键帧(DEFAULT_GOP 无限大,实际未用) |
|
||
| `TOKEN_TERMINAL_START` | 232 | PTY 终端子连接首包 |
|
||
| `TOKEN_TERMINAL_CLOSE` | 233 | 终端关闭通知 |
|
||
| `TOKEN_CONN_AUTH` | 246 | 子连接身份握手,含 clientID |
|
||
| (raw bytes) | — | 终端 sub-conn 绑定后裸字节即 shell 输出 |
|
||
|
||
### 服务端 → 客户端
|
||
|
||
| Command | 值 | 用途 |
|
||
| ---- | ---- | ---- |
|
||
| `COMMAND_SCREEN_SPY` | 16 | 启动屏幕捕获 |
|
||
| `COMMAND_SCREEN_CONTROL` | 20 | 鼠标 / 键盘输入(MSG64 批次) |
|
||
| `COMMAND_NEXT` | 30 | 解除客户端读线程阻塞 |
|
||
| `COMMAND_SHELL` | 40 | 请求开启 shell 子连接 |
|
||
| `CMD_TERMINAL_RESIZE` | 81 | PTY 尺寸 (cols / rows int16 LE) |
|
||
| `COMMAND_BYE` | 204 | 优雅断开屏幕 / 终端 |
|
||
| `CMD_MASTERSETTING` | 215 | 主控配置 + HMAC 签名 (1000B) |
|
||
| `CMD_HEARTBEAT_ACK` | 216 | 心跳响应(携带 Authorized 字段) |
|
||
| `TOKEN_CONN_AUTH` | 246 | 子连接身份握手响应 (256B) |
|
||
|
||
未列出的命令字节会被记录为 Debug 日志,按需扩展。
|
||
|
||
## 快速开始
|
||
|
||
### 安装依赖
|
||
|
||
```bash
|
||
cd server/go
|
||
go mod tidy
|
||
```
|
||
|
||
### 编译
|
||
|
||
推荐用 Makefile,编译前会自动从 `server/web/` 同步 HTML 到 `web/assets/`:
|
||
|
||
```bash
|
||
make build # 当前平台
|
||
make windows # Windows amd64
|
||
make linux # Linux amd64
|
||
```
|
||
|
||
也可以直接用 `go build`,但要先手动 sync:
|
||
|
||
```bash
|
||
make sync && go build -o simpleremoter-server ./cmd
|
||
```
|
||
|
||
VSCode F5 调试时由 `sync-web-assets` preLaunchTask 自动同步。
|
||
|
||
### 运行
|
||
|
||
```bash
|
||
./simpleremoter-server
|
||
```
|
||
|
||
默认监听 TCP 端口 `6543`(被控设备),HTTP 端口 `8080`(浏览器 Web UI)。日志写到 `logs/server.log`。
|
||
|
||
### 命令行参数
|
||
|
||
| 参数 | 默认值 | 说明 |
|
||
| ---- | ------ | ---- |
|
||
| `-port` / `-p` | `6543` | TCP 监听端口,分号分隔可多端口(如 `6543;6544`) |
|
||
| `-http-port` | `8080` | HTTP 监听端口(Web UI),传 `0` 禁用 |
|
||
| `-no-console` | `false` | 关闭控制台输出(守护进程模式) |
|
||
|
||
### 环境变量
|
||
|
||
| 变量 | 说明 | 示例 |
|
||
| ---- | ---- | ---- |
|
||
| `YAMA_PWDHASH` | 密码的 SHA256 哈希值 (64位十六进制) | `61f04dd6...` |
|
||
| `YAMA_PWD` | 超级密码,用于 HMAC 签名验证;也作为 Web admin 密码的默认来源 | `your_super_password` |
|
||
| `YAMA_WEB_ADMIN_PASS` | Web UI 的 admin 密码(明文);优先于 `YAMA_PWD`。两者都未设置时 Web 登录禁用 | `your_admin_password` |
|
||
| `YAMA_SIGN_PASSWORD` | **[LocalSigner 模式]** HMAC-SHA256 master key,直接给 CMD_MASTERSETTING 签名。Operator 自己的部署用。设置此变量后进入 LocalSigner 模式(见下方"签名模式")。 | `<deployment-shared-secret>` |
|
||
| `YAMA_LICENSE_SERVER` | **[RemoteSigner / Trial 模式]** License Server 公开 URL。**不设置则用 DefaultLicenseServerURL (`https://web.just-do-it.icu:8080`)**。客户部署设置此变量后进入 RemoteSigner 模式 —— 每次新设备登录会 HTTPS POST 给 License Server 拿签名,本机永远看不到 HMAC master key。 | `https://license.example.com` |
|
||
| `YAMA_LICENSE_TOKEN` | **[RemoteSigner 模式]** Operator 颁发的客户 JWT(RS256),作为 Bearer token 鉴权。每个客户一份。**未设置则进入 TRIAL 模式(匿名试用,按出口 IP 配额 2 台)**。 | `eyJhbGciOiJSUzI1NiI...` |
|
||
| `YAMA_LICENSE_DISABLED` | 设为 `1` 强制 NoOp 模式(既不读 token 也不连 License Server,客户端会拒绝屏幕/文件功能)。给本地开发 / 离线测试用。 | `1` |
|
||
| `YAMA_LICENSE_OFFLINE_HRS` | **[RemoteSigner / Trial 模式]** License Server 短暂不可达时,本地缓存签名的宽限期(小时)。默认 24。0 → 不缓存,每次新登录必须联网。 | `24` |
|
||
| `YAMA_LICENSE_PRIVATE_KEY` | **[issue-token 子命令]** RSA 私钥 PEM 路径,用于离线签发客户 JWT。与 `YAMA_LICENSE_PUBLIC_KEY` 配对。设置后 `issue-token` 子命令无需 `-key` 参数。 | `/opt/yama/license_priv.pem` |
|
||
| `YAMA_LICENSE_PUBLIC_KEY` | **[License Server 模式]** Operator 自己(已经是 LocalSigner)想顺便对外提供 License Server 时,用来验证客户提交的 JWT 的 RSA 公钥 PEM 路径。必须与 `YAMA_LICENSE_HTTP_ADDR` 同时设置。 | `./license_pub.pem` |
|
||
| `YAMA_LICENSE_HTTP_ADDR` | **[License Server 模式]** License Server HTTP 监听地址。**仅在 LocalSigner 模式下生效**(RemoteSigner 客户不能反向当 license server)。建议挂 nginx/Caddy 加 TLS 后对外。 | `:8443` |
|
||
| `YAMA_LICENSE_STATE_PATH` | **[License Server 模式]** Quota 状态持久化文件路径。设置后每次新设备入队或 slot 被驱逐时原子写入磁盘(tmp + rename),License Server 重启时从此文件恢复设备列表,消除重启期间因 tracker 清空导致的配额绕过窗口。不设则仅内存状态,重启后 tracker 清零。 | `/var/lib/yama/quota.json` |
|
||
| `YAMA_USERS_FILE` | Path to the JSON file that persists non-admin web users (allowed_groups, password hash, salt). Default is `users.json` in the working directory. | `users.json` |
|
||
| `YAMA_WEB_ALLOWED_ORIGINS` | Comma-separated WebSocket Origin allowlist for cross-origin upgrades. Empty (default) → only same-origin upgrades are accepted, which is correct when the web UI and `/ws` share a host. Add an entry per trusted PWA / dev origin. | `https://yama.example.com,https://yama-mobile.example.com` |
|
||
| `YAMA_WEB_TRUST_PROXY` | Set to `1` only when running behind a reverse proxy you control (caddy / nginx / cloudflare). Switches client-IP extraction to use the last entry of `X-Forwarded-For` instead of `RemoteAddr`, so per-IP login rate limit sees the real client. Direct-exposure deployments MUST leave this unset — otherwise attackers can spoof the header to evade rate limits. | `1` |
|
||
|
||
```bash
|
||
# Linux/macOS
|
||
export YAMA_PWDHASH="61f04dd637a74ee34493fc1025de2c131022536da751c29e3ff4e9024d8eec43"
|
||
export YAMA_PWD="your_super_password"
|
||
./simpleremoter-server
|
||
|
||
# Windows PowerShell
|
||
$env:YAMA_PWDHASH="61f04dd637a74ee34493fc1025de2c131022536da751c29e3ff4e9024d8eec43"
|
||
$env:YAMA_PWD="your_super_password"
|
||
.\simpleremoter-server.exe
|
||
```
|
||
|
||
## 签名模式(CMD_MASTERSETTING signer)
|
||
|
||
单个 Go 二进制按启动时的环境变量自动选择四种签名模式之一。同一个 master HMAC key 永远不会出现在客户机器上 —— 这是把 Go server 商业化部署给付费客户的核心安全前提。
|
||
|
||
| 模式 | 触发条件 | 用途 |
|
||
| ---- | -------- | ---- |
|
||
| **LocalSigner** | `YAMA_SIGN_PASSWORD` 已设 | Operator 自己的部署。master HMAC key 在本机内存,签名直连 HMAC,微秒级延迟。**可选**:再设 `YAMA_LICENSE_PUBLIC_KEY` + `YAMA_LICENSE_HTTP_ADDR` 让本进程同时对外提供 License Server HTTP 服务。 |
|
||
| **RemoteSigner (paid)** | `YAMA_LICENSE_TOKEN` 已设(`YAMA_LICENSE_SERVER` 可选,未设则用 `DefaultLicenseServerURL`) | 付费客户部署。本机**永远看不到** master HMAC key —— 每次新设备登录会 HTTPS POST 到 operator 的 License Server,带 Bearer JWT 鉴权,拿到签名后塞进 CMD_MASTERSETTING。同 (clientID, startTime) 元组的签名缓存 24h(可调,`YAMA_LICENSE_OFFLINE_HRS`),用于扛短暂网络故障。 |
|
||
| **RemoteSigner (trial)** | `YAMA_LICENSE_TOKEN` 未设、且未显式 `YAMA_LICENSE_DISABLED=1` | 匿名试用模式。没有 JWT,连默认 License Server URL;服务端按下级出口 IP 识别身份,配额 `FreeMaxDevices` (2 台),且匿名 `/license/sign` 受 IP 限流(10 req/min)。零配置直接跑就在这个模式。 |
|
||
| **NoOpSigner** | `YAMA_LICENSE_DISABLED=1` | 显式离线/开发模式。不连任何 License Server,返回空签名 → 客户端私有库拒绝启动 screen/file 功能。设备列表仍然可用。 |
|
||
|
||
注:`YAMA_SIGN_PASSWORD` 与 `YAMA_LICENSE_*` 同时设置时 LocalSigner 优先(operator 自己的 server 不应该回连自己)。
|
||
|
||
### License Server endpoints(仅 LocalSigner 暴露)
|
||
|
||
设了 `YAMA_LICENSE_PUBLIC_KEY` + `YAMA_LICENSE_HTTP_ADDR` 后,本进程会监听 `YAMA_LICENSE_HTTP_ADDR` 提供两个端点:
|
||
|
||
- `POST /license/sign` — body `{"client_id":"...","start_time":"..."}`,header `Authorization: Bearer <customer-JWT>`,回 `{"signature":"<64-hex>"}`。强制按 JWT 的 `tier` + `max_devices` 配额限制。
|
||
- `POST /license/heartbeat` — body `{"active_device_count":N,"active_device_ids":["..."]}`,回 `{"server_view_count":M,"drift":N-M}`。Drift 大于阈值时记日志,供 operator 反作弊审核。
|
||
|
||
部署建议:本进程只跑 plain HTTP,前面挂 nginx/Caddy/cloudflare 加 TLS。JWT 校验已经把 `alg` 锁死成 RS256,杜绝 `alg:none` 攻击。
|
||
|
||
### 颁发客户 JWT
|
||
|
||
**第一步:一次性生成 RSA 密钥对**(只在授权中心执行一次,私钥永久保管)
|
||
|
||
```bash
|
||
openssl genrsa -out license_priv.pem 2048
|
||
openssl rsa -in license_priv.pem -pubout -out license_pub.pem
|
||
```
|
||
|
||
- `license_priv.pem` — 私钥,设为 `YAMA_LICENSE_PRIVATE_KEY`,仅存于授权中心,**绝不外发**
|
||
- `license_pub.pem` — 公钥,设为 `YAMA_LICENSE_PUBLIC_KEY`,授权中心 License Server 用于验证客户 JWT
|
||
|
||
**第二步:颁发 JWT**
|
||
|
||
`server` 二进制内置 `issue-token` 子命令。授权中心已配置 `YAMA_LICENSE_PRIVATE_KEY` 时,只需要提供客户标识:
|
||
|
||
```bash
|
||
# 最简调用(私钥路径从 $YAMA_LICENSE_PRIVATE_KEY 读取)
|
||
server issue-token -sub customer-acme
|
||
|
||
# 完整参数
|
||
server issue-token \
|
||
-sub customer-acme \ # 客户唯一标识(必填)
|
||
-tier paid \ # paid 或 trial(默认 paid)
|
||
-devices 20 \ # 最大并发设备数(默认 10)
|
||
-ttl 8760h # 有效期(默认 8760h = 1 年)
|
||
```
|
||
|
||
命令将 JWT 字符串输出到 stdout,将其作为 `YAMA_LICENSE_TOKEN` 交给客户。
|
||
|
||
| 参数 | 默认值 | 说明 |
|
||
| ---- | ------ | ---- |
|
||
| `-key` | `$YAMA_LICENSE_PRIVATE_KEY` | RSA 私钥 PEM 路径;env 已设则无需重复指定 |
|
||
| `-sub` | (必填) | 客户唯一标识,建议用 `company-id` 格式 |
|
||
| `-tier` | `paid` | `paid` 或 `trial` |
|
||
| `-devices` | `10` | 并发设备上限;`paid` 必须显式设置合理值 |
|
||
| `-ttl` | `8760h` | Token 有效期,支持 Go duration 语法(`h`/`m`/`s`) |
|
||
|
||
## 使用示例
|
||
|
||
完整的 TCP + Hub + Web 集成示例就是 [`cmd/main.go`](cmd/main.go),那是程序入口本身、也是最权威的范例 —— 包含 handler 装配、hub 注册、web HTTP/WS 服务、信号优雅关闭等。
|
||
|
||
如果只想用 TCP 框架做自定义服务端(不要 Web/Hub),最小示例如下:
|
||
|
||
```go
|
||
package main
|
||
|
||
import (
|
||
"os"
|
||
"os/signal"
|
||
"syscall"
|
||
|
||
"github.com/yuanyuanxiang/SimpleRemoter/server/go/connection"
|
||
"github.com/yuanyuanxiang/SimpleRemoter/server/go/logger"
|
||
"github.com/yuanyuanxiang/SimpleRemoter/server/go/protocol"
|
||
"github.com/yuanyuanxiang/SimpleRemoter/server/go/server"
|
||
)
|
||
|
||
type MyHandler struct{ log *logger.Logger }
|
||
|
||
func (h *MyHandler) OnConnect(ctx *connection.Context) {}
|
||
func (h *MyHandler) OnDisconnect(ctx *connection.Context) {}
|
||
func (h *MyHandler) OnReceive(ctx *connection.Context, data []byte) {
|
||
if len(data) == 0 {
|
||
return
|
||
}
|
||
if data[0] == protocol.TokenLogin {
|
||
info, _ := protocol.ParseLoginInfo(data)
|
||
h.log.Info("login: %s (%s)", info.PCName, info.OsVerInfo)
|
||
}
|
||
}
|
||
|
||
func main() {
|
||
log := logger.New(logger.DefaultConfig())
|
||
srv := server.New(server.DefaultConfig())
|
||
srv.SetLogger(log.WithPrefix("Server"))
|
||
srv.SetHandler(&MyHandler{log: log})
|
||
if err := srv.Start(); err != nil {
|
||
log.Fatal("start: %v", err)
|
||
}
|
||
|
||
sig := make(chan os.Signal, 1)
|
||
signal.Notify(sig, syscall.SIGINT, syscall.SIGTERM)
|
||
<-sig
|
||
srv.Stop()
|
||
}
|
||
```
|
||
|
||
## 配置选项
|
||
|
||
| 配置项 | 默认值 | 说明 |
|
||
|--------|--------|------|
|
||
| Port | 8080 | 监听端口 |
|
||
| MaxConnections | 10000 | 最大连接数 |
|
||
| MinWorkers | 4 | 最小工作协程数 |
|
||
| MaxWorkers | 100 | 最大工作协程数 |
|
||
| ReadBufferSize | 8192 | 读缓冲区大小 |
|
||
| WriteBufferSize | 8192 | 写缓冲区大小 |
|
||
| KeepAliveTime | 5min | 连接保活时间 |
|
||
| ReadTimeout | 2min | 读超时时间 |
|
||
| WriteTimeout | 30s | 写超时时间 |
|
||
|
||
## 日志配置
|
||
|
||
| 配置项 | 默认值 | 说明 |
|
||
|--------|--------|------|
|
||
| Level | Info | 日志级别 (Debug/Info/Warn/Error/Fatal) |
|
||
| Console | true | 是否输出到控制台 |
|
||
| File | "" | 日志文件路径 (空则不写文件) |
|
||
| MaxSize | 100 | 单个日志文件最大 MB |
|
||
| MaxBackups | 3 | 保留的旧日志文件数量 |
|
||
| MaxAge | 30 | 旧日志保留天数 |
|
||
| Compress | true | 是否压缩轮转的日志 |
|
||
|
||
日志示例输出:
|
||
```json
|
||
{"level":"info","module":"Server","time":"2025-12-19T13:17:32+01:00","message":"Server started on port 6543"}
|
||
{"level":"info","module":"Handler","event":"login","client_id":1,"ip":"192.168.0.92","computer":"DESKTOP-BI6RGEJ","os":"Windows 10","version":"Dec 19 2025","time":"2025-12-19T13:17:32+01:00"}
|
||
{"level":"debug","module":"Handler","time":"2025-12-19T13:17:47+01:00","message":"Heartbeat from client 1 (DESKTOP-BI6RGEJ)"}
|
||
```
|
||
|
||
## 协议格式
|
||
|
||
数据包格式与 C++ 版本兼容:
|
||
|
||
```
|
||
+----------+------------+------------+------------------+
|
||
| Flag | TotalLen | OrigLen | Compressed Data |
|
||
| (N bytes)| (4 bytes) | (4 bytes) | (variable) |
|
||
+----------+------------+------------+------------------+
|
||
```
|
||
|
||
### 协议标识
|
||
|
||
| 标识 | Flag长度 | 压缩方式 | 说明 |
|
||
|------|----------|----------|------|
|
||
| HELL | 8 bytes | ZSTD | 主要协议 |
|
||
| Hello? | 8 bytes | None | 无压缩协议 |
|
||
| Shine | 5 bytes | ZSTD | 备用协议 |
|
||
| <<FUCK>> | 11 bytes | ZSTD | 备用协议 |
|
||
|
||
### 协议头加密
|
||
|
||
支持8种加密方式,服务端自动检测并解密:
|
||
- V0 (Default): 动态密钥,4种操作
|
||
- V1: 交替加减
|
||
- V2: 带旋转的异或
|
||
- V3: 带位置的动态密钥
|
||
- V4: 对称的伪随机异或
|
||
- V5: 带位移的动态密钥
|
||
- V6: 带位置的伪随机
|
||
- V7: 纯异或
|
||
|
||
### LOGIN_INFOR 结构
|
||
|
||
客户端登录信息结构体 (考虑 C++ 内存对齐):
|
||
|
||
| 字段 | 偏移 | 大小 | 说明 |
|
||
|------|------|------|------|
|
||
| bToken | 0 | 1 | 命令标识 (102) |
|
||
| OsVerInfoEx | 1 | 156 | 操作系统版本 |
|
||
| (padding) | 157 | 3 | 对齐填充 |
|
||
| dwCPUMHz | 160 | 4 | CPU 频率 |
|
||
| moduleVersion | 164 | 24 | 模块版本 |
|
||
| szPCName | 188 | 240 | 计算机名 |
|
||
| szMasterID | 428 | 20 | 主控 ID |
|
||
| bWebCamExist | 448 | 4 | 是否有摄像头 |
|
||
| dwSpeed | 452 | 4 | 网速 |
|
||
| szStartTime | 456 | 20 | 启动时间 |
|
||
| szReserved | 476 | 512 | 扩展字段(多字段以 `\|` 分隔) |
|
||
|
||
### Heartbeat 结构
|
||
|
||
客户端心跳包结构 (1024 字节):
|
||
|
||
| 字段 | 偏移 | 大小 | 说明 |
|
||
|------|------|------|------|
|
||
| Time | 0 | 8 | 时间戳 (uint64) |
|
||
| ActiveWnd | 8 | 512 | 当前活动窗口 |
|
||
| Ping | 520 | 4 | 延迟 (int) |
|
||
| HasSoftware | 524 | 4 | 软件标识 (int) |
|
||
| SN | 528 | 20 | 序列号 (用于授权验证) |
|
||
| Passcode | 548 | 44 | 授权码 (格式: v0-v1-v2-v3-v4-v5) |
|
||
| PwdHmac | 592 | 8 | HMAC 签名 (uint64) |
|
||
| Reserved | 600 | 424 | 保留字段 |
|
||
|
||
### HeartbeatACK 结构
|
||
|
||
服务端心跳响应结构 (32 字节):
|
||
|
||
| 字段 | 偏移 | 大小 | 说明 |
|
||
|------|------|------|------|
|
||
| Time | 0 | 8 | 原始时间戳 (uint64) |
|
||
| Authorized | 8 | 1 | 授权状态 (1=已授权, 0=未授权) |
|
||
| Reserved | 9 | 23 | 保留字段 |
|
||
|
||
### 授权验证流程
|
||
|
||
```
|
||
客户端 Heartbeat 服务端
|
||
│ │
|
||
│ SN + Passcode + PwdHmac │
|
||
│ ────────────────────────────────► │
|
||
│ │ 1. 验证 Passcode 格式
|
||
│ │ 2. 验证 Passcode 哈希
|
||
│ │ 3. 验证 HMAC 签名
|
||
│ HeartbeatACK │
|
||
│ ◄──────────────────────────────── │
|
||
│ (Authorized=1 或 0) │
|
||
```
|
||
|
||
## API 参考
|
||
|
||
### Server
|
||
|
||
```go
|
||
// 创建服务器
|
||
srv := server.New(config)
|
||
|
||
// 设置日志
|
||
srv.SetLogger(log)
|
||
|
||
// 设置事件处理器
|
||
srv.SetHandler(handler)
|
||
|
||
// 启动服务器
|
||
srv.Start()
|
||
|
||
// 停止服务器
|
||
srv.Stop()
|
||
|
||
// 发送数据到指定连接
|
||
srv.Send(ctx, data)
|
||
|
||
// 广播数据到所有连接
|
||
srv.Broadcast(data)
|
||
|
||
// 获取当前连接数
|
||
count := srv.ConnectionCount()
|
||
```
|
||
|
||
### Connection Context
|
||
|
||
```go
|
||
// 发送数据
|
||
ctx.Send(data)
|
||
|
||
// 关闭连接
|
||
ctx.Close()
|
||
|
||
// 获取客户端 IP
|
||
ip := ctx.GetPeerIP()
|
||
|
||
// 检查连接状态
|
||
closed := ctx.IsClosed()
|
||
|
||
// 获取/更新最后活跃时间 (线程安全)
|
||
lastActive := ctx.LastActive()
|
||
ctx.UpdateLastActive()
|
||
duration := ctx.TimeSinceLastActive()
|
||
|
||
// 设置/获取客户端信息
|
||
ctx.SetInfo(clientInfo)
|
||
info := ctx.GetInfo()
|
||
|
||
// 设置/获取用户数据
|
||
ctx.SetUserData(myData)
|
||
data := ctx.GetUserData()
|
||
```
|
||
|
||
### Protocol
|
||
|
||
```go
|
||
// 解析登录信息
|
||
info, err := protocol.ParseLoginInfo(data)
|
||
if err == nil {
|
||
fmt.Println(info.PCName) // 计算机名
|
||
fmt.Println(info.OsVerInfo) // 操作系统
|
||
fmt.Println(info.ModuleVersion) // 版本
|
||
fmt.Println(info.WebCamExist) // 是否有摄像头
|
||
}
|
||
|
||
// 获取扩展字段
|
||
reserved := info.ParseReserved() // 返回 []string
|
||
clientType := info.GetReservedField(0) // 客户端类型
|
||
cpuCores := info.GetReservedField(2) // CPU 核数
|
||
filePath := info.GetReservedField(4) // 文件路径
|
||
publicIP := info.GetReservedField(11) // 公网 IP
|
||
```
|
||
|
||
## 与 C++ 版本对比
|
||
|
||
| 特性 | C++ (IOCP) | Go |
|
||
| ---- | ---- | ---- |
|
||
| 并发模型 | IOCP + 线程池 | Goroutine 池 |
|
||
| 跨平台 | Windows | 全平台 |
|
||
| 内存管理 | 手动 | GC |
|
||
| 代码复杂度 | 高 | 低 |
|
||
| 压缩 / XOR / 头加密 | 完整 8 套加密方式 + XOREncoder16 + ZSTD | 完全对齐 |
|
||
| 字符编码 | GBK | UTF-8 直通 / GBK→UTF-8 (按客户端能力位) |
|
||
| 设备列表与监控 | MFC 列表控件 | Web UI |
|
||
| Web 远程桌面 | 内嵌浏览器 + H.264 | 完全对齐(WebCodecs 解码) |
|
||
| 鼠标键盘转发 | 已实现 | 完全对齐 |
|
||
| Web 终端 | 内嵌 xterm.js + ConPTY | 完全对齐(含旧 cmd-pipe 兼容) |
|
||
| 用户 / 分组管理 | 已实现 | users.json schema 互通 |
|
||
| 文件传输 / 摄像头 / 录音 等 | 已实现 | 暂未实现(按需扩展) |
|
||
|
||
## 依赖
|
||
|
||
- [github.com/klauspost/compress/zstd](https://github.com/klauspost/compress) - ZSTD 压缩
|
||
- [github.com/rs/zerolog](https://github.com/rs/zerolog) - 高性能日志
|
||
- [gopkg.in/natefinch/lumberjack.v2](https://github.com/natefinch/lumberjack) - 日志轮转
|
||
- [golang.org/x/text](https://pkg.go.dev/golang.org/x/text) - GBK 编码转换
|
||
|
||
## 配置和启动参数
|
||
|
||
### 启动参数说明
|
||
|
||
- `-p` / `--port`:受管设备 TCP 监听端口(默认 6543,可分号分隔多端口,如 `6543;6544;6545`)
|
||
- `--http-port`:Web 管理 HTTP 端口(默认 8080,设为 0 则禁用 Web 管理)
|
||
- `--no-console`:守护进程模式,不输出到控制台(日志仍写入 `logs/server.log`)
|
||
|
||
### (1)授权中心(由我运行)
|
||
|
||
**模式判定**:设置了 `YAMA_SIGN_PASSWORD`,本进程以 LocalSigner 持有主 HMAC 密钥;可选地开启
|
||
License Server HTTP(`YAMA_LICENSE_PUBLIC_KEY` + `YAMA_LICENSE_HTTP_ADDR`),向下级服务签发带 24h 缓存的授权。
|
||
|
||
```bash
|
||
export YAMA_PWD="授权码 HMAC 校验密钥(TCP 端 passcode 签名验证)"
|
||
export YAMA_WEB_ADMIN_PASS="Web 端登录密码"
|
||
export YAMA_SIGN_PASSWORD="主控签名 HMAC 主密钥(受管设备验证服务端身份)"
|
||
export YAMA_WEB_TRUST_PROXY=1
|
||
export YAMA_WEB_ALLOWED_ORIGINS="https://web.just-do-it.icu:8080"
|
||
export YAMA_LICENSE_PRIVATE_KEY="/opt/yama/license_priv.pem"
|
||
export YAMA_LICENSE_PUBLIC_KEY="/opt/yama/license_pub.pem"
|
||
export YAMA_LICENSE_HTTP_ADDR="127.0.0.1:8443"
|
||
|
||
nohup ./server_linux_amd64 -p 8000 --http-port=9001 --no-console &
|
||
```
|
||
|
||
由前端代理将公网流量(受管设备 8000、Web 9001、License Server 8443)转发到本进程。受管设备通过 8000 端口直连服务端;
|
||
下级服务(运行 RemoteSigner)通过代理对外暴露的 `YAMA_LICENSE_SERVER` 公网 URL 取授权签名,代理后端转发到本进程
|
||
绑定的 `YAMA_LICENSE_HTTP_ADDR (127.0.0.1:8443)`。
|
||
|
||
可选环境变量:
|
||
|
||
- `YAMA_PWDHASH`:TCP 授权码的 SHA256 哈希(未设置则使用代码内置默认值,启动 banner 会有警告)
|
||
- `YAMA_USERS_FILE`:额外 Web 用户列表 JSON 路径(默认 `users.json`)
|
||
|
||
### (2)下级服务(运营商部署)
|
||
|
||
下级二进制把"小白用户开箱即用"作为目标——**理想情况下,什么都不配,直接 `./server_linux_amd64` 就能跑起来**。
|
||
启动后默认行为:
|
||
|
||
- License Server:默认连到 `https://web.just-do-it.icu:8080`(DefaultLicenseServerURL)
|
||
- 模式:无 `YAMA_LICENSE_TOKEN` → 进入**试用模式**(TRIAL),授权中心按下级出口 IP 识别身份,最多 **2 台**
|
||
受管设备(FreeMaxDevices)
|
||
- Web 管理:无 `YAMA_WEB_ADMIN_PASS` → 使用默认账号 `admin/admin`,启动日志会大字警告
|
||
- 监听端口:受管设备 TCP 6543、Web 8080
|
||
|
||
#### 最简启动(零配置试用,2 台设备上限)
|
||
|
||
```bash
|
||
nohup ./server_linux_amd64 --no-console &
|
||
```
|
||
|
||
启动日志会显示:
|
||
|
||
```
|
||
WARN ⚠ YAMA_WEB_ADMIN_PASS / YAMA_PWD 均未设置,Web 管理使用默认密码 admin/admin — 生产环境务必覆盖
|
||
INFO Signer mode: TRIAL (anonymous试用模式, license server=https://web.just-do-it.icu:8080,
|
||
最多 2 台受管设备; 设置 YAMA_LICENSE_TOKEN 解锁付费配额)
|
||
```
|
||
|
||
#### 推荐生产配置(覆盖默认密码 + 付费 token)
|
||
|
||
```bash
|
||
export YAMA_WEB_ADMIN_PASS="自定义 Web 登录密码"
|
||
export YAMA_LICENSE_TOKEN="eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3M..." # 向授权中心申请
|
||
|
||
nohup ./server_linux_amd64 -p 6543 --http-port=8080 --no-console &
|
||
```
|
||
|
||
设置 `YAMA_LICENSE_TOKEN` 后切换为 REMOTE 模式(付费),配额由 JWT 的 `max_devices` 决定。
|
||
如果同时改 `YAMA_LICENSE_SERVER` 可以接到自部署的授权中心,否则继续走默认 URL。
|
||
|
||
#### 其他可选
|
||
|
||
- `YAMA_LICENSE_DISABLED=1`:完全禁用 License Server 通信(离线 / 内网测试),客户端会拒绝屏幕/文件功能但设备列表仍能用
|
||
- `YAMA_LICENSE_OFFLINE_HRS=24`:本地签名缓存的 TTL,默认 24h
|
||
- 受管设备的 `client.exe` 由 Windows 主控端生成(已绑定服务端地址和公钥),下级运营商把它分发给终端用户。
|
||
client 通过 6543 端口连接到本服务端;用户通过 8080 端口登录 Web 管理页面。
|
||
|
||
## License
|
||
|
||
MIT License
|