634b5a7bd0
- model: SimNet protocol fields + NormalizeSimnet; type+port protocol uniqueness - server config: OmnXT runtime config delivery via compatible() simnet case - credentials: derive per-user psk/key_id from subscription (pkg/simnet), no new table - subscription: adapter buildOmnxtSimnetConfigs + base64 buildOmnxtProtocolLinks + OmnXT SimNet application (migration 02161) - UA gating: hide experimental protocols from non first-party clients (download + JSON node-list) - admin: normalize simnet on create/update and on GET responses - tests: 21 simnet unit tests; full suite green
814 lines
32 KiB
Markdown
814 lines
32 KiB
Markdown
# PPanel Server Simnet 协议接入实施计划
|
||
|
||
本文档用于指导在现有自维护后端 `/Users/Apple/code_vpn/vpn/ppanel-server` 中接入 `simnet` 协议。目标不是把 Pro 新版后端整体迁移进来,而是在保留旧系统架构、数据库主链路和现有节点管理模型的前提下,把 `simnet` 做到管理端可配置、OmnXT 节点可拉取、SlagClient 可订阅连接、用户授权和流量统计闭环。
|
||
|
||
参考实现来自新版 Pro 后端:`/Users/Apple/Downloads/NPanelPro-pro/NPanel-backend`。
|
||
|
||
## 1. 项目背景
|
||
|
||
当前旧后端已经有完整的 Server、Node、Subscribe、Traffic、Online User 等链路,协议配置主要保存在 Server 的 `protocols` JSON 字段里,Node 侧用 `protocol + port + address` 描述对外节点。新版 Pro 后端已经加入了 `simnet` 协议字段、管理端接口、节点兼容接口和订阅交付逻辑,但它的整体工程结构和旧仓库不同。
|
||
|
||
旧仓库是 Gin/goctl/Gorm 风格,核心入口包括:
|
||
|
||
- API 定义:`apis/admin/server.api`、`apis/node/node.api`、`apis/public/subscribe.api`、`apis/types.api`
|
||
- 生成类型:`internal/types/types.go`
|
||
- 管理端 Server 逻辑:`internal/logic/admin/server/*`
|
||
- 节点服务端配置拉取:`internal/logic/server/getServerConfigLogic.go`
|
||
- 节点用户列表拉取:`internal/logic/server/getServerUserListLogic.go`
|
||
- 公共订阅节点返回:`internal/logic/public/subscribe/queryUserSubscribeNodeListLogic.go`
|
||
- 节点在线与流量上报:`internal/logic/server/pushOnlineUsersLogic.go`、`internal/logic/server/serverPushUserTrafficLogic.go`
|
||
|
||
新版 Pro 的关键参考入口包括:
|
||
|
||
- Simnet 管理端字段:`api/admin/server/v1/server.proto`
|
||
- OmnXT 节点兼容接口:`internal/server/http_compat_server.go`
|
||
- 公共订阅响应:`api/public/subscribe/v1/subscribe.proto`
|
||
- 公共订阅映射:`internal/service/public/subscribe/subscribe.go`
|
||
- UA/capability 过滤:`internal/biz/public/subscribe/subscribe.go`
|
||
- 节点交付数据:`internal/data/delivery_node.go`
|
||
- 协议模型和默认值:`internal/model/server/protocol.go`
|
||
|
||
## 2. 目标与非目标
|
||
|
||
### 目标
|
||
|
||
1. 在旧后端中完整支持 `simnet` 协议的保存、查询、下发、订阅和统计。
|
||
2. 继续使用旧系统 Server 的 `protocols` JSON 保存协议配置,不强制拆表保存管理端协议配置。
|
||
3. 第一版支持当前实际需要的能力:H2、TLS/SNI、AF、HTTPS Fallback。
|
||
4. Reverse 字段先纳入模型和接口,默认关闭;不在第一版强制上线 Reverse 转发能力。
|
||
5. 管理端配置、OmnXT 服务端运行配置、SlagClient 客户端订阅配置使用不同 DTO,避免敏感字段误下发。
|
||
6. 使用 `type + port` 唯一定位一个 Server 内的协议实例,支持同一 Server 未来存在多个协议。
|
||
7. OmnXT 拉取配置必须校验 `secret_key`。
|
||
8. Server 级 PSK 不得下发给普通用户。
|
||
9. 优先设计每用户独立 Simnet Key ID/PSK,使用户隔离、封禁、重置和审计可控。
|
||
10. SlagClient 订阅响应兼容 `protocols` JSON 和顶层 `simnet_*` 字段。
|
||
|
||
### 非目标
|
||
|
||
1. 不整体替换旧后端为 Pro 新后端。
|
||
2. 不一次性迁移 Pro 的全部协议字段、路由系统、完整 delivery node 架构。
|
||
3. 不第一版实现 OmniFlow 或其他新协议。
|
||
4. 不改变现有套餐、订单、余额、邀请等业务主链路。
|
||
5. 不把生产服务器凭据、JWT、节点 SSH 密码写入代码或文档。
|
||
|
||
## 3. 总体技术策略
|
||
|
||
最科学的迁移方式是“协议纵向切入”,而不是“代码横向搬运”。也就是沿着 `simnet` 从管理端保存到节点运行,再到用户订阅、授权、流量统计的完整链路逐层补齐。
|
||
|
||
建议分三段落地:
|
||
|
||
1. Server 侧先闭环:管理端能保存 `simnet`,OmnXT 能用 `secret_key` 拉到运行配置。
|
||
2. User 侧再闭环:每个用户生成独立凭据,OmnXT 用户列表和 SlagClient 订阅使用同一套凭据。
|
||
3. 运维侧最后闭环:流量、在线、到期、限额、TLS/AF/Fallback、灰度和回滚全部验证。
|
||
|
||
核心原则:
|
||
|
||
- 旧架构优先:沿用 goctl API、`internal/types`、现有 logic/model 风格。
|
||
- DTO 分层:管理端 DTO 可以看到完整配置;节点 DTO 只给 OmnXT 运行需要;订阅 DTO 只给用户连接需要。
|
||
- 敏感字段隔离:Server PSK、证书 DNS 环境变量、节点密钥不得进入普通用户订阅响应。
|
||
- 渐进兼容:老协议、老客户端、老节点不受影响。
|
||
- 可回滚:每个阶段都能通过关闭 `simnet` 协议或恢复旧接口行为回滚。
|
||
|
||
## 4. Simnet 数据链路
|
||
|
||
完整链路如下:
|
||
|
||
```text
|
||
Admin UI
|
||
-> POST /api/v1/admin/server/create or update
|
||
-> Server.protocols JSON contains type=simnet
|
||
|
||
OmnXT Node
|
||
-> GET /api/v1/server/config?server_id=...&protocol=simnet&secret_key=...
|
||
-> receives server runtime config, including server-side PSK and TLS/AF/Fallback settings
|
||
|
||
OmnXT Node
|
||
-> GET /api/v1/server/user/list?server_id=...&protocol=simnet&secret_key=...
|
||
-> receives active user authorization list and per-user simnet credentials
|
||
|
||
SlagClient
|
||
-> GET /api/v1/public/subscribe?token=... with capability headers
|
||
-> receives node address, port, TLS/SNI, path, AF/Fallback public fields and user credential
|
||
|
||
OmnXT Node
|
||
-> POST traffic / online user report
|
||
-> backend maps simnet user credential to user subscribe and records traffic
|
||
```
|
||
|
||
`simnet` 的运行配置不能只靠 `server.protocols` 原样下发,因为同一份 JSON 同时包含管理端字段、Server 密钥字段和用户连接字段。必须在每个出口做字段筛选和转换。
|
||
|
||
## 5. 阶段 0:建立基线与确认契约
|
||
|
||
### 目标
|
||
|
||
确认旧后端、OmnXT、SlagClient 对 `simnet` 的最小契约,先把边界钉牢,避免后续实现时字段名、鉴权方式或客户端解析格式反复改。
|
||
|
||
### 具体任务
|
||
|
||
1. 从新版 Pro 提取 `simnet` 管理字段、服务端字段、订阅字段的差异表。
|
||
2. 用当前 OmnXT 安装脚本部署的版本抓取真实请求路径和请求参数。
|
||
3. 用 SlagClient 抓取订阅请求 header,确认 capability header 名称和版本值。
|
||
4. 确认 `secret_key` 当前在旧仓库 `internal/middleware/serverMiddleware.go` 或节点接口 handler 中的校验方式。
|
||
5. 确认 `server_id + protocol` 是否已经足够定位节点运行配置;如果端口也会重复,需要补充 `port` 查询参数。
|
||
|
||
### 预计修改位置
|
||
|
||
本阶段原则上不改业务代码,只新增测试夹具或临时验证脚本。可新增:
|
||
|
||
- `tests/simnet/fixtures/`
|
||
- `docs/simnet-contract.md`,如需要更细的契约文档
|
||
|
||
### 依赖关系
|
||
|
||
- 需要可运行的旧后端本地环境或测试库。
|
||
- 需要 OmnXT 当前版本真实请求样本。
|
||
- 需要 SlagClient 当前版本订阅响应解析规则。
|
||
|
||
### 验收条件
|
||
|
||
1. 明确 OmnXT 配置接口路径、方法、请求参数和响应字段。
|
||
2. 明确 SlagClient 识别 `simnet` 的字段格式。
|
||
3. 明确 capability header 优先级:先 capability header,再 User-Agent 兜底。
|
||
4. 明确 `type + port` 是协议实例唯一键。
|
||
|
||
### 回滚点
|
||
|
||
本阶段不涉及生产行为,无需业务回滚。
|
||
|
||
## 6. 阶段 1:协议模型与参数校验
|
||
|
||
### 目标
|
||
|
||
让旧后端的 `Protocol` 类型可以完整表达第一版 `simnet` 配置,并在创建/更新 Server 时有默认值和校验。
|
||
|
||
### 具体任务
|
||
|
||
1. 在 `apis/types.api` 的 `Protocol` 结构加入 `simnet` 字段。
|
||
2. 重新生成 `internal/types/types.go`。
|
||
3. 在 `internal/model/node` 中的协议模型加入同名 JSON 字段,保证 Server 的 `protocols` JSON 能完整 marshal/unmarshal。
|
||
4. 新增 `simnet` 默认值函数,例如 `ApplySimnetDefaults`。
|
||
5. 新增 `simnet` 参数校验函数,例如 `ValidateSimnetProtocol`。
|
||
6. 校验 `type + port` 唯一,避免同一 Server 下出现两个 `simnet:443`。
|
||
7. 限制第一版允许值:`simnet_carrier=h2`、`security=tls|none`,生产建议默认 `tls`。
|
||
8. 校验 path 必须以 `/` 开头,fallback host 非空时端口必须在 1-65535。
|
||
9. 校验 `simnet_psk` 最小长度和字符集;自动生成时使用安全随机。
|
||
|
||
### 字段范围
|
||
|
||
核心字段:
|
||
|
||
```text
|
||
simnet_psk
|
||
simnet_key_id
|
||
simnet_ticket_id
|
||
simnet_path
|
||
simnet_carrier
|
||
```
|
||
|
||
TLS 字段:
|
||
|
||
```text
|
||
security
|
||
sni
|
||
allow_insecure
|
||
cert_mode
|
||
cert_dns_provider
|
||
cert_dns_env
|
||
```
|
||
|
||
AF 字段:
|
||
|
||
```text
|
||
simnet_af_enabled
|
||
simnet_af_path_mode
|
||
simnet_af_path_prefix
|
||
simnet_af_path_suffix
|
||
simnet_af_magic_mode
|
||
simnet_af_response_jitter_ms
|
||
simnet_af_handshake_polymorphism
|
||
simnet_af_settings_jitter
|
||
simnet_af_fake_header_injection
|
||
```
|
||
|
||
Fallback 字段:
|
||
|
||
```text
|
||
simnet_fallback_enabled
|
||
simnet_fallback_target_scheme
|
||
simnet_fallback_target_host
|
||
simnet_fallback_target_port
|
||
simnet_fallback_host_header
|
||
simnet_fallback_tls_sni
|
||
```
|
||
|
||
Reverse 字段:
|
||
|
||
```text
|
||
simnet_reverse_enabled
|
||
simnet_reverse_listen_addr
|
||
simnet_reverse_listen_port
|
||
simnet_reverse_target_host
|
||
simnet_reverse_target_port
|
||
```
|
||
|
||
### 默认值
|
||
|
||
建议默认值如下:
|
||
|
||
```text
|
||
port: 443
|
||
simnet_path: /simnet/session
|
||
simnet_carrier: h2
|
||
security: tls
|
||
allow_insecure: false
|
||
simnet_af_path_mode: api
|
||
simnet_af_magic_mode: derived
|
||
simnet_af_response_jitter_ms: 1
|
||
simnet_reverse_enabled: false
|
||
simnet_reverse_listen_addr: 127.0.0.1
|
||
simnet_fallback_enabled: true
|
||
simnet_fallback_target_scheme: https
|
||
simnet_fallback_target_port: 443
|
||
```
|
||
|
||
### 预计修改位置
|
||
|
||
- `apis/types.api`
|
||
- `internal/types/types.go`
|
||
- `internal/model/node/*` 或实际定义 `node.Protocol` 的文件
|
||
- `internal/logic/admin/server/createServerLogic.go`
|
||
- `internal/logic/admin/server/updateServerLogic.go`
|
||
- 可新增 `internal/logic/admin/server/protocol_simnet.go`
|
||
|
||
### 依赖关系
|
||
|
||
- 阶段 0 的字段契约。
|
||
- goctl 代码生成命令可用。
|
||
|
||
### 验收条件
|
||
|
||
1. 管理端提交 `type=simnet` 时,Server 可以保存完整 JSON。
|
||
2. 未传默认字段时自动补齐默认值。
|
||
3. 非法 path、非法 port、重复 `type + port` 会被拒绝。
|
||
4. 旧协议保存和返回不变。
|
||
|
||
### 回滚点
|
||
|
||
关闭管理端提交 `simnet` 的入口校验;或恢复 `apis/types.api` 和生成类型,旧协议数据仍可继续工作。
|
||
|
||
## 7. 阶段 2:管理端 Server 接口
|
||
|
||
### 目标
|
||
|
||
让管理端 Server 创建、更新、查询能完整展示和编辑 `simnet`,并保持 Node 更新接口与 Server 协议配置一致。
|
||
|
||
### 具体任务
|
||
|
||
1. 更新 `CreateServerRequest`、`UpdateServerRequest`、`FilterServerListResponse`、`GetServerProtocolsResponse` 中的协议字段。
|
||
2. 在 create/update Server 时对每个 protocol 先做 normalize,再落库。
|
||
3. 在 filter/list/detail 接口中返回规范化后的 `simnet` 字段。
|
||
4. 检查 `CreateNodeRequest`、`UpdateNodeRequest` 是否允许 `protocol=simnet`。
|
||
5. Node 端 `node_type=front` 的创建/更新要允许 `simnet`,并校验其 `port` 与 Server 里的 `simnet` 协议端口一致。
|
||
6. 如果管理端前端需要协议选项,`GetServerProtocols` 要返回 `simnet`,并带默认字段方便 UI 填充。
|
||
|
||
### 预计修改位置
|
||
|
||
- `apis/admin/server.api`
|
||
- `internal/types/types.go`
|
||
- `internal/logic/admin/server/createServerLogic.go`
|
||
- `internal/logic/admin/server/updateServerLogic.go`
|
||
- `internal/logic/admin/server/filterServerListLogic.go`
|
||
- `internal/logic/admin/server/getServerProtocolsLogic.go`
|
||
- `internal/logic/admin/server/createNodeLogic.go`
|
||
- `internal/logic/admin/server/updateNodeLogic.go`
|
||
|
||
### 依赖关系
|
||
|
||
- 阶段 1 协议模型已经可表达 `simnet`。
|
||
|
||
### 验收条件
|
||
|
||
1. 管理端能创建一个 Server,包含 `simnet:443`。
|
||
2. 管理端能更新 `simnet_path`、`sni`、AF 和 fallback 字段。
|
||
3. 管理端节点列表显示 `HK simnet` 这类节点时,协议类型不丢失。
|
||
4. `GetServerProtocols` 返回的 `protocols` JSON 与数据库一致且字段完整。
|
||
|
||
### 回滚点
|
||
|
||
从管理端把 `simnet` 协议 disabled,保留数据但不对节点下发;或回滚 Server 相关 API 和 logic。
|
||
|
||
## 8. 阶段 3:OmnXT 服务端配置下发
|
||
|
||
### 目标
|
||
|
||
让 OmnXT 节点通过旧后端节点 API 拉到可运行的 `simnet` 服务端配置。
|
||
|
||
### 具体任务
|
||
|
||
1. 检查 `apis/node/node.api` 中 `GetServerConfigRequest` 是否有 `secret_key`、`server_id`、`protocol`。
|
||
2. 在 `GetServerConfigLogic` 中加入 `protocol=simnet` 分支。
|
||
3. 根据 `server_id + protocol + port` 找到启用的 `simnet` 协议配置。
|
||
4. 验证 `secret_key`,失败时返回明确错误,并记录来源 IP 和 server_id。
|
||
5. 构造 OmnXT 服务端运行 DTO,包含 Server 运行需要的 PSK、path、carrier、TLS、SNI、AF、fallback、reverse 默认关闭字段。
|
||
6. 不把管理端专用字段、无关协议字段原样塞给 OmnXT。
|
||
7. 缓存 key 要包含 `server_id + protocol + port`,避免同端口多协议污染缓存。
|
||
8. OmnXT 配置变更后要能通过更新 Server 或清理缓存生效。
|
||
|
||
### 服务端 DTO 建议
|
||
|
||
```json
|
||
{
|
||
"protocol": "simnet",
|
||
"port": 443,
|
||
"listen": ":443",
|
||
"simnet_psk": "server-side-secret",
|
||
"simnet_path": "/simnet/session",
|
||
"simnet_carrier": "h2",
|
||
"security": "tls",
|
||
"sni": "example.com",
|
||
"allow_insecure": false,
|
||
"simnet_af_enabled": true,
|
||
"simnet_fallback_enabled": true
|
||
}
|
||
```
|
||
|
||
### 预计修改位置
|
||
|
||
- `apis/node/node.api`
|
||
- `internal/types/types.go`
|
||
- `internal/logic/server/getServerConfigLogic.go`
|
||
- `internal/logic/server/constant.go`
|
||
- `internal/middleware/serverMiddleware.go`
|
||
- 可新增 `internal/logic/server/simnet_config.go`
|
||
|
||
### 依赖关系
|
||
|
||
- 阶段 1 和阶段 2。
|
||
- OmnXT 实际接口字段确认完成。
|
||
|
||
### 验收条件
|
||
|
||
1. `secret_key` 正确时,OmnXT 能拉到 `simnet` 服务端配置。
|
||
2. `secret_key` 错误时,请求被拒绝。
|
||
3. 修改管理端 `simnet_path` 后,OmnXT 重启或刷新能拿到新 path。
|
||
4. Server PSK 只出现在 OmnXT 服务端配置中,不出现在普通用户订阅中。
|
||
|
||
### 回滚点
|
||
|
||
关闭 `simnet.enable` 或回滚 `GetServerConfigLogic` 的 `simnet` 分支;旧协议节点不受影响。
|
||
|
||
## 9. 阶段 4:用户级 Simnet 凭据
|
||
|
||
### 目标
|
||
|
||
为每个有效用户订阅生成独立 `simnet` 凭据,避免所有用户共享 Server PSK,支持单用户封禁、重置和流量归属。
|
||
|
||
### 具体任务
|
||
|
||
1. 新增用户级凭据模型,建议按 `user_subscribe_id + server_id + protocol + port` 维度唯一。
|
||
2. 字段建议包括:`id`、`user_id`、`user_subscribe_id`、`server_id`、`protocol`、`port`、`key_id`、`psk`、`ticket_id`、`enabled`、`created_at`、`updated_at`、`rotated_at`。
|
||
3. 添加数据库 migration,并在初始化兼容逻辑中保证表存在。
|
||
4. 用户第一次订阅或节点第一次拉用户列表时懒生成凭据。
|
||
5. 支持管理员重置某个用户订阅 token 时同步重置 `simnet` 凭据,避免旧凭据继续可用。
|
||
6. 凭据生成使用加密安全随机;`key_id` 可用递增 id 或稳定 hash,但必须避免全局冲突。
|
||
7. 保留 `ticket_id` 字段,第一版可为空或由 OmnXT 需要时生成。
|
||
|
||
### 预计修改位置
|
||
|
||
- `internal/model/user/*` 或新增 `internal/model/simnet/*`
|
||
- `initialize/migrate/*`
|
||
- `initialize/schema_compat.go`
|
||
- `internal/logic/public/subscribe/queryUserSubscribeNodeListLogic.go`
|
||
- `internal/logic/server/getServerUserListLogic.go`
|
||
- 用户订阅 token 重置逻辑:`internal/logic/admin/user/resetUserSubscribeTokenHandler.go` 对应 logic
|
||
|
||
### 依赖关系
|
||
|
||
- 阶段 0 确认 OmnXT 和 SlagClient 需要的用户凭据格式。
|
||
- 阶段 1 的协议模型完成。
|
||
|
||
### 验收条件
|
||
|
||
1. 同一用户同一节点多次订阅拿到稳定凭据。
|
||
2. 不同用户拿到不同凭据。
|
||
3. 重置用户订阅 token 后旧凭据失效,新凭据生效。
|
||
4. 凭据表有唯一约束,重复生成不会产生两条有效凭据。
|
||
|
||
### 回滚点
|
||
|
||
可以停止向 OmnXT 下发 `simnet` 用户授权,并禁用 `simnet` 节点。数据库表可保留,不影响旧协议。
|
||
|
||
## 10. 阶段 5:OmnXT 用户授权同步
|
||
|
||
### 目标
|
||
|
||
让 OmnXT 拉取用户列表时获得 `simnet` 可认证用户,并且用户到期、限额、禁用、套餐节点组变化后同步生效。
|
||
|
||
### 具体任务
|
||
|
||
1. 在 `GetServerUserListLogic` 中加入 `simnet` 用户映射。
|
||
2. 沿用旧系统的有效用户筛选条件:订阅有效、未到期、流量未超限、用户未禁用、节点组有权限。
|
||
3. 对 `simnet` 用户返回 `user_id`、`subscribe_id`、`uuid`、`key_id`、`psk`、`ticket_id`、限速字段。
|
||
4. OmnXT 请求 `protocol=simnet` 时,只返回有 `simnet` 权限的用户。
|
||
5. 缓存 key 加入 `protocol + port`,用户订阅变更、流量变更、节点组变更时能失效。
|
||
6. 对 `hysteria2` 等旧兼容映射不做破坏;`normalizeServerUserListProtocol` 仅新增 `simnet` 透传。
|
||
|
||
### 预计修改位置
|
||
|
||
- `apis/node/node.api`
|
||
- `internal/types/types.go`
|
||
- `internal/logic/server/getServerUserListLogic.go`
|
||
- `internal/logic/server/constant.go`
|
||
- 用户订阅、节点组、流量相关 model/service
|
||
- 可新增 `internal/logic/server/simnet_user.go`
|
||
|
||
### 依赖关系
|
||
|
||
- 阶段 4 用户级凭据。
|
||
- 现有用户有效性判断需要梳理清楚。
|
||
|
||
### 验收条件
|
||
|
||
1. OmnXT 拉用户列表时能看到有效用户的 `simnet` 凭据。
|
||
2. 用户到期、禁用或流量超限后,从 OmnXT 用户列表消失。
|
||
3. 套餐节点组取消该节点后,从 OmnXT 用户列表消失。
|
||
4. 老协议用户列表响应不变。
|
||
|
||
### 回滚点
|
||
|
||
保留凭据表,但关闭 `GetServerUserListLogic` 的 `simnet` 分支或禁用节点。
|
||
|
||
## 11. 阶段 6:公共订阅与 SlagClient
|
||
|
||
### 目标
|
||
|
||
让 SlagClient 冷启动、重启、重新订阅时都能拿到完整 `simnet` 节点,并正确构造连接。
|
||
|
||
### 具体任务
|
||
|
||
1. 在 `apis/public/subscribe.api` 的 `UserSubscribeNodeInfo` 加入用户连接需要的顶层 `simnet_*` 字段。
|
||
2. 保留 `protocols` JSON,确保 SlagClient 旧解析路径仍可读取。
|
||
3. 在 `QueryUserSubscribeNodeListLogic` 中解析 Server 的 `protocols` JSON,并把匹配 `node.protocol + node.port` 的 `simnet` 配置映射到订阅响应。
|
||
4. 订阅响应只下发用户级 `simnet_key_id`、用户级 `simnet_psk`、可公开 path/carrier/TLS/SNI/AF/Fallback 字段。
|
||
5. 不下发 Server 级 `simnet_psk`、DNS provider env、管理端密钥字段。
|
||
6. 新增 capability header 判断,例如 `X-Client-Capabilities: simnet` 或当前 SlagClient 实际 header。
|
||
7. 如果没有 capability header,则使用 User-Agent 作为兼容兜底;不应单纯依赖 UA。
|
||
8. 对不支持 `simnet` 的客户端隐藏 `simnet` 节点,避免客户端崩溃或展示不可用节点。
|
||
9. 如果 SlagClient 同时支持 `protocols` JSON 和顶层字段,优先让顶层字段完整,`protocols` 作为兼容冗余。
|
||
|
||
### 订阅 DTO 建议
|
||
|
||
```json
|
||
{
|
||
"id": 1,
|
||
"name": "HK simnet",
|
||
"protocol": "simnet",
|
||
"port": 443,
|
||
"address": "node.example.com",
|
||
"sni": "net.example.com",
|
||
"simnet_key_id": 10001,
|
||
"simnet_psk": "user-side-secret",
|
||
"simnet_ticket_id": "",
|
||
"simnet_path": "/simnet/session",
|
||
"simnet_carrier": "h2",
|
||
"security": "tls",
|
||
"allow_insecure": false,
|
||
"simnet_af_enabled": true,
|
||
"simnet_af_path_mode": "api",
|
||
"simnet_af_magic_mode": "derived",
|
||
"simnet_fallback_enabled": true,
|
||
"simnet_fallback_target_scheme": "https",
|
||
"simnet_fallback_target_host": "www.example.com",
|
||
"simnet_fallback_target_port": 443
|
||
}
|
||
```
|
||
|
||
### 预计修改位置
|
||
|
||
- `apis/public/subscribe.api`
|
||
- `internal/types/types.go`
|
||
- `internal/logic/public/subscribe/queryUserSubscribeNodeListLogic.go`
|
||
- `internal/logic/common/subscriptionTrace.go`,如有订阅 UA 或设备记录
|
||
- 可新增 `internal/logic/public/subscribe/simnet_mapper.go`
|
||
|
||
### 依赖关系
|
||
|
||
- 阶段 4 用户级凭据。
|
||
- SlagClient capability header 契约确认。
|
||
|
||
### 验收条件
|
||
|
||
1. SlagClient 冷启动订阅后能看到 `simnet` 节点。
|
||
2. SlagClient 重启后仍能从订阅恢复连接配置。
|
||
3. 不支持 `simnet` 的客户端订阅不返回 `simnet` 节点。
|
||
4. 普通用户订阅响应不包含 Server 级 PSK。
|
||
|
||
### 回滚点
|
||
|
||
订阅侧隐藏 `simnet` 节点或关闭 capability 开关;旧协议订阅不受影响。
|
||
|
||
## 12. 阶段 7:流量和在线用户映射
|
||
|
||
### 目标
|
||
|
||
让 OmnXT 上报的 `simnet` 在线用户和流量能正确归属到用户订阅,并触发旧系统现有的限额、日志、后台统计。
|
||
|
||
### 具体任务
|
||
|
||
1. 确认 OmnXT 上报用户标识是 `uuid`、`key_id`、`user_id` 还是其他字段。
|
||
2. 如果 OmnXT 上报 `key_id`,后端通过用户级凭据表反查 `user_subscribe_id` 和 `user_id`。
|
||
3. 如果 OmnXT 上报 `uuid`,需要确认 `uuid` 与 `simnet` 凭据绑定关系,不允许跨用户伪造。
|
||
4. 在 `serverPushUserTrafficLogic` 中加入 `simnet` 标识解析。
|
||
5. 在 `pushOnlineUsersLogic` 中加入 `simnet` 在线用户映射。
|
||
6. 更新后台节点在线数统计,确保 `simnet:443` 与其他协议隔离。
|
||
7. 失败上报要记录协议、server_id、port、用户标识和错误原因,方便排查。
|
||
|
||
### 预计修改位置
|
||
|
||
- `apis/node/node.api`
|
||
- `internal/types/types.go`
|
||
- `internal/logic/server/serverPushUserTrafficLogic.go`
|
||
- `internal/logic/server/pushOnlineUsersLogic.go`
|
||
- `internal/model/traffic/*`
|
||
- `internal/model/node/*`
|
||
- 凭据表 model
|
||
|
||
### 依赖关系
|
||
|
||
- 阶段 4 用户级凭据。
|
||
- OmnXT 上报格式确认。
|
||
|
||
### 验收条件
|
||
|
||
1. `simnet` 连接产生流量后,用户已用流量增加。
|
||
2. 节点后台能看到 `simnet` 在线人数。
|
||
3. 用户超限后 OmnXT 用户列表不再包含该用户。
|
||
4. 旧协议流量统计不受影响。
|
||
|
||
### 回滚点
|
||
|
||
禁用 `simnet` 流量上报分支或关闭 `simnet` 节点;旧协议统计不受影响。
|
||
|
||
## 13. 阶段 8:TLS、AF 与 Fallback
|
||
|
||
### 目标
|
||
|
||
把当前实际部署需要的 TLS/SNI、AF 和 HTTPS Fallback 做到可配置、可验证、可运维。
|
||
|
||
### 具体任务
|
||
|
||
1. TLS:支持 `security=tls`、`sni`、`allow_insecure=false`。
|
||
2. 证书模式:第一版支持 `cert_mode=http`;DNS provider 字段先保留,不在普通订阅下发。
|
||
3. AF:支持 `simnet_af_enabled`、`path_mode=api`、`magic_mode=derived`、`response_jitter_ms`。
|
||
4. Fallback:支持 fallback scheme、host、port、host header、TLS SNI。
|
||
5. Reverse:字段保存和下发给 OmnXT,但默认关闭;如果开启必须要求 target host/port 完整。
|
||
6. 添加配置快照日志,OmnXT 拉取时打印非敏感字段,便于确认线上配置是否生效。
|
||
7. 对真实节点做 `443` 端口监听、证书申请、fallback 站点访问验证。
|
||
|
||
### 预计修改位置
|
||
|
||
- `internal/logic/admin/server/protocol_simnet.go`
|
||
- `internal/logic/server/simnet_config.go`
|
||
- `internal/logic/public/subscribe/simnet_mapper.go`
|
||
- `etc/ppanel.yaml`,如需要新增全局开关
|
||
- 节点部署文档或运维脚本,视 OmnXT 实际需求决定
|
||
|
||
### 依赖关系
|
||
|
||
- 阶段 3 OmnXT 配置下发。
|
||
- 节点服务器域名、证书、端口和 fallback 目标准备完成。
|
||
|
||
### 验收条件
|
||
|
||
1. OmnXT 能在 `443` 启动 `simnet` H2 TLS。
|
||
2. SNI 与证书匹配。
|
||
3. AF 开启后 SlagClient 仍可连接。
|
||
4. Fallback 目标在非协议请求时可访问。
|
||
5. OmnXT 重启后配置仍然生效。
|
||
|
||
### 回滚点
|
||
|
||
关闭 AF 或 fallback;必要时把 `simnet.enable=false`,保留旧协议节点承载用户。
|
||
|
||
## 14. 阶段 9:自动化测试
|
||
|
||
### 目标
|
||
|
||
用测试保护 `simnet` 的关键契约,减少后续修改协议字段时再次出现“面板有配置、节点拿不到、客户端不识别”的问题。
|
||
|
||
### 具体任务
|
||
|
||
1. 协议模型测试:默认值、校验、marshal/unmarshal。
|
||
2. 管理端测试:create/update Server 保存 `simnet` 字段完整。
|
||
3. 节点配置测试:`secret_key` 正确/错误、`simnet` DTO 字段筛选。
|
||
4. 用户凭据测试:生成稳定性、用户隔离、重置失效。
|
||
5. 订阅测试:capability header 支持时返回 `simnet`;不支持时隐藏。
|
||
6. 敏感字段测试:普通订阅中不得出现 Server PSK、DNS env。
|
||
7. 流量测试:OmnXT 上报 `key_id` 后可归属用户。
|
||
8. 回归测试:现有 vless、trojan、hysteria2、shadowsocks 订阅不变。
|
||
|
||
### 预计修改位置
|
||
|
||
- `tests/acceptance/*`
|
||
- `internal/logic/admin/server/*_test.go`
|
||
- `internal/logic/server/*_test.go`
|
||
- `internal/logic/public/subscribe/*_test.go`
|
||
- `internal/model/simnet/*_test.go`
|
||
|
||
### 依赖关系
|
||
|
||
- 阶段 1 到阶段 7 基本实现完成。
|
||
|
||
### 验收条件
|
||
|
||
1. `go test ./...` 通过,或项目当前可执行测试集全部通过。
|
||
2. 新增测试能覆盖 Server、OmnXT、SlagClient、Traffic 四条主链路。
|
||
3. 任意敏感字段泄露测试失败时,CI 阻断。
|
||
|
||
### 回滚点
|
||
|
||
测试本身不影响生产;如果某阶段实现回滚,相应测试应标记待实现或一并回滚。
|
||
|
||
## 15. 阶段 10:灰度发布与回滚
|
||
|
||
### 目标
|
||
|
||
把 `simnet` 以可控方式上线,先让一个节点和少量测试用户跑通,再扩大范围。
|
||
|
||
### 具体任务
|
||
|
||
1. 增加全局或配置级开关:`simnet_enabled`。
|
||
2. 管理端先创建一个独立测试 Server 和一个 `simnet` front node。
|
||
3. 只给测试套餐或测试节点组分配该节点。
|
||
4. 部署 OmnXT,确认能拉配置、拉用户、启动监听。
|
||
5. 用测试用户订阅 SlagClient,验证冷启动、重启、切换网络、重拉订阅。
|
||
6. 观察在线用户、流量上报、错误日志、证书续期和 fallback 访问。
|
||
7. 稳定后把节点加入正式套餐节点组。
|
||
8. 保留旧协议节点作为回退路径,不把全部用户一次性切到 `simnet`。
|
||
|
||
### 预计修改位置
|
||
|
||
- `etc/ppanel.yaml`,如需要全局开关
|
||
- `internal/config/config.go`
|
||
- `internal/svc/serviceContext.go`
|
||
- 运维部署文档
|
||
|
||
### 依赖关系
|
||
|
||
- 阶段 1 到阶段 9 完成。
|
||
- 测试节点服务器、域名、证书、OmnXT 可用。
|
||
|
||
### 验收条件
|
||
|
||
1. 测试用户能稳定连接 `simnet`。
|
||
2. SlagClient 重启后无需人工操作即可恢复。
|
||
3. OmnXT 重启后能自动拉配置和用户授权。
|
||
4. 管理端能看到在线和流量。
|
||
5. 关闭 `simnet` 后用户可回退到旧协议节点。
|
||
|
||
### 回滚点
|
||
|
||
1. 管理端将 `simnet` 协议 `enable=false`。
|
||
2. 从套餐节点组移除 `simnet` 节点。
|
||
3. OmnXT 停止 `simnet` inbound。
|
||
4. 回滚后端到上一版本。
|
||
5. 保留凭据表和字段,后续排查后可再次启用。
|
||
|
||
## 16. 文件改动范围
|
||
|
||
预计完整生产可用版本会影响 27-45 个业务/配置文件、12-20 个测试文件,新增约 3,000-6,000 行代码和测试。实际数量取决于 goctl 生成文件体积、现有 model 组织方式和 OmnXT/SlagClient 契约是否稳定。
|
||
|
||
### 必改范围
|
||
|
||
- `apis/types.api`
|
||
- `apis/admin/server.api`
|
||
- `apis/node/node.api`
|
||
- `apis/public/subscribe.api`
|
||
- `internal/types/types.go`
|
||
- `internal/model/node/*`
|
||
- `internal/logic/admin/server/createServerLogic.go`
|
||
- `internal/logic/admin/server/updateServerLogic.go`
|
||
- `internal/logic/admin/server/getServerProtocolsLogic.go`
|
||
- `internal/logic/admin/server/filterServerListLogic.go`
|
||
- `internal/logic/server/getServerConfigLogic.go`
|
||
- `internal/logic/server/getServerUserListLogic.go`
|
||
- `internal/logic/server/serverPushUserTrafficLogic.go`
|
||
- `internal/logic/server/pushOnlineUsersLogic.go`
|
||
- `internal/logic/public/subscribe/queryUserSubscribeNodeListLogic.go`
|
||
|
||
### 可能新增范围
|
||
|
||
- `internal/model/simnet/*`
|
||
- `internal/logic/admin/server/protocol_simnet.go`
|
||
- `internal/logic/server/simnet_config.go`
|
||
- `internal/logic/server/simnet_user.go`
|
||
- `internal/logic/public/subscribe/simnet_mapper.go`
|
||
- `initialize/migrate/*simnet*`
|
||
- `tests/simnet/*`
|
||
- `docs/simnet-contract.md`
|
||
|
||
### 前端联动范围
|
||
|
||
如果管理端前端也要同步配置,需要在前端仓库补齐:
|
||
|
||
- Server 创建/编辑表单的 `simnet` 协议字段
|
||
- 协议默认值填充
|
||
- 字段校验提示
|
||
- Node 创建/更新时允许 `protocol=simnet`
|
||
- 隐藏 Server PSK 的展示或复制入口
|
||
|
||
## 17. 提交拆分
|
||
|
||
建议按以下提交拆分,方便 review 和回滚:
|
||
|
||
1. `simnet: add protocol model fields and validation`
|
||
2. `simnet: support admin server create/update/list`
|
||
3. `simnet: expose server runtime config for OmnXT`
|
||
4. `simnet: add per-user credentials`
|
||
5. `simnet: sync OmnXT user authorization`
|
||
6. `simnet: expose public subscribe fields for SlagClient`
|
||
7. `simnet: map traffic and online reports`
|
||
8. `simnet: add tls af fallback handling`
|
||
9. `simnet: add tests and rollout switch`
|
||
|
||
每个提交都应该能单独说明行为变化,并尽量避免把 goctl 生成文件和手写逻辑混在一个巨大提交里。如果生成文件不可避免较大,提交说明中要明确哪些是生成结果。
|
||
|
||
## 18. 验收标准
|
||
|
||
最终验收必须覆盖下面场景:
|
||
|
||
1. 管理端能创建 Server,协议为 `simnet`,端口 `443`,TLS/SNI、AF、Fallback 字段保存完整。
|
||
2. 管理端能创建或更新 Node,`protocol=simnet`,`address` 指向实际节点服务器。
|
||
3. OmnXT 使用正确 `secret_key` 能拉取 `simnet` 服务端运行配置。
|
||
4. OmnXT 使用错误 `secret_key` 被拒绝。
|
||
5. OmnXT 重启后自动恢复 `simnet` inbound。
|
||
6. 有效用户能通过 OmnXT 用户列表获得授权。
|
||
7. 不同用户的 `simnet_key_id` 或 `simnet_psk` 不相同。
|
||
8. 用户禁用、到期或流量超限后,OmnXT 用户列表移除该用户。
|
||
9. SlagClient 冷启动能通过订阅拿到 `simnet` 节点并连接。
|
||
10. SlagClient 重启后不丢失协议配置。
|
||
11. 不支持 `simnet` 的客户端订阅不会收到 `simnet` 节点。
|
||
12. 普通用户订阅响应不泄露 Server PSK、DNS provider env、节点 `secret_key`。
|
||
13. `simnet` 连接产生流量后,用户流量、节点流量、后台日志同步更新。
|
||
14. 关闭 `simnet` 后,旧协议订阅、节点运行和流量统计不受影响。
|
||
15. `go test ./...` 或项目当前有效测试集通过。
|
||
|
||
## 19. 风险清单
|
||
|
||
| 风险 | 影响 | 控制方式 |
|
||
| --- | --- | --- |
|
||
| Server PSK 被下发给普通用户 | 所有用户共享密钥,泄露后整节点风险扩大 | DTO 分层,订阅敏感字段测试阻断 |
|
||
| OmnXT 和后端字段名不一致 | 节点启动失败或配置不生效 | 阶段 0 固化契约,用真实 OmnXT 请求回放测试 |
|
||
| SlagClient 只读顶层字段或只读 protocols JSON | 客户端拿到节点但无法连接 | 双格式兼容,顶层字段和 protocols 都保持可读 |
|
||
| 单用户凭据缺失 | 无法隔离用户,封禁和流量归属困难 | 阶段 4 必须先做凭据表,不走全员共享 PSK |
|
||
| capability 判断不准确 | 老客户端看到不可用节点 | capability header 优先,UA 只兜底,默认隐藏不支持客户端 |
|
||
| 缓存 key 未包含 port | 多协议或同协议多端口串配置 | cache key 包含 `server_id + protocol + port` |
|
||
| 流量上报标识不明确 | 用户流量无法入账或串账 | 与 OmnXT 明确上报 `key_id`,后端反查凭据表 |
|
||
| TLS/证书/fallback 运维失败 | 节点 443 无法正常服务 | 灰度节点先跑,保留旧协议回退 |
|
||
| goctl 生成覆盖手写改动 | 代码冲突或字段丢失 | 所有类型先改 api 文件,再生成;手写扩展放独立文件 |
|
||
|
||
## 20. 工期估算
|
||
|
||
在 OmnXT 和 SlagClient 契约清楚、测试环境可用的情况下:
|
||
|
||
- 阶段 0:0.5-1 天
|
||
- 阶段 1-2:1.5-2 天
|
||
- 阶段 3:1-1.5 天
|
||
- 阶段 4:1.5-2 天
|
||
- 阶段 5:1-1.5 天
|
||
- 阶段 6:1-1.5 天
|
||
- 阶段 7:1-2 天
|
||
- 阶段 8:1 天
|
||
- 阶段 9:2-3 天
|
||
- 阶段 10:1 天
|
||
|
||
完整生产可用版本预计 10-15 个有效开发日。如果 OmnXT 或 SlagClient 字段契约需要同步改动,额外预留 2-4 天联调时间。
|
||
|
||
## 21. 推荐执行顺序
|
||
|
||
第一周先完成最小闭环:
|
||
|
||
1. 阶段 0:确认契约。
|
||
2. 阶段 1:协议模型与校验。
|
||
3. 阶段 2:管理端保存和查询。
|
||
4. 阶段 3:OmnXT 配置下发。
|
||
|
||
第二周完成用户链路:
|
||
|
||
1. 阶段 4:用户级凭据。
|
||
2. 阶段 5:OmnXT 用户授权。
|
||
3. 阶段 6:SlagClient 订阅。
|
||
4. 阶段 7:流量和在线用户映射。
|
||
|
||
最后做生产化:
|
||
|
||
1. 阶段 8:TLS、AF、Fallback 运维验证。
|
||
2. 阶段 9:自动化测试补齐。
|
||
3. 阶段 10:灰度发布和回滚演练。
|
||
|
||
## 22. 当前结论
|
||
|
||
最合理的方案是在旧后端内部补齐 `simnet` 的纵向链路,不建议整体迁移 Pro 新后端。这样风险最小,旧业务稳定性最好,也最贴近当前问题:SlagClient 和 OmnXT 需要的是一个一致、完整、不会泄露敏感字段的 `simnet` 契约。
|
||
|
||
第一版真正必须做的是:协议模型、管理端保存、OmnXT 配置、用户级凭据、OmnXT 授权、SlagClient 订阅、流量归属。只要这七个点闭环,`simnet` 就不是“配置看起来存在”,而是能在真实客户端和真实节点上稳定使用。
|