Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
450 changes: 332 additions & 118 deletions Cargo.lock

Large diffs are not rendered by default.

3 changes: 2 additions & 1 deletion clash/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ authors = { workspace = true }

[features]
default = ["standard"]
standard = ["shadowsocks", "tuic", "ssh", "clash_lib/zero_copy"]
standard = ["shadowsocks", "tuic", "ssh", "clash_lib/zero_copy", "shadowquic"]
plus = ["standard", "onion"]
perf = ["plus", "jemallocator"]

Expand All @@ -19,6 +19,7 @@ shadowsocks = ["clash_lib/shadowsocks"]
ssh = ["clash_lib/ssh"]
tuic = ["clash_lib/tuic"]
onion = ["clash_lib/onion"]
shadowquic = ["clash_lib/shadowquic"]

bench = ["clash_lib/bench"]
dhat-heap = ["dep:dhat"]
Expand Down
12 changes: 12 additions & 0 deletions clash/tests/data/config/shadowquic.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
inbound:
type: shadowquic
bind-addr: 0.0.0.0:10002
jls-pwd: "12345678"
jls-iv: "87654321"
jls-upstream: "echo.free.beeceptor.com:443" # domain + port, domain must be the same as client
alpn: ["h3"]
congestion-control: bbr
zero-rtt: true
outbound:
type: direct
log-level: "trace"
4 changes: 4 additions & 0 deletions clash_lib/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ shadowsocks = ["dep:shadowsocks"]
tuic = ["dep:tuic", "dep:tuic-quinn", "dep:register-count"]
ssh = ["dep:russh", "dep:dirs", "dep:totp-rs"]
onion = ["dep:arti-client", "dep:tor-rtcompat", "arti-client/onion-service-client"]
shadowquic = ["dep:shadowquic"]

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

# shadowquic
shadowquic = { version = "0.1.6", optional = true, git = "https://github.com/spongebob888/shadowquic" }

# experimental
downcast-rs = "2.0"

Expand Down
9 changes: 9 additions & 0 deletions clash_lib/src/app/outbound/manager.rs
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,8 @@ use crate::{

use super::utils::proxy_groups_dag_sort;

#[cfg(feature = "shadowquic")]
use crate::proxy::shadowquic;
#[cfg(feature = "shadowsocks")]
use crate::proxy::shadowsocks;
#[cfg(feature = "ssh")]
Expand Down Expand Up @@ -305,6 +307,13 @@ impl OutboundManager {
Arc::new(h) as _
});
}
#[cfg(feature = "shadowquic")]
OutboundProxyProtocol::ShadowQuic(sqcfg) => {
handlers.insert(sqcfg.common_opts.name.clone(), {
let h: shadowquic::Handler = sqcfg.try_into()?;
Arc::new(h) as _
});
}
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -160,6 +160,12 @@ impl ProxySetProvider {
let h: tuic::Handler = tuic.try_into()?;
Ok(Arc::new(h) as _)
}
#[cfg(feature = "shadowquic")]
OutboundProxyProtocol::ShadowQuic(sq) => {
let h: crate::proxy::shadowquic::Handler =
sq.try_into()?;
Ok(Arc::new(h) as _)
}
})
.collect::<Result<Vec<_>, crate::Error>>();
Ok(proxies?)
Expand Down
42 changes: 42 additions & 0 deletions clash_lib/src/config/internal/proxy.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
use crate::{Error, common::utils::default_bool_true, config::utils};
use serde::{Deserialize, de::value::MapDeserializer};
use serde_yaml::Value;
#[cfg(feature = "shadowquic")]
use shadowquic::config::CongestionControl as SQCongestionControl;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

you may need to gate the feature here to pass the build

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done in latest commit

