跳转到内容

从 Xray 迁移

etemenanki-app 沿用 Xray 的模型:入站接受客户端,路由器为每条流选择一个出站,出站再把流量转发出去。大多数协议的线上格式与 Xray 相同,所以 Xray 客户端可以连接 etemenanki-app 服务端,反之亦然。但配置文件是另一种写法:它用 TOML 而不是 JSON,键名是 snake_case,Xray 中的若干嵌套对象变成了扁平的键,还有一些 Xray 功能是有意不提供的。

如果你已经有一份能用的 Xray 配置,想在 etemenanki-app 上运行同样的服务,这一页就是为你准备的。它把 Xray 的各种常见写法对应到 TOML,指出默认值不同的地方,列出没有对应项的功能,并说明哪些组合经过了与真实 Xray 程序的对测。katana 是基于同一内核的面板节点 agent,它的节点设置来自面板而不是文件,所以本页只适用于 etemenanki-app。

Xray JSON etemenanki-app TOML 说明
log [log],只有一个键 level 日志输出到标准输出。没有访问日志。
inbounds[] [[inbound]] 每个入站对应一个表数组条目。
outbounds[] [[outbound]] 与 Xray 一样,第一个出站就是默认路由。
inbounds[].streamSettings [inbound.stream] 出站为 [outbound.stream]。共四种 network:tcp、tls、ws、grpc。
inbounds[].sniffing 入站上的 sniffing = true 布尔值。默认开启,且从不改写目标地址。
routing.rules[] [[route.rule]] 首个匹配的规则生效,但同一条规则内的条件是 OR 而不是 AND。
routing.domainStrategy 无 路由器从不解析域名,行为相当于 "AsIs"。
routing.balancers[] 顶层的 [[balancer]] 健康状态来自内置的 TCP 探测,而不是 observatory。
dns [dns] 只有一个上游解析器,不能按域名分配服务器,也没有 hosts。
policy 无 超时时间是固定的。唯一可以设置的连接上限在 hysteria2 和 tun 入站上,见上限。
observatory、burstObservatory 无 每个负载均衡器自行探测其成员。
stats、api、metrics 无 没有流量计数器,也没有控制 API。
fakedns、reverse、transport 无
多个配置文件、-confdir 一个 TOML 文件,用 -c 指定 不带 -c 时读取工作目录下的 config.toml。
xray run -test -c config.json etemenanki-app --test -c config.toml 检查所有内容,读取所有引用的文件,但不绑定任何端口。

TOML 文件中的每个表都拒绝未知键。从 Xray 照搬而没有转换的键不会被忽略,而是让配置报错停止,错误信息会指出这个键并列出可接受的键。唯一的例外是 freedom 和 blackhole 上的 [outbound.settings],见下文。

大多数转换错误都会让 --test 报错。下面这些则不会,或者报出的错误容易误读。切换流量之前,请逐条检查。

  1. 入站默认监听回环地址。 Xray 在没有 listen 时绑定 0.0.0.0,etemenanki-app 则绑定 127.0.0.1。因此需要从外部访问的服务端必须显式写出 listen = "0.0.0.0"(或某个具体地址)。

  2. TCP 上的 TLS 写作 network = "tls"。 Xray 的 "network": "tcp" 加 "security": "tls" 会被拒绝,而不是被转换。security = "tls" 只用于在 ws 或 grpc 下面加一层 TLS。

  3. 同一条规则内的条件是 OR。 Xray 只有在每个字段都匹配时(domain AND port AND network)才匹配一条规则。etemenanki-app 只要任意一个匹配条件满足就匹配该规则。一条带有 network = "udp" 和 port = ["443"] 的规则会命中所有 UDP 流量和所有 443 端口的流量,而不仅仅是 QUIC。见规则内的条件是 OR 而非 AND。

  4. 嗅探默认开启,且只影响路由。 Xray 只有在 sniffing.enabled 为 true 时才嗅探,而且除非设置了 routeOnly,否则会把目标地址改写为嗅探到的域名。etemenanki-app 会嗅探每一条以 IP 为目标的 TCP 流,除非你设置 sniffing = false;嗅探到的域名只用于路由,相当于 Xray 的 "routeOnly": true。

  5. freedom 和 blackhole 忽略自己的 settings。 这两个出站从不读取 [outbound.settings],所以写在那里的 Xray domainStrategy、redirect、fragment 和 response 能通过 --test,但不起任何作用。请在出站上用 address_family 代替 domainStrategy,其余几项没有对应项。

  6. SOCKS 入站默认中继 UDP。 Xray 的 SOCKS 入站默认关闭 udp,需要手动开启;etemenanki-app 则默认开启,除非你写 udp = false。

  7. "warning" 不是日志级别。 Xray 的默认级别是 "warning"。在 etemenanki-app 中,这个词会被当作日志 target 名称,结果所有日志行都被静默,包括说明 --test 为何失败的那条错误。请写 level = "warn"。

