diff --git a/README.md b/README.md index f408ba1..7f401af 100644 --- a/README.md +++ b/README.md @@ -6,7 +6,6 @@


- Donations goes to Wife, so she can allow me to work on the project

@@ -22,7 +21,7 @@ My inspiration came from [this project](https://www.npmjs.com/package/wstunnel) * Good error messages and debug informations * Static forward and reverse tunneling (TCP and UDP) -* Dynamic tunneling (socks5 proxy) +* Dynamic tunneling (Socks5 proxy and Transparent Proxy) * Support for http proxy (when behind one) * Support for tls/https server (with embedded self-signed certificate, see comment in the example section) * Support IPv6 diff --git a/src/main.rs b/src/main.rs index 6735ee8..f98cf8f 100644 --- a/src/main.rs +++ b/src/main.rs @@ -28,7 +28,7 @@ use tokio_rustls::rustls::{Certificate, PrivateKey, ServerName}; use tracing::{error, info, Level}; -use crate::LocalProtocol::ReverseTcp; +use crate::tunnel::to_host_port; use tracing_subscriber::EnvFilter; use url::{Host, Url}; @@ -56,6 +56,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 /// 'stdio://google.com:443' => listen for data from stdio, mainly for `ssh -o ProxyCommand="wstunnel client -L stdio://%h:%p ws://localhost:8080" my-server` + /// 'tproxy+tcp://[::1]:1212 => listen locally on tcp on port 1212 as a *transparent proxy* and forward dynamically requested tunnel (linux only requires sudo/CAP_NET_ADMIN) #[arg(short='L', long, value_name = "{tcp,udp,socks5,stdio}://[BIND:]PORT:HOST:PORT", value_parser = parse_tunnel_arg, verbatim_doc_comment)] local_to_remote: Vec, @@ -172,6 +173,7 @@ enum LocalProtocol { Udp { timeout: Option }, Stdio, Socks5, + TProxyTcp, ReverseTcp, ReverseUdp { timeout: Option }, } @@ -315,6 +317,16 @@ fn parse_tunnel_arg(arg: &str) -> Result { remote: (dest_host, dest_port), }) } + "tproxy+t" => { + let (local_bind, remaining) = parse_local_bind(&arg["tproxy+tcp://".len()..])?; + let x = format!("0.0.0.0:0?{}", remaining); + let (dest_host, dest_port, _options) = parse_tunnel_dest(&x)?; + Ok(LocalToRemote { + local_protocol: LocalProtocol::TProxyTcp, + local: local_bind, + remote: (dest_host, dest_port), + }) + } _ => Err(Error::new( ErrorKind::InvalidInput, format!("Invalid local protocol for tunnel {}", arg), @@ -551,7 +563,7 @@ async fn main() { let client_config = client_config.clone(); match &tunnel.local_protocol { LocalProtocol::Tcp => { - tunnel.local_protocol = ReverseTcp; + tunnel.local_protocol = LocalProtocol::ReverseTcp; tokio::spawn(async move { let remote = tunnel.remote.clone(); let cfg = client_config.clone(); @@ -592,7 +604,7 @@ async fn main() { match &tunnel.local_protocol { LocalProtocol::Tcp => { let remote = tunnel.remote.clone(); - let server = tcp::run_server(tunnel.local) + let server = tcp::run_server(tunnel.local, false) .await .unwrap_or_else(|err| panic!("Cannot start TCP server on {}: {}", tunnel.local, err)) .map_err(anyhow::Error::new) @@ -604,6 +616,28 @@ async fn main() { } }); } + #[cfg(target_os = "linux")] + LocalProtocol::TProxyTcp => { + let server = tcp::run_server(tunnel.local, true) + .await + .unwrap_or_else(|err| panic!("Cannot start TProxy server on {}: {}", tunnel.local, err)) + .map_err(anyhow::Error::new) + .map_ok(move |stream| { + // In TProxy mode local destination is the final ip:port destination + let dest = to_host_port(stream.local_addr().unwrap()); + (stream.into_split(), dest) + }); + + tokio::spawn(async move { + if let Err(err) = tunnel::client::run_tunnel(client_config, tunnel, server).await { + error!("{:?}", err); + } + }); + } + #[cfg(not(target_os = "linux"))] + LocalProtocol::TProxyTcp => { + panic!("Transparent proxy is not available for non Linux platform") + } LocalProtocol::Udp { timeout } => { let remote = tunnel.remote.clone(); let server = udp::run_server(tunnel.local, *timeout) @@ -630,28 +664,27 @@ async fn main() { } }); } + + #[cfg(target_family = "unix")] LocalProtocol::Stdio => { - #[cfg(target_family = "unix")] - { - let server = stdio::run_server().await.unwrap_or_else(|err| { - panic!("Cannot start STDIO server: {}", err); - }); - tokio::spawn(async move { - if let Err(err) = tunnel::client::run_tunnel( - client_config, - tunnel.clone(), - stream::once(async move { Ok((server, tunnel.remote)) }), - ) - .await - { - error!("{:?}", err); - } - }); - } - #[cfg(not(target_family = "unix"))] - { - panic!("stdio is not implemented for non unix platform") - } + let server = stdio::run_server().await.unwrap_or_else(|err| { + panic!("Cannot start STDIO server: {}", err); + }); + tokio::spawn(async move { + if let Err(err) = tunnel::client::run_tunnel( + client_config, + tunnel.clone(), + stream::once(async move { Ok((server, tunnel.remote)) }), + ) + .await + { + error!("{:?}", err); + } + }); + } + #[cfg(not(target_family = "unix"))] + LocalProtocol::Stdio => { + panic!("stdio is not implemented for non unix platform") } LocalProtocol::ReverseTcp => {} LocalProtocol::ReverseUdp { .. } => {} diff --git a/src/tcp.rs b/src/tcp.rs index 630c491..d957366 100644 --- a/src/tcp.rs +++ b/src/tcp.rs @@ -169,12 +169,19 @@ pub async fn connect_with_http_proxy( Ok(socket) } -pub async fn run_server(bind: SocketAddr) -> Result { +pub async fn run_server(bind: SocketAddr, ip_transparent: bool) -> Result { info!("Starting TCP server listening cnx on {}", bind); let listener = TcpListener::bind(bind) .await .with_context(|| format!("Cannot create TCP server {:?}", bind))?; + + #[cfg(target_os = "linux")] + if ip_transparent { + info!("TCP server listening in TProxy mode"); + socket2::SockRef::from(&listener).set_ip_transparent(ip_transparent)?; + } + Ok(TcpListenerStream::new(listener)) } diff --git a/src/tunnel/mod.rs b/src/tunnel/mod.rs index 3835e07..682e0f7 100644 --- a/src/tunnel/mod.rs +++ b/src/tunnel/mod.rs @@ -10,11 +10,13 @@ use once_cell::sync::Lazy; use serde::{Deserialize, Serialize}; use std::collections::HashSet; use std::io::{Error, IoSlice}; +use std::net::{IpAddr, SocketAddr}; use std::pin::Pin; use std::task::{Context, Poll}; use tokio::io::{AsyncRead, AsyncWrite, ReadBuf}; use tokio::net::TcpStream; use tokio_rustls::client::TlsStream; +use url::Host; use uuid::Uuid; #[derive(Debug, Clone, Serialize, Deserialize)] @@ -36,6 +38,7 @@ impl JwtTunnelConfig { LocalProtocol::Socks5 => LocalProtocol::Tcp, LocalProtocol::ReverseTcp => LocalProtocol::ReverseTcp, LocalProtocol::ReverseUdp { .. } => tunnel.local_protocol, + LocalProtocol::TProxyTcp => LocalProtocol::Tcp, }, r: tunnel.remote.0.to_string(), rp: tunnel.remote.1, @@ -141,3 +144,10 @@ impl ManageConnection for WsClientConfig { conn.is_none() } } + +pub fn to_host_port(addr: SocketAddr) -> (Host, u16) { + match addr.ip() { + IpAddr::V4(ip) => (Host::Ipv4(ip), addr.port()), + IpAddr::V6(ip) => (Host::Ipv6(ip), addr.port()), + } +} diff --git a/src/tunnel/server.rs b/src/tunnel/server.rs index b6ff519..41838b1 100644 --- a/src/tunnel/server.rs +++ b/src/tunnel/server.rs @@ -88,7 +88,7 @@ async fn from_query( let local_srv = (Host::parse(&jwt.claims.r)?, jwt.claims.rp); let bind = format!("{}:{}", local_srv.0, local_srv.1); - let listening_server = tcp::run_server(bind.parse()?); + let listening_server = tcp::run_server(bind.parse()?, false); let tcp = run_listening_server(&local_srv, SERVERS.deref(), listening_server).await?; let (local_rx, local_tx) = tcp.into_split();