Add transparent tcp proxy on linux
This commit is contained in:
parent
fb8742ef14
commit
33ca736a73
5 changed files with 77 additions and 28 deletions
|
@ -6,7 +6,6 @@
|
||||||
<p align="right">
|
<p align="right">
|
||||||
<a href="https://ko-fi.com/P5P4QCHMO"><img src="https://ko-fi.com/img/githubbutton_sm.svg"/></a>
|
<a href="https://ko-fi.com/P5P4QCHMO"><img src="https://ko-fi.com/img/githubbutton_sm.svg"/></a>
|
||||||
<br/>
|
<br/>
|
||||||
Donations goes to Wife, so she can allow me to work on the project
|
|
||||||
</p>
|
</p>
|
||||||
|
|
||||||
|
|
||||||
|
@ -22,7 +21,7 @@ My inspiration came from [this project](https://www.npmjs.com/package/wstunnel)
|
||||||
|
|
||||||
* Good error messages and debug informations
|
* Good error messages and debug informations
|
||||||
* Static forward and reverse tunneling (TCP and UDP)
|
* 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 http proxy (when behind one)
|
||||||
* Support for tls/https server (with embedded self-signed certificate, see comment in the example section)
|
* Support for tls/https server (with embedded self-signed certificate, see comment in the example section)
|
||||||
* Support IPv6
|
* Support IPv6
|
||||||
|
|
47
src/main.rs
47
src/main.rs
|
@ -28,7 +28,7 @@ use tokio_rustls::rustls::{Certificate, PrivateKey, ServerName};
|
||||||
|
|
||||||
use tracing::{error, info, Level};
|
use tracing::{error, info, Level};
|
||||||
|
|
||||||
use crate::LocalProtocol::ReverseTcp;
|
use crate::tunnel::to_host_port;
|
||||||
use tracing_subscriber::EnvFilter;
|
use tracing_subscriber::EnvFilter;
|
||||||
use url::{Host, Url};
|
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]
|
/// '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' => 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`
|
/// '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)]
|
#[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<LocalToRemote>,
|
local_to_remote: Vec<LocalToRemote>,
|
||||||
|
|
||||||
|
@ -172,6 +173,7 @@ enum LocalProtocol {
|
||||||
Udp { timeout: Option<Duration> },
|
Udp { timeout: Option<Duration> },
|
||||||
Stdio,
|
Stdio,
|
||||||
Socks5,
|
Socks5,
|
||||||
|
TProxyTcp,
|
||||||
ReverseTcp,
|
ReverseTcp,
|
||||||
ReverseUdp { timeout: Option<Duration> },
|
ReverseUdp { timeout: Option<Duration> },
|
||||||
}
|
}
|
||||||
|
@ -315,6 +317,16 @@ fn parse_tunnel_arg(arg: &str) -> Result<LocalToRemote, io::Error> {
|
||||||
remote: (dest_host, dest_port),
|
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(
|
_ => Err(Error::new(
|
||||||
ErrorKind::InvalidInput,
|
ErrorKind::InvalidInput,
|
||||||
format!("Invalid local protocol for tunnel {}", arg),
|
format!("Invalid local protocol for tunnel {}", arg),
|
||||||
|
@ -551,7 +563,7 @@ async fn main() {
|
||||||
let client_config = client_config.clone();
|
let client_config = client_config.clone();
|
||||||
match &tunnel.local_protocol {
|
match &tunnel.local_protocol {
|
||||||
LocalProtocol::Tcp => {
|
LocalProtocol::Tcp => {
|
||||||
tunnel.local_protocol = ReverseTcp;
|
tunnel.local_protocol = LocalProtocol::ReverseTcp;
|
||||||
tokio::spawn(async move {
|
tokio::spawn(async move {
|
||||||
let remote = tunnel.remote.clone();
|
let remote = tunnel.remote.clone();
|
||||||
let cfg = client_config.clone();
|
let cfg = client_config.clone();
|
||||||
|
@ -592,7 +604,7 @@ async fn main() {
|
||||||
match &tunnel.local_protocol {
|
match &tunnel.local_protocol {
|
||||||
LocalProtocol::Tcp => {
|
LocalProtocol::Tcp => {
|
||||||
let remote = tunnel.remote.clone();
|
let remote = tunnel.remote.clone();
|
||||||
let server = tcp::run_server(tunnel.local)
|
let server = tcp::run_server(tunnel.local, false)
|
||||||
.await
|
.await
|
||||||
.unwrap_or_else(|err| panic!("Cannot start TCP server on {}: {}", tunnel.local, err))
|
.unwrap_or_else(|err| panic!("Cannot start TCP server on {}: {}", tunnel.local, err))
|
||||||
.map_err(anyhow::Error::new)
|
.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 } => {
|
LocalProtocol::Udp { timeout } => {
|
||||||
let remote = tunnel.remote.clone();
|
let remote = tunnel.remote.clone();
|
||||||
let server = udp::run_server(tunnel.local, *timeout)
|
let server = udp::run_server(tunnel.local, *timeout)
|
||||||
|
@ -630,9 +664,9 @@ async fn main() {
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
LocalProtocol::Stdio => {
|
|
||||||
#[cfg(target_family = "unix")]
|
#[cfg(target_family = "unix")]
|
||||||
{
|
LocalProtocol::Stdio => {
|
||||||
let server = stdio::run_server().await.unwrap_or_else(|err| {
|
let server = stdio::run_server().await.unwrap_or_else(|err| {
|
||||||
panic!("Cannot start STDIO server: {}", err);
|
panic!("Cannot start STDIO server: {}", err);
|
||||||
});
|
});
|
||||||
|
@ -649,10 +683,9 @@ async fn main() {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
#[cfg(not(target_family = "unix"))]
|
#[cfg(not(target_family = "unix"))]
|
||||||
{
|
LocalProtocol::Stdio => {
|
||||||
panic!("stdio is not implemented for non unix platform")
|
panic!("stdio is not implemented for non unix platform")
|
||||||
}
|
}
|
||||||
}
|
|
||||||
LocalProtocol::ReverseTcp => {}
|
LocalProtocol::ReverseTcp => {}
|
||||||
LocalProtocol::ReverseUdp { .. } => {}
|
LocalProtocol::ReverseUdp { .. } => {}
|
||||||
}
|
}
|
||||||
|
|
|
@ -169,12 +169,19 @@ pub async fn connect_with_http_proxy(
|
||||||
Ok(socket)
|
Ok(socket)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn run_server(bind: SocketAddr) -> Result<TcpListenerStream, anyhow::Error> {
|
pub async fn run_server(bind: SocketAddr, ip_transparent: bool) -> Result<TcpListenerStream, anyhow::Error> {
|
||||||
info!("Starting TCP server listening cnx on {}", bind);
|
info!("Starting TCP server listening cnx on {}", bind);
|
||||||
|
|
||||||
let listener = TcpListener::bind(bind)
|
let listener = TcpListener::bind(bind)
|
||||||
.await
|
.await
|
||||||
.with_context(|| format!("Cannot create TCP server {:?}", bind))?;
|
.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))
|
Ok(TcpListenerStream::new(listener))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -10,11 +10,13 @@ use once_cell::sync::Lazy;
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
use std::collections::HashSet;
|
use std::collections::HashSet;
|
||||||
use std::io::{Error, IoSlice};
|
use std::io::{Error, IoSlice};
|
||||||
|
use std::net::{IpAddr, SocketAddr};
|
||||||
use std::pin::Pin;
|
use std::pin::Pin;
|
||||||
use std::task::{Context, Poll};
|
use std::task::{Context, Poll};
|
||||||
use tokio::io::{AsyncRead, AsyncWrite, ReadBuf};
|
use tokio::io::{AsyncRead, AsyncWrite, ReadBuf};
|
||||||
use tokio::net::TcpStream;
|
use tokio::net::TcpStream;
|
||||||
use tokio_rustls::client::TlsStream;
|
use tokio_rustls::client::TlsStream;
|
||||||
|
use url::Host;
|
||||||
use uuid::Uuid;
|
use uuid::Uuid;
|
||||||
|
|
||||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||||
|
@ -36,6 +38,7 @@ impl JwtTunnelConfig {
|
||||||
LocalProtocol::Socks5 => LocalProtocol::Tcp,
|
LocalProtocol::Socks5 => LocalProtocol::Tcp,
|
||||||
LocalProtocol::ReverseTcp => LocalProtocol::ReverseTcp,
|
LocalProtocol::ReverseTcp => LocalProtocol::ReverseTcp,
|
||||||
LocalProtocol::ReverseUdp { .. } => tunnel.local_protocol,
|
LocalProtocol::ReverseUdp { .. } => tunnel.local_protocol,
|
||||||
|
LocalProtocol::TProxyTcp => LocalProtocol::Tcp,
|
||||||
},
|
},
|
||||||
r: tunnel.remote.0.to_string(),
|
r: tunnel.remote.0.to_string(),
|
||||||
rp: tunnel.remote.1,
|
rp: tunnel.remote.1,
|
||||||
|
@ -141,3 +144,10 @@ impl ManageConnection for WsClientConfig {
|
||||||
conn.is_none()
|
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()),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -88,7 +88,7 @@ async fn from_query(
|
||||||
|
|
||||||
let local_srv = (Host::parse(&jwt.claims.r)?, jwt.claims.rp);
|
let local_srv = (Host::parse(&jwt.claims.r)?, jwt.claims.rp);
|
||||||
let bind = format!("{}:{}", local_srv.0, local_srv.1);
|
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 tcp = run_listening_server(&local_srv, SERVERS.deref(), listening_server).await?;
|
||||||
let (local_rx, local_tx) = tcp.into_split();
|
let (local_rx, local_tx) = tcp.into_split();
|
||||||
|
|
||||||
|
|
Loading…
Reference in a new issue