下面是一份典型的 Xray 服务端配置:443 端口上的 VLESS over WebSocket 和 TLS、一个直连出口,以及两条拦截规则。两个标签页分别是 Xray 原文件和 etemenanki-app 的转换结果。只要证书和 geodata 文件存在于给定路径,这份 TOML 就能通过 --test。

config.json
{
"log": { "loglevel": "warning" },
"inbounds": [
{
"tag": "vless-ws-in",
"listen": "0.0.0.0",
"port": 443,
"protocol": "vless",
"settings": {
"clients": [
{ "id": "11111111-2222-3333-4444-555555555555", "email": "alice@example.com" },
{ "id": "11111111-2222-3333-4444-666666666666", "email": "bob@example.com" }
],
"decryption": "none"
},
"streamSettings": {
"network": "ws",
"security": "tls",
"tlsSettings": {
"certificates": [
{
"certificateFile": "/usr/local/etc/xray/fullchain.pem",
"keyFile": "/usr/local/etc/xray/privkey.pem"
}
]
},
"wsSettings": { "path": "/ray", "host": "proxy.example.com" }
},
"sniffing": {
"enabled": true,
"destOverride": ["http", "tls"],
"routeOnly": true
}
}
],
"outbounds": [
{ "tag": "direct", "protocol": "freedom" },
{ "tag": "block", "protocol": "blackhole" }
],
"routing": {
"domainStrategy": "AsIs",
"rules": [
{ "type": "field", "ip": ["geoip:private"], "outboundTag": "block" },
{ "type": "field", "domain": ["geosite:category-ads-all"], "outboundTag": "block" }
]
}
}

从上到下,改动如下:

  • loglevel 变成 level,"warning" 变成 "warn"。
  • streamSettings 变成 [inbound.stream] 表,WebSocket 和 TLS 设置分别放在它的 ws 和 tls 子表中。certificates[0] 变成 cert_file 和 key_file 两个键。
  • clients 变成 users。email 标签被去掉了,因为这里的 VLESS 和 VMess 用户只带一个 id。decryption 也被去掉了,因为只有普通 VLESS 这一种模式。
  • sniffing 对象变成一个布尔值。destOverride 没有对应项:总是尝试 TLS 和 HTTP,其他协议一律不嗅探。
  • geodata 文件在 [route] 中写明了路径。Xray 会在其程序旁边寻找 geoip.dat 和 geosite.dat;etemenanki-app 只读取你给出的路径,而且只在有规则用到时才读取。
  • geoip: 和 geosite: 前缀移进了键名。"type": "field" 和 domainStrategy 被去掉了。
  • 这里的 [route] default = "direct" 是可选的,因为第一个出站本来就是默认路由。显式写出来,可以保证调整出站顺序后文件依然正确。

由于同一条规则内的匹配条件是 OR,这两条拦截规则也可以合并为一条:在一个 [[route.rule]] 中同时写 geoip = ["private"] 和 geosite = ["category-ads-all"]。包括证书和测试请求在内的完整步骤,见 VLESS over WebSocket 和 TLS 示例。

同一服务的客户端:一个本地 SOCKS 端口,把所有流量发往上面的服务端。

config.json
{
"inbounds": [
{
"tag": "socks-in",
"listen": "127.0.0.1",
"port": 1080,
"protocol": "socks",
"settings": { "auth": "noauth", "udp": true }
}
],
"outbounds": [
{
"tag": "proxy",
"protocol": "vless",
"settings": {
"vnext": [
{
"address": "proxy.example.com",
"port": 443,
"users": [{ "id": "11111111-2222-3333-4444-555555555555", "encryption": "none" }]
}
]
},
"streamSettings": {
"network": "ws",
"security": "tls",
"tlsSettings": { "serverName": "proxy.example.com" },
"wsSettings": { "path": "/ray", "host": "proxy.example.com" }
}
}
]
}

