From 3232408c26827a2b3e2c831afdb6d996ae473c8e Mon Sep 17 00:00:00 2001 From: ihciah Date: Thu, 9 Mar 2023 00:55:52 +0800 Subject: [PATCH] feat: support wildcard sni --- Cargo.lock | 50 +++++++++++++++++++++++----------------------- Cargo.toml | 2 +- docker-compose.yml | 1 + entrypoint.sh | 5 +++++ src/lib.rs | 2 +- src/main.rs | 35 ++++++++++++++++++++++---------- src/server.rs | 46 ++++++++++++++++++++++++++++++++---------- src/util.rs | 16 +++++++++++++++ 8 files changed, 109 insertions(+), 48 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index d9f39dc..6b972e7 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -61,9 +61,9 @@ checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" [[package]] name = "clap" -version = "4.1.6" +version = "4.1.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ec0b0588d44d4d63a87dbd75c136c166bbfd9a86a31cb89e09906521c7d3f5e3" +checksum = "c3d7ae14b20b94cb02149ed21a86c423859cbe18dc7ed69845cace50e52b40a5" dependencies = [ "bitflags", "clap_derive", @@ -76,9 +76,9 @@ dependencies = [ [[package]] name = "clap_derive" -version = "4.1.0" +version = "4.1.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "684a277d672e91966334af371f1a7b5833f9aa00b07c84e92fbce95e00208ce8" +checksum = "44bec8e5c9d09e439c4335b1af0abaab56dcf3b94999a936e1bb47b9134288f0" dependencies = [ "heck", "proc-macro-error", @@ -89,9 +89,9 @@ dependencies = [ [[package]] name = "clap_lex" -version = "0.3.1" +version = "0.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "783fe232adfca04f90f56201b26d79682d4cd2625e0bc7290b95123afe558ade" +checksum = "350b9cf31731f9957399229e9b2adc51eeabdfbe9d71d9a0552275fd12710d09" dependencies = [ "os_str_bytes", ] @@ -240,9 +240,9 @@ dependencies = [ [[package]] name = "io-lifetimes" -version = "1.0.5" +version = "1.0.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1abeb7a0dd0f8181267ff8adc397075586500b81b28a73e8a0208b00fc170fb3" +checksum = "cfa919a82ea574332e2de6e74b4c36e74d41982b335080fa59d4ef31be20fdf3" dependencies = [ "libc", "windows-sys", @@ -250,9 +250,9 @@ dependencies = [ [[package]] name = "io-uring" -version = "0.5.12" +version = "0.5.13" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "41c85eff7f7c8d3ab8c7ec87313c0c194bbaf4371bb7d40f80293ba01bce8264" +checksum = "dd1e1a01cfb924fd8c5c43b6827965db394f5a3a16c599ce03452266e1cf984c" dependencies = [ "bitflags", "libc", @@ -260,9 +260,9 @@ dependencies = [ [[package]] name = "is-terminal" -version = "0.4.3" +version = "0.4.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "22e18b0a45d56fe973d6db23972bf5bc46f988a4a2385deac9cc29572f09daef" +checksum = "21b6b32576413a8e69b90e952e4a026476040d81017b80445deda5f2d3921857" dependencies = [ "hermit-abi", "io-lifetimes", @@ -580,9 +580,9 @@ checksum = "08d43f7aa6b08d49f382cde6a7982047c3426db949b1424bc4b7ec9ae12c6ce2" [[package]] name = "rustix" -version = "0.36.8" +version = "0.36.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f43abb88211988493c1abb44a70efa56ff0ce98f233b7b276146f1f3f7ba9644" +checksum = "fd5c6ff11fecd55b40746d1995a02f2eb375bf8c00d192d521ee09f42bef37bc" dependencies = [ "bitflags", "errno", @@ -644,7 +644,7 @@ dependencies = [ [[package]] name = "shadow-tls" -version = "0.2.18" +version = "0.2.19" dependencies = [ "anyhow", "byteorder", @@ -682,9 +682,9 @@ checksum = "a507befe795404456341dfab10cef66ead4c041f62b8b11bbb92bffe5d0953e0" [[package]] name = "socket2" -version = "0.4.7" +version = "0.4.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "02e2d2db9033d13a1567121ddd7a095ee144db4e1ca1b1bda3419bc0da294ebd" +checksum = "64a4a911eed85daf18834cfaa86a79b7d266ff93ff5ba14005426219480ed662" dependencies = [ "libc", "winapi", @@ -716,9 +716,9 @@ checksum = "6bdef32e8150c2a081110b42772ffe7d7c9032b606bc226c8260fd97e0976601" [[package]] name = "syn" -version = "1.0.107" +version = "1.0.109" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1f4064b5b16e03ae50984a5a8ed5d4f8803e6bc1fd170a3cda91a1be4b18e3f5" +checksum = "72b64191b275b66ffe2469e8af2c1cfe3bafa67b529ead792a6d0160888b4237" dependencies = [ "proc-macro2", "quote", @@ -736,18 +736,18 @@ dependencies = [ [[package]] name = "thiserror" -version = "1.0.38" +version = "1.0.39" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6a9cd18aa97d5c45c6603caea1da6628790b37f7a34b6ca89522331c5180fed0" +checksum = "a5ab016db510546d856297882807df8da66a16fb8c4101cb8b30054b0d5b2d9c" dependencies = [ "thiserror-impl", ] [[package]] name = "thiserror-impl" -version = "1.0.38" +version = "1.0.39" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1fb327af4685e4d03fa8cbcf1716380da910eeb2bb8be417e7f9fd3fb164f36f" +checksum = "5420d42e90af0c38c3290abcca25b9b3bdf379fc9f55c528f53a269d9c9a267e" dependencies = [ "proc-macro2", "quote", @@ -834,9 +834,9 @@ checksum = "497961ef93d974e23eb6f433eb5fe1b7930b659f06d12dec6fc44a8f554c0bba" [[package]] name = "unicode-ident" -version = "1.0.6" +version = "1.0.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "84a22b9f218b40614adcb3f4ff08b703773ad44fa9423e4e0d346d5db86e4ebc" +checksum = "e5464a87b239f13a63a501f2701565754bae92d243d4bb7eb12f6d57d2269bf4" [[package]] name = "untrusted" diff --git a/Cargo.toml b/Cargo.toml index 1aa3caa..362420e 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -7,7 +7,7 @@ license = "MIT/Apache-2.0" name = "shadow-tls" readme = "README.md" repository = "https://github.com/ihciah/shadow-tls" -version = "0.2.18" +version = "0.2.19" [dependencies] monoio = {version = "0.0.9"} diff --git a/docker-compose.yml b/docker-compose.yml index 196c22b..61eb462 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -22,6 +22,7 @@ services: # ALPN(optional): set alpns(like http/1.1, http/1.1;h2, recommend to leave it blank if you don't know it) # THREADS(optional): set threads number(recommend to leave it blank) # DISABLE_NODELAY(optional): disable TCP_NODELAY(recommend to leave it blank) +# WILDCARD_SNI: Use sni:443 as handshake server(off/authed/all) # Note: # Multiple SNIs is supported now. diff --git a/entrypoint.sh b/entrypoint.sh index 256f4c8..426eb68 100755 --- a/entrypoint.sh +++ b/entrypoint.sh @@ -28,6 +28,11 @@ then then parameter="$parameter --tls $TLS" fi + + if [ ! -z "$WILDCARD_SNI" ] + then + parameter="$parameter --wildcard-sni $WILDCARD_SNI" + fi fi if [ "$MODE" = "client" ] diff --git a/src/lib.rs b/src/lib.rs index aa09a04..227ed98 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -11,7 +11,7 @@ use std::{fmt::Display, thread::JoinHandle}; pub use crate::{ client::{ShadowTlsClient, TlsExtConfig, TlsNames}, server::{ShadowTlsServer, TlsAddrs}, - util::V3Mode, + util::{V3Mode, WildcardSNI}, }; pub enum RunningArgs { diff --git a/src/main.rs b/src/main.rs index 78977e5..3897f5e 100644 --- a/src/main.rs +++ b/src/main.rs @@ -2,11 +2,12 @@ use std::{collections::HashMap, process::exit}; -use clap::{Parser, Subcommand}; +use clap::{Parser, Subcommand, ValueEnum}; use tracing_subscriber::{filter::LevelFilter, fmt, prelude::*, EnvFilter}; use shadow_tls::{ sip003::parse_sip003_options, RunningArgs, TlsAddrs, TlsExtConfig, TlsNames, V3Mode, + WildcardSNI, }; #[derive(Parser, Debug)] @@ -86,6 +87,12 @@ enum Commands { tls_addr: TlsAddrs, #[clap(long = "password", help = "Password")] password: String, + #[clap( + long = "wildcard-sni", + default_value = "off", + help = "Use sni:443 as handshake server without predefining mapping(useful for bypass billing system like airplane wifi without modifying server config)" + )] + wildcard_sni: WildcardSNI, }, } @@ -124,16 +131,20 @@ impl From for RunningArgs { Commands::Server { listen, server_addr, - tls_addr, - password, - } => Self::Server { - listen_addr: listen, - target_addr: server_addr, - tls_addr, + mut tls_addr, password, - nodelay: !args.opts.disable_nodelay, - v3, - }, + wildcard_sni, + } => { + tls_addr.set_wildcard_sni(wildcard_sni); + Self::Server { + listen_addr: listen, + target_addr: server_addr, + tls_addr, + password, + nodelay: !args.opts.disable_nodelay, + v3, + } + } } } } @@ -189,12 +200,16 @@ pub(crate) fn get_sip003_arg() -> Option { .expect("tls param must be specified(like tls=xxx.com:443)"); let tls_addrs = parse_server_addrs(tls_addr) .expect("tls param parse failed(like tls=xxx.com:443 or tls=yyy.com:1.2.3.4:443;zzz.com:443;xxx.com)"); + let wildcard_sni = + WildcardSNI::from_str(opts.get("tls").map(AsRef::as_ref).unwrap_or_default(), true) + .expect("wildcard_sni format error"); Args { cmd: crate::Commands::Server { listen: format!("{ss_remote_host}:{ss_remote_port}"), server_addr: format!("{ss_local_host}:{ss_local_port}"), tls_addr: tls_addrs, password: passwd.to_owned(), + wildcard_sni, }, opts: args_opts, } diff --git a/src/server.rs b/src/server.rs index 095e163..f0fae66 100644 --- a/src/server.rs +++ b/src/server.rs @@ -27,6 +27,7 @@ use crate::{ bind_with_pretty_error, copy_bidirectional, copy_until_eof, kdf, mod_tcp_conn, prelude::*, support_tls13, verified_relay, xor_slice, CursorExt, Hmac, V3Mode, }, + WildcardSNI, }; /// ShadowTlsServer. @@ -44,19 +45,31 @@ pub struct ShadowTlsServer { pub struct TlsAddrs { dispatch: rustc_hash::FxHashMap, fallback: String, + wildcard_sni: WildcardSNI, } impl TlsAddrs { - fn find(&self, key: Option<&str>) -> &str { + fn find<'a>(&'a self, key: Option<&str>, auth: bool) -> Cow<'a, str> { match key { - Some(k) => self.dispatch.get(k).unwrap_or(&self.fallback), - None => &self.fallback, + Some(k) => match self.dispatch.get(k) { + Some(v) => Cow::Borrowed(v), + None => match self.wildcard_sni { + WildcardSNI::Authed if auth => Cow::Owned(format!("{k}:443")), + WildcardSNI::All => Cow::Owned(format!("{k}:443")), + _ => Cow::Borrowed(&self.fallback), + }, + }, + None => Cow::Borrowed(&self.fallback), } } fn is_empty(&self) -> bool { self.dispatch.is_empty() } + + pub fn set_wildcard_sni(&mut self, wildcard_sni: WildcardSNI) { + self.wildcard_sni = wildcard_sni; + } } impl TryFrom<&str> for TlsAddrs { @@ -103,7 +116,11 @@ impl TryFrom<&str> for TlsAddrs { bail!("duplicate server addrs part found"); } } - Ok(TlsAddrs { dispatch, fallback }) + Ok(TlsAddrs { + dispatch, + fallback, + wildcard_sni: Default::default(), + }) } } @@ -186,8 +203,10 @@ impl ShadowTlsServer { // choose handshake server addr and connect let server_name = server_name.and_then(|s| String::from_utf8(s).ok()); - let addr = self.tls_addr.find(server_name.as_ref().map(AsRef::as_ref)); - let mut out_stream = TcpStream::connect(addr).await?; + let addr = self + .tls_addr + .find(server_name.as_ref().map(AsRef::as_ref), true); + let mut out_stream = TcpStream::connect(addr.as_ref()).await?; mod_tcp_conn(&mut out_stream, true, self.nodelay); tracing::debug!("handshake server connected: {addr}"); @@ -247,8 +266,10 @@ impl ShadowTlsServer { // connect handshake server let server_name = sni.and_then(|s| String::from_utf8(s).ok()); - let addr = self.tls_addr.find(server_name.as_ref().map(AsRef::as_ref)); - let mut handshake_stream = TcpStream::connect(addr).await?; + let addr = self + .tls_addr + .find(server_name.as_ref().map(AsRef::as_ref), client_hello_pass); + let mut handshake_stream = TcpStream::connect(addr.as_ref()).await?; mod_tcp_conn(&mut handshake_stream, true, self.nodelay); tracing::debug!("handshake server connected: {addr}"); tracing::trace!("ClientHello frame {first_client_frame:?}"); @@ -887,7 +908,8 @@ mod tests { TlsAddrs::try_from("google.com").unwrap(), TlsAddrs { dispatch: map![], - fallback: s!("google.com:443") + fallback: s!("google.com:443"), + wildcard_sni: Default::default(), } ); assert_eq!( @@ -897,7 +919,8 @@ mod tests { "feishu.cn" => "feishu.cn:443", "cloudflare.com" => "1.1.1.1:80", ], - fallback: s!("google.com:443") + fallback: s!("google.com:443"), + wildcard_sni: Default::default(), } ); assert_eq!( @@ -906,7 +929,8 @@ mod tests { dispatch: map![ "captive.apple.com" => "captive.apple.com:443", ], - fallback: s!("feishu.cn:80") + fallback: s!("feishu.cn:80"), + wildcard_sni: Default::default(), } ); } diff --git a/src/util.rs b/src/util.rs index 61afc08..6b5bc1a 100644 --- a/src/util.rs +++ b/src/util.rs @@ -73,6 +73,22 @@ impl V3Mode { } } +#[derive(Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Debug, clap::ValueEnum)] +pub enum WildcardSNI { + /// Disabled + Off, + /// For authenticated client only(may be differentiable); in v2 protocol it is eq to all. + Authed, + /// For all request(may cause service abused but not differentiable) + All, +} + +impl Default for WildcardSNI { + fn default() -> Self { + Self::Off + } +} + pub(crate) async fn copy_until_eof(mut read_half: R, mut write_half: W) -> std::io::Result<()> where R: monoio::io::AsyncReadRent,