跳转到内容

Hysteria 2 节点

Hysteria 2 节点在一个 UDP 端口上通过 QUIC 承载代理流量。当节点的 [node.api].node_type 为 Hysteria2(也接受 hysteria、hy2,不区分大小写)时,katana 就会提供这种节点。和其他节点一样,用户仍由面板下发,流量也仍上报给面板。但监听器本身需要几项任何面板都不会下发的设置,所以它们放在节点的 [node.hysteria] 表中。

本页介绍运行这种节点的两种方式:由面板描述节点,或由 katana 根据配置文件描述节点。本页列出 [node.hysteria] 下的所有键、面板必须下发的内容、官方 Hysteria 2 客户端如何认证,以及可能遇到的错误。关于 etemenanki-app 所提供的协议本身,见 etemenanki-app 中的 Hysteria 2。

  • 它监听 UDP。 katana 把 listen_ip:port 绑定为 UDP 套接字,因此 Hysteria 2 节点可以与同一地址上的 TCP 节点共用端口号,例如 TCP 443 上的 Trojan 节点和 UDP 443 上的 Hysteria 2 节点。
  • 它总是使用 TLS。 TLS 握手是 QUIC 的一部分,因此没有明文模式。无论面板对 TLS 怎么设置,每个 Hysteria 2 节点都需要 mode = "file" 的 [node.controller.cert]。
  • 它的用户表原地替换。 面板的用户列表变化时,katana 只替换认证器。UDP 套接字保持绑定,其余用户保持连接。
  • --test 可以检查它。 [node.hysteria] 下的设置都是本地的,所以 katana --test 会检查它们,并读取证书和私钥。对于其他节点类型,--test 无法检查面板将要下发的内容。

键 [node.hysteria].port 决定节点的端口和混淆设置从哪里来:

flowchart LR
  A["node_type = Hysteria2"] --> B{"[node.hysteria] port"}
  B -->|"0"| C["面板节点配置:port、obfs"]
  B -->|"非零"| D["[node.hysteria]:port、obfs"]
  C --> L["QUIC 监听器"]
  D --> L
  E["[node.hysteria]:credential、udp、masquerade"] --> L
  F["[node.controller.cert]"] --> L
  G["面板用户列表"] --> L
面板描述(port = 0) 本地描述(port 非零)
监听端口 面板的端口 [node.hysteria].port
混淆 面板的 obfs 和 obfs-password [node.hysteria].obfs 和 obfs_password
节点配置请求(/UniProxy/config、/mod_mu/nodes/…/info) 每次轮询都发送 从不发送
用户列表和流量上报 从面板获取,上报到面板 从面板获取,上报到面板
SSPanel 的节点级 node_speedlimit 生效 不读取。[node.api].speed_limit 仍然生效
来自节点 routes 的 Xboard 审计规则 加载 不加载;只有 rule_list_path 生效
credential、udp、udp_idle_timeout、伪装响应 [node.hysteria] [node.hysteria]

当面板无法描述节点时,就在本地描述它,例如在 SSPanel 上你不想使用 custom_config,或者你希望把端口和混淆设置保存在配置文件里。面板仍然必须知道这个节点:katana 会向面板请求 node_id 的用户,并以该 ID 上报他们的流量。在 Xboard 上,查找还会用到节点类型,所以该节点在面板中必须以 Hysteria 节点的形式存在。

下面的文件在 UDP 端口 443 上提供一个 Hysteria 2 节点,启用 Salamander 混淆和 UDP 中继。用户和流量上报都经由一个 Xboard 面板。

