[RFC] SSPanel-UIM 可扩展节点协议配置 Schema v2 设计提案
提案:为 SSPanel-UIM 设计可扩展的新协议节点配置规范
背景
目前 SSPanel-UIM 已经通过 ss_node.type 区分不同节点协议,并使用协议对应的自定义配置保存部分节点参数。
随着代理协议不断增加,如果继续为每一种协议单独增加字段,或者继续扩展旧的分号字符串配置,后续在节点管理、后端对接、订阅生成以及客户端兼容方面都会越来越难维护。
因此希望提出一套新的、统一的、可扩展的节点协议配置规范。
本提案计划覆盖或为以下协议预留支持:
AnyTLS
Naive
Shadowsocks
ShadowsocksR
Snell v1 / v2 / v3 / v4 / v5 / v6
Trojan
VLESS
VMess
Sudoku
TUIC
Hysteria2
Mieru
本 Issue 的主要目的并不是要求一次性实现以上所有协议,而是希望先讨论并确定一套统一的新协议节点配置规范,之后再逐步实现不同协议。
设计目标
希望新的节点协议规范满足以下几点:
保留现有
ss_node.type整数类型,尽量兼容现有数据库和节点逻辑。协议特定参数统一使用结构化 JSON 保存。
SSPanel 内部节点配置不要直接绑定 Mihomo、sing-box 或某一个特定客户端。
将以下部分尽量解耦:
节点基础配置
协议配置
TLS 配置
Transport 传输层
用户认证信息
订阅输出格式
同一个协议的不同版本通过
version字段区分,而不是每个版本占用一个新的type。不同订阅生成器可以自行判断目标客户端是否支持对应协议或协议版本。
为以后继续增加新协议、新 Transport、新 TLS 特性预留扩展能力。
协议 Type 建议
现有协议编号保持不变,新协议可以从一段新的编号范围开始。
例如:
Type | 协议 -- | -- 0 | Shadowsocks 1 | Shadowsocks 2022 2 | TUIC 11 | VMess 14 | Trojan 20 | AnyTLS 21 | Naive 22 | ShadowsocksR 23 | Snell 24 | VLESS 25 | Sudoku 26 | Hysteria2 27 | Mieru具体编号可以再讨论。
这里更重要的是:
同一个协议的不同版本不应该重复分配 type。
例如 Snell:
{
"version": 6
}仍然使用:
type = 23而不是:
Snell v1 = 23
Snell v2 = 24
Snell v3 = 25
...TUIC v4 / v5 同理。
通用节点配置
建议新格式增加配置版本:
{
"schema": 2,
"offset_port_user": 443,
"offset_port_node": 8443,
"udp": true
}schema
表示节点配置格式版本。
例如:
{
"schema": 2
}以后如果节点配置结构再次调整,可以继续增加:
{
"schema": 3
}这样可以保留对旧节点的兼容能力。
offset_port_user
用户订阅中看到的端口。
offset_port_node
节点后端实际监听的端口。
udp
当前协议是否启用 UDP。
并不是所有协议都必须使用该字段,具体由对应协议决定。
TLS 配置统一化
建议所有需要 TLS 的协议共用一套 TLS 结构。
例如:
{
"tls": {
"enabled": true,
"server_name": "example.com",
"alpn": [
"h2",
"http/1.1"
],
"skip_cert_verify": false,
"client_fingerprint": "chrome"
}
}如果协议支持 Reality:
{
"tls": {
"enabled": true,
"server_name": "www.example.com",
"client_fingerprint": "chrome",
"reality": {
"enabled": true,
"public_key": "PUBLIC_KEY",
"short_id": "0123456789abcdef"
}
}
}内部字段建议统一使用:
server_name而不是为了不同客户端同时出现:
sni
servername
server-name
server_name客户端订阅生成器负责最终字段转换。
例如:
SSPanel 内部:
{
"server_name": "example.com"
}生成 Mihomo 配置时转换成对应字段。
生成 sing-box 配置时再转换成 sing-box 所需要的字段。
这样可以避免 SSPanel 的内部格式与某个客户端实现强耦合。
Transport 统一化
VMess、VLESS、Trojan 等协议可以复用一套 Transport 配置。
TCP
{
"transport": {
"type": "tcp"
}
}WebSocket
{
"transport": {
"type": "ws",
"path": "/proxy",
"host": "cdn.example.com",
"headers": {
"Host": "cdn.example.com"
}
}
}gRPC
{
"transport": {
"type": "grpc",
"service_name": "proxy"
}
}XHTTP
{
"transport": {
"type": "xhttp",
"path": "/xhttp",
"host": "example.com",
"mode": "auto",
"headers": {}
}
}以后增加新的 Transport 时,只需要扩展 Transport 模块,而不需要重新设计整个节点结构。
AnyTLS
建议:
{
"schema": 2,
"offset_port_user": 443,
"offset_port_node": 8443,
"udp": true,
"tls": {
"enabled": true,
"server_name": "example.com",
"alpn": [
"h2",
"http/1.1"
],
"skip_cert_verify": false,
"client_fingerprint": "chrome"
},
"session": {
"idle_check_interval": 30,
"idle_timeout": 30,
"min_idle": 0
}
}用户认证信息不要直接写入节点配置。
例如:
{
"password": "USER_PASSWORD"
}可以由用户认证模块单独生成。
Naive
建议:
{
"schema": 2,
"offset_port_user": 443,
"offset_port_node": 443,
"transport": "h2",
"tls": {
"enabled": true,
"server_name": "naive.example.com",
"skip_cert_verify": false
},
"insecure_concurrency": 0,
"extra_headers": {},
"udp_over_tcp": true,
"quic": {
"enabled": false,
"congestion_control": "bbr"
}
}用户认证:
{
"username": "USER",
"password": "PASSWORD"
}Naive 也是一个很典型的例子:
并不是所有客户端核心都支持 Naive。
因此 SSPanel 内部只负责保存标准节点配置,最后是否向某个客户端输出该节点,应由订阅生成器根据客户端能力决定。
Shadowsocks
现有 Shadowsocks 可以逐步标准化为:
{
"schema": 2,
"method": "aes-128-gcm",
"udp": true,
"udp_over_tcp": false,
"plugin": {
"name": "",
"options": {}
}
}Shadowsocks 2022 可以继续保留现有 type。
例如:
{
"schema": 2,
"method": "2022-blake3-aes-256-gcm",
"server_key": "BASE64_SERVER_KEY",
"udp": true
}ShadowsocksR
建议:
{
"schema": 2,
"method": "chacha20-ietf",
"protocol": "auth_sha1_v4",
"protocol_param": "",
"obfs": "tls1.2_ticket_auth",
"obfs_param": "",
"udp": true
}Snell
Snell 建议只使用一个 type。
不同版本通过:
{
"version": 1
}到:
{
"version": 6
}区分。
完整示例:
{
"schema": 2,
"version": 6,
"offset_port_user": 443,
"offset_port_node": 8443,
"psk": "SERVER_PSK",
"udp": true,
"reuse": false,
"obfs": {
"mode": "none",
"host": ""
},
"v6": {
"mode": "default"
}
}支持:
version = 1
version = 2
version = 3
version = 4
version = 5
version = 6协议版本兼容性由订阅生成器处理。
例如:
Snell v5
-> 某客户端支持
-> 正常输出
Snell v6
-> 某客户端不支持
-> 不输出该节点这样 SSPanel 本身不需要为了不同客户端修改数据库结构。
Trojan
建议:
{
"schema": 2,
"offset_port_user": 443,
"offset_port_node": 8443,
"udp": true,
"tls": {
"enabled": true,
"server_name": "trojan.example.com",
"alpn": [
"h2",
"http/1.1"
],
"skip_cert_verify": false,
"client_fingerprint": "chrome"
},
"transport": {
"type": "tcp"
}
}用户认证:
{
"password": "USER_PASSWORD"
}如果使用 WebSocket,只需要替换 Transport:
{
"transport": {
"type": "ws",
"path": "/trojan",
"host": "cdn.example.com"
}
}VLESS
建议:
{
"schema": 2,
"offset_port_user": 443,
"offset_port_node": 8443,
"udp": true,
"flow": "",
"packet_encoding": "xudp",
"encryption": "",
"tls": {
"enabled": true,
"server_name": "vless.example.com",
"skip_cert_verify": false,
"client_fingerprint": "chrome"
},
"transport": {
"type": "tcp"
}
}Reality:
{
"schema": 2,
"offset_port_user": 443,
"offset_port_node": 443,
"udp": true,
"flow": "xtls-rprx-vision",
"packet_encoding": "xudp",
"tls": {
"enabled": true,
"server_name": "www.example.com",
"client_fingerprint": "chrome",
"reality": {
"enabled": true,
"public_key": "PUBLIC_KEY",
"short_id": "0123456789abcdef"
}
},
"transport": {
"type": "tcp"
}
}用户认证:
{
"uuid": "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx"
}VMess
现有 VMess 也可以逐步迁移到统一结构:
{
"schema": 2,
"offset_port_user": 443,
"offset_port_node": 8443,
"udp": true,
"alter_id": 0,
"cipher": "auto",
"packet_encoding": "xudp",
"global_padding": false,
"authenticated_length": false,
"tls": {
"enabled": true,
"server_name": "vmess.example.com",
"skip_cert_verify": false,
"client_fingerprint": "chrome"
},
"transport": {
"type": "ws",
"path": "/vmess",
"host": "cdn.example.com"
}
}Sudoku
Sudoku 本身具有自己的协议特性,因此可以保留独立结构。
例如:
{
"schema": 2,
"offset_port_user": 443,
"offset_port_node": 443,
"aead_method": "chacha20-poly1305",
"padding": {
"min": 2,
"max": 7
},
"table": {
"type": "prefer_ascii",
"custom": []
},
"multiplex": "off",
"httpmask": {
"enabled": true,
"mode": "auto",
"tls": true,
"host": "cdn.example.com",
"path_root": "sudoku",
"multiplex": "off"
},
"pure_downlink": false
}用户认证:
{
"key": "USER_KEY"
}TUIC
TUIC 可以继续保留当前节点 type。
协议版本通过:
{
"version": 4
}或者:
{
"version": 5
}表示。
例如:
{
"schema": 2,
"version": 5,
"offset_port_user": 443,
"offset_port_node": 8443,
"udp": true,
"tls": {
"server_name": "tuic.example.com",
"alpn": [
"h3"
],
"skip_cert_verify": false
},
"heartbeat_interval": 10000,
"reduce_rtt": true,
"request_timeout": 8000,
"udp_relay_mode": "native",
"congestion_control": "bbr"
}不同版本认证可以分别处理。
TUIC v4:
{
"token": "USER_TOKEN"
}TUIC v5:
{
"uuid": "UUID",
"password": "USER_PASSWORD"
}Hysteria2
建议:
{
"schema": 2,
"offset_port_user": 443,
"offset_port_node": 443,
"ports": [
"20000-30000"
],
"hop_interval": "30s",
"bandwidth": {
"up_mbps": 100,
"down_mbps": 500
},
"obfs": {
"type": "salamander",
"password": "OBFS_PASSWORD"
},
"tls": {
"server_name": "hy2.example.com",
"alpn": [
"h3"
],
"skip_cert_verify": false
}
}用户认证:
{
"password": "USER_PASSWORD"
}以后如果协议增加新的混淆方式,也可以直接扩展:
{
"obfs": {
"type": "other"
}
}而不需要修改整个节点配置结构。
Mieru
建议:
{
"schema": 2,
"offset_port_user": 443,
"offset_port_node": 443,
"transport": "TCP",
"port_range": "",
"multiplexing": "MULTIPLEXING_LOW",
"handshake_mode": "HANDSHAKE_STANDARD",
"traffic_pattern": ""
}用户认证:
{
"username": "USER",
"password": "PASSWORD"
}节点配置和用户认证分离
这里建议将:
NodeConfig
和:
UserCredential
完全区分开。
节点配置描述:
这个节点是什么、监听在哪里、使用什么协议、TLS 和 Transport 怎么配置。
用户认证描述:
当前用户应该使用什么凭据登录这个节点。
例如 VLESS 节点:
{
"protocol": "vless",
"tls": {
"enabled": true
},
"transport": {
"type": "tcp"
}
}用户:
{
"uuid": "USER_UUID"
}AnyTLS:
{
"password": "USER_PASSWORD"
}TUIC v5:
{
"uuid": "USER_UUID",
"password": "USER_PASSWORD"
}Hysteria2:
{
"password": "USER_PASSWORD"
}可以抽象成:
NodeConfig
描述节点
UserCredential
描述用户而不是将两者混在同一个节点 JSON 中。
增加订阅 Serializer / Capability 层
另一个建议是,不要假设所有协议都可以输出到所有客户端。
可以增加类似:
NodeConfig
|
+-------------+-------------+
| | |
v v v
Mihomo sing-box URI
Serializer Serializer Serializer例如:
Snell v6
-> 客户端 A 支持
-> 输出
Snell v6
-> 客户端 B 不支持
-> 跳过
Naive
-> sing-box 支持
-> 输出
Naive
-> Mihomo 不支持
-> 跳过Serializer 可以负责:
判断协议是否支持。
判断协议版本是否支持。
将内部字段转换为对应客户端字段。
忽略客户端不支持的可选参数。
必要时直接跳过整个节点。
这样可以避免为了适配不同客户端,在数据库中同时保存大量重复字段。
推荐的内部结构
整体可以抽象为:
ss_node
|
v
NodeConfig
|
+-- BasicConfig
|
+-- ProtocolConfig
|
+-- TLSConfig
|
+-- TransportConfig
|
+-- UserCredential
|
+-- Serializer
|
+-- Mihomo
+-- sing-box
+-- URI
+-- Node API也就是说:
节点配置负责描述“节点是什么”
Serializer 负责描述“如何输出给客户端”两者不应该强耦合。
向后兼容
本提案不建议直接废弃现有节点格式。
可以同时支持:
Legacy Node Config和:
Schema v2 Node Config例如:
{
"schema": 2
}明确表示:
当前节点使用新版协议配置格式。
不存在:
{
"schema": 2
}的旧节点,继续交给目前已有的 Legacy Parser 解析。
这样可以实现渐进式迁移,不需要一次性修改所有已有节点。
实现阶段建议
为了避免一次性改动过大,可以拆分成多个阶段。
第一阶段:只定义规范
先实现或确定:
Protocol Type Registry
通用 NodeConfig Schema
TLS Schema
Transport Schema
UserCredential 接口
Serializer 接口
Client Capability 接口
这个阶段甚至可以不增加任何新协议。
重点是先把基础结构稳定下来。
第二阶段:实现较常用的新协议
例如:
AnyTLS
ShadowsocksR
VLESS
Hysteria2
第三阶段
继续增加:
Snell
Mieru
Sudoku
Naive
第四阶段
逐步把已有协议迁移到统一结构:
VMess
Trojan
TUIC
Shadowsocks
旧格式继续兼容。
最终目的
这个提案最主要的目的并不是单纯给 SSPanel-UIM 增加更多:
type = xx而是希望建立一套以后能够继续扩展的协议配置体系。
核心思路可以概括为:
稳定的整数协议 Type
+
带版本号的 JSON 配置
+
可复用的 TLS / Transport 配置
+
节点与用户认证分离
+
针对不同客户端的 Serializer这样以后即使继续增加:
新协议
新协议版本
新 Transport
新 TLS 特性
新客户端核心
也不需要重新设计整个 SSPanel-UIM 的节点数据格式。
希望先听取维护者对这种设计方向的意见。
如果整体方向可以接受,后续协议实现可以拆分成多个独立 PR,而不是一次性提交所有协议。
Source: Anankke/SSPanel-UIM