Skip to content

Commit e2095df

Browse files
feat: support ss v2ray plugin(without mux)
1 parent 7260c56 commit e2095df

File tree

4 files changed

+108
-36
lines changed

4 files changed

+108
-36
lines changed

clash_lib/src/proxy/converters/shadowsocks.rs

Lines changed: 7 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -126,6 +126,11 @@ impl TryFrom<HashMap<String, serde_yaml::Value>> for V2RayOBFSOption {
126126
.get("mode")
127127
.and_then(|x| x.as_str())
128128
.ok_or(Error::InvalidConfig("obfs mode is required".to_owned()))?;
129+
let port = value
130+
.get("port")
131+
.and_then(|x| x.as_u64())
132+
.ok_or(Error::InvalidConfig("obfs port is required".to_owned()))?
133+
as u16;
129134

130135
if mode != "websocket" {
131136
return Err(Error::InvalidConfig(format!(
@@ -134,10 +139,7 @@ impl TryFrom<HashMap<String, serde_yaml::Value>> for V2RayOBFSOption {
134139
)));
135140
}
136141

137-
let path = value
138-
.get("path")
139-
.and_then(|x| x.as_str())
140-
.ok_or(Error::InvalidConfig("obfs path is required".to_owned()))?;
142+
let path = value.get("path").and_then(|x| x.as_str()).unwrap_or("");
141143
let mux = value.get("mux").and_then(|x| x.as_bool()).unwrap_or(false);
142144
let tls = value.get("tls").and_then(|x| x.as_bool()).unwrap_or(false);
143145
let skip_cert_verify = value
@@ -159,6 +161,7 @@ impl TryFrom<HashMap<String, serde_yaml::Value>> for V2RayOBFSOption {
159161
Ok(V2RayOBFSOption {
160162
mode: mode.to_owned(),
161163
host: host.to_owned(),
164+
port,
162165
path: path.to_owned(),
163166
tls,
164167
headers,

clash_lib/src/proxy/shadowsocks/mod.rs

Lines changed: 13 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -31,8 +31,9 @@ use shadowsocks::{
3131
};
3232
use std::{collections::HashMap, fmt::Debug, io, sync::Arc};
3333
use tracing::debug;
34+
use v2ray::new_websocket_stream;
3435

35-
#[derive(Clone, Copy)]
36+
#[derive(Clone, Copy, Debug)]
3637
pub enum SimpleOBFSMode {
3738
Http,
3839
Tls,
@@ -47,14 +48,14 @@ pub struct SimpleOBFSOption {
4748
pub struct V2RayOBFSOption {
4849
pub mode: String,
4950
pub host: String,
51+
pub port: u16,
5052
pub path: String,
51-
pub tls: bool,
5253
pub headers: HashMap<String, String>,
54+
pub tls: bool,
5355
pub skip_cert_verify: bool,
5456
pub mux: bool,
5557
}
5658

57-
#[derive(Debug)]
5859
pub struct ShadowTlsOption {
5960
pub host: String,
6061
pub password: String,
@@ -106,7 +107,7 @@ impl Handler {
106107
&self,
107108
s: AnyStream,
108109
sess: &Session,
109-
#[allow(unused_variables)] _resolver: ThreadSafeDNSResolver,
110+
_resolver: ThreadSafeDNSResolver,
110111
) -> std::io::Result<AnyStream> {
111112
let stream: AnyStream = match &self.opts.plugin_opts {
112113
Some(plugin) => match plugin {
@@ -121,12 +122,16 @@ impl Handler {
121122
simple_obfs::SimpleObfsTLS::new(s, opts.host.clone()).into()
122123
}
123124
},
124-
OBFSOption::V2Ray(_opt) => {
125-
todo!("v2ray-plugin is not implemented yet")
125+
OBFSOption::V2Ray(opt) => {
126+
new_websocket_stream(
127+
s,
128+
self.opts.server.clone(),
129+
self.opts.port,
130+
opt,
131+
)
132+
.await?
126133
}
127134
OBFSOption::ShadowTls(opts) => {
128-
tracing::trace!("using shadow-tls");
129-
130135
(shadow_tls::Connector::wrap(opts, s).await?) as _
131136
}
132137
},
Lines changed: 64 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1 +1,65 @@
1+
use std::io;
2+
3+
use crate::proxy::{
4+
transport::{self, TLSOptions},
5+
AnyStream,
6+
};
7+
8+
use super::V2RayOBFSOption;
9+
110
pub(crate) mod mux;
11+
12+
pub async fn new_websocket_stream(
13+
mut stream: AnyStream,
14+
server: String,
15+
port: u16,
16+
opt: &V2RayOBFSOption,
17+
) -> std::io::Result<AnyStream> {
18+
// this shall already be checked in the config parser
19+
if opt.mode != "websocket" {
20+
return Err(io::Error::new(
21+
io::ErrorKind::Other,
22+
format!("invalid obfs mode: {}", opt.mode),
23+
));
24+
}
25+
26+
if opt.mux {
27+
return Err(io::Error::new(
28+
io::ErrorKind::Other,
29+
"v2ray plugin does not support mux",
30+
));
31+
}
32+
33+
let mut headers = opt.headers.clone();
34+
if !headers.contains_key("Host") {
35+
headers.insert("Host".to_owned(), opt.host.clone());
36+
}
37+
let ws_builder = transport::WebsocketStreamBuilder::new(
38+
server,
39+
port,
40+
if opt.path.is_empty() {
41+
"/".to_owned()
42+
} else {
43+
opt.path.clone()
44+
},
45+
headers,
46+
None,
47+
0,
48+
"".to_owned(),
49+
);
50+
51+
if opt.tls {
52+
stream = transport::tls::wrap_stream(
53+
stream,
54+
TLSOptions {
55+
sni: opt.host.clone(),
56+
skip_cert_verify: opt.skip_cert_verify,
57+
alpn: Some(vec!["http/1.1".to_owned()]),
58+
},
59+
None,
60+
)
61+
.await?;
62+
}
63+
64+
ws_builder.proxy_stream(stream).await
65+
}

clash_lib/src/proxy/vmess/mod.rs

Lines changed: 24 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -72,6 +72,28 @@ impl Handler {
7272
}
7373
}
7474

75+
/// v2ray may serve as plugin for other outbound handlers
76+
/// so we expose this method
77+
pub async fn proxy_stream(
78+
&self,
79+
sess: &Session,
80+
resolver: ThreadSafeDNSResolver,
81+
connector: &dyn RemoteConnector,
82+
) -> io::Result<AnyStream> {
83+
let stream = connector
84+
.connect_stream(
85+
resolver,
86+
self.opts.server.as_str(),
87+
self.opts.port,
88+
sess.iface.as_ref(),
89+
#[cfg(any(target_os = "linux", target_os = "android"))]
90+
sess.so_mark,
91+
)
92+
.await?;
93+
94+
self.inner_proxy_stream(stream, sess, false).await
95+
}
96+
7597
async fn inner_proxy_stream<'a>(
7698
&'a self,
7799
s: AnyStream,
@@ -242,18 +264,7 @@ impl OutboundHandler for Handler {
242264
resolver: ThreadSafeDNSResolver,
243265
connector: &dyn RemoteConnector,
244266
) -> io::Result<BoxedChainedStream> {
245-
let stream = connector
246-
.connect_stream(
247-
resolver,
248-
self.opts.server.as_str(),
249-
self.opts.port,
250-
sess.iface.as_ref(),
251-
#[cfg(any(target_os = "linux", target_os = "android"))]
252-
sess.so_mark,
253-
)
254-
.await?;
255-
256-
let s = self.inner_proxy_stream(stream, sess, false).await?;
267+
let s = self.proxy_stream(sess, resolver, connector).await?;
257268
let chained = ChainedStreamWrapper::new(s);
258269
chained.append_to_chain(self.name()).await;
259270
Ok(Box::new(chained))
@@ -265,18 +276,7 @@ impl OutboundHandler for Handler {
265276
resolver: ThreadSafeDNSResolver,
266277
connector: &dyn RemoteConnector,
267278
) -> io::Result<BoxedChainedDatagram> {
268-
let stream = connector
269-
.connect_stream(
270-
resolver,
271-
self.opts.server.as_str(),
272-
self.opts.port,
273-
sess.iface.as_ref(),
274-
#[cfg(any(target_os = "linux", target_os = "android"))]
275-
sess.so_mark,
276-
)
277-
.await?;
278-
279-
let stream = self.inner_proxy_stream(stream, sess, true).await?;
279+
let stream = self.proxy_stream(sess, resolver, connector).await?;
280280

281281
let d = OutboundDatagramVmess::new(stream, sess.destination.clone());
282282

0 commit comments

Comments
 (0)