/etc/katana/config.toml
# katana Hysteria 2 node described locally: the port and the obfuscation
# come from [node.hysteria], so katana never asks the panel for the node's
# config. The panel still supplies the user list and receives the traffic
# reports for node_id 1.
[log]
level = "info"
[[node]]
panel_type = "NewV2board"
[node.api]
host = "https://panel.example.com"
node_id = 1
key = "replace-with-the-panel-key"
# Sent to the panel as node_type=hysteria2, which Xboard maps to its
# "hysteria" node type.
node_type = "Hysteria2"
[node.controller]
listen_ip = "0.0.0.0"
update_periodic = 60
# Every Hysteria 2 node needs a certificate file. Clients that verify it
# also need a subjectAltName for the name they use as SNI.
[node.controller.cert]
mode = "file"
cert_file = "/etc/katana/cert/fullchain.pem"
key_file = "/etc/katana/cert/privkey.pem"
[node.hysteria]
# Non-zero: this node is described here. Clients connect to UDP port 443.
port = 443
# The client's auth string is the user's UUID (the default).
credential = "uuid"
# Relay UDP as well as TCP; a quiet UDP session ends after 60 seconds.
udp = true
udp_idle_timeout = 60
# Salamander obfuscation. Clients need the same password.
obfs = "salamander"
obfs_password = "replace-with-a-long-random-password"
# What anyone without a valid credential sees.
[node.hysteria.masquerade]
status = 404
body = "<html><body><h1>404 Not Found</h1></body></html>\n"
content_type = "text/html; charset=utf-8"
  1. 检查文件。 --test 会读取证书和私钥,并检查 [node.hysteria] 下的每个键。它不会联系面板。

    终端窗口
    katana --test -c /etc/katana/config.toml

    文件有效时打印 Configuration OK 并以状态 0 退出。文件无效时把原因打印到 stderr,并以状态 1 退出:

    configuration error: node 1: udp_idle_timeout must be between 2 and 600 seconds
  2. 启动 katana。 面板返回至少一个用户后,节点绑定套接字并记录:

    INFO katana::manager::node: node 1: listening on 0.0.0.0:443

    如果用户列表为空,katana 不绑定任何端口,在出现用户之前不为任何人提供服务。如果节点无法启动,例如面板无法访问或端口已被占用,katana 会记录原因并重试。常见错误列出了这些消息。

  3. 放行端口。 在主机防火墙和云安全组中放行入站 UDP 443。TCP 443 的规则不覆盖它。

  4. 连接客户端。 把用户的 UUID 作为认证字符串交给客户端,同时提供混淆密码和证书上的服务器名称。客户端认证中有完整的客户端配置。

如果要改由面板描述同一个节点,把 port 设为 0(或省略),并删除 obfs 和 obfs_password:

/etc/katana/config.toml
[node.hysteria]
port = 0
udp = true

只有当 node_type 是 Hysteria 2 的取值时,katana 才会读取这个表;对其他节点则忽略它。未知键会被拒绝,因此像 obsf 这样的拼写错误会让 --test 报 unknown field `obsf`, expected one of `port`, `credential`, `udp`, `udp_idle_timeout`, `obfs`, `obfs_password`, `masquerade` 并停止。

键类型必填默认值说明
portu16否0监听的 UDP 端口。非零值表示这是一个本地描述的节点:katana 用本表构造节点,从不调用面板的节点配置接口。0 表示像其他节点类型一样,向面板获取端口和混淆设置。
credentialstring (enum)否""客户端的认证字符串如何标识用户。"" 或 uuid:整个字符串就是用户的 UUID。user_pass:字符串为 label:uuid,按第一个冒号拆分;在 Xboard/V2board 上 label 是 <uuid>@v2board.user,在 SSPanel 上是数字用户 ID;label 比较时不区分大小写。本键的值区分大小写,其他取值会报 unknown hysteria credential kind。
udpbool否false除 TCP 外也中继 UDP。默认关闭,这一点与上游服务端不同:此时监听器会告诉客户端 UDP 已禁用,只中继 TCP。
udp_idle_timeoutu64否60UDP 会话在没有任何数据报的情况下最多保持多少秒,超时后 katana 将其关闭。取值范围 2 到 600。默认值 60 只在 udp = true 时生效:本键只能在开启 UDP 时设置,udp 关闭时设置会报 udp_idle_timeout is set but udp is not enabled。
obfsstring (enum)否—本地描述节点的数据包混淆。唯一接受的值是小写的 salamander。port = 0 时使用面板的混淆设置,运行时忽略本键,但 --test 仍会检查它。
obfs_passwordstring视情况—所有客户端共用的 Salamander 密钥,至少 4 字节。设置了 obfs 时必填。只设它而不设 obfs 会被拒绝,这样拼写错误就不会悄悄关闭混淆。与 obfs 一样只对本地描述的节点生效。
masqueradetable否{}对所有未携带有效凭据的请求返回的 HTTP/3 响应,写作 [node.hysteria.masquerade]。本地描述和面板描述的节点都适用。

