跳转到内容

入站

入站是一个监听器:它以某种协议接受客户端连接,并把解码出的每条流交给路由。配置文件里的每个 [[inbound]] 表创建一个入站。一份配置可以有任意多个入站,协议可以任意混用,每个入站由它的 tag 标识。

本页介绍所有入站共有的键、每种协议能承载什么、listen 和 port 如何决定绑定什么、Unix socket 监听器,以及每个监听器的固定上限。各协议专有的 [inbound.settings] 键在对应协议的页面中说明。

katana 不读取 [[inbound]] 表:在 katana 中,每个节点的协议、端口和传输层由面板决定。下文所述都针对 etemenanki-app。

一个供本机程序使用的 SOCKS 代理:

config.toml
[[inbound]]
tag = "socks-in"
protocol = "socks"
port = 1080 # listen 默认为 127.0.0.1
[[outbound]]
tag = "direct"
protocol = "freedom"

由于没有设置 listen,它绑定的是 127.0.0.1:1080,只能从本机访问。启动时日志会写明这一点:

INFO etemenanki_app::instance: inbound socks-in listening on 127.0.0.1:1080

服务器通常不止一个入站。下面的配置同时提供一个本地 SOCKS 代理、一个基于 WebSocket 和 TLS 的公网 VLESS 端点,以及一个供本地服务使用、监听 Unix socket 的 HTTP 代理:

config.toml
[[inbound]]
tag = "local-socks"
protocol = "socks"
port = 1080 # 仅 127.0.0.1
[[inbound]]
tag = "public-vless"
protocol = "vless"
listen = "0.0.0.0" # 所有 IPv4 网卡
port = 443
[inbound.stream]
network = "ws"
security = "tls"
[inbound.stream.ws]
path = "/ray"
[inbound.stream.tls]
cert_file = "/etc/etemenanki/cert.pem"
key_file = "/etc/etemenanki/key.pem"
[inbound.settings]
users = [{ id = "11111111-2222-3333-4444-555555555555" }]
[[inbound]]
tag = "app-http"
protocol = "http"
listen = "/run/etemenanki/http.sock" # Unix socket:不设 port
[[outbound]]
tag = "direct"
protocol = "freedom"

启动前先用 etemenanki-app --test -c config.toml 检查配置。--test 会构建每个入站并读取它的证书和私钥文件,但不会绑定任何东西(见 --test 检查不出的问题)。

这些键直接写在每个 [[inbound]] 表中。未知的键会报错,因此把 listen 拼错会让配置无法通过,而不是悄悄回退到默认值。

键类型必填默认值说明
tagstring是—入站的名称,在所有入站中必须唯一,否则配置报错 duplicate inbound tag。路由规则用 inbound_tag 匹配它,日志里也用它标识入站。入站和出站的 tag 各自独立,互不冲突。
protocolstring (enum)是—客户端使用的协议:socks、http、trojan、vless、vmess、shadowsocks、hysteria2(别名 hysteria 和 hy2)或 tun。区分大小写。wireguard 会被拒绝并报 wireguard cannot be used as an inbound (no server implementation);其他值报 unknown protocol。
listenstring否"127.0.0.1"要绑定的地址。默认是回环地址,这是有意为之;要接受来自网络的连接,请写 0.0.0.0 或 ::。IPv6 地址直接写,不加方括号(写 ::,不写 [::])。主机名在绑定监听器时解析。以 / 开头的值是 Unix socket 路径;以 @ 开头的值(抽象 socket)会被拒绝。tun 入站不能设置此项。
portu16视情况—要绑定的端口,取值 0 到 65535。listen 未设置、是 IP 地址或主机名时必填(否则报 port is required)。Unix socket 不能设置(报 a unix socket listen has no port; remove port),tun 也不能设置。也接受 0,此时由内核挑选一个空闲端口。hysteria2 的端口是 UDP 端口。
streamtable否—协议下面的传输层:network(tcp、tls、ws、grpc)、security,以及 tls、ws、grpc 子表。只有监听 IP 地址的 http、trojan、vless、vmess 会用到它。其他情况下,network 不是 tcp 或 security 不是 none 都会报错。
address_familystring否—为了让入站和出站的表结构一致而保留,对入站没有任何作用,值也不做校验。
sniffingbool否true对目标是纯 IP 的流,从开头的字节里读取 TLS SNI 或 HTTP Host,让按域名写的规则也能匹配它。目标本身是域名的流不会被检查。
settingstable否{}各协议自己的设置,见对应的协议页面。未知的键会被拒绝,报 inbound TAG: invalid settings: ...。这类错误不带行号。