vnext 数组没有了:server 和 port 直接写在 [[outbound]] 上,唯一用户的 id 移到 [outbound.settings]。一个出站只连接一台服务器。要把流量分散到多台服务器,请为每台服务器定义一个出站,再把它们放进一个负载均衡器。

Xray etemenanki-app 区别
tag(可选) tag(必填) 在所有入站中必须唯一。
listen,默认 "0.0.0.0" listen,默认 "127.0.0.1" 以 / 开头的值是 Unix socket 路径。抽象 socket(@name)会被拒绝。
port:一个数字、"10000-10010" 这样的范围,或一个列表 port:一个数字 范围和列表会产生类型错误。请每个端口定义一个入站。
protocol protocol 见协议名称。
settings [inbound.settings] 因协议而异,见用户与凭据。
streamSettings [inbound.stream] 见传输层设置。
sniffing 对象 sniffing 布尔值,默认 true 见嗅探。
allocate 无

入站键的完整列表见入站。

Xray protocol 入站 出站
socks socks(SOCKS4、4a 和 5) socks(SOCKS5)
http http http(仅 CONNECT)
vless vless vless
vmess vmess vmess
trojan trojan trojan
shadowsocks shadowsocks,仅 TCP shadowsocks,仅 TCP
wireguard 拒绝:wireguard cannot be used as an inbound (no server implementation) wireguard
freedom 不适用 freedom,别名 direct
blackhole 不适用 blackhole,别名 block
dokodemo-door、tunnel 拒绝:unknown protocol 不适用
dns、loopback 不适用 拒绝:unknown protocol
hysteria(版本 2) hysteria2,别名 hysteria 和 hy2 同左
tun tun 不适用

协议名区分大小写:"Freedom" 会被拒绝。

Hysteria 2 的配置结构与 Xray 不同。hysteria2 上的 [inbound.stream] 和 [outbound.stream] 会被拒绝,它的证书、TLS 和认证相关的键改为写在 [inbound.settings] 或 [outbound.settings] 中。见 Hysteria 2。

Xray etemenanki-app 区别
tag(可选) tag(必填) 必须唯一,且不能与负载均衡器的 tag 相同。
protocol protocol
settings.vnext[0].address、.port(VLESS、VMess) server、port 直接写在 [[outbound]] 表上。每个出站只能有一台服务器。
settings.servers[0].address、.port(Trojan、Shadowsocks、SOCKS、HTTP) server、port 同上。
streamSettings [outbound.stream] 键与入站一侧相同。
mux 无 作为未知字段被拒绝。出站为每条流单独建立一个连接。
sendThrough、proxySettings、streamSettings.sockopt 无 作为未知字段被拒绝。不支持出站链式代理。
freedom 的 settings.domainStrategy 出站上的 address_family 见下表。

address_family 是与 Xray 在 freedom 上的 domainStrategy 最接近的对应项。除 blackhole 外的所有出站都支持它:在 freedom 上,它对目标的地址进行筛选和排序;在代理出站上,它对 server 域名做同样的处理。

Xray freedom domainStrategy address_family
AsIs、UseIP、ForceIP "auto"(默认值)
UseIPv4、ForceIPv4 "ipv4_only"
UseIPv6、ForceIPv6 "ipv6_only"
UseIPv4v6、ForceIPv4v6 "prefer_ipv4"
UseIPv6v4、ForceIPv6v4 "prefer_ipv6"

域名始终由 [dns] 中的解析器解析,因此 Xray 的 AsIs(系统解析器)和 UseIP(内置 DNS)在这里没有区别。可接受的写法和细节见出站。

协议与方向 Xray etemenanki-app 去掉或拒绝的字段
VLESS 入站 settings.clients[].id users = [{ id = "…" }] flow、email、level、decryption、fallbacks
VLESS 出站 vnext[0].users[0].id id flow、encryption
VMess 入站 settings.clients[].id users = [{ id = "…" }] alterId、email、level
VMess 出站 vnext[0].users[0].id、.security id、security alterId
Trojan 入站 settings.clients[].password、.email users = [{ password = "…", email = "…" }] level、flow、fallbacks
Trojan 出站 servers[0].password password
Shadowsocks 入站 settings.method、.password、.clients[] method、password、users(也接受 clients 这个名字) network、level
Shadowsocks 出站 servers[0].method、.password method、password uot、level
SOCKS 入站 auth = "noauth" 或 "password"、accounts[]、udp、ip auth = "none" 或 "password"、accounts、udp、udp_bind userLevel
HTTP 入站 accounts[]、allowTransparent accounts、allow_transparent timeout、userLevel
SOCKS 和 HTTP 出站 servers[0].users[0].user、.pass user、pass