Hysteria 2 服务端看起来像一个 HTTP/3 Web 服务器。凭据错误或缺失的请求,或者指向任何其他路径的请求,都会得到这个固定响应,因此密码错误看起来和 URL 错误完全一样。省略这个表,或只设置其中部分键时,缺失的键取下面的默认值。这些默认值与未配置的上游服务端的回复一致。

键类型必填默认值说明
statusu16否404HTTP 状态码。可以是 100 到 999 之间除 233 以外的任何值:233 是 Hysteria 2 认证成功的状态码,用它会告诉探测者认证已通过。超出范围的值会报 is not an HTTP status code。
bodystring否"404 page not found\n"响应正文,原样发送,并带有相应的 Content-Length。
content_typestring否"text/plain; charset=utf-8"Content-Type 头的值。katana 不检查它。如果它不是合法的 HTTP 头部值(例如含有换行符),伪装响应会变成不带任何头部的 200 响应,因此请只使用可打印字符。

katana 只在 QUIC 内部提供伪装响应,不会为它打开 TCP 监听器。启用 Salamander 时,没有混淆密码的客户端无法完成 QUIC 握手,所以根本到不了伪装响应。

证书来自节点的 [node.controller.cert],从不来自面板。对于 Hysteria 2 节点:

键 要求
mode 必须为 "file"。默认值 "none" 以及 ACME 模式 "dns"、"http" 和 "tls" 都会报 hysteria2 node requires cert.mode = "file"。
cert_file PEM 证书链,叶子证书在前。两个路径都必须设置,否则节点报 TLS node requires cert.cert_file and cert.key_file。DER 文件会报 hysteria2: the certificate file contains no certificates。
key_file 与证书匹配的 PEM 私钥,格式为 PKCS#8、PKCS#1 或 SEC1。
reject_unknown_sni 必须保持 false。设为 true 会报 node requests kernel-unsupported feature: cert.reject_unknown_sni。

监听器只使用 TLS 1.3,并提供 ALPN h3。

katana 在构建监听器时读取这些文件。续期后的证书即使保存到相同路径,也要等监听器重建后才会生效,所以每次续期后都要重启 katana。其他做法见 热重载。

port = 0 时,katana 每次轮询都从面板获取端口和混淆设置。面板下发的其他节点信息 katana 都不使用:TLS 设置、服务器名称、带宽数值(up_mbps、down_mbps)和端口跳跃设置对监听器都没有影响。不过,katana 仍会在两次轮询之间比较其中一些未使用的值:Xboard 上节点的 host 或 server_name 变化,或 SSPanel 上 host、path 等 custom_config 字段变化,都会在下一次轮询时重建监听器并断开所有连接。

在面板中创建一个类型为 Hysteria 的节点,并把协议版本设为 2。katana 从 GET /api/v1/server/UniProxy/config 读取以下字段:

字段 面板设置 katana 的用途
server_port 节点的监听端口(不是展示给用户的端口) UDP 端口。0 会报 newV2board: server port must be > 0。
obfs 开启混淆时为 salamander,关闭时为 null 混淆类型
obfs-password 混淆密码 Salamander 密钥

在 katana 配置中设置 panel_type = "NewV2board",以及 node_type = "Hysteria2" 或 "hysteria"。katana 会把 node_type 转为小写后发给面板,Xboard 会把 hysteria2 映射为它的 hysteria 类型。Xboard 不认识 hy2,所以按这种方式配置的节点发出的每个请求都会被拒绝。

Xboard 的订阅链接把用户的 UUID 作为 Hysteria 2 密码,这与 katana 默认的 credential 一致。

katana 在构建监听器时检查面板下发的值,规则与本地键相同:salamander 以外的任何类型报 unknown obfs,密钥过短报 obfs_password must be at least 4 bytes for salamander。本地的 obfs 和 obfs_password 键对面板描述的节点不起作用,但 --test 仍会检查它们。