拼错的键会连同所在行号一起报告:

configuration invalid: TOML parse error at line 4, column 1
|
4 | lisen = "0.0.0.0"
| ^^^^^
unknown field `lisen`, expected one of `tag`, `protocol`, `listen`, `port`, `stream`, `address_family`, `sniffing`, `settings`
protocol 监听在 [inbound.stream] UDP mux.cool / XUDP 客户端认证 Unix socket
socks TCP 否 SOCKS5 UDP ASSOCIATE,默认开启(udp = true) 否 无,或用户名和密码 支持,需设置 udp_bind 或 udp = false
http TCP tcp、tls、ws、grpc 否 否 设置了 accounts 时使用 Basic 认证,否则不认证 支持
trojan TCP tcp、tls、ws、grpc 是 是 每个用户一个密码 支持
vless TCP tcp、tls、ws、grpc 是 是 每个用户一个 UUID 支持
vmess TCP tcp、tls、ws、grpc 是 是 每个用户一个 UUID 支持
shadowsocks TCP 否 否,仅 TCP 否 加密方式和密码,可选每个用户一个密码 支持
hysteria2、hysteria、hy2 UDP(QUIC) 否 udp = true 时支持(默认关闭) 否;每条 circuit 是独立的 QUIC 流 一个共享密码,或每个用户一组用户名和密码 不支持
tun 一个网络接口 否 udp = true 时支持(默认开启) 否 无;设备在本机 不适用

关于上表:

  • 监听在 指 etemenanki-app 绑定的对象。hysteria2 绑定的是 UDP socket,所以可以和某个 TCP 入站共用同一个端口号,例如 Trojan over TLS 和 Hysteria 2 都用 443 端口。
  • mux.cool / XUDP 是 Xray 多路复用的服务端。Trojan、VLESS 和 VMess 入站会自动接受它,没有开关它的键。一条承载连接最多容纳 256 条子流。
  • UDP 表示该入站能中继数据报,而不只是字节流。HTTP 和 Shadowsocks 入站只承载 TCP。
  • wireguard 只能用作出站,见 WireGuard。把它用作入站会被拒绝,报 wireguard cannot be used as an inbound (no server implementation)。

etemenanki-app 在构建配置时就决定要绑定什么,因此 --test 拒绝的配置形式与正式启动时完全相同。

flowchart TB
  A["[[inbound]]"] --> B{"protocol = tun?"}
  B -- 是 --> T["TUN 设备:不能设置 listen 和 port"]
  B -- 否 --> C{"listen 以 / 开头?"}
  C -- 是 --> U["Unix socket:不能设置 port"]
  C -- 否 --> D{"listen 以 @ 开头?"}
  D -- 是 --> E["报错:不支持抽象 socket"]
  D -- 否 --> F{"设置了 port?"}
  F -- 否 --> G["报错:port is required"]
  F -- 是 --> H{"protocol"}
  H -- "hysteria2, hysteria, hy2" --> UDP["在 listen:port 上的 UDP socket"]
  H -- "socks, http, trojan, vless, vmess, shadowsocks" --> TCP["在 listen:port 上的 TCP 监听器"]
  H -- "其他值" --> X["报错:unknown protocol,或拒绝 wireguard"]

除了 tun 检查之外,listen 和 port 的检查都在查看协议之前进行。因此,一个协议未知且没有端口的入站报的是 port is required,而不是 unknown protocol。

listen 未设置时,入站绑定 127.0.0.1。这是有意为之:本应只在本机使用的代理,绝不能因为一处拼写错误或键嵌套错误而暴露到网络上。需要接受其他机器连接的服务器必须明确写出来:

listen = "0.0.0.0"
port = 443
  • IPv6 地址直接写,不加方括号:"::" 或 "2001:db8::10"。带方括号的 "[::]" 能通过 --test,但在监听器绑定时失败:failed to lookup address information: Name or service not known。
  • "localhost" 这样的主机名在监听器绑定时解析。如果解析出多个地址,就使用第一个能绑定成功的地址,并且只使用这一个。
  • port 是 16 位整数。70000 会导致解析错误:invalid value: integer `70000`, expected u16。
  • port = 0 让内核挑选一个空闲端口。启动日志打印的是配置值(127.0.0.1:0),而不是内核实际选择的端口,所以从日志中看不出正在使用哪个端口。
  • 1024 以下的端口需要 root 权限或 CAP_NET_BIND_SERVICE capability。这一限制由内核在监听器绑定时执行。

tun 入站创建的是一个网络接口,而不是绑定 socket。设置 listen 或 port 都会被拒绝,报 tun owns a network interface and has no listener; remove listen/port。设备的名称、地址和路由写在它的 [inbound.settings] 中,见 TUN。

listen 的值以 / 开头时,入站会在该路径上监听一个 Unix 域 socket,而不是监听 TCP。当本机程序或同一主机上的 Web 服务器需要不经过网络协议栈与代理通信时,可以使用它。

[[inbound]]
tag = "socks-unix"
protocol = "socks"
listen = "/run/etemenanki/socks.sock"
[inbound.settings]
udp = true
udp_bind = "127.0.0.1" # Unix socket 没有可供 UDP 中继使用的本地 IP
规则 违反时的错误
路径必须是绝对路径。run/x.sock 这样的相对路径会被当作主机名。 inbound TAG: port is required
不能设置 port。 inbound TAG: a unix socket listen has no port; remove port
不支持抽象 socket(@name)。 inbound TAG: abstract unix sockets are not supported; use a filesystem path
不能使用传输层:[inbound.stream] 中的 network 只能是 tcp,security 只能是 none。 inbound TAG: protocol vless over a unix socket does not support stream network "ws"(或 stream security "tls")
不能是需要 UDP 的 hysteria2。 inbound TAG: hysteria2 listens on UDP and cannot use a unix socket
udp = true(默认值)的 socks 还必须设置 udp_bind,或者关闭 UDP。 inbound TAG: socks over a unix socket has no local IP for UDP associate; set udp_bind or udp = false

其他所有基于流的协议都能在 Unix socket 上使用:socks、http、trojan、vless、vmess 和 shadowsocks。协议直接运行在 socket 上,没有 TLS、WebSocket 或 gRPC 层,因为本地 socket 上的流量不需要伪装。

  • 每次绑定时(无论是启动还是重载),如果该路径上已有一个 socket 文件,etemenanki-app 会认为它是残留文件(来自崩溃的进程或上一代 generation),将其删除并绑定一个新的。
  • 如果路径上是 其他类型的文件,则保持不动,绑定失败并报 PATH exists and is not a socket。
  • 父目录 必须已经存在。etemenanki-app 不会创建它;目录不存在时报 No such file or directory (os error 2)。
  • 路径长度 受操作系统限制(Linux 上约 107 字节)。更长的路径报 path must be shorter than SUN_LEN。
  • 关闭时(SIGINT 或 SIGTERM)以及 每次重载时,入站停止监听后会删除自己的 socket 文件,下一代 generation 再绑定一个新的。
  • 权限 取决于进程的 umask。etemenanki-app 不会修改 socket 的权限或属主,因此请通过所在目录的权限来控制谁可以连接。

通过 Unix socket 建立的连接没有客户端 IP 地址。匹配 source_cidr 的路由规则永远不会匹配来自 Unix socket 入站的流;SOCKS 入站也没有可以放置 UDP 中继的本地 IP,这就是它需要 udp_bind 的原因。