use std::{
collections::HashMap,
fmt::{Display, Formatter},
Expand Down Expand Up @@ -73,6 +75,9 @@ pub enum OutboundProxyProtocol {
#[serde(rename = "ssh")]
#[cfg(feature = "ssh")]
Ssh(OutBoundSsh),
#[serde(rename = "shadowquic")]
#[cfg(feature = "shadowquic")]
ShadowQuic(OutboundShadowQuic),
}

impl OutboundProxyProtocol {
Expand All @@ -95,6 +100,8 @@ impl OutboundProxyProtocol {
OutboundProxyProtocol::Hysteria2(hysteria2) => &hysteria2.name,
#[cfg(feature = "ssh")]
OutboundProxyProtocol::Ssh(ssh) => &ssh.common_opts.name,
#[cfg(feature = "shadowquic")]
OutboundProxyProtocol::ShadowQuic(sq) => &sq.common_opts.name,
}
}
}
Expand Down Expand Up @@ -133,6 +140,8 @@ impl Display for OutboundProxyProtocol {
OutboundProxyProtocol::Hysteria2(_) => write!(f, "Hysteria2"),
#[cfg(feature = "ssh")]
OutboundProxyProtocol::Ssh(_) => write!(f, "Ssh"),
#[cfg(feature = "shadowquic")]
OutboundProxyProtocol::ShadowQuic(_) => write!(f, "ShadowQUIC"),
}
}
}
Expand Down Expand Up @@ -292,6 +301,39 @@ pub struct OutboundTuic {
pub receive_window: Option<u64>,
}

#[cfg(feature = "shadowquic")]
#[derive(serde::Serialize, serde::Deserialize, Debug, Default)]
#[serde(rename_all = "kebab-case")]
pub struct OutboundShadowQuic {
#[serde(flatten)]
pub common_opts: CommonConfigOptions,
/// jls password, must be the same as the server
pub jls_pwd: String,
/// jls initial vector, must be the same as the server
pub jls_iv: String,
/// server name, must be the same as the server jls_upstream
/// domain name
pub server_name: String,
/// alpn, default to "h3"
pub alpn: Option<Vec<String>>,
/// initial mtu, must be larger than min mtu, at least to be 1200.
/// 1400 is recommended for high packet loss network. default to be 1300
pub initial_mtu: Option<u16>,
/// congestion control, default to "bbr"
pub congestion_control: Option<SQCongestionControl>, // bbr, new-reno, cubic
/// set to true to enable zero rtt, default to true
pub zero_rtt: Option<bool>,
/// if true, use quic stream to send UDP, otherwise use quic datagram
/// extension, similar to native UDP in TUIC
pub over_stream: Option<bool>,
/// minimum mtu, must be smaller than initial mtu, at least to be 1200.
/// 1400 is recommended for high packet loss network. default to be 1290
pub min_mtu: Option<u16>,
/// keep alive interval in milliseconds
/// 0 means disable keep alive, should be smaller than 30_000(idle time)
pub keep_alive_interval: Option<u32>,
}

#[cfg(feature = "ssh")]
#[derive(serde::Serialize, serde::Deserialize, Debug, Default)]
#[serde(rename_all = "kebab-case")]
Expand Down
2 changes: 2 additions & 0 deletions clash_lib/src/proxy/converters/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -12,4 +12,6 @@ pub mod tuic;
pub mod vmess;
pub mod wireguard;

#[cfg(feature = "shadowquic")]
pub mod shadowquic;
mod utils;
51 changes: 51 additions & 0 deletions clash_lib/src/proxy/converters/shadowquic.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
use shadowquic::config::{
default_alpn, default_congestion_control, default_initial_mtu,
default_keep_alive_interval, default_min_mtu, default_over_stream,
default_zero_rtt,
};

use crate::{
config::proxy::OutboundShadowQuic,
proxy::shadowquic::{Handler, HandlerOptions},
session::SocksAddr,
};

impl TryFrom<OutboundShadowQuic> for Handler {
type Error = crate::Error;

fn try_from(value: OutboundShadowQuic) -> Result<Self, Self::Error> {
(&value).try_into()
}
}