Hysteria 2 客户端发送一个认证字符串。[node.hysteria].credential 决定 katana 如何从中找出用户:

credential Xboard / V2board 上的认证字符串 SSPanel 上的认证字符串
"" 或 "uuid"(默认) <uuid> <uuid>
"user_pass" <uuid>@v2board.user:<uuid> <user id>:<uuid>
  • 在 uuid 模式下,整个字符串会与面板列出的每个用户的 UUID 逐字节比较,因此大小写有影响。除非你的客户端是按上游的 userpass 认证方式配置的,否则使用这种模式。
  • 在 user_pass 模式下,katana 在第一个冒号处拆分字符串。冒号之前是用户的标签,比较时不区分大小写:在 UniProxy 面板上是 <uuid>@v2board.user,在 SSPanel 上是数字用户 ID。冒号之后是 UUID。这正是上游服务端的 userpass 格式,所以为上游服务端写的客户端配置无需改动即可使用。
  • 两种模式下,katana 的流量计费都用同一个标签来标识用户。

认证字符串错误时会得到伪装响应。官方客户端随后会报告伪装响应的状态码,例如 authentication error, HTTP status code: 404。

client.yaml
server: proxy.example.com:443
auth: 11111111-2222-3333-4444-555555555555
obfs:
type: salamander
salamander:
password: replace-with-a-long-random-password
tls:
sni: proxy.example.com
socks5:
listen: 127.0.0.1:1080

节点没有混淆时,省略 obfs 块。混淆设置与节点不一致的客户端永远无法完成 QUIC 握手,它报告的是超时,而不是认证错误。

udp 默认关闭。此时 katana 会在认证回复中告诉每个客户端 UDP 不可用,并且只中继 TCP。上游服务端默认启用 UDP,所以如果你的用户需要 UDP,请设置 udp = true。开启 UDP 后,如果某个 UDP 会话在 udp_idle_timeout 秒内没有任何数据报,katana 会将其关闭;这项检查每秒进行一次。

katana 没有实现 Hysteria 的 Brutal 拥塞控制。它对每个客户端都回复 Hysteria-CC-RX: auto,并忽略客户端声明的带宽以及面板的 up_mbps 和 down_mbps。Hysteria 2 用户受到的唯一限速是 katana 的每用户令牌桶,见 限速。

以下上限适用于每个 Hysteria 2 节点,且无法修改:

上限 值 达到上限时
每节点 QUIC 连接数 4,096 新连接一到达就被拒绝。
每节点活跃的代理流和 UDP 会话数 65,536 新的流或会话被拒绝,客户端的其他流不受影响。
每个 QUIC 连接的并发流数 1,024 客户端要等到有空闲的流,才能再打开新的流。
QUIC 空闲超时 30 s 连接在 30 秒内(如果客户端自己的空闲超时更短,则按客户端的)没有收到任何数据包时被关闭。客户端的 keep-alive 也算作数据包。

这些上限都不是按用户计算的。拒绝会以 debug 级别记录,例如 hysteria2: refusing a connection; the listener is full。

Hysteria 2 的流与其他节点上的流走相同的路径:准入、路由、审计和计量。[node.controller].disable_sniffing = true 同样会关闭 Hysteria 2 流的嗅探。Xboard 上本地描述的节点从不拉取节点配置,所以没有来自面板 routes 的审计规则;只有 rule_list_path 指定的文件生效。

修改 何时生效
面板的用户列表 下一次轮询时生效,无需重新绑定。其余用户保持连接。
面板的端口或混淆设置(port = 0) 下一次轮询时生效,表现为重建监听器并断开所有连接
文件中 [node.hysteria] 下的任何键:port、credential、udp、udp_idle_timeout、obfs、obfs_password 或伪装响应 立即生效,表现为重建监听器并断开所有连接。设置或清除 port 会在本地描述和面板描述之间切换。
文件中的 [node.controller].listen_ip、[node.controller].disable_sniffing 或 [node.controller.cert] 立即生效,表现为重建监听器
文件中的 [node.api].speed_limit 立即生效,无需重新绑定。已打开的连接保持不变;修改之后打开的流按新速率运行。

