Skip to content

Commit da81bdf

Browse files
committed
fix(hysteria): accept Bearer auth and unify rustls provider
1 parent fec5744 commit da81bdf

File tree

6 files changed

+40
-20
lines changed

6 files changed

+40
-20
lines changed

agent-docs/hysteria.md

Lines changed: 21 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -39,8 +39,10 @@
3939

4040
### 认证(HTTP/3 `/auth`
4141
- QUIC 建连后,client 必须发送 HTTP 请求到 `/auth`
42-
- `Host`(authority)必须为 `hysteria`
43-
- 必须带 `Authorization` header(值由服务端配置决定)。
42+
- 规范要求 `Host`(authority)为 `hysteria`(本项目 client 发送 `https://hysteria/auth`,满足该要求)。
43+
- 必须带 `Authorization` header(值由服务端配置决定)。实测上游实现存在两种形式:
44+
- `Authorization: <password>`
45+
- `Authorization: Bearer <password>`(本项目 server 兼容两者)
4446
- 成功响应的 HTTP status 为 `233`
4547
- 可选请求 header:
4648
- `Hysteria-CC-RX`: 下行 bps(不实现复杂 CC,先按文档要求透传/默认)
@@ -83,6 +85,7 @@
8385
- 2026-02-10:本地联调验证:`rabbit-digger-pro` 通过 HY2(TCP) 成功代理访问本机 `python -m http.server`(socks5 -> rdp -> hysteria server -> localhost 目标)
8486
- 2026-02-11:新增 `protocol/hysteria` server(QUIC + H3 `/auth` + TCP/UDP 转发,编译通过)
8587
- 2026-02-11:新增联调单测:`protocol/hysteria/src/interop_tests.rs`(TCP + UDP)
88+
- 2026-02-11:兼容 `Authorization: Bearer <password>`;并将 rustls provider 选择收敛到统一入口(避免运行时 panic)
8689

8790
---
8891

