0
0
0
架构与控制面/数据面划分
文章摘要
|
来源:
intranet-tunnel/README.md→## 一、架构设计(原文 2353 字符)
一、架构设计
1.1 双层连接模型
系统严格分离控制平面与数据平面:
| 连接类型 | 用途 | 数量 | 生命周期 | 传输内容 |
|---|---|---|---|---|
| 控制连接 | 鉴权、心跳、隧道注册、规则下发 | 每客户端 1 条 | 持久,断开自动重连 | 控制协议消息(JSON 帧) |
| 工作连接 | 实际数据转发 | 按需创建,可池化 | 临时,用后回收(空闲 60s) | 原始 TCP 字节流 |
控制连接与工作连接复用同一个监听端口(47800),靠首帧消息类型区分(Login vs NewWorkConn),因此只需对外暴露一个端口。
1.2 数据流时序
公网用户 Nginx Server Client 内网服务
│ │ │ │ │
│── HTTPS 请求 ───────>│ │ │ │
│ │ SSL 终结 │ │ │
│ │── 明文 HTTP ────────>│ │ │
│ │ (Host: a.b.com) │ 按 Host 匹配路由表 │ │
│ │ │── ReqWorkConn ─────────>│ │
│ │ │<─ 新建 TCP 工作连接 ─────│ │
│ │ │── NewWorkConn(指派) ───>│ │
│ │ │ │── net.Dial ───────>│
│ │ │<═══ 双向 io.Copy ══════>│<══ 双向 io.Copy ══>│
│<────── 响应 ─────────│<─────────────────────│ │ │关键点:
- 服务端不处理 SSL 证书,HTTPS 完全由 Nginx 卸载,服务端只按
Host头路由明文 HTTP; - 工作连接由客户端主动发起,因此内网侧无需任何入站端口开放;
- 启用 yamux 多路复用后,工作连接不再新建 TCP,而是直接在既有连接上开一条逻辑流,省掉握手与 TLS 开销。
1.3 关键设计决策
| 决策 | 原因 |
|---|---|
控制消息采用 [4 字节长度][JSON] 自定义帧 | 变长消息必须有长度前缀才能可靠分帧;上限 1 MiB 防御超长帧耗尽内存 |
| 工作连接复用一个监听端口、靠首帧类型区分 | 减少对外暴露端口;TLS 握手只需一种配置 |
数据面自解析 HTTP 请求行与 Host 头,而非 net/http.Server | 需要把原始 TCP 连接整体交给后端(keep-alive 复用、WebSocket 升级),http.Server 的缓冲与 Hijack 语义存在字节丢失风险 |
| 隧道标识统一使用数据库自增主键的十进制字符串 | 协议层与存储层一一对应,避免额外 ID 生成器 |
| 统计在内存聚合、周期性落库 | 数据面按字节写库不可行;30 秒窗口写入兼顾实时性与数据库压力 |
| 时间序列分桶在 Go 侧完成 | date_trunc / DATE_FORMAT / DATEPART 在四种目标数据库间不通用 |
custom_domain / remote_port 使用可空类型 | 让「未绑定」在 PostgreSQL / MySQL / SQLite / SQL Server 的唯一索引下语义一致 |
SQLite 使用纯 Go 驱动(glebarez/sqlite) | 保证 CGO_ENABLED=0 静态编译与 alpine 运行镜像可用 |
来源:
intranet-tunnel/README.md→## 三、端口规划(原文 580 字符)
三、端口规划
全部使用高位端口,避免与宿主机常规服务冲突。
| 端口 | 用途 | 建议暴露范围 |
|---|---|---|
| 47800 | 控制连接监听(客户端接入 + 工作连接) | 对外(客户端需从公网接入) |
| 47801 | Web 管理 API + 管理后台前端 | 仅 127.0.0.1,由 Nginx 代理 |
| 48080 | 公网 HTTP/HTTPS 流量入口 | 仅 127.0.0.1,由 Nginx 代理 |
| 48081-48100 | TCP 隧道端口池 | 对外(或用 Nginx stream 解耦) |
| 48443 | 内置 HTTPS 入口(证书自动化启用后) | 对外或用 Nginx 终止 TLS |
| 49000-49100 | TCP/UDP 端口转发范围(FORWARD_PORT_RANGE) | 按需对外(含 UDP) |
若希望服务端控制端口也不直接对外,可把
docker-compose.yml中 47800 的映射改为127.0.0.1:47800:47800,并启用deploy/nginx/conf.d/stream/tunnel-stream.conf中注释掉的「控制连接转发」段落。
来源:
intranet-tunnel/README.md→## 九、控制协议(原文 1509 字符)
九、控制协议
9.1 帧格式
+----------------+----------------------------+
| 4 字节大端长度 | JSON 消息体(≤ 1 MiB) |
+----------------+----------------------------+{
"type": "Login",
"transaction_id": "可选,用于请求/响应对应",
"payload": { }
}9.2 消息类型
| Type | 方向 | 说明 |
|---|---|---|
Login | C → S | 携带 client_id、token、版本、主机信息、是否启用多路复用、本地静态隧道 |
LoginResp | S → C | 返回 run_id、心跳间隔、是否启用多路复用,以及该客户端的完整隧道列表 |
NewTunnel | S → C | 下发/更新隧道配置(服务端启动或后台新增时) |
NewTunnelResp | C → S | 隧道配置处理结果 |
CloseTunnel | S → C | 通知客户端注销隧道 |
ReqWorkConn | S → C | 请求客户端新建一条工作连接 |
NewWorkConn | 双向 | 工作连接握手:客户端侧携带 run_id 认证;服务端侧携带 tunnel_id 指派隧道 |
Heartbeat / HeartbeatResp | C ↔ S | 心跳与响应 |
Notify | S → C | 主动通知(re_login=true 时客户端立即重连) |
9.3 关键流程
登录与多路复用协商
- 客户端在原始 TCP(或 TLS)连接上发送
Login; - 服务端校验 Token(HMAC 摘要常数时间比对)后回复
LoginResp(此时仍在原始连接上); - 若双方都支持多路复用,服务端在该连接上建立 yamux Server 会话,客户端建立 Client 会话;
- 客户端
Open()第一条流作为控制流,服务端Accept()该流,此后所有控制消息走此流; - 后续服务端需要数据通道时直接
Open()新流,客户端Accept()—— 无需任何 TCP/TLS 握手。
工作连接获取(服务端视角)
公网连接到达
├─ 多路复用可用? → 直接在 yamux 会话上 Open 一条流 → 写 NewWorkConn{TunnelID} 指派
└─ 否则
├─ 池中有空闲连接? → 取出 → 写 NewWorkConn{TunnelID} 指派
└─ 池空 → 每 2 秒发送 ReqWorkConn(最多到超时)
→ 客户端拨号并发送 NewWorkConn{RunID} 认证
→ 服务端校验 RunID 后放入连接池 → 唤醒等待者在线判定:服务端对控制连接设置读超时(默认 90 秒 = 3 × 心跳间隔),同时心跳巡检协程每 15 秒兜底检查,超时即关闭会话、释放工作连接、更新数据库状态为 offline。
