diff --git a/protocol/obfs/Cargo.toml b/protocol/obfs/Cargo.toml index 92ad28e6..233dcaab 100644 --- a/protocol/obfs/Cargo.toml +++ b/protocol/obfs/Cargo.toml @@ -12,3 +12,4 @@ tokio = "1.0" pin-project-lite = "0.2.6" futures = "0.3" rand = "0.8.3" +base64 = "0.13.0" diff --git a/protocol/obfs/src/http_simple.rs b/protocol/obfs/src/http_simple.rs index 13d035b2..7bfc1421 100644 --- a/protocol/obfs/src/http_simple.rs +++ b/protocol/obfs/src/http_simple.rs @@ -15,10 +15,22 @@ use rd_interface::{ }; use tokio::io::AsyncRead; +fn def_method() -> String { + "GET".to_string() +} + +fn def_uri() -> String { + "/".to_string() +} + #[rd_config] -#[derive(Debug)] +#[derive(Debug, Clone)] pub struct HttpSimple { - obfs_param: String, + #[serde(default = "def_method")] + method: String, + #[serde(default = "def_uri")] + uri: String, + host: String, } impl Obfs for HttpSimple { @@ -28,7 +40,7 @@ impl Obfs for HttpSimple { _ctx: &mut rd_interface::Context, _addr: &Address, ) -> Result { - Ok(Connect::new(tcp, &self.obfs_param).into_dyn()) + Ok(Connect::new(tcp, self.clone()).into_dyn()) } fn tcp_accept(&self, _tcp: TcpStream, _addr: std::net::SocketAddr) -> Result { @@ -53,17 +65,17 @@ pin_project! { inner: TcpStream, write: WriteState, read: ReadState, - obfs_param: String, + param: HttpSimple, } } impl Connect { - fn new(tcp: TcpStream, param: &str) -> Connect { + fn new(tcp: TcpStream, param: HttpSimple) -> Connect { Connect { inner: tcp, write: WriteState::Wait, read: ReadState::Read(vec![0u8; 8192], 0), - obfs_param: param.to_string(), + param, } } } @@ -128,17 +140,28 @@ impl ITcpStream for Connect { loop { match &mut self.write { WriteState::Wait => { - let head_len = thread_rng().gen_range(0..64usize).min(buf.len()); - let head = &buf[..head_len]; - let body = &buf[head_len..]; + let major = thread_rng().next_u32() % 51; + let minor = thread_rng().next_u32() % 2; + + let key_bytes: [u8; 16] = thread_rng().gen(); + let key = base64::encode(key_bytes); let mut cursor = Cursor::new(Vec::::with_capacity(1024)); cursor.write_fmt(format_args!( - "GET /{path} HTTP/1.1\r\nHost: {host}\r\n\r\n", - path = UrlEncode(head), - host = self.obfs_param + "{method} {path} HTTP/1.1\r +Host: {host}\r +User-Agent: curl/7.{major}.{minor}\r +Upgrade: websocket\r +Connection: Upgrade\r +Sec-WebSocket-Key: {key}\r +Content-Length: {len}\r +\r\n", + method = self.param.method, + path = self.param.uri, + host = self.param.host, + len = buf.len(), ))?; - cursor.write_all(body)?; + cursor.write_all(buf)?; let buf = cursor.into_inner(); diff --git a/protocol/obfs/src/lib.rs b/protocol/obfs/src/lib.rs index 04c6f012..4d6d96b3 100644 --- a/protocol/obfs/src/lib.rs +++ b/protocol/obfs/src/lib.rs @@ -35,29 +35,23 @@ pub trait Obfs { #[rd_config] #[derive(Debug)] -#[serde(rename_all = "snake_case", tag = "obfs_type")] +#[serde(rename_all = "snake_case")] pub enum ObfsType { - HttpSimple(http_simple::HttpSimple), + Http(http_simple::HttpSimple), Plain(plain::Plain), } -impl Default for ObfsType { - fn default() -> Self { - ObfsType::Plain(plain::Plain) - } -} - impl Obfs for ObfsType { fn tcp_connect(&self, tcp: TcpStream, ctx: &mut Context, addr: &Address) -> Result { match self { - ObfsType::HttpSimple(i) => i.tcp_connect(tcp, ctx, addr), + ObfsType::Http(i) => i.tcp_connect(tcp, ctx, addr), ObfsType::Plain(i) => i.tcp_connect(tcp, ctx, addr), } } fn tcp_accept(&self, tcp: TcpStream, addr: SocketAddr) -> Result { match self { - ObfsType::HttpSimple(i) => i.tcp_accept(tcp, addr), + ObfsType::Http(i) => i.tcp_accept(tcp, addr), ObfsType::Plain(i) => i.tcp_accept(tcp, addr), } } diff --git a/protocol/obfs/src/obfs_net.rs b/protocol/obfs/src/obfs_net.rs index 67bda589..cb69eff0 100644 --- a/protocol/obfs/src/obfs_net.rs +++ b/protocol/obfs/src/obfs_net.rs @@ -13,7 +13,7 @@ type BoxObfs = Arc; pub struct ObfsNetConfig { #[serde(default)] pub net: NetRef, - #[serde(default, flatten)] + #[serde(flatten)] pub obfs_type: ObfsType, } diff --git a/src/config/importer/clash.rs b/src/config/importer/clash.rs index 01ceac32..3032e899 100644 --- a/src/config/importer/clash.rs +++ b/src/config/importer/clash.rs @@ -133,21 +133,15 @@ impl Clash { if let (Some(plugin), Some(plugin_opts)) = (params.plugin, params.plugin_opts) { if plugin == "obfs" { - // only http is supported - if let Some("http") = plugin_opts.get("mode").map(AsRef::as_ref) { - } else { - return Err(anyhow!("obfs only support http")); - } - // TODO: support other modes - let mode_param = plugin_opts - .get("host") + let obfs_mode = plugin_opts + .get("mode") .map(|i| i.to_string()) .unwrap_or_default(); + let obfs_net = Net::new( "obfs", json!({ - "obfs_type": "http_simple", - "obfs_param": mode_param, + obfs_mode: plugin_opts, }), );