有些取值规则与 Xray 不同:

  • UUID 必须是真正的 UUID。 Xray 会把 "my-custom-id" 这样的任意短字符串转换成 UUID,etemenanki-app 则以 invalid uuid "my-custom-id" 拒绝它。请为这类用户分配正式的 UUID,并相应更新客户端。
  • VMess 只支持 AEAD。 alterId = 0 不需要替代项,直接删除即可。使用大于 0 的 alterId 的客户端无法连接。在出站上,security = "auto" 始终表示 AES-128-GCM,"none" 和 "zero" 会以 unknown vmess security 被拒绝。入站只接受 AES-128-GCM 和 ChaCha20-Poly1305 加密的数据,因此设置为 "none" 或 "zero" 的 Xray 客户端无法连接。
  • Shadowsocks 不支持 UDP。 两侧都只承载 TCP,也没有流加密方式。2022 系列加密方法的密钥写法与 Xray 相同:服务端 PSK 写在 password 中,每个用户的 PSK 写在 users[].password 中,连接多用户服务端时,出站的 password 写作 "server-psk:user-psk"。

详见各协议页面:VLESS、VMess、Trojan、Shadowsocks、SOCKS 和 HTTP。

Xray settings [outbound.settings] 区别
secretKey private_key Base64 或十六进制,32 字节。
address,写成 "10.0.0.2/32" 这样的 CIDR address,写成 "10.0.0.2" 这样的纯 IP 带前缀长度会以 invalid IP address syntax 被拒绝。
peers[0].publicKey peer_public_key 只支持一个 peer。
peers[0].preSharedKey preshared_key
peers[0].endpoint endpoint host:port,用系统解析器解析。
peers[0].keepAlive keepalive 单位为秒。
peers[0].allowedIPs 无 路由到该出站的所有流量都进入隧道。
mtu mtu 默认 1420。
reserved reserved 必须恰好三个字节。
domainStrategy 出站上的 address_family 作用于隧道内的目标地址。
noKernelTun 无 隧道始终运行在用户态。

其余内容见 WireGuard。

streamSettings 变成 [inbound.stream] 或 [outbound.stream] 表。network 与 security 的组合按下表转换:

Xray network + security [.stream] network + security
tcp 或 raw,无 security 省略 [.stream],或写 network = "tcp"
tcp 或 raw + tls network = "tls",省略 security
ws,无 security network = "ws"
ws + tls network = "ws"、security = "tls"
grpc,无 security network = "grpc"
grpc + tls network = "grpc"、security = "tls"
任意 + reality 不支持:unknown stream security "reality" (expected "tls" or "none")
httpupgrade、splithttp、xhttp、h2、http、kcp、quic 不支持:unknown stream network "…"

各传输方式的设置对象对应到子表:

Xray etemenanki-app 说明
wsSettings.path [.stream.ws] path 默认 /。缺少开头的 / 时会自动补上。与 Xray 一样,路径中的 ?ed=2048 会在出站上开启 early data;入站接受任何客户端的 early data。
wsSettings.host,或 wsSettings.headers.Host [.stream.ws] host 在入站上,Host 不同的请求会得到 404。在出站上,依次回退到 tls.server_name、server。
wsSettings.headers(其他请求头)、heartbeatPeriod、acceptProxyProtocol 无
grpcSettings.serviceName [.stream.grpc] service_name 必填。只支持普通的服务名形式:路径为 /<service_name>/Tun 和 /<service_name>/TunMulti。
grpcSettings.authority [.stream.grpc] authority 仅出站。依次回退到 tls.server_name、server。
grpcSettings.multiMode 无 入站两种模式都接受。出站始终使用默认的(Tun)模式,因此无论 Xray 服务端如何设置都能配合使用。
grpcSettings.idle_timeout、health_check_timeout、user_agent 等调优项 无 使用固定值。
tlsSettings.serverName [.stream.tls] server_name 出站:用作 SNI,也是校验证书时使用的名称,默认为 server。在入站上被忽略。
tlsSettings.allowInsecure [.stream.tls] allow_insecure 仅出站,在入站上被忽略。不能与 ca_file 同时使用。
tlsSettings.certificates[0].certificateFile、.keyFile [.stream.tls] cert_file、key_file 仅入站。每个入站只有一对证书和私钥,均为 PEM 文件;证书文件可以包含完整证书链。不支持内联的 certificate 和 key 数组。
带 "usage": "verify" 的证书 [.stream.tls] ca_file 仅出站,在入站上被忽略。该 CA 会被加入系统根证书集合。
alpn 无 固定值:ws 为 http/1.1,grpc 为 h2,tls 不设置。
fingerprint、pinnedPeerCertSha256、minVersion、maxVersion、cipherSuites、rejectUnknownSni、ECH 无 两侧都使用 TLS 1.2 或更高版本,加密套件设置固定。
tcpSettings.header(HTTP 伪装) 无
sockopt 无

