VLESS
VLESS 是 V2Ray/Xray 家族中的一种轻量代理协议。客户端先发送一个简短的请求头,其中包含用户 UUID、命令和目标地址,之后连接上原样承载你的流量。etemenanki-app 既可以作为 VLESS 服务端(在 [[inbound]] 中写 protocol = "vless"),也可以作为客户端(在 [[outbound]] 中),并且能通过 TLS、WebSocket 和 gRPC 与 Xray 互通。
本页涵盖两种角色:所有设置项、VLESS 如何与传输层组合、UDP 和 mux 支持、有意未实现的 Xray 功能,以及可能遇到的错误。katana 使用同一份协议代码提供 VLESS 节点;在 katana 中,用户和传输层由面板下发,因此本页的设置只适用于 etemenanki-app。
| 入站(服务端) | 出站(客户端) | |
|---|---|---|
| 凭据 | users = [{ id = "…" }],只接受 UUID |
id = "…",一个 UUID |
| 传输层 | tcp、tls、ws 和 grpc;ws 和 grpc 可带或不带 TLS |
相同 |
| TCP | 支持 | 支持 |
| UDP | 支持,始终开启 | 支持,每个 UDP 流只有一个目标 |
| mux.cool 和 XUDP | 自动接受,无需设置 | 不支持:每个流都单独建立连接 |
XTLS flow(Vision) |
不支持 | 不支持 |
| REALITY | 不支持 | 不支持 |
一个在 443 端口上通过 WebSocket 和 TLS 接受 VLESS 的服务端,以及一个提供本地 SOCKS 端口、把所有流量发往该服务端的客户端。证书必须对 proxy.example.com 有效,因为客户端会按这个名称验证证书。
[[inbound]]tag = "vless-in"protocol = "vless"listen = "0.0.0.0" # 默认值是 127.0.0.1port = 443
[inbound.stream]network = "ws"security = "tls"
[inbound.stream.ws]path = "/vless"
[inbound.stream.tls]cert_file = "/etc/etemenanki/fullchain.pem"key_file = "/etc/etemenanki/privkey.pem"
[inbound.settings]users = [ { id = "11111111-2222-3333-4444-555555555555" }, { id = "11111111-2222-3333-4444-666666666666" },]
[[outbound]]tag = "direct"protocol = "freedom"
[route]default = "direct"[[inbound]]tag = "socks-in"protocol = "socks"port = 1080 # 监听 127.0.0.1
[[outbound]]tag = "proxy"protocol = "vless"server = "proxy.example.com"port = 443
[outbound.stream]network = "ws"security = "tls"
[outbound.stream.ws]path = "/vless" # 必须与服务端一致
[outbound.settings]id = "11111111-2222-3333-4444-555555555555"
[route]default = "proxy"启动前先用 etemenanki-app --test -c server.toml 检查每个文件。这里的客户端不需要 [outbound.stream.tls] 表:TLS 服务器名和 WebSocket Host 都会回退为 server。包含证书和测试请求的完整步骤,请参考 VLESS over WebSocket and TLS 配方。
用 uuidgen 或 cat /proc/sys/kernel/random/uuid 为每个用户生成真实的 UUID。
这些键写在 protocol = "vless" 的入站的 [inbound.settings] 中。通用入站键(tag、listen、port、sniffing、stream)见入站页面。
| 键 | 类型 | 必填 | 默认值 | 说明 |
|---|---|---|---|---|
users | array of tables | 否 | [] | 允许连接的用户,每个用户用一个 UUID 标识。列表为空或不写时配置仍能构建,但所有客户端都会被拒绝。Xray 的 clients、decryption 和 fallbacks 键在这里不存在,写上会被视为未知字段,导致配置失败。 |
users[].id | string | 是 | — | 用户的 UUID。可接受的写法:带连字符(11111111-2222-3333-4444-555555555555)、不带连字符的 32 位十六进制,或者把带连字符的写法用花括号包起来、加上小写的 urn:uuid: 前缀;十六进制数字大小写均可。其他写法报 invalid uuid。id 是条目中唯一的键:flow、email 和 level 都会被拒绝。 |
UUID 无效时整个配置都会失败,错误信息会指出入站和出错的值:
configuration invalid: inbound vless-in: invalid uuid "not-a-uuid": invalid character: found `n` at 0服务端如何匹配用户:
- 服务端在
users中查找该 UUID,找不到就关闭连接。不会回落到其他服务:未知客户端得不到任何响应。 - 与 Xray 一样,服务端比较 id 时忽略 UUID 的第三组(
11111111-2222-3333-4444-555555555555中的3333)。只在这一组上不同的两个条目被视为同一个用户,以后一个为准。 - 条目不带
email或其他标签。所有用户的待遇相同,没有按用户的路由。
这些键写在 protocol = "vless" 的出站的 [outbound.settings] 中。出站还需要通用键 server 和 port,即 VLESS 服务器的地址。缺少它们时构建会失败,报 outbound proxy: missing server 或 outbound proxy: missing port;如果 network 是 tls、ws 或 grpc 且没有 server,会更早因缺少 TLS 名称或 host 而失败。address_family 决定如何解析服务器名。这些键见出站页面。
| 键 | 类型 | 必填 | 默认值 | 说明 |
|---|---|---|---|---|
id | string | 是 | — | 用于认证的 UUID,必须与服务端的某个 users[].id 一致。可接受的写法与入站相同:带连字符、32 位十六进制,或者把带连字符的写法用花括号包起来、加上小写的 urn:uuid: 前缀。缺少时报 invalid settings: missing field,格式错误时报 invalid uuid。id 是唯一的键:Xray 的 flow、encryption 和 level 都会被拒绝,也没有 mux 设置。 |
出站不做多路复用。它承载的每个 TCP 流和每个 UDP 流都会单独建立一条到服务端的连接:先是 TCP 连接,然后是 TLS,若传输层需要,再进行 WebSocket 升级或建立 HTTP/2 连接。由于 VLESS 出站有 server 和 port,它可以作为负载均衡器的成员。
VLESS 可以运行在 [inbound.stream] 或 [outbound.stream] 中的任意流式传输层之上。两端必须使用相同的 network、相同的 WebSocket path 或 gRPC service_name,并且在是否使用 TLS 上保持一致。这些键本身见传输层页面。
network |
security |
线上传输的内容 | Xray 对应写法 |
|---|---|---|---|
"tls" |
不写、"none" 或 "tls" |
TCP 上的 TLS 中的 VLESS | "network": "tcp"、"security": "tls" |
"ws" |
"tls" |
TLS 中的 WebSocket 中的 VLESS | "network": "ws"、"security": "tls" |
"grpc" |
"tls" |
TLS 中的 gRPC(HTTP/2)中的 VLESS | "network": "grpc"、"security": "tls" |
"tcp"(默认) |
不写或 "none" |
明文 VLESS:UUID 可被读取 | "network": "tcp" |
"ws" 或 "grpc" |
不写或 "none" |
明文 WebSocket 或 gRPC:UUID 可被读取 | 相同,但不写 security |
明文的几行适用于已经由其他组件提供 TLS 的部署,例如同一主机上的反向代理终止 TLS,再把 WebSocket 转发到回环端口。监听 Unix socket(listen 为路径)的 VLESS 入站始终以明文运行。它拒绝 "tcp" 以外的任何 network 和 "none" 以外的任何 security,例如 inbound vless-in: protocol vless over a unix socket does not support stream network "ws"。
连接如何建立
Section titled “连接如何建立”sequenceDiagram
participant C as 客户端
participant S as VLESS 入站
participant T as 目标
C->>S: TLS 和 WebSocket 握手
C->>S: 包含 UUID、命令和目标的请求头,随后是载荷
Note over S: 查找 UUID,未知则关闭连接
S->>T: 通过路由选出的出站连接
T-->>S: 已连接
S-->>C: 响应头,2 字节
S->>T: 载荷
T-->>S: 回复
S-->>C: 回复
客户端不会等待响应头:它紧跟在请求后面发送第一段载荷,服务端会先保留这段数据,直到连上目标。请求头格式如下:
| 字段 | 大小 | 接受的值 |
|---|---|---|
| Version | 1 字节 | 只接受 0 |
| User id | 16 字节 | users 中的某个 UUID |
| Addons length | 1 字节 | 只接受 0。非零值表示 XTLS flow,会被拒绝 |
| Command | 1 字节 | 1 TCP,2 UDP,3 mux |
| Target | 可变 | 端口(2 字节),然后是地址类型(1 IPv4,2 域名,3 IPv6)和地址。mux 命令没有此字段 |
服务端会检查每个固定字段,遇到其他值就关闭连接。它用两字节的响应头应答(version 为 0,无 addons):
- 对 TCP 请求,只在连上目标之后才应答。如果连接目标失败,服务端直接关闭客户端连接,不发送响应;
- 对 UDP 或 mux 请求,在用户认证通过后立即应答。
客户端必须在 10 秒内完成 VLESS 请求;已建立的连接如果 300 秒内两个方向都没有数据,就会被关闭。这些限制对所有协议通用,见限制。
当入站的 sniffing 开启(默认开启)且 TCP 请求给出的是 IP 地址而不是域名时,服务端会暂存载荷的最初几个字节,最多 300 ms、4 KiB,用来还原 TLS 服务器名或 HTTP Host 以供路由。UDP 请求不做嗅探。
VLESS 在同一条流式连接内承载 UDP。请求头只指定一个目标,两个方向上的每个包都以 2 字节大端长度加载荷的形式分帧。包上不带地址:该连接上的所有包都发往请求头中的目标,客户端也把所有回复视为来自该目标。
- 入站。 UDP 始终开启,没有关闭它的设置。每个 UDP 请求都像其他流一样经过路由,因此
network = "udp"的[[route.rule]]对它生效。 - 出站。 出站把一个 UDP 流作为一个 VLESS UDP 请求承载,目标是该流经这个出站发送的第一个目的地址。请求头固定了目标,因此该流之后路由到这个出站的每个包,无论原本要发往哪个地址,都会发往那个第一个目的地址,所有回复也都报告为来自它。因此,一个经 VLESS 出站与多个对端通信的 SOCKS UDP 关联只能到达第一个对端。Xray 用 XUDP 避免这个问题,而本出站不支持 XUDP。
Mux.cool 和 XUDP
Section titled “Mux.cool 和 XUDP”Xray 客户端经常开启 mux,在一条 VLESS 连接上发送多个流。VLESS 入站会自动接受这种连接,没有启用或禁用它的键。
- mux 连接是命令为
3的 VLESS 请求。它本身没有目标:其伪目的地址v1.mux.cool:0永远不会被拨号。 - 其中每个子流都指定自己的目的地址,并单独路由,就像它是通过独立连接到达的一样。开启
sniffing时,发往 IP 地址的子流只根据其打开帧中携带的载荷进行嗅探;承载连接不会为此等待更多数据。子流可以是 TCP 流或 UDP 关联;XUDP 子流的每个包都带有地址,因此一个关联可以到达多个对端,并且每个回复都会归属到实际发送它的那个对端。 - 一条 mux 连接同时最多承载 256 个子流。客户端如果打开更多子流,或者复用一个仍处于打开状态的子流的 id,该子流会被
End帧拒绝,而 mux 连接本身保持不变。 - 关闭 mux 连接会结束其中所有子流。
出站从不发送 mux。如果写了 Xray 风格的 [outbound.mux],配置会失败,报 unknown field `mux`。
不支持的功能
Section titled “不支持的功能”以下 Xray 功能在 etemenanki-app 中不存在。每一项都会明确报错,而不是被忽略。
| Xray 功能 | 在这里的结果 |
|---|---|
XTLS flow,包括 xtls-rprx-vision |
两端的 flow 键都是未知字段。客户端如果仍然发送 flow,会被断开,并报 vless addons (xtls flow) are not supported。 |
| REALITY | security = "reality" 报 unknown stream security。请使用真实证书和 TLS。 |
fallbacks |
在 [inbound.settings] 中是未知字段。未认证的连接会被关闭,从不转发。 |
decryption / encryption |
未知字段。只支持明文 VLESS,即 Xray 中的 "none"。 |
任意文本形式的 id |
Xray 会从简短的文本 id 派生出 UUID;这里 id 必须是 UUID,否则构建失败,报 invalid uuid。 |
用户的 email 和 level |
未知字段。 |
出站 mux |
未知字段。出站为每个流单独建立一条连接。 |
从 Xray 迁移
Section titled “从 Xray 迁移”| Xray JSON | etemenanki-app TOML |
|---|---|
入站 settings.clients[].id |
[inbound.settings] users[].id |
入站 settings.decryption = "none" |
不写 |
出站 settings.vnext[0].address 和 .port |
[[outbound]] 本身的 server 和 port |
出站 vnext[0].users[0].id |
[outbound.settings] id |
出站 users[0].encryption = "none" |
不写 |
streamSettings.network = "tcp" 加 security = "tls" |
[.stream] network = "tls" |
wsSettings.path |
[.stream.ws] path |
grpcSettings.serviceName |
[.stream.grpc] service_name |
tlsSettings.serverName |
[.stream.tls] server_name |
tlsSettings.certificates[0] |
[.stream.tls] cert_file 和 key_file |
配置的其余部分见 Xray 迁移页面。
与 Xray 的互通测试
Section titled “与 Xray 的互通测试”集成测试让 etemenanki-app 与真实的 xray-core 二进制程序对接,传输流量并比对字节。对于 VLESS,测试覆盖:
| 传输层 | etemenanki-app 作客户端,Xray 作服务端 | Xray 作客户端,etemenanki-app 作服务端 |
|---|---|---|
TCP 上的 TLS(network = "tls",Xray tcp + tls) |
已测试 | 已测试 |
| WebSocket,明文和 TLS | 已测试 | 已测试 |
| gRPC,明文和 TLS | 已测试 | 已测试 |
开启 mux 的 Xray 客户端:单个流、多个并发流,以及经 WebSocket 加 TLS |
不适用 | 已测试 |
| XUDP(mux 上的 UDP):单个对端,以及同一关联中的两个对端,每个回复都归属到正确的对端 | 不适用 | 已测试 |
配置错误会在运行 --test 或启动程序时出现,热重载被拒绝时也会出现:
| 错误 | 原因 | 解决方法 |
|---|---|---|
inbound vless-in: invalid uuid "…": … |
某个 users[].id 不是 UUID |
使用真实的 UUID;可接受的写法见上文 |
outbound proxy: invalid uuid "…": … |
出站的 id 不是 UUID |
同上 |
invalid settings: unknown field `flow`, expected `id` |
用户条目或出站设置中写了 flow、email 或 level |
删除该键;不支持 XTLS |
invalid settings: unknown field `decryption`, expected `users` |
[inbound.settings] 中有 Xray 独有的键,clients 或 fallbacks 同理 |
删除该键,或把 clients 改名为 users |
outbound proxy: invalid settings: missing field `id` |
[outbound.settings] 中没有 id |
添加 UUID |
outbound proxy: missing server / missing port |
出站没有上游地址 | 添加 server 和 port |
outbound proxy: ws stream needs ws.host or server(或 tls stream needs tls.server_name or server) |
传输层需要 host 或 TLS 名称的出站没有写 server;这项检查在 missing server 之前执行 |
添加 server 和 port |
security = "tls" is not valid with network = "tcp"; … |
使用了 Xray 对 TCP 上 TLS 的写法 | 使用 network = "tls" |
unknown stream security "reality" (expected "tls" or "none") |
security 写了 REALITY、XTLS 或有拼写错误 |
使用 "tls" |
inbound vless-in: tls stream needs tls.cert_file(或 tls.key_file) |
入站开启了 TLS,但没有证书或私钥 | 在 [inbound.stream.tls] 下添加 cert_file 和 key_file |
grpc stream needs grpc.service_name |
network = "grpc" 但没有服务名 |
在两端的 [.stream.grpc] 中设置 service_name |
在协议层面出错的客户端会被断开。设置 [log] level = "debug" 后,服务端对每个失败的连接记录一行日志,格式为 vless connection from … ended: …。协议错误带有 proxy core: 前缀,后接以下原因之一:
| 原因 | 含义 |
|---|---|
invalid vless request user id |
UUID 不在 users 中。检查客户端和服务端是否使用同一个 id。 |
vless addons (xtls flow) are not supported |
客户端设置了 flow,通常是 xtls-rprx-vision。在客户端上删除它。 |
invalid vless request version: … |
客户端说的不是 VLESS,或者两端的 TLS 或传输层设置不一致。 |
invalid vless command: … |
命令不是 TCP、UDP 或 mux。客户端使用了本服务端未实现的 VLESS 功能。 |
client did not complete its request in time |
客户端开始发送请求,但没有在 10 秒内完成。完全不发送任何数据的客户端会被记录为 inbound handshake timed out after 10s,不带该前缀。 |
如果客户端报告的是 TLS 或 WebSocket 错误,说明两端的传输层设置不同:检查 network、security、WebSocket path 和证书名称。