diff --git a/Cargo.lock b/Cargo.lock index 3fb6ce8..22b8208 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -92,6 +92,17 @@ version = "1.0.75" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a4668cab20f66d8d020e1fbc0ebe47217433c1b6c8f2040faf858554e394ace6" +[[package]] +name = "async-trait" +version = "0.1.74" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a66537f1bb974b254c98ed142ff995236e81b9d0fe4db0575f46612cb15eb0f9" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + [[package]] name = "atomic" version = "0.5.3" @@ -267,6 +278,20 @@ dependencies = [ "crypto-common", ] +[[package]] +name = "fast-socks5" +version = "0.9.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "aa6a4a9ab1f87e2d8e9345ce944de7c45d065ee89f5942512f32698e48a8429a" +dependencies = [ + "anyhow", + "async-trait", + "log", + "thiserror", + "tokio", + "tokio-stream", +] + [[package]] name = "fastwebsockets" version = "0.4.4" @@ -467,12 +492,12 @@ dependencies = [ [[package]] name = "jsonwebtoken" -version = "8.3.0" +version = "9.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6971da4d9c3aa03c3d8f3ff0f4155b534aad021292003895a469716b2a230378" +checksum = "1e863f95209c79b9b8b001c4b03463385f890a765dbc4e0802cb8d4177e3e410" dependencies = [ "base64", - "ring", + "ring 0.17.4", "serde", "serde_json", ] @@ -779,12 +804,26 @@ dependencies = [ "cc", "libc", "once_cell", - "spin", - "untrusted", + "spin 0.5.2", + "untrusted 0.7.1", "web-sys", "winapi", ] +[[package]] +name = "ring" +version = "0.17.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fce3045ffa7c981a6ee93f640b538952e155f1ae3a1a02b84547fc7a56b7059a" +dependencies = [ + "cc", + "getrandom", + "libc", + "spin 0.9.8", + "untrusted 0.9.0", + "windows-sys", +] + [[package]] name = "rustc-demangle" version = "0.1.23" @@ -798,7 +837,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "cd8d6c9f025a446bc4d18ad9632e69aec8f287aa84499ee335599fabd20c3fd8" dependencies = [ "log", - "ring", + "ring 0.16.20", "rustls-webpki", "sct", ] @@ -830,8 +869,8 @@ version = "0.101.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3c7d5dece342910d9ba34d259310cae3e0154b873b35408b787b59bce53d34fe" dependencies = [ - "ring", - "untrusted", + "ring 0.16.20", + "untrusted 0.7.1", ] [[package]] @@ -861,8 +900,8 @@ version = "0.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d53dcdb7c9f8158937a7981b48accfd39a43af418591a5d008c7b22b5e1b7ca4" dependencies = [ - "ring", - "untrusted", + "ring 0.16.20", + "untrusted 0.7.1", ] [[package]] @@ -995,6 +1034,12 @@ version = "0.5.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6e63cff320ae2c57904679ba7cb63280a3dc4613885beafb148ee7bf9aa9042d" +[[package]] +name = "spin" +version = "0.9.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6980e8d7511241f8acf4aebddbb1ff938df5eebe98691418c4468d0b72a96a67" + [[package]] name = "strsim" version = "0.10.0" @@ -1257,6 +1302,12 @@ version = "0.7.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a156c684c91ea7d62626509bce3cb4e1d9ed5c4d978f7b4352658f96a4c26b4a" +[[package]] +name = "untrusted" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8ecb6da28b8a351d773b68d5825ac39017e680750f980f3a1a85cd8dd28a47c1" + [[package]] name = "url" version = "2.4.1" @@ -1478,11 +1529,13 @@ dependencies = [ "anyhow", "base64", "clap", + "fast-socks5", "fastwebsockets", "futures-util", "hyper", "jsonwebtoken", "libc", + "log", "once_cell", "pin-project", "rustls-native-certs", diff --git a/Cargo.toml b/Cargo.toml index e7ad84c..c99cdad 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -19,19 +19,21 @@ ahash = { version = "0.8.3", features = []} pin-project = "1" scopeguard = "1.2.0" uuid = { version = "1.4.1", features = ["v7", "serde"] } -jsonwebtoken = { version = "8.3.0", default-features = false } +jsonwebtoken = { version = "9.0.0", default-features = false } rustls-pemfile = { version = "1.0.3", features = [] } rustls-native-certs = { version = "0.6.3", features = [] } tokio = { version = "1.32.0", features = ["full"] } tokio-rustls = { version = "0.24.1", features = ["tls12", "dangerous_configuration", "early-data"] } tokio-stream = { version = "0.1.14", features = ["net"] } +fast-socks5 = { version = "0.9.1", features = [] } futures-util = { version = "0.3.28" } tracing = { version = "0.1.37", features = ["log"] } tracing-subscriber = { version = "0.3.17", features = ["env-filter", "fmt", "local-time"] } base64 = "0.21.4" serde = { version = "1.0.188", features = ["derive"] } +log = "0.4.20" [target.'cfg(target_family = "unix")'.dependencies] tokio-fd = "0.3.0" diff --git a/src/main.rs b/src/main.rs index 65e4ba2..d591492 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,4 +1,5 @@ mod embedded_certificate; +mod socks5; #[cfg(target_family = "unix")] mod stdio; mod tcp; diff --git a/src/socks5.rs b/src/socks5.rs new file mode 100644 index 0000000..5b39a4b --- /dev/null +++ b/src/socks5.rs @@ -0,0 +1,101 @@ +use anyhow::Context; +use fast_socks5::server::{Config, DenyAuthentication, Socks5Server}; +use fast_socks5::util::target_addr::TargetAddr; +use futures_util::{stream, Stream, StreamExt}; +use std::net::SocketAddr; +use std::pin::Pin; +use std::task::Poll; +use tokio::net::TcpStream; + +use log::warn; +use tracing::{info, warn}; +use url::Host; + +pub struct Socks5Listener { + stream: Pin>>>, +} + +impl Stream for Socks5Listener { + type Item = anyhow::Result<(TcpStream, Host, u16)>; + + fn poll_next( + self: Pin<&mut Self>, + cx: &mut std::task::Context<'_>, + ) -> Poll> { + unsafe { self.map_unchecked_mut(|x| &mut x.stream) }.poll_next(cx) + } +} + +pub async fn run_server(bind: SocketAddr) -> Result { + info!("Starting TCP server listening cnx on {}", bind); + + let server = Socks5Server::::bind(bind) + .await + .with_context(|| format!("Cannot create socks5 server {:?}", bind))?; + + let mut cfg = Config::::default(); + cfg.set_allow_no_auth(true); + cfg.set_dns_resolve(false); + cfg.set_execute_command(false); + + let server = server.with_config(cfg); + let stream = stream::unfold(server, move |server| async { + let mut acceptor = server.incoming(); + loop { + let cnx = match acceptor.next().await { + None => return None, + Some(Err(err)) => { + drop(acceptor); + return Some((Err(anyhow::Error::new(err)), server)); + } + Some(Ok(cnx)) => cnx, + }; + + let cnx = match cnx.upgrade_to_socks5().await { + Ok(cnx) => cnx, + Err(err) => { + warn!("Rejecting socks5 cnx: {}", err); + continue; + } + }; + + let Some(target) = cnx.target_addr() else { + warn!("Rejecting socks5 cnx: no target addr"); + continue; + }; + + let (host, port) = match target { + TargetAddr::Ip(SocketAddr::V4(ip)) => (Host::Ipv4(*ip.ip()), ip.port()), + TargetAddr::Ip(SocketAddr::V6(ip)) => (Host::Ipv6(*ip.ip()), ip.port()), + TargetAddr::Domain(host, port) => (Host::Domain(host.clone()), *port), + }; + drop(acceptor); + return Some((Ok((cnx.into_inner(), host, port)), server)); + } + }); + + let listener = Socks5Listener { + stream: Box::pin(stream), + }; + + Ok(listener) +} + +#[cfg(test)] +mod test { + use super::*; + use futures_util::StreamExt; + use std::str::FromStr; + + #[tokio::test] + async fn socks5_server() { + let mut x = run_server(SocketAddr::from_str("[::]:4343").unwrap()) + .await + .unwrap(); + + loop { + let cnx = x.next().await.unwrap().unwrap(); + eprintln!("{:?}", cnx); + } + } +} diff --git a/src/transport.rs b/src/transport.rs index ff07bd6..072e4d6 100644 --- a/src/transport.rs +++ b/src/transport.rs @@ -10,7 +10,7 @@ use anyhow::Context; use fastwebsockets::{ Frame, OpCode, Payload, WebSocket, WebSocketError, WebSocketRead, WebSocketWrite, }; -use futures_util::{pin_mut}; +use futures_util::pin_mut; use hyper::header::{AUTHORIZATION, SEC_WEBSOCKET_VERSION, UPGRADE}; use hyper::header::{CONNECTION, HOST, SEC_WEBSOCKET_KEY}; use hyper::server::conn::Http;