feat: Add http proxy support
This commit is contained in:
parent
b077819cc6
commit
6e10c27dbb
5 changed files with 285 additions and 7 deletions
145
src/http_proxy.rs
Normal file
145
src/http_proxy.rs
Normal file
|
@ -0,0 +1,145 @@
|
||||||
|
use anyhow::Context;
|
||||||
|
use std::future::Future;
|
||||||
|
|
||||||
|
use bytes::Bytes;
|
||||||
|
use log::{debug, error};
|
||||||
|
use std::net::{Ipv4Addr, SocketAddr};
|
||||||
|
use std::pin::Pin;
|
||||||
|
|
||||||
|
use base64::Engine;
|
||||||
|
use futures_util::{future, stream, Stream};
|
||||||
|
use http_body_util::Empty;
|
||||||
|
use hyper::body::Incoming;
|
||||||
|
use hyper::server::conn::http1;
|
||||||
|
use hyper::service::service_fn;
|
||||||
|
use hyper::{Request, Response};
|
||||||
|
use hyper_util::rt::TokioTimer;
|
||||||
|
use parking_lot::Mutex;
|
||||||
|
use std::time::Duration;
|
||||||
|
use tokio::net::{TcpListener, TcpStream};
|
||||||
|
use tracing::log::info;
|
||||||
|
use url::Host;
|
||||||
|
|
||||||
|
#[allow(clippy::type_complexity)]
|
||||||
|
pub struct HttpProxyListener {
|
||||||
|
listener: Pin<Box<dyn Stream<Item = anyhow::Result<(TcpStream, (Host, u16))>> + Send>>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Stream for HttpProxyListener {
|
||||||
|
type Item = anyhow::Result<(TcpStream, (Host, u16))>;
|
||||||
|
|
||||||
|
fn poll_next(self: Pin<&mut Self>, cx: &mut std::task::Context<'_>) -> std::task::Poll<Option<Self::Item>> {
|
||||||
|
unsafe { self.map_unchecked_mut(|x| &mut x.listener) }.poll_next(cx)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn server_client(
|
||||||
|
credentials: &Option<String>,
|
||||||
|
dest: &Mutex<(Host, u16)>,
|
||||||
|
req: Request<Incoming>,
|
||||||
|
) -> impl Future<Output = Result<Response<Empty<Bytes>>, &'static str>> {
|
||||||
|
const PROXY_AUTHORIZATION_PREFIX: &str = "Basic ";
|
||||||
|
let ok_response = |forward_to: (Host, u16)| -> Result<Response<Empty<Bytes>>, _> {
|
||||||
|
*dest.lock() = forward_to;
|
||||||
|
Ok(Response::builder().status(200).body(Empty::new()).unwrap())
|
||||||
|
};
|
||||||
|
fn err_response() -> Result<Response<Empty<Bytes>>, &'static str> {
|
||||||
|
info!("Un-authorized connection to http proxy");
|
||||||
|
Err("Un-authorized")
|
||||||
|
}
|
||||||
|
|
||||||
|
if req.method() != hyper::Method::CONNECT {
|
||||||
|
return future::ready(err_response());
|
||||||
|
}
|
||||||
|
|
||||||
|
debug!("HTTP Proxy CONNECT request to {}", req.uri());
|
||||||
|
let forward_to = (
|
||||||
|
Host::parse(req.uri().host().unwrap_or_default()).unwrap_or(Host::Ipv4(Ipv4Addr::new(0, 0, 0, 0))),
|
||||||
|
req.uri().port_u16().unwrap_or(443),
|
||||||
|
);
|
||||||
|
|
||||||
|
let Some(token) = credentials else {
|
||||||
|
return future::ready(ok_response(forward_to));
|
||||||
|
};
|
||||||
|
|
||||||
|
let Some(auth) = req.headers().get(hyper::header::PROXY_AUTHORIZATION) else {
|
||||||
|
return future::ready(err_response());
|
||||||
|
};
|
||||||
|
|
||||||
|
let auth = auth.to_str().unwrap_or_default().trim();
|
||||||
|
if auth.starts_with(PROXY_AUTHORIZATION_PREFIX) && &auth[PROXY_AUTHORIZATION_PREFIX.len()..] == token {
|
||||||
|
return future::ready(ok_response(forward_to));
|
||||||
|
}
|
||||||
|
|
||||||
|
future::ready(err_response())
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn run_server(
|
||||||
|
bind: SocketAddr,
|
||||||
|
timeout: Option<Duration>,
|
||||||
|
credentials: Option<(String, String)>,
|
||||||
|
) -> Result<HttpProxyListener, anyhow::Error> {
|
||||||
|
info!(
|
||||||
|
"Starting http proxy server listening cnx on {} with credentials {:?}",
|
||||||
|
bind, credentials
|
||||||
|
);
|
||||||
|
|
||||||
|
let listener = TcpListener::bind(bind)
|
||||||
|
.await
|
||||||
|
.with_context(|| format!("Cannot create TCP server {:?}", bind))?;
|
||||||
|
|
||||||
|
let http1 = {
|
||||||
|
let mut builder = http1::Builder::new();
|
||||||
|
builder
|
||||||
|
.timer(TokioTimer::new())
|
||||||
|
.header_read_timeout(timeout)
|
||||||
|
.keep_alive(false);
|
||||||
|
builder
|
||||||
|
};
|
||||||
|
let auth_header =
|
||||||
|
credentials.map(|(user, pass)| base64::engine::general_purpose::STANDARD.encode(format!("{}:{}", user, pass)));
|
||||||
|
|
||||||
|
let listener = stream::unfold((listener, http1, auth_header), |(listener, http1, auth_header)| async {
|
||||||
|
loop {
|
||||||
|
let (mut stream, _) = match listener.accept().await {
|
||||||
|
Ok(v) => v,
|
||||||
|
Err(err) => {
|
||||||
|
error!("Error while accepting connection {:?}", err);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
let forward_to = Mutex::new((Host::Ipv4(Ipv4Addr::new(0, 0, 0, 0)), 0));
|
||||||
|
let conn_fut = http1.serve_connection(
|
||||||
|
hyper_util::rt::TokioIo::new(&mut stream),
|
||||||
|
service_fn(|req| server_client(&auth_header, &forward_to, req)),
|
||||||
|
);
|
||||||
|
match conn_fut.await {
|
||||||
|
Ok(_) => return Some((Ok((stream, forward_to.into_inner())), (listener, http1, auth_header))),
|
||||||
|
Err(err) => {
|
||||||
|
info!("Error while serving connection: {}", err);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
Ok(HttpProxyListener {
|
||||||
|
listener: Box::pin(listener),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
//#[cfg(test)]
|
||||||
|
//mod tests {
|
||||||
|
// use super::*;
|
||||||
|
// use tracing::level_filters::LevelFilter;
|
||||||
|
//
|
||||||
|
// #[tokio::test]
|
||||||
|
// async fn test_run_server() {
|
||||||
|
// tracing_subscriber::fmt()
|
||||||
|
// .with_ansi(true)
|
||||||
|
// .with_max_level(LevelFilter::TRACE)
|
||||||
|
// .init();
|
||||||
|
// let x = run_server("127.0.0.1:1212".parse().unwrap(), None, None).await;
|
||||||
|
// }
|
||||||
|
//}
|
113
src/main.rs
113
src/main.rs
|
@ -1,5 +1,6 @@
|
||||||
mod dns;
|
mod dns;
|
||||||
mod embedded_certificate;
|
mod embedded_certificate;
|
||||||
|
mod http_proxy;
|
||||||
mod restrictions;
|
mod restrictions;
|
||||||
mod socks5;
|
mod socks5;
|
||||||
mod socks5_udp;
|
mod socks5_udp;
|
||||||
|
@ -108,6 +109,9 @@ struct Client {
|
||||||
/// '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
|
||||||
/// 'socks5://[::1]:1212?login=admin&password=admin' => listen locally with socks5 on port 1212 and only accept connection with login=admin and password=admin
|
/// 'socks5://[::1]:1212?login=admin&password=admin' => listen locally with socks5 on port 1212 and only accept connection with login=admin and password=admin
|
||||||
///
|
///
|
||||||
|
/// 'httpproxy://[::1]:1212' => start a http proxy on port 1212 and forward dynamically requested tunnel
|
||||||
|
/// 'httpproxy://[::1]:1212?login=admin&password=admin' => start a http proxy 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+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
|
/// 'tproxy+udp://[::1]:1212?timeout_sec=10' listen locally on udp on port 1212 as a *transparent proxy* and forward dynamically requested tunnel
|
||||||
/// linux only and requires sudo/CAP_NET_ADMIN
|
/// linux only and requires sudo/CAP_NET_ADMIN
|
||||||
|
@ -123,6 +127,7 @@ struct Client {
|
||||||
/// '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
|
/// '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
|
/// '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 (login/password is supported)
|
/// 'socks5://[::1]:1212' => listen on server for incoming socks5 request on port 1212 and forward dynamically request from local machine (login/password is supported)
|
||||||
|
/// 'httpproxy://[::1]:1212' => listen on server for incoming http proxy 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
|
/// '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)]
|
#[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>,
|
remote_to_local: Vec<LocalToRemote>,
|
||||||
|
@ -230,7 +235,7 @@ struct Client {
|
||||||
/// Obviously, this is not going to work for tunneling traffic
|
/// Obviously, this is not going to work for tunneling traffic
|
||||||
/// - if you have wstunnel behind a reverse proxy, most of them (i.e: nginx) are going to turn http2 request into http1
|
/// - if you have wstunnel behind a reverse proxy, most of them (i.e: nginx) are going to turn http2 request into http1
|
||||||
/// This is not going to work, because http1 does not support streaming naturally
|
/// This is not going to work, because http1 does not support streaming naturally
|
||||||
/// The only way to make it works with http2 is to have wstunnel directly exposed to the internet without any reverse proxy in front of it
|
/// - The only way to make it works with http2 is to have wstunnel directly exposed to the internet without any reverse proxy in front of it
|
||||||
#[arg(value_name = "ws[s]|http[s]://wstunnel.server.com[:port]", value_parser = parse_server_url, verbatim_doc_comment)]
|
#[arg(value_name = "ws[s]|http[s]://wstunnel.server.com[:port]", value_parser = parse_server_url, verbatim_doc_comment)]
|
||||||
remote_addr: Url,
|
remote_addr: Url,
|
||||||
|
|
||||||
|
@ -380,6 +385,11 @@ enum LocalProtocol {
|
||||||
TProxyUdp {
|
TProxyUdp {
|
||||||
timeout: Option<Duration>,
|
timeout: Option<Duration>,
|
||||||
},
|
},
|
||||||
|
HttpProxy {
|
||||||
|
timeout: Option<Duration>,
|
||||||
|
credentials: Option<(String, String)>,
|
||||||
|
proxy_protocol: bool,
|
||||||
|
},
|
||||||
ReverseTcp,
|
ReverseTcp,
|
||||||
ReverseUdp {
|
ReverseUdp {
|
||||||
timeout: Option<Duration>,
|
timeout: Option<Duration>,
|
||||||
|
@ -388,6 +398,10 @@ enum LocalProtocol {
|
||||||
timeout: Option<Duration>,
|
timeout: Option<Duration>,
|
||||||
credentials: Option<(String, String)>,
|
credentials: Option<(String, String)>,
|
||||||
},
|
},
|
||||||
|
ReverseHttpProxy {
|
||||||
|
timeout: Option<Duration>,
|
||||||
|
credentials: Option<(String, String)>,
|
||||||
|
},
|
||||||
ReverseUnix {
|
ReverseUnix {
|
||||||
path: PathBuf,
|
path: PathBuf,
|
||||||
},
|
},
|
||||||
|
@ -398,7 +412,14 @@ enum LocalProtocol {
|
||||||
|
|
||||||
impl LocalProtocol {
|
impl LocalProtocol {
|
||||||
pub const fn is_reverse_tunnel(&self) -> bool {
|
pub const fn is_reverse_tunnel(&self) -> bool {
|
||||||
matches!(self, Self::ReverseTcp | Self::ReverseUdp { .. } | Self::ReverseSocks5 { .. })
|
matches!(
|
||||||
|
self,
|
||||||
|
Self::ReverseTcp
|
||||||
|
| Self::ReverseUdp { .. }
|
||||||
|
| Self::ReverseSocks5 { .. }
|
||||||
|
| Self::ReverseUnix { .. }
|
||||||
|
| Self::ReverseHttpProxy { .. }
|
||||||
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -539,7 +560,7 @@ fn parse_tunnel_arg(arg: &str) -> Result<LocalToRemote, io::Error> {
|
||||||
}
|
}
|
||||||
_ => match &arg[..8] {
|
_ => match &arg[..8] {
|
||||||
"socks5:/" => {
|
"socks5:/" => {
|
||||||
let (local_bind, remaining) = parse_local_bind(&arg[9..])?;
|
let (local_bind, remaining) = parse_local_bind(&arg["socks5://".len()..])?;
|
||||||
let x = format!("0.0.0.0:0?{}", remaining);
|
let x = format!("0.0.0.0:0?{}", remaining);
|
||||||
let (dest_host, dest_port, options) = parse_tunnel_dest(&x)?;
|
let (dest_host, dest_port, options) = parse_tunnel_dest(&x)?;
|
||||||
let timeout = options
|
let timeout = options
|
||||||
|
@ -556,8 +577,31 @@ fn parse_tunnel_arg(arg: &str) -> Result<LocalToRemote, io::Error> {
|
||||||
remote: (dest_host, dest_port),
|
remote: (dest_host, dest_port),
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
"httpprox" => {
|
||||||
|
let (local_bind, remaining) = parse_local_bind(&arg["httpproxy://".len()..])?;
|
||||||
|
let x = format!("0.0.0.0:0?{}", remaining);
|
||||||
|
let (dest_host, dest_port, options) = parse_tunnel_dest(&x)?;
|
||||||
|
let proxy_protocol = options.contains_key("proxy_protocol");
|
||||||
|
let timeout = options
|
||||||
|
.get("timeout_sec")
|
||||||
|
.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::HttpProxy {
|
||||||
|
timeout,
|
||||||
|
credentials,
|
||||||
|
proxy_protocol,
|
||||||
|
},
|
||||||
|
local: local_bind,
|
||||||
|
remote: (dest_host, dest_port),
|
||||||
|
})
|
||||||
|
}
|
||||||
"stdio://" => {
|
"stdio://" => {
|
||||||
let (dest_host, dest_port, _options) = parse_tunnel_dest(&arg[8..])?;
|
let (dest_host, dest_port, _options) = parse_tunnel_dest(&arg["stdio://".len()..])?;
|
||||||
Ok(LocalToRemote {
|
Ok(LocalToRemote {
|
||||||
local_protocol: LocalProtocol::Stdio,
|
local_protocol: LocalProtocol::Stdio,
|
||||||
local: SocketAddr::V4(SocketAddrV4::new(Ipv4Addr::from(0), 0)),
|
local: SocketAddr::V4(SocketAddrV4::new(Ipv4Addr::from(0), 0)),
|
||||||
|
@ -1055,6 +1099,39 @@ async fn main() {
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
LocalProtocol::HttpProxy {
|
||||||
|
timeout, credentials, ..
|
||||||
|
} => {
|
||||||
|
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::ReverseHttpProxy { timeout, credentials },
|
||||||
|
host,
|
||||||
|
port,
|
||||||
|
};
|
||||||
|
let connect_to_dest = |remote: Option<RemoteAddr>| {
|
||||||
|
let so_mark = cfg.socket_so_mark;
|
||||||
|
let timeout = cfg.timeout_connect;
|
||||||
|
let dns_resolver = &cfg.dns_resolver;
|
||||||
|
async move {
|
||||||
|
let Some(remote) = remote else {
|
||||||
|
return Err(anyhow!("Missing remote destination for reverse socks5"));
|
||||||
|
};
|
||||||
|
|
||||||
|
tcp::connect(&remote.host, remote.port, so_mark, timeout, dns_resolver).await
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
if let Err(err) =
|
||||||
|
tunnel::client::run_reverse_tunnel(client_config, remote, connect_to_dest).await
|
||||||
|
{
|
||||||
|
error!("{:?}", err);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
#[cfg(unix)]
|
#[cfg(unix)]
|
||||||
LocalProtocol::Unix { path } => {
|
LocalProtocol::Unix { path } => {
|
||||||
let path = path.clone();
|
let path = path.clone();
|
||||||
|
@ -1095,7 +1172,8 @@ async fn main() {
|
||||||
| LocalProtocol::ReverseTcp
|
| LocalProtocol::ReverseTcp
|
||||||
| LocalProtocol::ReverseUdp { .. }
|
| LocalProtocol::ReverseUdp { .. }
|
||||||
| LocalProtocol::ReverseSocks5 { .. }
|
| LocalProtocol::ReverseSocks5 { .. }
|
||||||
| LocalProtocol::ReverseUnix { .. } => {
|
| LocalProtocol::ReverseHttpProxy { .. } => {}
|
||||||
|
LocalProtocol::ReverseUnix { .. } => {
|
||||||
panic!("Invalid protocol for reverse tunnel");
|
panic!("Invalid protocol for reverse tunnel");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1251,6 +1329,30 @@ async fn main() {
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
LocalProtocol::HttpProxy {
|
||||||
|
timeout,
|
||||||
|
credentials,
|
||||||
|
proxy_protocol,
|
||||||
|
} => {
|
||||||
|
let proxy_protocol = *proxy_protocol;
|
||||||
|
let server = http_proxy::run_server(tunnel.local, *timeout, credentials.clone())
|
||||||
|
.await
|
||||||
|
.unwrap_or_else(|err| panic!("Cannot start http proxy server on {}: {}", tunnel.local, err))
|
||||||
|
.map_ok(move |(stream, (host, port))| {
|
||||||
|
let remote = RemoteAddr {
|
||||||
|
protocol: LocalProtocol::Tcp { proxy_protocol },
|
||||||
|
host,
|
||||||
|
port,
|
||||||
|
};
|
||||||
|
(tokio::io::split(stream), remote)
|
||||||
|
});
|
||||||
|
|
||||||
|
tokio::spawn(async move {
|
||||||
|
if let Err(err) = tunnel::client::run_tunnel(client_config, server).await {
|
||||||
|
error!("{:?}", err);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
LocalProtocol::Stdio => {
|
LocalProtocol::Stdio => {
|
||||||
let (server, mut handle) = stdio::server::run_server().await.unwrap_or_else(|err| {
|
let (server, mut handle) = stdio::server::run_server().await.unwrap_or_else(|err| {
|
||||||
|
@ -1287,6 +1389,7 @@ async fn main() {
|
||||||
LocalProtocol::ReverseUdp { .. } => {}
|
LocalProtocol::ReverseUdp { .. } => {}
|
||||||
LocalProtocol::ReverseSocks5 { .. } => {}
|
LocalProtocol::ReverseSocks5 { .. } => {}
|
||||||
LocalProtocol::ReverseUnix { .. } => {}
|
LocalProtocol::ReverseUnix { .. } => {}
|
||||||
|
LocalProtocol::ReverseHttpProxy { .. } => {}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -78,6 +78,7 @@ pub enum ReverseTunnelConfigProtocol {
|
||||||
Udp,
|
Udp,
|
||||||
Socks5,
|
Socks5,
|
||||||
Unix,
|
Unix,
|
||||||
|
HttpProxy,
|
||||||
Unknown,
|
Unknown,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -160,11 +161,13 @@ impl From<&LocalProtocol> for ReverseTunnelConfigProtocol {
|
||||||
| LocalProtocol::Socks5 { .. }
|
| LocalProtocol::Socks5 { .. }
|
||||||
| LocalProtocol::TProxyTcp { .. }
|
| LocalProtocol::TProxyTcp { .. }
|
||||||
| LocalProtocol::TProxyUdp { .. }
|
| LocalProtocol::TProxyUdp { .. }
|
||||||
|
| LocalProtocol::HttpProxy { .. }
|
||||||
| LocalProtocol::Unix { .. } => Self::Unknown,
|
| LocalProtocol::Unix { .. } => Self::Unknown,
|
||||||
LocalProtocol::ReverseTcp => Self::Tcp,
|
LocalProtocol::ReverseTcp => Self::Tcp,
|
||||||
LocalProtocol::ReverseUdp { .. } => Self::Udp,
|
LocalProtocol::ReverseUdp { .. } => Self::Udp,
|
||||||
LocalProtocol::ReverseSocks5 { .. } => Self::Socks5,
|
LocalProtocol::ReverseSocks5 { .. } => Self::Socks5,
|
||||||
LocalProtocol::ReverseUnix { .. } => Self::Unix,
|
LocalProtocol::ReverseUnix { .. } => Self::Unix,
|
||||||
|
LocalProtocol::ReverseHttpProxy { .. } => Self::HttpProxy,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -179,6 +182,8 @@ impl From<&LocalProtocol> for TunnelConfigProtocol {
|
||||||
| LocalProtocol::Socks5 { .. }
|
| LocalProtocol::Socks5 { .. }
|
||||||
| LocalProtocol::TProxyTcp { .. }
|
| LocalProtocol::TProxyTcp { .. }
|
||||||
| LocalProtocol::TProxyUdp { .. }
|
| LocalProtocol::TProxyUdp { .. }
|
||||||
|
| LocalProtocol::HttpProxy { .. }
|
||||||
|
| LocalProtocol::ReverseHttpProxy { .. }
|
||||||
| LocalProtocol::Unix { .. } => Self::Unknown,
|
| LocalProtocol::Unix { .. } => Self::Unknown,
|
||||||
LocalProtocol::Tcp { .. } => Self::Tcp,
|
LocalProtocol::Tcp { .. } => Self::Tcp,
|
||||||
LocalProtocol::Udp { .. } => Self::Udp,
|
LocalProtocol::Udp { .. } => Self::Udp,
|
||||||
|
|
|
@ -41,6 +41,7 @@ impl JwtTunnelConfig {
|
||||||
LocalProtocol::Udp { .. } => dest.protocol.clone(),
|
LocalProtocol::Udp { .. } => dest.protocol.clone(),
|
||||||
LocalProtocol::Stdio => LocalProtocol::Tcp { proxy_protocol: false },
|
LocalProtocol::Stdio => LocalProtocol::Tcp { proxy_protocol: false },
|
||||||
LocalProtocol::Socks5 { .. } => LocalProtocol::Tcp { proxy_protocol: false },
|
LocalProtocol::Socks5 { .. } => LocalProtocol::Tcp { proxy_protocol: false },
|
||||||
|
LocalProtocol::HttpProxy { .. } => dest.protocol.clone(),
|
||||||
LocalProtocol::ReverseTcp => LocalProtocol::ReverseTcp,
|
LocalProtocol::ReverseTcp => LocalProtocol::ReverseTcp,
|
||||||
LocalProtocol::ReverseUdp { .. } => dest.protocol.clone(),
|
LocalProtocol::ReverseUdp { .. } => dest.protocol.clone(),
|
||||||
LocalProtocol::ReverseSocks5 { .. } => dest.protocol.clone(),
|
LocalProtocol::ReverseSocks5 { .. } => dest.protocol.clone(),
|
||||||
|
@ -48,6 +49,7 @@ impl JwtTunnelConfig {
|
||||||
LocalProtocol::TProxyUdp { timeout } => LocalProtocol::Udp { timeout },
|
LocalProtocol::TProxyUdp { timeout } => LocalProtocol::Udp { timeout },
|
||||||
LocalProtocol::Unix { .. } => LocalProtocol::Tcp { proxy_protocol: false },
|
LocalProtocol::Unix { .. } => LocalProtocol::Tcp { proxy_protocol: false },
|
||||||
LocalProtocol::ReverseUnix { .. } => dest.protocol.clone(),
|
LocalProtocol::ReverseUnix { .. } => dest.protocol.clone(),
|
||||||
|
LocalProtocol::ReverseHttpProxy { .. } => dest.protocol.clone(),
|
||||||
},
|
},
|
||||||
r: dest.host.to_string(),
|
r: dest.host.to_string(),
|
||||||
rp: dest.port,
|
rp: dest.port,
|
||||||
|
|
|
@ -15,7 +15,7 @@ use std::sync::Arc;
|
||||||
use std::time::Duration;
|
use std::time::Duration;
|
||||||
|
|
||||||
use super::{tunnel_to_jwt_token, JwtTunnelConfig, RemoteAddr, JWT_DECODE, JWT_HEADER_PREFIX};
|
use super::{tunnel_to_jwt_token, JwtTunnelConfig, RemoteAddr, JWT_DECODE, JWT_HEADER_PREFIX};
|
||||||
use crate::{socks5, tcp, tls, udp, LocalProtocol, TlsServerConfig, WsServerConfig};
|
use crate::{http_proxy, socks5, tcp, tls, udp, LocalProtocol, TlsServerConfig, WsServerConfig};
|
||||||
use hyper::body::{Frame, Incoming};
|
use hyper::body::{Frame, Incoming};
|
||||||
use hyper::header::{CONTENT_TYPE, COOKIE, SEC_WEBSOCKET_PROTOCOL};
|
use hyper::header::{CONTENT_TYPE, COOKIE, SEC_WEBSOCKET_PROTOCOL};
|
||||||
use hyper::http::HeaderValue;
|
use hyper::http::HeaderValue;
|
||||||
|
@ -150,6 +150,25 @@ async fn run_tunnel(
|
||||||
};
|
};
|
||||||
Ok((remote, Box::pin(local_rx), Box::pin(local_tx)))
|
Ok((remote, Box::pin(local_rx), Box::pin(local_tx)))
|
||||||
}
|
}
|
||||||
|
LocalProtocol::ReverseHttpProxy { timeout, credentials } => {
|
||||||
|
#[allow(clippy::type_complexity)]
|
||||||
|
static SERVERS: Lazy<Mutex<HashMap<(Host<String>, u16), mpsc::Receiver<(TcpStream, (Host, u16))>>>> =
|
||||||
|
Lazy::new(|| Mutex::new(HashMap::with_capacity(0)));
|
||||||
|
|
||||||
|
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 = http_proxy::run_server(bind.parse()?, timeout, credentials);
|
||||||
|
let (stream, local_srv) = run_listening_server(&local_srv, SERVERS.deref(), listening_server).await?;
|
||||||
|
let (local_rx, local_tx) = tokio::io::split(stream);
|
||||||
|
|
||||||
|
let remote = RemoteAddr {
|
||||||
|
protocol: LocalProtocol::Tcp { proxy_protocol: false },
|
||||||
|
host: local_srv.0,
|
||||||
|
port: local_srv.1,
|
||||||
|
};
|
||||||
|
Ok((remote, Box::pin(local_rx), Box::pin(local_tx)))
|
||||||
|
}
|
||||||
#[cfg(unix)]
|
#[cfg(unix)]
|
||||||
LocalProtocol::ReverseUnix { ref path } => {
|
LocalProtocol::ReverseUnix { ref path } => {
|
||||||
use crate::unix_socket;
|
use crate::unix_socket;
|
||||||
|
@ -181,6 +200,7 @@ async fn run_tunnel(
|
||||||
| LocalProtocol::Socks5 { .. }
|
| LocalProtocol::Socks5 { .. }
|
||||||
| LocalProtocol::TProxyTcp
|
| LocalProtocol::TProxyTcp
|
||||||
| LocalProtocol::TProxyUdp { .. }
|
| LocalProtocol::TProxyUdp { .. }
|
||||||
|
| LocalProtocol::HttpProxy { .. }
|
||||||
| LocalProtocol::Unix { .. } => {
|
| LocalProtocol::Unix { .. } => {
|
||||||
error!("Received an unsupported target protocol {:?}", remote);
|
error!("Received an unsupported target protocol {:?}", remote);
|
||||||
Err(anyhow::anyhow!("Invalid upgrade request"))
|
Err(anyhow::anyhow!("Invalid upgrade request"))
|
||||||
|
@ -572,7 +592,10 @@ async fn ws_server_upgrade(
|
||||||
.instrument(Span::current()),
|
.instrument(Span::current()),
|
||||||
);
|
);
|
||||||
|
|
||||||
if matches!(req_protocol, LocalProtocol::ReverseSocks5 { .. }) {
|
if matches!(
|
||||||
|
req_protocol,
|
||||||
|
LocalProtocol::ReverseSocks5 { .. } | LocalProtocol::ReverseHttpProxy { .. }
|
||||||
|
) {
|
||||||
let Ok(header_val) = HeaderValue::from_str(&tunnel_to_jwt_token(Uuid::from_u128(0), &remote_addr)) else {
|
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);
|
error!("Bad headervalue for reverse socks5: {} {}", remote_addr.host, remote_addr.port);
|
||||||
return http::Response::builder()
|
return http::Response::builder()
|
||||||
|
|
Loading…
Reference in a new issue