feat(sock5): Add login/passzord support

This commit is contained in:
Σrebe - Romain GERARD 2024-07-09 08:14:03 +02:00
parent ba57b76a73
commit d797fa135c
No known key found for this signature in database
GPG key ID: 7A42B4B97E0332F4
5 changed files with 67 additions and 29 deletions

View file

@ -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<LocalToRemote>,
@ -342,22 +343,40 @@ struct Server {
#[derive(Clone, Debug, Eq, PartialEq, Serialize, Deserialize)]
enum LocalProtocol {
Tcp { proxy_protocol: bool },
Udp { timeout: Option<Duration> },
Tcp {
proxy_protocol: bool,
},
Udp {
timeout: Option<Duration>,
},
Stdio,
Socks5 { timeout: Option<Duration> },
Socks5 {
timeout: Option<Duration>,
credentials: Option<(String, String)>,
},
TProxyTcp,
TProxyUdp { timeout: Option<Duration> },
TProxyUdp {
timeout: Option<Duration>,
},
ReverseTcp,
ReverseUdp { timeout: Option<Duration> },
ReverseSocks5,
ReverseUnix { path: PathBuf },
Unix { path: PathBuf },
ReverseUdp {
timeout: Option<Duration>,
},
ReverseSocks5 {
timeout: Option<Duration>,
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<LocalToRemote, io::Error> {
.and_then(|x| x.parse::<u64>().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 { .. } => {}
}
}

View file

@ -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 { .. }

View file

@ -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<Duration>) -> Result<Socks5Listener, anyhow::Error> {
info!("Starting SOCKS5 server listening cnx on {}", bind);
pub async fn run_server(
bind: SocketAddr,
timeout: Option<Duration>,
credentials: Option<(String, String)>,
) -> Result<Socks5Listener, anyhow::Error> {
info!(
"Starting SOCKS5 server listening cnx on {} with credentials {:?}",
bind, credentials
);
let server = Socks5Server::<DenyAuthentication>::bind(bind)
.await
.with_context(|| format!("Cannot create socks5 server {:?}", bind))?;
let mut cfg = Config::<DenyAuthentication>::default();
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);

View file

@ -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 },

View file

@ -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<Mutex<HashMap<(Host<String>, 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()