Skip to content

Commit a1e13ce

Browse files
authored
feat(proxy): add shadowquic protocol (#784)
* feat: imple shadowquic tcp proxy * chore: fix fmt * feat: impl shadowquic udp proxy * chore: fix fmt * fix: impl handler name * chore: fix clippy * fix: ipv6 sever addr may failed * test: adding tests for shadowquic * fix: ipv6 server addr * chore: bump shadowquic version * doc: update shadowquic document --------- Co-authored-by: spongebob888 <[email protected]>
1 parent 53e28fd commit a1e13ce

File tree

13 files changed

+862
-119
lines changed

13 files changed

+862
-119
lines changed

Cargo.lock

Lines changed: 332 additions & 118 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

clash/Cargo.toml

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ authors = { workspace = true }
88

99
[features]
1010
default = ["standard"]
11-
standard = ["shadowsocks", "tuic", "ssh", "clash_lib/zero_copy"]
11+
standard = ["shadowsocks", "tuic", "ssh", "clash_lib/zero_copy", "shadowquic"]
1212
plus = ["standard", "onion"]
1313
perf = ["plus", "jemallocator"]
1414

@@ -19,6 +19,7 @@ shadowsocks = ["clash_lib/shadowsocks"]
1919
ssh = ["clash_lib/ssh"]
2020
tuic = ["clash_lib/tuic"]
2121
onion = ["clash_lib/onion"]
22+
shadowquic = ["clash_lib/shadowquic"]
2223

2324
bench = ["clash_lib/bench"]
2425
dhat-heap = ["dep:dhat"]
Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
inbound:
2+
type: shadowquic
3+
bind-addr: 0.0.0.0:10002
4+
jls-pwd: "12345678"
5+
jls-iv: "87654321"
6+
jls-upstream: "echo.free.beeceptor.com:443" # domain + port, domain must be the same as client
7+
alpn: ["h3"]
8+
congestion-control: bbr
9+
zero-rtt: true
10+
outbound:
11+
type: direct
12+
log-level: "trace"

clash_lib/Cargo.toml

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ shadowsocks = ["dep:shadowsocks"]
1313
tuic = ["dep:tuic", "dep:tuic-quinn", "dep:register-count"]
1414
ssh = ["dep:russh", "dep:dirs", "dep:totp-rs"]
1515
onion = ["dep:arti-client", "dep:tor-rtcompat", "arti-client/onion-service-client"]
16+
shadowquic = ["dep:shadowquic"]
1617

1718
zero_copy = []
1819
bench = ["dep:criterion"]
@@ -154,6 +155,9 @@ russh = { version = "0.52", features = ["async-trait"] , optional = true }
154155
dirs = { version = "6.0", optional = true }
155156
totp-rs = { version = "^5.7", features = ["serde_support"] , optional = true }
156157

158+
# shadowquic
159+
shadowquic = { version = "0.1.6", optional = true, git = "https://github.com/spongebob888/shadowquic" }
160+
157161
# experimental
158162
downcast-rs = "2.0"
159163

clash_lib/src/app/outbound/manager.rs

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,8 @@ use crate::{
4343

4444
use super::utils::proxy_groups_dag_sort;
4545

46+
#[cfg(feature = "shadowquic")]
47+
use crate::proxy::shadowquic;
4648
#[cfg(feature = "shadowsocks")]
4749
use crate::proxy::shadowsocks;
4850
#[cfg(feature = "ssh")]
@@ -305,6 +307,13 @@ impl OutboundManager {
305307
Arc::new(h) as _
306308
});
307309
}
310+
#[cfg(feature = "shadowquic")]
311+
OutboundProxyProtocol::ShadowQuic(sqcfg) => {
312+
handlers.insert(sqcfg.common_opts.name.clone(), {
313+
let h: shadowquic::Handler = sqcfg.try_into()?;
314+
Arc::new(h) as _
315+
});
316+
}
308317
}
309318
}
310319

