Add forward traffic to another HTTP proxy for the server (only for LocalProtocol::Tcp) (#326)

* Add forward traffic to another HTTP proxy for the server (only for LocalProtocol::Tcp)

* Update to the newest code:
- Add `connect_with_http_proxy` to `TunnelConnector`

* Remove unnecessary error checks

* Resolve conflict again
This commit is contained in:
Islam Nofl 2024-07-31 22:11:13 +03:00 committed by GitHub
parent 58c34ccc41
commit ce04666b8b
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
6 changed files with 138 additions and 21 deletions

View file

@ -351,6 +351,29 @@ struct Server {
/// The ca will be automatically reloaded if it changes /// The ca will be automatically reloaded if it changes
#[arg(long, value_name = "FILE_PATH", verbatim_doc_comment)] #[arg(long, value_name = "FILE_PATH", verbatim_doc_comment)]
tls_client_ca_certs: Option<PathBuf>, tls_client_ca_certs: Option<PathBuf>,
/// If set, will use this http proxy to connect to the client
#[arg(
short = 'p',
long,
value_name = "USER:PASS@HOST:PORT",
verbatim_doc_comment,
env = "HTTP_PROXY"
)]
http_proxy: Option<String>,
/// If set, will use this login to connect to the http proxy. Override the one from --http-proxy
#[arg(long, value_name = "LOGIN", verbatim_doc_comment, env = "WSTUNNEL_HTTP_PROXY_LOGIN")]
http_proxy_login: Option<String>,
/// If set, will use this password to connect to the http proxy. Override the one from --http-proxy
#[arg(
long,
value_name = "PASSWORD",
verbatim_doc_comment,
env = "WSTUNNEL_HTTP_PROXY_PASSWORD"
)]
http_proxy_password: Option<String>,
} }
#[derive(Clone, Debug, Eq, PartialEq, Serialize, Deserialize)] #[derive(Clone, Debug, Eq, PartialEq, Serialize, Deserialize)]
@ -750,7 +773,24 @@ async fn main() -> anyhow::Result<()> {
TransportScheme::from_str(args.remote_addr.scheme()).expect("invalid scheme in server url"); TransportScheme::from_str(args.remote_addr.scheme()).expect("invalid scheme in server url");
let tls = match transport_scheme { let tls = match transport_scheme {
TransportScheme::Ws | TransportScheme::Http => None, TransportScheme::Ws | TransportScheme::Http => None,
TransportScheme::Wss | TransportScheme::Https => Some(TlsClientConfig { TransportScheme::Wss => Some(TlsClientConfig {
tls_connector: Arc::new(RwLock::new(
tls::tls_connector(
args.tls_verify_certificate,
transport_scheme.alpn_protocols(),
!args.tls_sni_disable,
tls_certificate,
tls_key,
)
.expect("Cannot create tls connector"),
)),
tls_sni_override: args.tls_sni_override,
tls_verify_certificate: args.tls_verify_certificate,
tls_sni_disabled: args.tls_sni_disable,
tls_certificate_path: args.tls_certificate.clone(),
tls_key_path: args.tls_private_key.clone(),
}),
TransportScheme::Https => Some(TlsClientConfig {
tls_connector: Arc::new(RwLock::new( tls_connector: Arc::new(RwLock::new(
tls::tls_connector( tls::tls_connector(
args.tls_verify_certificate, args.tls_verify_certificate,
@ -1136,6 +1176,26 @@ async fn main() -> anyhow::Result<()> {
restriction_cfg restriction_cfg
}; };
let http_proxy = if let Some(proxy) = args.http_proxy {
let mut proxy = if proxy.starts_with("http://") {
Url::parse(&proxy).expect("Invalid http proxy url")
} else {
Url::parse(&format!("http://{}", proxy)).expect("Invalid http proxy url")
};
if let Some(login) = args.http_proxy_login {
proxy.set_username(login.as_str()).expect("Cannot set http proxy login");
}
if let Some(password) = args.http_proxy_password {
proxy
.set_password(Some(password.as_str()))
.expect("Cannot set http proxy password");
}
Some(proxy)
} else {
None
};
let server_config = WsServerConfig { let server_config = WsServerConfig {
socket_so_mark: args.socket_so_mark, socket_so_mark: args.socket_so_mark,
bind: args.remote_addr.socket_addrs(|| Some(8080)).unwrap()[0], bind: args.remote_addr.socket_addrs(|| Some(8080)).unwrap()[0],
@ -1151,6 +1211,7 @@ async fn main() -> anyhow::Result<()> {
) )
.expect("Cannot create DNS resolver"), .expect("Cannot create DNS resolver"),
restriction_config: args.restrict_config, restriction_config: args.restrict_config,
http_proxy,
}; };
let server = WsServer::new(server_config); let server = WsServer::new(server_config);

View file

@ -1,17 +1,24 @@
mod sock5; use tokio::io::{AsyncRead, AsyncWrite};
mod tcp; use url::Url;
mod udp;
pub use sock5::Socks5TunnelConnector; pub use sock5::Socks5TunnelConnector;
pub use tcp::TcpTunnelConnector; pub use tcp::TcpTunnelConnector;
pub use udp::UdpTunnelConnector; pub use udp::UdpTunnelConnector;
use crate::tunnel::RemoteAddr; use crate::tunnel::RemoteAddr;
use tokio::io::{AsyncRead, AsyncWrite};
mod sock5;
mod tcp;
mod udp;
pub trait TunnelConnector { pub trait TunnelConnector {
type Reader: AsyncRead + Send + 'static; type Reader: AsyncRead + Send + 'static;
type Writer: AsyncWrite + Send + 'static; type Writer: AsyncWrite + Send + 'static;
async fn connect(&self, remote: &Option<RemoteAddr>) -> anyhow::Result<(Self::Reader, Self::Writer)>; async fn connect(&self, remote: &Option<RemoteAddr>) -> anyhow::Result<(Self::Reader, Self::Writer)>;
async fn connect_with_http_proxy(
&self,
proxy: &Url,
remote: &Option<RemoteAddr>,
) -> anyhow::Result<(Self::Reader, Self::Writer)>;
} }

View file

@ -6,6 +6,7 @@ use std::time::Duration;
use anyhow::anyhow; use anyhow::anyhow;
use tokio::io::{AsyncRead, AsyncWrite, ReadBuf}; use tokio::io::{AsyncRead, AsyncWrite, ReadBuf};
use tokio::net::tcp::{OwnedReadHalf, OwnedWriteHalf}; use tokio::net::tcp::{OwnedReadHalf, OwnedWriteHalf};
use url::Url;
use crate::protocols::dns::DnsResolver; use crate::protocols::dns::DnsResolver;
use crate::protocols::udp; use crate::protocols::udp;
@ -61,6 +62,14 @@ impl TunnelConnector for Socks5TunnelConnector<'_> {
_ => Err(anyhow!("Invalid protocol for reverse socks5 {:?}", remote.protocol)), _ => Err(anyhow!("Invalid protocol for reverse socks5 {:?}", remote.protocol)),
} }
} }
async fn connect_with_http_proxy(
&self,
proxy: &Url,
remote: &Option<RemoteAddr>,
) -> anyhow::Result<(Self::Reader, Self::Writer)> {
Err(anyhow!("SOCKS5 tunneling is not supported with HTTP proxy"))
}
} }
pub enum Socks5Reader { pub enum Socks5Reader {

View file

@ -1,10 +1,12 @@
use std::time::Duration;
use tokio::net::tcp::{OwnedReadHalf, OwnedWriteHalf};
use url::{Host, Url};
use crate::protocols; use crate::protocols;
use crate::protocols::dns::DnsResolver; use crate::protocols::dns::DnsResolver;
use crate::tunnel::connectors::TunnelConnector; use crate::tunnel::connectors::TunnelConnector;
use crate::tunnel::RemoteAddr; use crate::tunnel::RemoteAddr;
use std::time::Duration;
use tokio::net::tcp::{OwnedReadHalf, OwnedWriteHalf};
use url::Host;
pub struct TcpTunnelConnector<'a> { pub struct TcpTunnelConnector<'a> {
host: &'a Host, host: &'a Host,
@ -45,4 +47,26 @@ impl TunnelConnector for TcpTunnelConnector<'_> {
let stream = protocols::tcp::connect(host, port, self.so_mark, self.connect_timeout, self.dns_resolver).await?; let stream = protocols::tcp::connect(host, port, self.so_mark, self.connect_timeout, self.dns_resolver).await?;
Ok(stream.into_split()) Ok(stream.into_split())
} }
async fn connect_with_http_proxy(
&self,
proxy: &Url,
remote: &Option<RemoteAddr>,
) -> anyhow::Result<(Self::Reader, Self::Writer)> {
let (host, port) = match remote {
Some(remote) => (&remote.host, remote.port),
None => (self.host, self.port),
};
let stream = protocols::tcp::connect_with_http_proxy(
proxy,
host,
port,
self.so_mark,
self.connect_timeout,
self.dns_resolver,
)
.await?;
Ok(stream.into_split())
}
} }

View file

@ -1,10 +1,13 @@
use std::time::Duration;
use anyhow::anyhow;
use url::{Host, Url};
use crate::protocols; use crate::protocols;
use crate::protocols::dns::DnsResolver; use crate::protocols::dns::DnsResolver;
use crate::protocols::udp::WsUdpSocket; use crate::protocols::udp::WsUdpSocket;
use crate::tunnel::connectors::TunnelConnector; use crate::tunnel::connectors::TunnelConnector;
use crate::tunnel::RemoteAddr; use crate::tunnel::RemoteAddr;
use std::time::Duration;
use url::Host;
pub struct UdpTunnelConnector<'a> { pub struct UdpTunnelConnector<'a> {
host: &'a Host, host: &'a Host,
@ -43,4 +46,12 @@ impl TunnelConnector for UdpTunnelConnector<'_> {
Ok((stream.clone(), stream)) Ok((stream.clone(), stream))
} }
async fn connect_with_http_proxy(
&self,
proxy: &Url,
remote: &Option<RemoteAddr>,
) -> anyhow::Result<(Self::Reader, Self::Writer)> {
Err(anyhow!("UDP tunneling is not supported with HTTP proxy"))
}
} }

View file

@ -48,7 +48,7 @@ use tokio::sync::mpsc;
use tokio_rustls::rustls::pki_types::{CertificateDer, PrivateKeyDer}; use tokio_rustls::rustls::pki_types::{CertificateDer, PrivateKeyDer};
use tokio_rustls::TlsAcceptor; use tokio_rustls::TlsAcceptor;
use tracing::{error, info, span, warn, Instrument, Level, Span}; use tracing::{error, info, span, warn, Instrument, Level, Span};
use url::Host; use url::{Host, Url};
#[derive(Debug)] #[derive(Debug)]
pub struct TlsServerConfig { pub struct TlsServerConfig {
@ -69,6 +69,7 @@ pub struct WsServerConfig {
pub tls: Option<TlsServerConfig>, pub tls: Option<TlsServerConfig>,
pub dns_resolver: DnsResolver, pub dns_resolver: DnsResolver,
pub restriction_config: Option<PathBuf>, pub restriction_config: Option<PathBuf>,
pub http_proxy: Option<Url>,
} }
#[derive(Clone)] #[derive(Clone)]
@ -172,28 +173,32 @@ impl WsServer {
) -> anyhow::Result<(RemoteAddr, Pin<Box<dyn AsyncRead + Send>>, Pin<Box<dyn AsyncWrite + Send>>)> { ) -> anyhow::Result<(RemoteAddr, Pin<Box<dyn AsyncRead + Send>>, Pin<Box<dyn AsyncWrite + Send>>)> {
match remote.protocol { match remote.protocol {
LocalProtocol::Udp { timeout, .. } => { LocalProtocol::Udp { timeout, .. } => {
let (rx, tx) = UdpTunnelConnector::new( let connector = UdpTunnelConnector::new(
&remote.host, &remote.host,
remote.port, remote.port,
self.config.socket_so_mark, self.config.socket_so_mark,
timeout.unwrap_or(Duration::from_secs(10)), timeout.unwrap_or(Duration::from_secs(10)),
&self.config.dns_resolver, &self.config.dns_resolver,
) );
.connect(&None) let (rx, tx) = match &self.config.http_proxy {
.await?; None => connector.connect(&None).await?,
Some(_) => Err(anyhow!("UDP tunneling is not supported with HTTP proxy"))?,
};
Ok((remote, Box::pin(rx), Box::pin(tx))) Ok((remote, Box::pin(rx), Box::pin(tx)))
} }
LocalProtocol::Tcp { proxy_protocol } => { LocalProtocol::Tcp { proxy_protocol } => {
let (rx, mut tx) = TcpTunnelConnector::new( let connector = TcpTunnelConnector::new(
&remote.host, &remote.host,
remote.port, remote.port,
self.config.socket_so_mark, self.config.socket_so_mark,
Duration::from_secs(10), Duration::from_secs(10),
&self.config.dns_resolver, &self.config.dns_resolver,
) );
.connect(&None) let (rx, mut tx) = match &self.config.http_proxy {
.await?; None => connector.connect(&None).await?,
Some(proxy_url) => connector.connect_with_http_proxy(proxy_url, &None).await?,
};
if proxy_protocol { if proxy_protocol {
let header = ppp::v2::Builder::with_addresses( let header = ppp::v2::Builder::with_addresses(
@ -201,8 +206,8 @@ impl WsServer {
ppp::v2::Protocol::Stream, ppp::v2::Protocol::Stream,
(client_address, tx.local_addr().unwrap()), (client_address, tx.local_addr().unwrap()),
) )
.build() .build()
.unwrap(); .unwrap();
let _ = tx.write_all(&header).await; let _ = tx.write_all(&header).await;
} }