@@ -107,3 +110,19 @@ server:
107110
bind: 127.0.0.1:1080
108111
net: hy2_local
109112
```
113+
114+
---
115+
116+
## 覆盖范围与已知差异(实现完整性说明)
117+
118+
当前实现目标是“能在本项目内自洽运行 + 联调通过”的最小可用子集,已覆盖:
119+
- QUIC 传输 + HTTP/3 `/auth`(client/server)
120+
- TCP:`0x401` 建连消息 + 双向转发(client/server)
121+
- UDP:datagram `0x3/0x2` + 分片重组(client/server)
122+
- Salamander:作为底层 UDP socket 的可选混淆层(client/server)
123+
- 真实端口联调单测:`protocol/hysteria/src/interop_tests.rs`
124+
125+
仍未覆盖/可能与规范或上游实现存在差异的点(后续互通性风险来源):
126+
- `/auth` 校验:server 目前**不强制** `:authority == hysteria`(规范要求但先放宽兼容),仅校验 path 与 `Authorization`。
127+
- `/auth` header 语义:`Hysteria-CC-RX`、`Hysteria-Padding` 等仅按“能通过握手/不破坏互通”处理,未实现完整的拥塞控制/带宽协商逻辑。
128+
- 连接与生命周期:server 侧 H3 driver 目前是最小保活模型(能跑通测试),对更复杂的并发/长连接场景可能还需打磨。

protocol/hysteria/src/client.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ use rd_interface::{
1111
};
1212
use tokio::sync::OnceCell;
1313

14+
use crate::crypto_provider::ensure_rustls_provider_installed;
1415
use crate::{codec::write_tcp_request, stream::Hy2Stream, transport, udp::Hy2Udp};
1516

1617
#[rd_config]
@@ -81,6 +82,7 @@ impl HysteriaNet {
8182

8283
impl Hy2Client {
8384
async fn connect(cfg: &HysteriaNetConfig, net: &Net) -> Result<Self> {
85+
ensure_rustls_provider_installed();
8486
let server_addr = resolve_server_addr(net, &cfg.server).await?;
8587
let server_name = resolve_server_name(cfg)?;
8688

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
pub(crate) fn ensure_rustls_provider_installed() {
2+
static ONCE: std::sync::Once = std::sync::Once::new();
3+
ONCE.call_once(|| {
4+
// Quinn uses rustls. With rustls 0.23+, the process-level provider may need
5+
// to be selected explicitly depending on feature resolution.
6+
let _ = quinn::rustls::crypto::ring::default_provider().install_default();
7+
});
8+
}

protocol/hysteria/src/interop_tests.rs

Lines changed: 0 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -14,13 +14,6 @@ use crate::{
1414
server::{create_endpoint, serve_endpoint, HysteriaServerConfig},
1515
};
1616

17-
fn install_rustls_provider() {
18-
static ONCE: std::sync::Once = std::sync::Once::new();
19-
ONCE.call_once(|| {
20-
let _ = quinn::rustls::crypto::ring::default_provider().install_default();
21-
});
22-
}
23-
2417
fn write_test_cert(dir: &TempDir) -> (String, String, String) {
2518
let rcgen::CertifiedKey { cert, key_pair } =
2619
rcgen::generate_simple_self_signed(vec!["localhost".into()]).unwrap();
@@ -45,7 +38,6 @@ fn local_net() -> Net {
4538

4639
#[tokio::test]
4740
async fn test_hy2_server_client_tcp() {
48-
install_rustls_provider();
4941
let dir = TempDir::new().unwrap();
5042
let (cert_path, key_path, ca_path) = write_test_cert(&dir);
5143

@@ -123,7 +115,6 @@ async fn test_hy2_server_client_tcp() {
123115

124116
#[tokio::test]
125117
async fn test_hy2_server_client_udp() {
126-
install_rustls_provider();
127118
let dir = TempDir::new().unwrap();
128119
let (cert_path, key_path, ca_path) = write_test_cert(&dir);
129120

protocol/hysteria/src/lib.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ use server::{HysteriaServer, HysteriaServerConfig};
44

55
mod client;
66
mod codec;
7+
mod crypto_provider;
78
mod salamander;
89
mod server;
910
mod stream;

protocol/hysteria/src/server.rs

Lines changed: 8 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ use rd_std::ContextExt;
1414
use tokio::io::AsyncReadExt;
1515
use tokio::sync::mpsc;
1616

17+
use crate::crypto_provider::ensure_rustls_provider_installed;
1718
use crate::{codec::decode_varint, stream::Hy2Stream, transport};
1819

1920
#[rd_config]
@@ -60,6 +61,7 @@ impl IServer for HysteriaServer {
6061
}
6162

6263
pub(crate) fn create_endpoint(cfg: &HysteriaServerConfig) -> Result<quinn::Endpoint> {
64+
ensure_rustls_provider_installed();
6365
let bind = match &cfg.bind {
6466
Address::SocketAddr(sa) => *sa,
6567
Address::Domain(_, _) => {
@@ -183,18 +185,15 @@ fn is_auth_request(req: &Request<()>, cfg: &HysteriaServerConfig) -> bool {
183185
if req.uri().path() != "/auth" {
184186
return false;
185187
}
186-
if req.uri().authority().map(|a| a.as_str()) != Some("hysteria") {
187-
return false;
188-
}
189-
if let Some(auth) = req
188+
let Some(auth) = req
190189
.headers()
191190
.get("authorization")
192191
.and_then(|v| v.to_str().ok())
193-
{
194-
auth == cfg.auth
195-
} else {
196-
false
197-
}
192+
else {
193+
return false;
194+
};
195+
196+
auth == cfg.auth || auth.strip_prefix("Bearer ").is_some_and(|v| v == cfg.auth)
198197
}
199198

200199
async fn respond_auth_ok(

0 commit comments

Comments
 (0)