SOCKS 的 UDP 中继只接受一个客户端的数据报。走 TCP 时,这个客户端就是控制连接来源的 IP 地址。Unix socket 没有这样的地址,所以客户端必须自己声明来源:在 UDP ASSOCIATE 请求中,它必须给出其数据报将要使用的确切 IP 地址和端口,而且该地址的地址族必须是 udp_bind 能收到的:udp_bind 为 IPv4 或 IPv4 映射地址(如 ::ffff:192.0.2.10)时须为 IPv4,为其他 IPv6 地址时须为 IPv6,为 :: 时两者皆可。例如 udp_bind = "127.0.0.1" 时,就需要一个 IPv4 地址,如 127.0.0.1:5000。此后中继只接受来自该地址和端口的数据报。

如果请求中的地址或端口未指定或为零、给出的是域名而不是 IP 地址,或者地址所属的地址族中继无法接收,入站会拒绝该请求:它回复 SOCKS5 应答码 0x02(“connection not allowed by ruleset”),并关闭控制连接。RFC 1928 允许客户端在不知道自己的来源地址时发送全零,许多客户端也确实这样做。这样的客户端可以通过 Unix socket 使用 CONNECT,但不能使用 UDP ASSOCIATE。--test 无法发现这一点,上面的表格也没有列出它,因为入站是在运行时拒绝请求的,配置本身是有效的。UDP 中继的完整说明见 SOCKS 页面。

[inbound.stream] 在协议下面加一层传输层:TLS、WebSocket 或 gRPC,其中 WebSocket 和 gRPC 下面还可以再加 TLS。只有四种协议会用到它,并且只在监听 IP 地址时可用:http、trojan、vless 和 vmess。

[inbound.stream]
network = "grpc"
security = "tls"
[inbound.stream.grpc]
service_name = "example"
[inbound.stream.tls]
cert_file = "/etc/etemenanki/cert.pem"
key_file = "/etc/etemenanki/key.pem"

其他入站遇到无法运行的传输层时会直接拒绝,而不是悄悄忽略,这样运维人员就不会在以为流量已经伪装的情况下实际暴露一个裸端口。对于 socks、shadowsocks、hysteria2 和 tun,以及任何监听 Unix socket 的入站,network 不是 tcp 或 security 不是 none 都会报错:

inbound ss: protocol shadowsocks does not support stream security "tls"

hysteria2 自带 TLS:它的证书和私钥写在 [inbound.settings] 中,而不是 [inbound.stream.tls]。所有 network 和 security 取值、ws、grpc 和 tls 的键,以及它们产生的错误,见 传输层 页面。

sniffing = true(默认值)时,如果一条流的目标是纯 IP 地址,入站会从流开头的字节中读取域名:TLS ClientHello 的 SNI,或 HTTP 请求的 Host 头。之后路由除了匹配地址,还会用这个域名去匹配域名和 geosite 规则。流实际拨号的目标不会改变。

  • 只检查以 IP 寻址的流。已经带有域名的流会立即按该域名路由。
  • 入站最多等待 300 毫秒,最多读取 4 KiB。像 SSH 或 SMTP 这样客户端不先发数据的协议,会在等待超时后按 IP 路由。
  • 格式错误或无法识别的字节绝不会导致连接失败;这样的流按地址路由。
  • mux.cool 子流只根据随其首帧到达的数据进行嗅探,因此一条子流永远不会拖住同一承载连接上的其他子流。

对于 SOCKS、HTTP CONNECT 和 Hysteria 2,嗅探会改变客户端收到应答的时机。通常入站会先拨号出站,拨号成功后才回复“已连接”,否则回复拒绝。当它需要嗅探一个以 IP 寻址的请求时,就必须先回复,因为客户端在收到应答之前不会发送任何数据。此时如果目标不可达,客户端只能从连接关闭得知,而不是从应答中得知。

如果某个入站的客户端总是发送域名,或者你只按 IP、端口或入站 tag 路由它的流量,可以对它设置 sniffing = false。嗅探出的域名如何与规则匹配,见 路由 页面。

所有接受 socket 连接的入站(除 hysteria2 和 tun 之外的所有协议,无论监听 TCP 还是 Unix socket)都有以下固定上限,不可配置。