传输层设置适用于 http、trojan、vless 和 vmess 入站,以及 socks、http、trojan、vless、vmess 和 shadowsocks 出站。每个键的说明见传输层。

每条 Xray 规则变成一个 [[route.rule]] 表。无论目标是出站还是负载均衡器,都写成 outbound;每个条件变成一个或多个匹配条件键。

Xray 规则字段 [[route.rule]] 键 说明
outboundTag outbound
balancerTag outbound 负载均衡器与出站共用同一套 tag。
domain(以及 domains) domain_suffix、domain_keyword、domain_full、domain_regex、geosite 按前缀拆分,见下表。
ip cidr、geoip 纯 IP 和 CIDR 写入 cidr,geoip: 条目写入 geoip。
port port 字符串数组,每项是一个端口或一个范围:"53,443,1000-2000" 变成 ["53", "443", "1000-2000"]。
network network 只能有一个值,"tcp" 或 "udp"。若是 "tcp,udp",省略该键。
source、sourceIP source_cidr 客户端的地址。
inboundTag inbound_tag
type: "field"、ruleTag 无 省略即可。
sourcePort、localIP、localPort、user、protocol、attrs、process、vlessRoute、webhook 无 未知字段。

域名条目按前缀拆分:

Xray domain 条目 etemenanki-app 匹配范围
"domain:example.com" domain_suffix = ["example.com"] example.com 及其所有子域名,不包括 notexample.com
"full:example.com" domain_full = ["example.com"] 仅 example.com 本身
"keyword:example" domain_keyword = ["example"] 包含该文本的任意域名
"example"(无前缀) domain_keyword = ["example"] 同上:Xray 把不带前缀的字符串视为关键字
"regexp:\\.example\\.com$" domain_regex = ['\.example\.com$'] Rust regex 语法,与 Go 一样不支持环视(look-around)
"geosite:cn" geosite = ["cn"] 代码名不区分大小写
"geosite:google@ads" geosite = ["google@ads"] 只匹配带有该属性的条目
"ext:file.dat:tag" 无 每份配置只能有一个 geosite 文件
"dotless:" domain_regex = ['^[^.]*$'] 需要自己写正则

IP 条目:

Xray ip 条目 etemenanki-app
"10.0.0.0/8"、"192.0.2.1" cidr = ["10.0.0.0/8", "192.0.2.1"]
"geoip:cn" geoip = ["cn"]
"geoip:!cn" geoip = ["!cn"]
"geoip:private" geoip = ["private"],你的 geoip.dat 中必须有这个代码
"ext:file.dat:tag" 无

域名匹配条件按小写比较:domain_suffix、domain_keyword 和 domain_full 的值会被转成小写,被匹配的域名也是如此。domain_regex 的模式则不会,所以请用小写书写。cidr 的主机位必须为零:"10.0.0.1/8" 会以 invalid cidr "10.0.0.1/8": host part of address was not zero 失败,而 Xray 会接受它。

geodata 文件与 Xray 使用的 v2ray 格式 geoip.dat 和 geosite.dat 相同。在 [route] 中用 geoip 和 geosite 设置它们的路径。只有当某条规则用到文件时才会读取它;如果规则用到了文件却没有配置路径,会以 a geosite matcher is used but no geosite file is configured 失败。

在 Xray 中,带有多个字段的规则只有在所有字段都匹配时才匹配。在 etemenanki-app 中,只要规则中任意一个匹配条件匹配(无论它在哪个键下),整条规则就匹配。与 Xray 一样,规则仍然按顺序尝试,首个匹配的规则生效。

下面这条 Xray 规则只拦截 QUIC:

{ "type": "field", "network": "udp", "port": "443", "outboundTag": "block" }

逐键转换后,它会拦截所有 UDP 流量,以及所有发往 443 端口的 TCP 流量:

[[route.rule]]
outbound = "block"
network = "udp" # 匹配每一条 UDP 流……
port = ["443"] # ……并且另外匹配每一条发往 443 端口的流

这样的组合条件无法表达。转换带有多个字段的规则之前,先判断每个字段单独生效是否可行,再拆分规则或删去字段,直到满足要求。只有一个字段、或者一个字段中有多个值的规则,可以原样转换。

etemenanki-app 的路由器从不解析域名。它的行为相当于 Xray 的 "domainStrategy": "AsIs",没有 IPIfNonMatch 或 IPOnDemand:

  • cidr 或 geoip 匹配条件永远不会匹配以域名为目标的流。
  • 域名匹配条件(包括 geosite)永远不会匹配以 IP 为目标的流,除非嗅探为它找回了域名。
flowchart TB
    F["流到达"] --> D{"目标是 IP 吗?"}
    D -- "否,是域名" --> R["按顺序检查规则:域名匹配条件看到该域名"]
    D -- "是" --> S{"是 TCP,且 sniffing = true?"}
    S -- "是" --> N["读取 TLS SNI 或 HTTP Host"]
    S -- "否" --> I["按顺序检查规则:IP 匹配条件看到该地址"]
    N --> I2["按顺序检查规则:IP 匹配条件看到该地址,域名匹配条件看到嗅探到的域名"]
    R --> O["第一条有任一匹配条件命中的规则生效"]
    I --> O
    I2 --> O
    O -- "都不匹配" --> DEF["route.default"]

如果你的 Xray 配置用 IPIfNonMatch 来按 IP(geoip:cn)把国内网站直连,请把同一规则的域名一侧也加上(geosite = ["cn"]),这样以域名为目标的流也能匹配。

Xray sniffing etemenanki-app
enabled 入站上的 sniffing = true 或 false,默认为 true。
destOverride 无。总是尝试 TLS SNI 和 HTTP Host;不嗅探 QUIC、fakedns 和 BitTorrent。
routeOnly 实际上始终开启:嗅探到的域名用于路由,流仍然发往原始地址。
metadataOnly、domainsExcluded、ipsExcluded 无

只有目标是 IP 地址的 TCP 流才会被嗅探,最多等待 300 毫秒、读取首批载荷的 4 KiB。以域名为目标的流直接按该域名路由,不需要等待。见入站和路由。

Xray 的负载均衡器位于 routing 之内,依赖 observatory 获取健康状态。在 etemenanki-app 中,[[balancer]] 是一个顶层表,自行探测它的成员。

{
"routing": {
"balancers": [
{
"tag": "auto",
"selector": ["proxy-"],
"strategy": { "type": "leastPing" },
"fallbackTag": "direct"
}
],
"rules": [{ "type": "field", "network": "tcp,udp", "balancerTag": "auto" }]
},
"observatory": {
"subjectSelector": ["proxy-"],
"probeUrl": "https://www.gstatic.com/generate_204",
"probeInterval": "1m"
}
}
Xray etemenanki-app
selector(tag 前缀) outbounds,精确出站 tag 的列表。其他负载均衡器不能作为成员。
strategy.type = "random"(默认值)、"roundRobin"、"leastPing"、"leastLoad" strategy = "failover"(默认值:按列表顺序选第一个健康的成员)或 "round_robin"。其他值会以 unknown balancer strategy 失败。
fallbackTag 无。所有成员都不可用时,使用第一个成员。
observatory.probeUrl、probeInterval 每隔 probe_interval 秒(默认 30)向每个成员的 server 和 port 发起一次 TCP 连接,超时为 probe_timeout(默认 5)。

由于探测是向 server 和 port 发起 TCP 连接,只有上游是 TCP 的出站才能作为成员。freedom、blackhole 和 wireguard 没有 server,hysteria2 监听的是 UDP,所以这四种都会以 balancer auto: outbound direct has no upstream a TCP health probe can reach, so it cannot be balanced 被拒绝。见负载均衡器和故障转移示例。

Xray 的 dns 对象列出多个服务器,并可以按域名在其中选择。etemenanki-app 的 [dns] 只配置一个上游,代理自己需要解析的域名几乎都交给它:freedom 的目标、代理出站的 server 名称、WireGuard 隧道内的目标,以及负载均衡器的探测。唯一的例外是 WireGuard 的 endpoint,它始终交给主机解析器。在默认的 backend = "system" 下,[dns] 本身就是主机解析器,会遵循 /etc/hosts。