重建监听器时会先关闭旧监听器,再构建新监听器。配置重载会拒绝 [node.hysteria] 下的未知键,但 katana 只在构建监听器时才检查它们的取值,所以保存了一个无法构建的修改(例如 udp_idle_timeout = 1)后,节点会没有监听器:katana 记录 node 1: rebuild failed: …,并在每次轮询时重试,直到你修正文件。保存之前先运行 katana --test。

完整的重载过程见 热重载。

--test 和正在启动的节点报出的错误都带有节点前缀,例如 configuration error: node 1: …。

消息 原因 解决办法
hysteria2 node requires cert.mode = "file" 没有配置证书,或设置了 ACME 模式。 设置 [node.controller.cert],包括 mode = "file"、cert_file 和 key_file。
No such file or directory (os error 2) 证书或私钥路径不存在。 检查两个路径;使用绝对路径。
hysteria2: the certificate file contains no certificates cert_file 不是 PEM 证书。 让它指向 PEM 证书链,而不是 DER 文件或私钥。
hysteria2: the key file contains no private key key_file 中没有 PEM 私钥,例如它指向了证书。 让它指向 PEM 私钥。
hysteria2: certificate and key do not match: … key_file 属于另一张证书。 使用与这张证书一起签发的私钥。
obfs_password is set but obfs is not; did you mean obfs = "salamander"? 设置了密码但没有设置类型,可能在本地,也可能来自关闭了混淆的 Xboard。 添加 obfs = "salamander",或删除密码。
unknown obfs "Salamander" (expected "salamander") 拼写或大小写错误,或者 Xboard 节点仍是协议版本 1。 用小写写 salamander;把 Xboard 节点设为版本 2。
obfs_password must be at least 4 bytes for salamander 密钥短于 4 字节或缺失。 使用较长的随机值,例如由 openssl rand -base64 24 生成。
udp_idle_timeout is set but udp is not enabled 设置了 udp_idle_timeout 但没有 udp = true。 设置 udp = true,或删除该超时。
udp_idle_timeout must be between 2 and 600 seconds 取值不在 2 到 600 之间。 选择范围内的值。
unknown hysteria credential kind "UUID" (expected "uuid" or "user_pass") 取值不是可接受的字符串之一。它区分大小写。 使用 uuid 或 user_pass。
hysteria2: 233 is the authentication success status and cannot be used for the masquerade 伪装响应的状态码为 233。 使用其他状态码,例如 404。
hysteria2: 1000 is not an HTTP status code 伪装响应的状态码不在 100 到 999 之间。 使用真实的 HTTP 状态码。
newV2board: server port must be > 0 或 panel returned port 0 面板下发的端口为 0:Xboard 的 server_port,或 SSPanel 的 offset_port_node。 在面板中设置节点端口,或在本地描述节点。
Address already in use (os error 98) 其他进程或节点已占用 listen_ip 上的该 UDP 端口。 释放该端口或换一个端口。

节点无法启动时,katana 会记录原因并重试。node 1: node_info failed: …; retrying in <N>s 表示无法拉取或解析面板的节点配置,node 1: user_list failed: …; retrying in <N>s 表示用户列表出现同样的问题,node 1: initial start failed: …; retrying in <N>s 表示无法构建监听器。第一次重试在 1 秒后进行,之后每次失败等待时间翻倍,最长 60 秒;如果 update_periodic 更短,则最长为 update_periodic。每次尝试都会重新向面板请求用户列表,对于面板描述的节点,还会重新请求节点配置。保存对该节点 [[node]] 条目的修改会立即触发重试,所以你可以直接在文件中排除原因,无需重启 katana。之后的重建如果失败(例如面板修改了混淆设置之后),会记录 node 1: rebuild failed: …,katana 会在下一次轮询时重试。

客户端一侧:

客户端现象 原因
authentication error, HTTP status code: 404(或你设置的伪装状态码) 认证字符串与 credential 模式不匹配,或者该用户不在面板为此节点下发的列表中。
超时,没有认证错误 混淆设置或密码与节点不一致、UDP 端口被阻断,或者节点没有在监听。
x509: certificate relies on legacy Common Name field, use SANs instead 证书没有 subjectAltName。