Hysteria 2
Hysteria 2 是运行在 QUIC 之上的代理协议,因此走的是 UDP 而不是 TCP。在没有凭据的人看来,Hysteria 2 服务端就是一个 HTTP/3 Web 服务器:它以 ALPN h3 完成 TLS 1.3 握手,并用一个普通页面回应请求。客户端在每个 QUIC 连接上发送一次 HTTP/3 请求来证明身份,此后每个被代理的 TCP 连接都是该连接上的一个 QUIC 流,每个 UDP 包都是该连接上的一个 QUIC 数据报。
etemenanki-app 实现了两端:
- 作为入站(
[[inbound]]下的protocol = "hysteria2"),它在一个 UDP 端口上为 Hysteria 2 客户端(包括上游的参考客户端)提供服务; - 作为出站(
[[outbound]]下的protocol = "hysteria2"),它是一个 Hysteria 2 客户端,把 TCP 和 UDP 流发往 Hysteria 2 服务端。
协议名也可以写作别名 hysteria 和 hy2,但只接受小写。两者都表示 Hysteria 2,不支持 Hysteria 1。katana 的 Hysteria 2 节点使用同一个监听器,但设置来自 katana 自己的节点配置,详见 katana 指南。
| 入站 | 出站 | |
|---|---|---|
| 承载方式 | 一个 UDP 端口上的 QUIC | 连向一个 UDP 端口的 QUIC |
| TLS | rustls,仅 TLS 1.3,ALPN h3,相关键写在 [inbound.settings] 中 |
rustls,相关键写在 [outbound.settings] 中 |
| 凭据 | 一个共享的 password,或一张 users 表,以 user:pass 形式发送 |
一个 password 字符串 |
| TCP | 支持 | 支持 |
| UDP | 仅在 udp = true 时支持(默认关闭) |
支持,前提是服务端中继 UDP |
| 混淆 | salamander |
salamander |
传输层([.stream]) |
拒绝 | 拒绝 |
| Unix socket 监听器 | 拒绝 | 不适用 |
| 负载均衡器成员 | 不适用 | 拒绝 |
sequenceDiagram
participant C as 客户端
participant S as Hysteria 2 入站
participant O as 路由选中的出站
C->>S: QUIC 握手,TLS 1.3,ALPN h3
C->>S: 带 Hysteria-Auth 头的 HTTP/3 POST /auth
alt 凭据匹配
S-->>C: 状态码 233,以及是否中继 UDP
C->>S: 每个 TCP 连接一个 QUIC 流
S->>O: 为每个目标路由并拨号
C->>S: UDP 包作为 QUIC 数据报(若 udp = true)
else 凭据错误,或任何其他请求
S-->>C: 伪装响应,默认 404
end
认证在每个 QUIC 连接上进行一次,而不是每个被代理的连接一次。不是有效认证的请求(包括凭据错误的请求)得到的回应,与请求任何其他 URL 完全相同:都是伪装响应。
下面这一对配置中,服务端使用一个共享密码并开启 UDP;本地客户端开放一个 SOCKS5 端口,把所有流量都经服务端发出。
[[inbound]]tag = "hy2-in"protocol = "hysteria2"listen = "0.0.0.0" # 默认值是 127.0.0.1port = 443 # UDP 端口
[inbound.settings]cert_file = "/etc/etemenanki/cert.pem"key_file = "/etc/etemenanki/key.pem"password = "replace-with-a-long-random-password"udp = true
[[outbound]]tag = "direct"protocol = "freedom"[[inbound]]tag = "socks-in"protocol = "socks"listen = "127.0.0.1"port = 1080
[[outbound]]tag = "hy2-out"protocol = "hysteria2"server = "proxy.example.com"port = 443
[outbound.settings]password = "replace-with-a-long-random-password"启动这一对配置的步骤:
-
为服务端的域名申请证书。公共 CA(例如 Let’s Encrypt)签发的证书无需额外设置就能被所有客户端接受。证书必须包含客户端所连接名称的
subjectAltName,见证书。 -
把两个文件中的密码替换成同一个随机值,例如
openssl rand -base64 24的输出。 -
检查两个文件。
--test会读取并解析证书和私钥,因此能发现文件缺失或私钥与证书不匹配。终端窗口 etemenanki-app --test -c server.tomletemenanki-app --test -c client.toml -
在服务端防火墙和云厂商的安全组中放行 UDP 端口。放行 TCP 443 的规则并不包括它。以 ufw 为例:
终端窗口 ufw allow 443/udp -
先启动服务端,再启动客户端,然后通过客户端的 SOCKS 端口发送请求:
终端窗口 curl -x socks5h://127.0.0.1:1080 https://example.com/
客户端的出站采用惰性连接:在第一个流被路由到它之前不会拨号,所以密码或证书错误会在第一次请求时出现在客户端日志中,而不是在启动时。Hysteria 2 实践搭建了一套使用按用户凭据和混淆的完整部署。
入站使用 [[inbound]] 的通用键(tag、listen、port、sniffing),见入站。它自己的键写在 [inbound.settings] 中:
| 键 | 类型 | 必填 | 默认值 | 说明 |
|---|---|---|---|---|
cert_file | path | 是 | — | QUIC 监听器出示的 PEM 证书链,叶子证书在前。构建配置时用 rustls 读取并解析,所以 --test 能发现文件缺失或无法解析。监听器本身接受不带 subjectAltName 的证书,但基于 rustls 的客户端(包括 hysteria2 出站)和当前的上游客户端都会拒绝,所以证书里要有 DNS 或 IP 类型的 SAN。 |
key_file | path | 是 | — | cert_file 对应的 PEM 私钥(PKCS#8、PKCS#1 或 SEC1)。可以和 cert_file 是同一个文件。两者缺一会报 hysteria2 needs both cert_file and key_file。 |
password | string | 视情况 | — | 所有客户端共用的一个凭据。password 和 users 必须且只能设置一个。不能为空,并且应只含可打印 ASCII 字符:含其他字符的凭据永远无法匹配。客户端把它原样作为 auth 字符串发送。 |
users | array of tables | 视情况 | — | 按用户区分的凭据,格式为 { user, pass, email }。password 和 users 必须且只能设置一个;空列表视为未设置。客户端以 user:pass 作为 auth 字符串发送。 |
users[].user | string | 是 | — | 用户名。不能为空,应只含可打印 ASCII 字符,也不能包含 :,因为线路上用它分隔用户名和密码。比较时不区分大小写(仅限 ASCII),所以仅大小写不同的两个用户名会被拒绝。 |
users[].pass | string | 是 | — | 用户的密码。不能为空,并且应只含可打印 ASCII 字符。按原样精确比较,可以包含 :,因为服务端在第一个冒号处拆分凭据。 |
users[].email | string | 否 | "" | 随该用户的流一起传递、用作其用户名的标签。为空时改用 user。它不会在线路上发送;etemenanki-app 没有读取它的路由规则。 |
udp | bool | 否 | false | 除 TCP 外,还通过 QUIC 数据报中继 UDP。默认关闭,这一点和 socks 入站不同。服务端在每个客户端认证时告知其是否提供 UDP。 |
udp_idle_timeout | u64 | 否 | 60 | UDP 关联在两个方向上都持续静默多少秒后被关闭。取值范围为 2 到 600。udp 为 false 时设置它会报错,而不是被忽略。 |
max_connections | integer | 否 | 4096 | 该监听器同时服务的 QUIC 连接数。超出上限的客户端握手会被立即拒绝。至少为 1。 |
max_circuits | integer | 否 | 65536 | 整个监听器上同时存在的 circuit 数,每个 TCP 流和每个 UDP 关联各算一个。超出上限时,新的流会被重置,新的 UDP 关联会被丢弃。至少为 1。 |
obfs | string (enum) | 否 | — | QUIC 之下的数据包混淆。唯一接受的值是 salamander,必须完全一致且小写;其他值会报 unknown obfs。客户端必须使用相同的 obfs 和 obfs_password。 |
obfs_password | string | 视情况 | — | salamander 的预共享密钥,至少 4 字节。设置了 obfs 时必填;没有 obfs 却设置了它会报错,这样 obfs 拼写错误不会悄悄关闭混淆。 |
masquerade | table | 否 | — | 对所有不是有效认证的请求(包括凭据错误的请求)返回的 HTTP/3 响应。见下方的 masquerade 字段表。 |
未知键会被拒绝。例如把键误写成 obfuscation = "salamander",会报 invalid settings: unknown field 并列出可接受的键,而不是启动一个没有混淆的服务端。
一个带两个用户、UDP、混淆、较低上限和 HTML 伪装页的服务端:
[[inbound]]tag = "hy2-in"protocol = "hysteria2"listen = "0.0.0.0"port = 443
[inbound.settings]cert_file = "/etc/etemenanki/cert.pem"key_file = "/etc/etemenanki/key.pem"users = [ { user = "alice", pass = "replace-with-a-long-random-password", email = "alice@example.com" }, { user = "bob", pass = "replace-with-another-long-random-password" },]udp = trueudp_idle_timeout = 120max_connections = 2048max_circuits = 32768obfs = "salamander"obfs_password = "replace-with-a-shared-obfs-key"
[inbound.settings.masquerade]status = 404body = "<html><body><h1>Not Found</h1></body></html>\n"content_type = "text/html; charset=utf-8"
[[outbound]]tag = "direct"protocol = "freedom"监听器使用 UDP
Section titled “监听器使用 UDP”入站在 listen 和 port 上绑定一个 UDP socket。这带来几点影响:
listen默认为127.0.0.1,与所有入站相同。需要客户端通过网络访问的服务端,要设置listen = "0.0.0.0"或一个明确的地址。- 端口是 UDP 端口。 它不会与同一端口号上的 TCP 入站冲突,所以 UDP 443 上的 Hysteria 2 入站可以和 TCP 443 上基于 TLS 的入站同时运行。
- Unix socket 路径会被拒绝:
hysteria2 listens on UDP and cannot use a unix socket。 - 没有
[inbound.stream]。 QUIC 自带 TLS,证书写在[inbound.settings]中。如果[inbound.stream]块中的network不是tcp,或security不是none,会报protocol hysteria2 does not support stream network "…"或… stream security "…"。这项检查只看这两个键:[inbound.stream.tls]表能通过--test,但不起任何作用,所以请把证书写在[inbound.settings]中。
cert_file 和 key_file 必填,没有明文模式。两者都是 PEM 格式;私钥可以是 PKCS#8、PKCS#1(RSA)或 SEC1(EC),也可以和证书放在同一个文件里。监听器只使用 TLS 1.3,并协商 ALPN h3。
证书在构建配置时加载,所以证书有问题时 --test 和重载都会失败:
| 问题 | 错误 |
|---|---|
| 缺少其中一个键 | hysteria2 needs both cert_file and key_file |
| 文件不存在 | No such file or directory (os error 2)(消息中不包含文件名) |
cert_file 中没有 PEM 证书 |
hysteria2: the certificate file contains no certificates |
key_file 中没有 PEM 私钥 |
hysteria2: the key file contains no private key |
| 私钥属于另一张证书 | hysteria2: certificate and key do not match: … |
相对路径相对于进程的工作目录解析,而不是相对于配置文件所在的目录。
凭据:password 或 users
Section titled “凭据:password 或 users”password 和 users 必须且只能设置一个。两者都不设置会报 hysteria2 needs password or users,两者都设置会报 password and users cannot both be set; a credential would have two answers。空的 users = [] 视为未设置。
协议只携带一个字符串,即客户端的 auth 值。服务端如何解读它,取决于你设置了哪个键:
| 入站设置 | 客户端发送 | 匹配方式 |
|---|---|---|
password = "s3cret" |
s3cret |
整个字符串必须等于 password。 |
users = [{ user = "alice", pass = "s3cret" }] |
alice:s3cret |
在第一个 : 处拆分。用户名比较时不区分大小写,密码精确比较。 |
由于在第一个冒号处拆分,用户名不能包含 :,密码则可以。因此 Alice:pa:ss 会以用户 alice、密码 pa:ss 通过认证。users 表的规则在构建配置时检查:
| 规则 | 错误 |
|---|---|
每一项都有 user 和 pass |
invalid settings: missing field pass … |
| 两者都不为空 | hysteria2: a user needs both a name and a password |
user 中不含 : |
hysteria2: a username cannot contain ':' — it separates the two on the wire |
| 转成小写后用户名不重复 | hysteria2: two users share a name once lower-cased |
共享的 password 同样不能为空:hysteria2: the password must not be empty。
用户名和密码请只使用可打印 ASCII 字符。入站只在 Hysteria-Auth 头仅包含可见 ASCII 字符和空格时才读取它;任何其他字符(例如非 ASCII 字母)都会使凭据被读作空值,因而永远无法匹配。配置检查不会发现这个问题。
每个用户的 email(email 为空时则是 user 名)会作为标签随该用户的流一起传递。etemenanki-app 没有基于它匹配的路由规则。用共享 password 认证的流携带的标签为空。
UDP 中继
Section titled “UDP 中继”UDP 默认关闭。当 udp = false 时,服务端在认证时告诉每个客户端它不中继 UDP,并忽略收到的所有数据报。
当 udp = true 时,客户端把每个包作为一个 QUIC 数据报发送,并标上它自己选择的会话 ID。入站为每个会话打开一个 UDP 关联,并对每个包单独路由,所以同一会话的不同包可能走不同的出站,这与其他所有 UDP 入站一致。协议中没有关闭会话的消息;会话在两个方向上都连续 udp_idle_timeout 秒没有包时,由入站关闭,该检查每秒进行一次。
udp_idle_timeout 接受 2 到 600 秒,默认 60。除非 udp = true,否则设置它会被拒绝:
| 设置 | 错误 |
|---|---|
udp_idle_timeout = 1 或 601 |
udp_idle_timeout must be between 2 and 600 seconds |
设置了 udp_idle_timeout,但 udp 为 false 或未设置 |
udp_idle_timeout is set but udp is not enabled |
大于单个 QUIC 数据报的包会被拆成分片,并在另一端重组,两个方向都是如此。重组后的包最大 4096 字节,与上游实现的上限相同。
混淆(Salamander)
Section titled “混淆(Salamander)”obfs = "salamander" 会在线路上包装每个 QUIC 包:每个包附加一个随机的 8 字节 salt,并与由 obfs_password 和该 salt 派生出的密钥流做 XOR。没有密钥时,流量看起来就不再像 QUIC。这是混淆而不是加密,内容仍由 QUIC 自身的 TLS 保护。开启 obfs 后,不使用相同密钥的客户端连 QUIC 握手都无法完成,因此也访问不到伪装页。
两端必须使用相同的 obfs_password。入站和出站的检查规则相同,并且都是 fail closed(出错即拒绝),所以拼写错误绝不会导致混淆被关闭:
| 设置 | 结果 |
|---|---|
没有 obfs,也没有 obfs_password |
不混淆。 |
obfs = "salamander",且 obfs_password 不少于 4 字节 |
使用该密钥进行 Salamander 混淆。 |
obfs = "salamander",但 obfs_password 更短或缺失 |
obfs_password must be at least 4 bytes for salamander |
有 obfs_password 而没有 obfs |
obfs_password is set but obfs is not; did you mean obfs = "salamander"? |
其他任何 obfs 值,包括 "Salamander" 或 "" |
unknown obfs "Salamander" (expected "salamander") |
使用错误密钥的客户端,或未开启混淆去连接开启了混淆的服务端的客户端,完全得不到回应。它最终以超时失败,而不是认证错误。
伪装响应(masquerade)是入站对所有未成功认证的请求返回的固定 HTTP/3 响应,这些请求包括凭据错误、缺少凭据,或者浏览器请求 /。默认是 Go 的纯文本 404 page not found,与未配置伪装的上游服务端返回的内容相同。
在 [inbound.settings.masquerade] 中设置。未写出的键保持默认值:
| 键 | 类型 | 必填 | 默认值 | 说明 |
|---|---|---|---|---|
status | u16 | 否 | 404 | 响应的 HTTP 状态码。接受 100 到 999 之间的任何值,但 233 除外:它是 Hysteria 认证成功的状态码,返回它等于告诉探测者认证已经通过。小于 100 或大于 999 的值会报 is not an HTTP status code。 |
body | string | 否 | "404 page not found\n" | 响应体,原样发送,并带上相应的 Content-Length。默认值与 Go 的 http.NotFound 返回的内容逐字节一致。 |
content_type | string | 否 | "text/plain; charset=utf-8" | 响应头 Content-Type 的值。 |
上面的服务端示例返回一个状态码为 404 的小 HTML 页面。
status = 233 会被拒绝,报 hysteria2: 233 is the authentication success status and cannot be used for the masquerade:233 是 Hysteria 2 服务端对正确凭据的应答状态码,把它返回给探测者,等于告诉对方认证已经通过。伪装只能是固定响应,没有提供文件或反向代理其他网站的选项。
连接和 circuit 上限
Section titled “连接和 circuit 上限”有两个上限用来保护服务端,两者都必须至少为 1(max_connections must be at least 1、max_circuits must be at least 1):
| 键 | 默认值 | 计数对象 | 超出上限时 |
|---|---|---|---|
max_connections |
4096 | 该监听器上的 QUIC 连接,无论是否已认证 | 新连接的握手被拒绝。 |
max_circuits |
65536 | 该监听器所有连接上的 TCP 流加 UDP 关联 | 新的流被重置;新 UDP 关联的包被丢弃。 |
circuit 在整个生命周期内都占用名额,直到中继结束或 UDP 关联超时。另有两个固定上限:每个客户端连接最多同时打开 1024 个流(QUIC 流量控制会让客户端等待更多名额),最多 256 个 UDP 关联。
何时发送代理应答
Section titled “何时发送代理应答”对每个被代理的 TCP 连接,入站通常先拨号目标,再回应客户端。这样客户端是从一次拒绝得知目标不可达,而不是从一个打开后随即结束的流中得知。
例外是请求中的目标为 IP 地址且 sniffing 开启(默认开启)的情况。客户端在收到应答前不会发送任何数据,所以入站会立即回应“已连接”,从最先收到的字节中(最多 4 KiB,最长 300 ms)读取 TLS 服务器名或 HTTP Host,并用这个域名进行路由。此时拨号失败表现为流被关闭。如果你更想要拒绝应答而不是嗅探到的域名,请在入站上设置 sniffing = false。
来自该入站的流携带入站的 tag,可用于 inbound_tag 规则。source_cidr 规则匹配的是打开该 QUIC 连接的客户端地址,而不是监听器地址,尽管所有客户端共用一个 UDP socket。TCP 流匹配 network = "tcp",中继的 UDP 包匹配 network = "udp"。
出站需要通用的 server 和 port 键:Hysteria 2 服务端的域名或地址,以及它的 UDP 端口。缺少时会报构建错误 missing server 和 missing port。address_family 控制服务端名称的解析方式,名称通过 DNS 中配置的解析器解析。其余设置写在 [outbound.settings] 中:
| 键 | 类型 | 必填 | 默认值 | 说明 |
|---|---|---|---|---|
password | string | 是 | — | 每个 QUIC 连接发送一次的凭据,放在 Hysteria-Auth 头中。不能为空。服务端使用 users 表时,写成 user:pass。 |
server_name | string | 否 | — | TLS 服务器名称(SNI),也是校验证书时使用的名称。默认取出站的 server。当 server 是 IP 地址而证书上写的是域名时,需要设置它。 |
allow_insecure | bool | 否 | false | 接受任何服务端证书,不校验证书链和名称。不能与 ca_file 同时设置。只用于测试。 |
ca_file | path | 否 | — | 额外 CA 证书的 PEM 文件,这些证书会加入系统信任库,而不是替换它。--test 会读取该文件,但内容要到第一次连接时才解析,所以不含证书的文件会在那时报 hysteria2: the CA file contains no certificates。 |
obfs | string (enum) | 否 | — | QUIC 之下的数据包混淆。唯一接受的值是 salamander;其他值会报 unknown obfs。必须与服务端一致。 |
obfs_password | string | 视情况 | — | salamander 的预共享密钥,至少 4 字节,必须与服务端相同。设置了 obfs 时必填;没有 obfs 却设置它会报错。 |
max_concurrent_streams | integer | 否 | 102400 | 该出站在其唯一的共享连接上可同时打开的 TCP 流数,所有用户共用。超出上限的流会立即失败,报 connection is at its concurrent-stream limit。至少为 1,没有上限。 |
未知键会被拒绝。没有带宽相关的键:up = "100 mbps" 会报 invalid settings: unknown field up。
[[outbound]]tag = "hy2-out"protocol = "hysteria2"server = "203.0.113.10"port = 443
[outbound.settings]password = "alice:replace-with-a-long-random-password"server_name = "proxy.example.com" # 证书上的名称obfs = "salamander"obfs_password = "replace-with-a-shared-obfs-key"共享同一个连接
Section titled “共享同一个连接”路由到某个 hysteria2 出站的所有流共享同一个 QUIC 连接。每个 TCP 流在其上打开一个流,每个 UDP 流在其数据报通道上打开一个会话;凭据只在建立连接时发送一次。这是协议本身的工作方式。相比之下,vmess、vless、trojan 和 shadowsocks 出站会在每个流中发送凭据。
- 连接在第一个流到来时建立,而不是在启动时。如果服务端名称解析出多个地址,会按顺序逐个尝试,每个地址的 QUIC 握手和认证共有 10 秒时间。
- 连接建立后,每 10 秒发送一次 keep-alive 以保持连接。
- 连接断开后,下一个流会触发重连。如果连接尝试失败,或上一次连接持续不到 10 秒,出站会等待一段时间再重试:从 2 秒开始,每次翻倍,最多 30 秒。在此期间路由到它的流会立即失败,报
hysteria2: connection is down, waiting before the next attempt。 max_concurrent_streams限制该连接上同时打开的 TCP 流数,所有用户共用。超出的流会立即失败。UDP 会话另有 256 个的上限。
出站用 rustls 根据系统信任库校验服务端证书。校验所用的名称是 server_name,未设置时为 server。该名称是 DNS 名时还会作为 SNI 发送;IP 地址只用于校验,不会发送。
| 设置 | 信任 | 适用场景 |
|---|---|---|
| 什么都不设置 | 系统 CA 库 | 公共 CA 签发的证书。 |
ca_file |
系统 CA 库加上 ca_file 中的证书 |
私有 CA,或按下文方式创建的自签名证书。 |
allow_insecure = true |
任何证书、任何名称 | 仅用于测试。 |
allow_insecure 和 ca_file 不能同时设置:allow_insecure and ca_file cannot both be set。TLS 相关的键写在 [outbound.settings] 中,而不是 [outbound.stream.tls],见不支持 stream 块和负载均衡器。
当 server 是 IP 地址时,证书必须列出这个 IP 地址,否则就要把 server_name 设为证书上列出的某个 DNS 名。不然握手会失败,报 certificate not valid for name "203.0.113.10"。
除非设置了 allow_insecure,出站都需要系统信任库,即使设置了 ca_file 也一样。在没有 CA 证书的主机上(例如精简的容器镜像),每次连接都会失败,报 hysteria2: no system root certificates could be loaded;请安装发行版的 CA 证书包(Debian 和 Ubuntu 上是 ca-certificates)。
--test 会读取 ca_file,但不会解析它。不含证书的文件能通过检查,要到第一次连接时才失败,报 hysteria2: the CA file contains no certificates。
通过出站传输 UDP
Section titled “通过出站传输 UDP”出站把 UDP 流作为 QUIC 数据报传输,放不下的包会被分片。它需要服务端中继 UDP:如果服务端表示不中继,路由到该出站的每个 UDP 流都会失败,出站会记录一条警告 hysteria2: the server does not relay UDP; datagrams routed to this outbound are dropped。对于 etemenanki-app 服务端,这意味着服务端入站需要设置 udp = true。
不支持 stream 块和负载均衡器
Section titled “不支持 stream 块和负载均衡器”出站自己持有 UDP socket,因此不接受 [outbound.stream] 传输层。network 不是 tcp 或 security 不是 none 时,会报 protocol hysteria2 does not support stream security "tls" 之类的错误。
hysteria2 出站不能作为负载均衡器的成员。负载均衡器通过 TCP 连接检查成员,而 Hysteria 2 服务端只监听 UDP,所以该成员会一直被判定为不可用。这样的配置会被拒绝,报 balancer pool: outbound hy2-out has no upstream a TCP health probe can reach, so it cannot be balanced。
使用上游 Hysteria 客户端
Section titled “使用上游 Hysteria 客户端”入站可以与上游 Hysteria 2 客户端以及其他遵循协议规范的客户端配合使用。下面的客户端配置对应上文使用 users 表和 Salamander 的入站:
server: proxy.example.com:443auth: alice:replace-with-a-long-random-passwordobfs: type: salamander salamander: password: replace-with-a-shared-obfs-keytls: sni: proxy.example.comsocks5: listen: 127.0.0.1:1080对于使用共享 password 的服务端,auth 只填该密码即可。使用自签名测试证书时,在 tls 下加上 insecure: true。
与上游 Hysteria 的差异
Section titled “与上游 Hysteria 的差异”| 方面 | 上游 Hysteria 2 | etemenanki-app |
|---|---|---|
| 拥塞控制 | 设置了 bandwidth 时使用 Brutal(以固定速率发送);否则使用可配置的控制算法,默认 BBR |
没有带宽设置。出站把自己的接收速率报告为未知,入站总是回应 auto,两端都使用 quinn 默认的拥塞控制(Cubic)。由于回应的是 auto,上游客户端连接本服务端时会忽略自己的 bandwidth 设置,改用其配置的控制算法而不是 Brutal。 |
| 端口跳跃 | 客户端可以在一个端口范围内跳跃 | 出站只连接一个 server 和 port。 |
| Fast open | 客户端可选 | 未实现:出站等到服务端应答后才开始中继。 |
| 认证 | password、userpass、HTTP 和命令后端 |
password 或 users 表(userpass)。 |
| 伪装 | 提供文件、反向代理网站或固定字符串 | 固定的 status、body 和 content_type。 |
| QUIC 和 TLS 实现 | quic-go 和 Go 的 TLS | quinn 和 rustls。 |
固定上限和超时
Section titled “固定上限和超时”以下值是内置的,不能配置:
| 项目 | 值 |
|---|---|
| QUIC 空闲超时(两端) | 30 s |
| 客户端 keep-alive 间隔 | 10 s |
| 流接收窗口 / 连接接收窗口 | 8 MiB / 20 MiB |
| 每个客户端连接同时打开的流数(入站) | 1024 |
| 每个连接的 UDP 关联数(两端) | 256 |
| 重组后最大的 UDP 包 | 4096 字节 |
| UDP 空闲检查间隔(入站) | 1 s |
| 出站每个解析地址的连接尝试 | 10 s |
| 出站打开一个流 | 5 s |
| 出站重连退避 | 2 s,翻倍直至 30 s |
限制中的通用上限也同时适用。
对配置文件的任何修改都会重建整个 generation(一代实例),见热重载。对 Hysteria 2 入站来说,这意味着所有 QUIC 连接都会被关闭。旧监听器最多等待几秒,让 UDP 端口释放后再由新监听器绑定,因此新 generation 可以复用同一端口。客户端会自行重连。构建失败的配置会被记录到日志,正在运行的 generation 保持不变。
构建错误,由 --test、启动和重载时报告:
| 错误 | 原因和解决办法 |
|---|---|
hysteria2 needs both cert_file and key_file |
入站缺少证书或私钥。没有明文模式。 |
hysteria2 needs password or users |
设置 password,或一张非空的 users 表。 |
password and users cannot both be set; a credential would have two answers |
两者只保留一个。 |
hysteria2 listens on UDP and cannot use a unix socket |
listen 是一个路径。请使用 IP 地址。 |
port is required |
入站没有设置 port。 |
protocol hysteria2 does not support stream network "ws" |
删除 [inbound.stream] 或 [outbound.stream] 块。 |
unknown protocol "Hysteria2" |
协议名区分大小写:hysteria2、hysteria 或 hy2。 |
invalid settings: unknown field … |
[inbound.settings]、[inbound.settings.masquerade] 或 [outbound.settings] 中有拼错的键。消息中会列出可接受的键。 |
hysteria2 password must not be empty |
出站的 password 为 ""。 |
max_concurrent_streams must be at least 1 |
不写该键以使用默认值,或设为正数。 |
出站的运行时错误,日志形式为 hysteria2: connect failed: hysteria2: no address answered (…),括号内是具体原因:
| 原因 | 含义 |
|---|---|
hysteria2 authentication rejected with status 404 Not Found |
服务端不接受该凭据,状态码是其伪装响应的状态码。检查 password;对使用 users 表的服务端,请写成 user:pass。 |
invalid peer certificate: UnknownIssuer |
证书不是由受信任的 CA 签发的。设置 ca_file,或使用公共 CA 签发的证书。 |
invalid peer certificate: certificate not valid for name "…" |
server_name(或 server)不在证书上。 |
invalid peer certificate: Other(OtherError(CaUsedAsEndEntity)) |
服务端证书是 CA 证书。用 basicConstraints=critical,CA:FALSE 重新创建。 |
timed out |
10 秒内没有收到 QUIC 应答:UDP 端口被封锁、服务端未运行,或两端的 obfs 设置不一致。 |