Xray dns.servers 条目 [dns]
"localhost" backend = "system",默认值
"1.1.1.1" backend = "udp"、server = "1.1.1.1:53"
"https://cloudflare-dns.com/dns-query" backend = "https"、server = "1.1.1.1:443"、url = "https://cloudflare-dns.com/dns-query"
"tcp://…"、"quic+local://…"、"fakedns" 无
— backend = "tls"、server = "1.1.1.1:853"、server_name = "cloudflare-dns.com"(DNS over TLS)

server 始终是带端口的 IP 地址:只写 "1.1.1.1" 会以 dns: invalid server address: invalid socket address syntax 失败。hosts、按服务器设置的 domains 和 expectIPs、queryStrategy、clientIp 以及 disableCache 都没有对应项。查询结果总是会被缓存。见 DNS。

下列 Xray 功能没有对应项。其中大多数会明确报错,所以 --test 会帮你找出来。标为“接受但忽略”的几行则不会报错,需要你自己在配置中搜索。

Xray 功能 在 etemenanki-app 中的结果
XTLS flow,包括 xtls-rprx-vision 两侧都是未知字段。仍然发送 flow 的客户端会被断开。
REALITY unknown stream security "reality"。请改用证书和 tls。
httpupgrade、splithttp、xhttp 传输方式 unknown stream network。h2、http、kcp 和 quic 也一样。
出站上的客户端 mux 未知字段。VLESS、VMess 和 Trojan 入站无需任何设置即可接受来自 Xray 客户端的 mux.cool(以及其中的 XUDP)。
VLESS 和 Trojan 上的 fallbacks 未知字段。认证失败的连接会被关闭。
Hysteria 2 的 Brutal 拥塞控制(up 和 down 带宽) 没有带宽相关的键。连接以“速率未知”协商,由拥塞控制决定速率。
freedom 的 domainStrategy 或 targetStrategy、redirect、fragment、noises 接受但忽略:freedom 从不读取自己的 settings。策略请用 address_family 设置。
blackhole 的 response 接受但忽略。被拦截的流收不到任何数据,也没有 HTTP 403 页面。
由 observatory 或 burstObservatory 驱动的负载均衡器 未知顶层字段。每个负载均衡器运行自己的 TCP 探测。
leastPing、leastLoad 和 random 负载均衡策略 unknown balancer strategy。Xray 的 roundRobin 在这里写作 round_robin。
stats、api、metrics、按用户的流量计数器 未知顶层字段。katana 为面板节点提供按用户的流量计费。
policy 等级以及用户上的 level 未知字段。超时时间是固定的。
dokodemo-door 入站,dns 和 loopback 出站 unknown protocol。
WireGuard 入站 被拒绝:没有服务端实现。
fakedns、reverse、出站链式代理(proxySettings、dialerProxy) 未知字段。
uTLS fingerprint、证书固定(pinning)、ECH 未知字段。
入站端口范围、allocate 类型错误或未知字段。
VLESS 和 VMess 用户上的 email 未知字段。Trojan、Shadowsocks 和 Hysteria 2 的用户可以带 email。
Shadowsocks over UDP 两侧都不支持。

项目的集成测试让 etemenanki-app 与真实的 xray-core 程序对接,让流量同时经过两者,并比对返回的字节。

组合 etemenanki-app 作客户端,Xray 作服务端 Xray 作客户端,etemenanki-app 作服务端
VLESS over TLS(network = "tls",Xray tcp + tls) 已测试 已测试
VLESS over WebSocket,明文和 TLS 已测试 已测试
VLESS over gRPC,明文和 TLS 已测试 已测试
VMess over gRPC,带 TLS 未测试 已测试
VMess over 明文 WebSocket,带 early data(path = "/vmess?ed=2048") 已测试 已测试
来自 Xray 客户端的 mux.cool:VLESS over TCP 和 over WebSocket + TLS、Trojan over WebSocket + TLS 不适用 已测试
来自 Xray 客户端的 mux.cool:VMess over TCP,包括一次 64 KiB 的上传,它同时被切分成多个 VMess chunk 和多个 8 KiB 的 mux 帧 不适用 已测试
经 VLESS 和 VMess 的 XUDP(mux 内的 UDP) 不适用 已测试
经 VLESS 的 XUDP:一个 UDP 关联与两个对端通信,每个回复都归属到发出它的那个对端 不适用 已测试
嗅探来自 Xray 客户端、以 IP 为目标的 VLESS 流,并按 HTTP Host 或 TLS SNI 路由 不适用 已测试