impl TryFrom<&OutboundShadowQuic> for Handler {
type Error = crate::Error;

fn try_from(s: &OutboundShadowQuic) -> Result<Self, Self::Error> {
Ok(Handler::new(
s.common_opts.name.clone(),
HandlerOptions {
addr: SocksAddr::try_from((
s.common_opts.server.clone(),
s.common_opts.port,
))?
.to_string(),
jls_pwd: s.jls_pwd.clone(),
jls_iv: s.jls_iv.clone(),
server_name: s.server_name.clone(),
alpn: s.alpn.clone().unwrap_or(default_alpn()),
initial_mtu: s.initial_mtu.unwrap_or(default_initial_mtu()),
congestion_control: s
.congestion_control
.clone()
.unwrap_or(default_congestion_control()),
zero_rtt: s.zero_rtt.unwrap_or(default_zero_rtt()),
over_stream: s.over_stream.unwrap_or(default_over_stream()),
min_mtu: s.min_mtu.unwrap_or(default_min_mtu()),
keep_alive_interval: s
.keep_alive_interval
.unwrap_or(default_keep_alive_interval()),
},
))
}
}
4 changes: 4 additions & 0 deletions clash_lib/src/proxy/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,8 @@ pub(crate) mod datagram;

pub mod converters;
pub mod hysteria2;
#[cfg(feature = "shadowquic")]
pub mod shadowquic;
#[cfg(feature = "shadowsocks")]
pub mod shadowsocks;
pub mod socks;
Expand Down Expand Up @@ -124,6 +126,7 @@ pub enum OutboundType {
Socks5,
Hysteria2,
Ssh,
ShadowQuic,

#[serde(rename = "URLTest")]
UrlTest,
Expand All @@ -148,6 +151,7 @@ impl Display for OutboundType {
OutboundType::Socks5 => write!(f, "Socks5"),
OutboundType::Hysteria2 => write!(f, "Hysteria2"),
OutboundType::Ssh => write!(f, "ssh"),
OutboundType::ShadowQuic => write!(f, "ShadowQuic"),

OutboundType::UrlTest => write!(f, "URLTest"),
OutboundType::Selector => write!(f, "Selector"),
Expand Down
72 changes: 72 additions & 0 deletions clash_lib/src/proxy/shadowquic/compat.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
use std::io;

use bytes::Bytes;
use futures::{Sink, SinkExt, Stream};
use shadowquic::msgs::socks5::SocksAddr as SQAddr;
use tokio::sync::mpsc::Receiver;
use tokio_util::sync::PollSender;

use crate::{
common::errors::new_io_error, proxy::datagram::UdpPacket, session::SocksAddr,
};

use super::{to_clash_socks_addr, to_sq_socks_addr};

pub struct UdpSessionWrapper {
pub s: PollSender<(Bytes, SQAddr)>,
pub r: Receiver<(Bytes, SQAddr)>,
pub src_addr: SocksAddr, /* source addres of local socket, binded during
* associate task
* started */
}
impl Sink<UdpPacket> for UdpSessionWrapper {
type Error = io::Error;

fn poll_ready(
self: std::pin::Pin<&mut Self>,
cx: &mut std::task::Context<'_>,
) -> std::task::Poll<Result<(), Self::Error>> {
self.get_mut().s.poll_ready_unpin(cx).map_err(new_io_error)
}

fn start_send(
self: std::pin::Pin<&mut Self>,
item: UdpPacket,
) -> Result<(), Self::Error> {
self.get_mut()
.s
.start_send_unpin((item.data.into(), to_sq_socks_addr(item.dst_addr)))
.map_err(new_io_error)
}

fn poll_flush(
self: std::pin::Pin<&mut Self>,
cx: &mut std::task::Context<'_>,
) -> std::task::Poll<Result<(), Self::Error>> {
self.get_mut().s.poll_flush_unpin(cx).map_err(new_io_error)
}

fn poll_close(
self: std::pin::Pin<&mut Self>,
cx: &mut std::task::Context<'_>,
) -> std::task::Poll<Result<(), Self::Error>> {
self.get_mut().s.poll_close_unpin(cx).map_err(new_io_error)
}
}

impl Stream for UdpSessionWrapper {
type Item = UdpPacket;

fn poll_next(
mut self: std::pin::Pin<&mut Self>,
cx: &mut std::task::Context<'_>,
) -> std::task::Poll<Option<Self::Item>> {
self.r.poll_recv(cx).map(|x| {
x.map(|x| UdpPacket {
data: x.0.into(),
src_addr: self.src_addr.clone(),
dst_addr: to_clash_socks_addr(x.1),
})
})
}
}
Loading
Loading