clash_lib/src/app/remote_content_manager/providers/proxy_provider/proxy_set_provider.rs

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -160,6 +160,12 @@ impl ProxySetProvider {
160160
let h: tuic::Handler = tuic.try_into()?;
161161
Ok(Arc::new(h) as _)
162162
}
163+
#[cfg(feature = "shadowquic")]
164+
OutboundProxyProtocol::ShadowQuic(sq) => {
165+
let h: crate::proxy::shadowquic::Handler =
166+
sq.try_into()?;
167+
Ok(Arc::new(h) as _)
168+
}
163169
})
164170
.collect::<Result<Vec<_>, crate::Error>>();
165171
Ok(proxies?)

clash_lib/src/config/internal/proxy.rs

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
11
use crate::{Error, common::utils::default_bool_true, config::utils};
22
use serde::{Deserialize, de::value::MapDeserializer};
33
use serde_yaml::Value;
4+
#[cfg(feature = "shadowquic")]
5+
use shadowquic::config::CongestionControl as SQCongestionControl;
46
use std::{
57
collections::HashMap,
68
fmt::{Display, Formatter},
@@ -73,6 +75,9 @@ pub enum OutboundProxyProtocol {
7375
#[serde(rename = "ssh")]
7476
#[cfg(feature = "ssh")]
7577
Ssh(OutBoundSsh),
78+
#[serde(rename = "shadowquic")]
79+
#[cfg(feature = "shadowquic")]
80+
ShadowQuic(OutboundShadowQuic),
7681
}
7782

7883
impl OutboundProxyProtocol {
@@ -95,6 +100,8 @@ impl OutboundProxyProtocol {
95100
OutboundProxyProtocol::Hysteria2(hysteria2) => &hysteria2.name,
96101
#[cfg(feature = "ssh")]
97102
OutboundProxyProtocol::Ssh(ssh) => &ssh.common_opts.name,
103+
#[cfg(feature = "shadowquic")]
104+
OutboundProxyProtocol::ShadowQuic(sq) => &sq.common_opts.name,
98105
}
99106
}
100107
}
@@ -133,6 +140,8 @@ impl Display for OutboundProxyProtocol {
133140
OutboundProxyProtocol::Hysteria2(_) => write!(f, "Hysteria2"),
134141
#[cfg(feature = "ssh")]
135142
OutboundProxyProtocol::Ssh(_) => write!(f, "Ssh"),
143+
#[cfg(feature = "shadowquic")]
144+
OutboundProxyProtocol::ShadowQuic(_) => write!(f, "ShadowQUIC"),
136145
}
137146
}
138147
}
@@ -292,6 +301,39 @@ pub struct OutboundTuic {
292301
pub receive_window: Option<u64>,
293302
}
294303

304+
#[cfg(feature = "shadowquic")]
305+
#[derive(serde::Serialize, serde::Deserialize, Debug, Default)]
306+
#[serde(rename_all = "kebab-case")]
307+
pub struct OutboundShadowQuic {
308+
#[serde(flatten)]
309+
pub common_opts: CommonConfigOptions,
310+
/// jls password, must be the same as the server
311+
pub jls_pwd: String,
312+
/// jls initial vector, must be the same as the server
313+
pub jls_iv: String,
314+
/// server name, must be the same as the server jls_upstream
315+
/// domain name
316+
pub server_name: String,
317+
/// alpn, default to "h3"
318+
pub alpn: Option<Vec<String>>,
319+
/// initial mtu, must be larger than min mtu, at least to be 1200.
320+
/// 1400 is recommended for high packet loss network. default to be 1300
321+
pub initial_mtu: Option<u16>,
322+
/// congestion control, default to "bbr"
323+
pub congestion_control: Option<SQCongestionControl>, // bbr, new-reno, cubic
324+
/// set to true to enable zero rtt, default to true
325+
pub zero_rtt: Option<bool>,
326+
/// if true, use quic stream to send UDP, otherwise use quic datagram
327+
/// extension, similar to native UDP in TUIC
328+
pub over_stream: Option<bool>,
329+
/// minimum mtu, must be smaller than initial mtu, at least to be 1200.
330+
/// 1400 is recommended for high packet loss network. default to be 1290
331+
pub min_mtu: Option<u16>,
332+
/// keep alive interval in milliseconds
333+
/// 0 means disable keep alive, should be smaller than 30_000(idle time)
334+
pub keep_alive_interval: Option<u32>,
335+
}
336+
295337
#[cfg(feature = "ssh")]
296338
#[derive(serde::Serialize, serde::Deserialize, Debug, Default)]
297339
#[serde(rename_all = "kebab-case")]