不带 mux 的 Trojan、Shadowsocks、SOCKS、HTTP 和 WireGuard 没有与 Xray 程序对测。Hysteria 2 在两个方向上都与上游 Hysteria 客户端和服务端做了测试,见 Hysteria 2。

  1. 列出你的配置用到的 Xray 功能,与不支持的功能对照。如果依赖 REALITY、XTLS Vision、xhttp 或客户端 mux,这部分请继续使用 Xray,或者先修改客户端。

  2. 借助上面的表格逐节转换文件:入站、出站、传输层设置,然后是路由。保持 tag 不变,这样规则仍然指向正确的出站。

  3. 逐一检查带有多个条件的规则,按 OR 语义重写。

  4. 把 geoip.dat 和 geosite.dat 复制到固定位置,并在 [route] 中设置它们的路径。请使用绝对路径:相对路径是相对于工作目录解析的,而不是相对于配置文件。

  5. 检查文件,反复修改直到通过。每次只报告第一个错误。

    终端窗口
    etemenanki-app --test -c /etc/etemenanki/config.toml
  6. 在文件中搜索 listen、freedom 或 blackhole 下的 [outbound.settings],以及 [log],与会改变行为的差异对照。这些是 --test 发现不了的错误。

  7. 在一个空闲端口上启动 etemenanki-app,让一个客户端连接它,确认流量能通,并且每条规则都把流量送到预期的地方。然后再把监听器移到生产端口。

以下是 Xray 使用习惯最常引发的错误。与某个具体条目相关的构建错误会带有 inbound <tag>: 或 outbound <tag>: 前缀。

错误 Xray 习惯 修正
security = "tls" is not valid with network = "tcp"; … "network": "tcp", "security": "tls" network = "tls"
unknown stream security "reality" (expected "tls" or "none") REALITY 使用 security = "tls" 和证书
unknown stream network "xhttp" 不支持的传输方式 使用 ws 或 grpc
unknown field `mux`, expected one of `tag`, `protocol`, `server`, `port`, `stream`, `address_family`, `settings` 出站上的 mux 删除它
invalid settings: unknown field `clients`, expected `users` VLESS、VMess 或 Trojan 入站上的 clients 改名为 users
invalid settings: unknown field `flow`, expected `id` 用户上的 XTLS Vision,或 email、level 删除该键
invalid settings: unknown field `alterId`, expected `id` 入站用户上的 VMess alterId 删除它
outbound <tag>: missing server 地址写在 settings 里的 vnext 或 servers 中 把地址移到出站上的 server 和 port
invalid settings: unknown field `vnext`, expected `id` 同上,但已经设置了 server 删除 vnext,把用户的 id 直接写在 [outbound.settings] 中
unknown socks auth "noauth" Xray 对无认证的叫法 auth = "none"
位于 sniffing 的 invalid type: map, expected a boolean Xray 的 sniffing 对象 sniffing = true
invalid type: string "10000-10010", expected u16 入站上的端口范围 每个端口一个入站
unknown field `outboundTag` 规则中的 outboundTag 或 balancerTag outbound = "…"
unknown field `domain` 规则中的 domain 或 ip 拆分为上文列出的匹配条件键
invalid rule network "tcp,udp" (expected "tcp" or "udp") 一个字符串里写了两种 network 省略 network
invalid port spec: "53,443" 逗号分隔的端口列表 port = ["53", "443"]
invalid type: integer `443`, expected a string 规则 port 中写了数字 port = ["443"]
geosite code not found: geosite:cn 取值中保留了前缀 geosite = ["cn"]
unknown balancer strategy "leastPing" (expected "failover" or "round_robin") Xray 的策略名,包括 "roundRobin" "failover" 或 "round_robin"
unknown field `selector` 负载均衡器的 tag 前缀 在 outbounds 中列出精确的 tag
unknown field `servers`, expected one of `backend`, `server`, `server_name`, `url`, `ca_file` Xray 的 DNS 服务器列表 只配置一个解析器,见 DNS
unknown field `stats` (或 api、policy、observatory) Xray 的顶层配置段 删除它们
--test 没有任何输出,以状态码 1 退出 level = "warning" level = "warn"。运行 RUST_LOG=info etemenanki-app --test -c <file> 查看被隐藏的错误。