上限 值 达到上限时
每个入站的活动连接数 65,536 新连接在被接受后立即关闭。连接从被接受起计数,直到关闭为止;一条 gRPC 连接无论承载多少个 stream 都只算一个。
每个入站的并发握手数 2,048 新连接在被接受后立即关闭。
握手时间 10 秒 未能按时完成协议请求的客户端会被断开。在它之前的传输层步骤(TLS、WebSocket 升级、gRPC 连接前言)另有各自的 10 秒。
每条承载连接的 mux.cool 子流数 256 超出的子流会被拒绝;承载连接和它的其他子流不受影响。
嗅探等待时间 / 字节数 300 ms / 4 KiB 流按地址路由。

因达到上限而断开的连接只在 debug 级别记录日志。另外两种入站的上限是可配置的设置:

入站 设置 默认值
hysteria2 max_connections(QUIC 连接数) 4096
hysteria2 max_circuits(整个监听器上的活动 circuit 数) 65,536
tun max_flows(整个设备上的活动流数) 65,536

etemenanki-app 的所有上限都汇总在 上限 页面。

配置文件变化时,etemenanki-app 会先完整构建新配置,然后才动正在运行的配置。新配置无效时,旧 generation 继续提供服务。新配置有效时,旧 generation 的所有入站都会停止(包括设置没有变化的入站),上面的所有连接都会断开,然后新 generation 绑定它的监听器。

绑定在启动时和重载时的行为不同:

  • 启动时,第一个绑定失败的入站会让进程退出:failed to start: inbound TAG bind 127.0.0.1:1080 failed: Address already in use (os error 98)。
  • 重载时,绑定失败的监听器会记录为 inbound TAG bind ADDRESS failed: ... 并保持停止状态,其他入站照常启动。etemenanki-app 只会在下一次重载时再尝试绑定它,而下一次重载需要配置文件的内容发生变化。

完整流程见 热重载 页面。

--test 会构建每个入站并读取每个证书,但不会绑定任何东西。以下问题只有在监听器绑定时(启动或重载时)才会出现:

  • 端口已被占用,包括被同一文件中的另一个入站占用;
  • 带方括号的 IPv6 地址(如 "[::]"),或无法解析的主机名;
  • 没有相应权限却绑定 1024 以下的端口;
  • Unix socket 路径的目录不存在、路径过长,或路径上已存在一个不是 socket 的文件;
  • 创建 tun 设备,这需要相应的权限。

构建错误在 --test 中打印为 configuration invalid: ...,在启动时打印为 failed to start: ...。大多数错误都带有入站的 tag。

错误 原因 解决方法
inbound TAG: port is required 监听 IP 地址,或 listen 缺失或为相对路径,却没有设置 port。 添加 port,或改用绝对 socket 路径。
inbound TAG: unknown protocol "SOCKS" 协议名是小写的,并且区分大小写。 使用上表中的取值之一。
wireguard cannot be used as an inbound (no server implementation) 在 [[inbound]] 中写了 protocol = "wireguard"。 WireGuard 只能用作出站。
duplicate inbound tag: TAG 两个入站使用了同一个 tag。 给其中一个改名。
inbound TAG: a unix socket listen has no port; remove port 同时设置了 socket 路径和 port。 删除 port。
inbound TAG: tun owns a network interface and has no listener; remove listen/port 在 tun 入站上设置了 listen 或 port。 两者都删除。
inbound TAG: protocol socks does not support stream network "ws" 给不能承载传输层的入站设置了传输层。 删除 [inbound.stream],或改用 vless、vmess、trojan 或 http。
inbound TAG: tls stream needs tls.cert_file 要求使用 TLS 却没有提供证书。 在 [inbound.stream.tls] 下设置 cert_file 和 key_file。
inbound TAG: invalid settings: unknown field ... [inbound.settings] 中有拼错的键。这类错误不带行号。 对照协议页面检查。
inbound TAG bind unix:PATH failed: PATH exists and is not a socket Unix socket 路径上有一个不是 socket 的文件。 移走该文件,或换一个路径。

更多不会报错但容易出乎意料的行为,见 常见陷阱 页面。