clash_lib/src/proxy/converters/mod.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,4 +12,6 @@ pub mod tuic;
1212
pub mod vmess;
1313
pub mod wireguard;
1414

15+
#[cfg(feature = "shadowquic")]
16+
pub mod shadowquic;
1517
mod utils;
Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,51 @@
1+
use shadowquic::config::{
2+
default_alpn, default_congestion_control, default_initial_mtu,
3+
default_keep_alive_interval, default_min_mtu, default_over_stream,
4+
default_zero_rtt,
5+
};
6+
7+
use crate::{
8+
config::proxy::OutboundShadowQuic,
9+
proxy::shadowquic::{Handler, HandlerOptions},
10+
session::SocksAddr,
11+
};
12+
13+
impl TryFrom<OutboundShadowQuic> for Handler {
14+
type Error = crate::Error;
15+
16+
fn try_from(value: OutboundShadowQuic) -> Result<Self, Self::Error> {
17+
(&value).try_into()
18+
}
19+
}
20+
21+
impl TryFrom<&OutboundShadowQuic> for Handler {
22+
type Error = crate::Error;
23+
24+
fn try_from(s: &OutboundShadowQuic) -> Result<Self, Self::Error> {
25+
Ok(Handler::new(
26+
s.common_opts.name.clone(),
27+
HandlerOptions {
28+
addr: SocksAddr::try_from((
29+
s.common_opts.server.clone(),
30+
s.common_opts.port,
31+
))?
32+
.to_string(),
33+
jls_pwd: s.jls_pwd.clone(),
34+
jls_iv: s.jls_iv.clone(),
35+
server_name: s.server_name.clone(),
36+
alpn: s.alpn.clone().unwrap_or(default_alpn()),
37+
initial_mtu: s.initial_mtu.unwrap_or(default_initial_mtu()),
38+
congestion_control: s
39+
.congestion_control
40+
.clone()
41+
.unwrap_or(default_congestion_control()),
42+
zero_rtt: s.zero_rtt.unwrap_or(default_zero_rtt()),
43+
over_stream: s.over_stream.unwrap_or(default_over_stream()),
44+
min_mtu: s.min_mtu.unwrap_or(default_min_mtu()),
45+
keep_alive_interval: s
46+
.keep_alive_interval
47+
.unwrap_or(default_keep_alive_interval()),
48+
},
49+
))
50+
}
51+
}

clash_lib/src/proxy/mod.rs

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,8 @@ pub(crate) mod datagram;
3636

3737
pub mod converters;
3838
pub mod hysteria2;
39+
#[cfg(feature = "shadowquic")]
40+
pub mod shadowquic;
3941
#[cfg(feature = "shadowsocks")]
4042
pub mod shadowsocks;
4143
pub mod socks;
@@ -124,6 +126,7 @@ pub enum OutboundType {
124126
Socks5,
125127
Hysteria2,
126128
Ssh,
129+
ShadowQuic,
127130

128131
#[serde(rename = "URLTest")]
129132
UrlTest,
@@ -148,6 +151,7 @@ impl Display for OutboundType {
148151
OutboundType::Socks5 => write!(f, "Socks5"),
149152
OutboundType::Hysteria2 => write!(f, "Hysteria2"),
150153
OutboundType::Ssh => write!(f, "ssh"),
154+
OutboundType::ShadowQuic => write!(f, "ShadowQuic"),
151155

152156
OutboundType::UrlTest => write!(f, "URLTest"),
153157
OutboundType::Selector => write!(f, "Selector"),

0 commit comments

Comments
 (0)