diff --git a/src/main.rs b/src/main.rs index a85c09d..a2fd2d5 100644 --- a/src/main.rs +++ b/src/main.rs @@ -106,6 +106,7 @@ struct Client { /// 'udp://1212:1.1.1.1:53?timeout_sec=10' timeout_sec on udp force close the tunnel after 10sec. Set it to 0 to disable the timeout [default: 30] /// /// 'socks5://[::1]:1212' => listen locally with socks5 on port 1212 and forward dynamically requested tunnel + /// 'socks5://[::1]:1212?login=admin&password=admin' => listen locally with socks5 on port 1212 and only accept connection with login=admin and password=admin /// /// 'tproxy+tcp://[::1]:1212' => listen locally on tcp on port 1212 as a *transparent proxy* and forward dynamically requested tunnel /// 'tproxy+udp://[::1]:1212?timeout_sec=10' listen locally on udp on port 1212 as a *transparent proxy* and forward dynamically requested tunnel @@ -121,7 +122,7 @@ struct Client { /// examples: /// 'tcp://1212:google.com:443' => listen on server for incoming tcp cnx on port 1212 and forward to google.com on port 443 from local machine /// 'udp://1212:1.1.1.1:53' => listen on server for incoming udp on port 1212 and forward to cloudflare dns 1.1.1.1 on port 53 from local machine - /// 'socks5://[::1]:1212' => listen on server for incoming socks5 request on port 1212 and forward dynamically request from local machine + /// 'socks5://[::1]:1212' => listen on server for incoming socks5 request on port 1212 and forward dynamically request from local machine (login/password is supported) /// 'unix://wstunnel.sock:g.com:443' => listen on server for incoming data from unix socket of path wstunnel.sock and forward to g.com:443 from local machine #[arg(short='R', long, value_name = "{tcp,udp,socks5,unix}://[BIND:]PORT:HOST:PORT", value_parser = parse_tunnel_arg, verbatim_doc_comment)] remote_to_local: Vec, @@ -342,22 +343,40 @@ struct Server { #[derive(Clone, Debug, Eq, PartialEq, Serialize, Deserialize)] enum LocalProtocol { - Tcp { proxy_protocol: bool }, - Udp { timeout: Option }, + Tcp { + proxy_protocol: bool, + }, + Udp { + timeout: Option, + }, Stdio, - Socks5 { timeout: Option }, + Socks5 { + timeout: Option, + credentials: Option<(String, String)>, + }, TProxyTcp, - TProxyUdp { timeout: Option }, + TProxyUdp { + timeout: Option, + }, ReverseTcp, - ReverseUdp { timeout: Option }, - ReverseSocks5, - ReverseUnix { path: PathBuf }, - Unix { path: PathBuf }, + ReverseUdp { + timeout: Option, + }, + ReverseSocks5 { + timeout: Option, + credentials: Option<(String, String)>, + }, + ReverseUnix { + path: PathBuf, + }, + Unix { + path: PathBuf, + }, } impl LocalProtocol { pub const fn is_reverse_tunnel(&self) -> bool { - matches!(self, Self::ReverseTcp | Self::ReverseUdp { .. } | Self::ReverseSocks5) + matches!(self, Self::ReverseTcp | Self::ReverseUdp { .. } | Self::ReverseSocks5 { .. }) } } @@ -506,8 +525,11 @@ fn parse_tunnel_arg(arg: &str) -> Result { .and_then(|x| x.parse::().ok()) .map(|d| if d == 0 { None } else { Some(Duration::from_secs(d)) }) .unwrap_or(Some(Duration::from_secs(30))); + let credentials = options + .get("login") + .and_then(|login| options.get("password").map(|p| (login.to_string(), p.to_string()))); Ok(LocalToRemote { - local_protocol: LocalProtocol::Socks5 { timeout }, + local_protocol: LocalProtocol::Socks5 { timeout, credentials }, local: local_bind, remote: (dest_host, dest_port), }) @@ -953,16 +975,18 @@ async fn main() { } }); } - LocalProtocol::Socks5 { .. } => { + LocalProtocol::Socks5 { timeout, credentials } => { trait T: AsyncWrite + AsyncRead + Unpin + Send {} impl T for TcpStream {} impl T for MyUdpSocket {} + let credentials = credentials.clone(); + let timeout = *timeout; tokio::spawn(async move { let cfg = client_config.clone(); let (host, port) = to_host_port(tunnel.local); let remote = RemoteAddr { - protocol: LocalProtocol::ReverseSocks5, + protocol: LocalProtocol::ReverseSocks5 { timeout, credentials }, host, port, }; @@ -1037,7 +1061,7 @@ async fn main() { | LocalProtocol::TProxyUdp { .. } | LocalProtocol::ReverseTcp | LocalProtocol::ReverseUdp { .. } - | LocalProtocol::ReverseSocks5 + | LocalProtocol::ReverseSocks5 { .. } | LocalProtocol::ReverseUnix { .. } => { panic!("Invalid protocol for reverse tunnel"); } @@ -1175,8 +1199,8 @@ async fn main() { } }); } - LocalProtocol::Socks5 { timeout } => { - let server = socks5::run_server(tunnel.local, *timeout) + LocalProtocol::Socks5 { timeout, credentials } => { + let server = socks5::run_server(tunnel.local, *timeout, credentials.clone()) .await .unwrap_or_else(|err| panic!("Cannot start Socks5 server on {}: {}", tunnel.local, err)) .map_ok(|(stream, (host, port))| { @@ -1228,7 +1252,7 @@ async fn main() { } LocalProtocol::ReverseTcp => {} LocalProtocol::ReverseUdp { .. } => {} - LocalProtocol::ReverseSocks5 => {} + LocalProtocol::ReverseSocks5 { .. } => {} LocalProtocol::ReverseUnix { .. } => {} } } diff --git a/src/restrictions/types.rs b/src/restrictions/types.rs index ba01c45..28a41d0 100644 --- a/src/restrictions/types.rs +++ b/src/restrictions/types.rs @@ -163,7 +163,7 @@ impl From<&LocalProtocol> for ReverseTunnelConfigProtocol { | LocalProtocol::Unix { .. } => Self::Unknown, LocalProtocol::ReverseTcp => Self::Tcp, LocalProtocol::ReverseUdp { .. } => Self::Udp, - LocalProtocol::ReverseSocks5 => Self::Socks5, + LocalProtocol::ReverseSocks5 { .. } => Self::Socks5, LocalProtocol::ReverseUnix { .. } => Self::Unix, } } @@ -173,7 +173,7 @@ impl From<&LocalProtocol> for TunnelConfigProtocol { match value { LocalProtocol::ReverseTcp | LocalProtocol::ReverseUdp { .. } - | LocalProtocol::ReverseSocks5 + | LocalProtocol::ReverseSocks5 { .. } | LocalProtocol::ReverseUnix { .. } | LocalProtocol::Stdio | LocalProtocol::Socks5 { .. } diff --git a/src/socks5.rs b/src/socks5.rs index 8084548..084d7eb 100644 --- a/src/socks5.rs +++ b/src/socks5.rs @@ -1,7 +1,7 @@ use crate::socks5_udp::Socks5UdpStream; use crate::{socks5_udp, LocalProtocol}; use anyhow::Context; -use fast_socks5::server::{Config, DenyAuthentication, Socks5Server}; +use fast_socks5::server::{Config, DenyAuthentication, SimpleUserPassword, Socks5Server}; use fast_socks5::util::target_addr::TargetAddr; use fast_socks5::{consts, ReplyError}; use futures_util::{stream, Stream, StreamExt}; @@ -45,15 +45,29 @@ impl Stream for Socks5Listener { } } -pub async fn run_server(bind: SocketAddr, timeout: Option) -> Result { - info!("Starting SOCKS5 server listening cnx on {}", bind); +pub async fn run_server( + bind: SocketAddr, + timeout: Option, + credentials: Option<(String, String)>, +) -> Result { + info!( + "Starting SOCKS5 server listening cnx on {} with credentials {:?}", + bind, credentials + ); 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); + let mut cfg = Config::default(); + cfg = if let Some((username, password)) = credentials { + cfg.set_allow_no_auth(false); + cfg.with_authentication(SimpleUserPassword { username, password }) + } else { + cfg.set_allow_no_auth(true); + cfg + }; + cfg.set_dns_resolve(false); cfg.set_execute_command(false); cfg.set_udp_support(true); diff --git a/src/tunnel/mod.rs b/src/tunnel/mod.rs index f72c97b..a044160 100644 --- a/src/tunnel/mod.rs +++ b/src/tunnel/mod.rs @@ -43,7 +43,7 @@ impl JwtTunnelConfig { LocalProtocol::Socks5 { .. } => LocalProtocol::Tcp { proxy_protocol: false }, LocalProtocol::ReverseTcp => LocalProtocol::ReverseTcp, LocalProtocol::ReverseUdp { .. } => dest.protocol.clone(), - LocalProtocol::ReverseSocks5 => LocalProtocol::ReverseSocks5, + LocalProtocol::ReverseSocks5 { .. } => dest.protocol.clone(), LocalProtocol::TProxyTcp => LocalProtocol::Tcp { proxy_protocol: false }, LocalProtocol::TProxyUdp { timeout } => LocalProtocol::Udp { timeout }, LocalProtocol::Unix { .. } => LocalProtocol::Tcp { proxy_protocol: false }, diff --git a/src/tunnel/server.rs b/src/tunnel/server.rs index e17b40d..9fde71c 100644 --- a/src/tunnel/server.rs +++ b/src/tunnel/server.rs @@ -129,7 +129,7 @@ async fn run_tunnel( }; Ok((remote, Box::pin(local_rx), Box::pin(local_tx))) } - LocalProtocol::ReverseSocks5 => { + LocalProtocol::ReverseSocks5 { timeout, credentials } => { #[allow(clippy::type_complexity)] static SERVERS: Lazy, u16), mpsc::Receiver<(Socks5Stream, (Host, u16))>>>> = Lazy::new(|| Mutex::new(HashMap::with_capacity(0))); @@ -137,7 +137,7 @@ async fn run_tunnel( let remote_port = find_mapped_port(remote.port, restriction); let local_srv = (remote.host, remote_port); let bind = format!("{}:{}", local_srv.0, local_srv.1); - let listening_server = socks5::run_server(bind.parse()?, None); + let listening_server = socks5::run_server(bind.parse()?, timeout, credentials); let (stream, local_srv) = run_listening_server(&local_srv, SERVERS.deref(), listening_server).await?; let protocol = stream.local_protocol(); let (local_rx, local_tx) = tokio::io::split(stream); @@ -571,7 +571,7 @@ async fn ws_server_upgrade( .instrument(Span::current()), ); - if req_protocol == LocalProtocol::ReverseSocks5 { + if matches!(req_protocol, LocalProtocol::ReverseSocks5 { .. }) { let Ok(header_val) = HeaderValue::from_str(&tunnel_to_jwt_token(Uuid::from_u128(0), &remote_addr)) else { error!("Bad headervalue for reverse socks5: {} {}", remote_addr.host, remote_addr.port); return http::Response::builder() @@ -691,7 +691,7 @@ async fn http_server_upgrade( .instrument(Span::current()), ); - if req_protocol == LocalProtocol::ReverseSocks5 { + if matches!(req_protocol, LocalProtocol::ReverseSocks5 { .. }) { let Ok(header_val) = HeaderValue::from_str(&tunnel_to_jwt_token(Uuid::from_u128(0), &remote_addr)) else { error!("Bad header value for reverse socks5: {} {}", remote_addr.host, remote_addr.port); return http::Response::builder()