use std::future::Future;
use std::net::SocketAddr;
use std::path::{Path, PathBuf};
use std::str::FromStr;
use std::sync::Arc;
use brane_cfg::certs::{load_certstore, load_identity};
use brane_cfg::info::Info as _;
use brane_cfg::node::{NodeConfig, NodeSpecificConfig};
use brane_cfg::proxy::ProxyProtocol;
use log::{debug, error, info};
use never_say_never::Never;
use rustls::client::ClientConfig;
use rustls::{Certificate, ConfigBuilder, PrivateKey, RootCertStore, ServerName};
use socksx::{Socks5Client, Socks6Client};
use specifications::address::Address;
use tokio::net::{TcpListener, TcpStream};
use tokio_rustls::TlsConnector;
use tokio_rustls::client::TlsStream;
use url::Url;
pub use crate::errors::RedirectError as Error;
use crate::spec::{Context, NewPathRequestTlsOptions};
pub enum RemoteClient {
Direct,
Socks5(Socks5Client, Address),
Socks6(Socks6Client, Address),
}
impl RemoteClient {
async fn connect(&self, address: impl AsRef<str>) -> Result<TcpStream, Error> {
let address: &str = address.as_ref();
use RemoteClient::*;
match self {
Direct => match TcpStream::connect(address).await {
Ok(conn) => Ok(conn),
Err(err) => Err(Error::TcpStreamConnectError { address: address.into(), err }),
},
Socks5(client, proxy) => match client.connect(address.to_string()).await {
Ok((conn, addr)) => {
debug!("{:?}", addr);
Ok(conn)
},
Err(err) => Err(Error::Socks5ConnectError { address: address.into(), proxy: proxy.clone(), err }),
},
Socks6(client, proxy) => match client.connect(address.to_string(), None, None).await {
Ok((conn, addr)) => {
debug!("{:?}", addr);
Ok(conn)
},
Err(err) => Err(Error::Socks6ConnectError { address: address.into(), proxy: proxy.clone(), err }),
},
}
}
}
pub async fn path_server_factory(
context: &Arc<Context>,
socket_addr: SocketAddr,
remote_addr: String,
tls: Option<NewPathRequestTlsOptions>,
) -> Result<impl Future<Output = Never>, Error> {
let remote_addr: Url = match Url::from_str(&remote_addr) {
Ok(url) => url,
Err(err) => {
return Err(Error::IllegalUrl { raw: remote_addr, err });
},
};
let hostname: &str = match remote_addr.domain() {
Some(hostname) => hostname,
None => {
return Err(Error::NoDomainName { raw: remote_addr.to_string() });
},
};
let tls: Option<(ServerName, NewPathRequestTlsOptions)> = if let Some(tls) = tls {
match ServerName::try_from(hostname) {
Ok(name) => {
if !matches!(name, ServerName::DnsName(_)) {
return Err(Error::TlsWithNonHostnameError { kind: hostname.into() });
}
Some((name, tls))
},
Err(err) => {
return Err(Error::IllegalServerName { raw: hostname.into(), err });
},
}
} else {
None
};
let listener: TcpListener = match TcpListener::bind(socket_addr).await {
Ok(listener) => listener,
Err(err) => {
return Err(Error::ListenerCreateError { address: socket_addr, err });
},
};
if let Some(proxy_cfg) = &context.proxy.forward {
let client: RemoteClient = match proxy_cfg.protocol {
ProxyProtocol::Socks5 => {
match Socks5Client::new(proxy_cfg.address.to_string(), None).await {
Ok(client) => RemoteClient::Socks5(client, proxy_cfg.address.clone()),
Err(err) => {
return Err(Error::Socks5CreateError { address: proxy_cfg.address.clone(), err });
},
}
},
ProxyProtocol::Socks6 => {
match Socks6Client::new(proxy_cfg.address.to_string(), None).await {
Ok(client) => RemoteClient::Socks6(client, proxy_cfg.address.clone()),
Err(err) => {
return Err(Error::Socks6CreateError { address: proxy_cfg.address.clone(), err });
},
}
},
};
Ok(path_server(context.node_config_path.clone(), listener, client, socket_addr, remote_addr, tls))
} else {
Ok(path_server(context.node_config_path.clone(), listener, RemoteClient::Direct, socket_addr, remote_addr, tls))
}
}
pub async fn path_server(
node_config_path: PathBuf,
listener: TcpListener,
client: RemoteClient,
socket_addr: SocketAddr,
address: Url,
tls: Option<(ServerName, NewPathRequestTlsOptions)>,
) -> Never {
info!("Initiated new path ':{}' to '{}'", socket_addr, address);
loop {
debug!(":{}->{}: Ready for new connection", socket_addr.port(), address);
let (mut iconn, client_addr): (TcpStream, SocketAddr) = match listener.accept().await {
Ok(res) => res,
Err(err) => {
error!(":{}->{}: Failed to accept incoming request: {}", socket_addr.port(), address, err);
continue;
},
};
debug!(":{}->{}: Got new connection from '{}'", socket_addr.port(), address, client_addr);
let addr: String = format!("{}:{}", address.domain().unwrap(), address.port().unwrap());
debug!("Connecting to '{}'...", addr);
let mut oconn: TcpStream = match client.connect(&addr).await {
Ok(oconn) => oconn,
Err(err) => {
error!(":{}->{}: Failed to connect to remote '{}': {}", socket_addr.port(), address, addr, err);
continue;
},
};
if let Some((domain, tls)) = &tls {
debug!(":{}->{}: Setting up TLS for location '{}'...", socket_addr.port(), address, tls.location);
let node_config: NodeConfig = match NodeConfig::from_path(&node_config_path) {
Ok(config) => config,
Err(err) => {
error!(":{}->{}: Failed to load NodeConfig file: {}", socket_addr.port(), address, err);
std::process::exit(1);
},
};
let cert_path: &Path = match &node_config.node {
NodeSpecificConfig::Central(node) => &node.paths.certs,
NodeSpecificConfig::Worker(node) => &node.paths.certs,
NodeSpecificConfig::Proxy(node) => &node.paths.certs,
};
let ca_path: PathBuf = cert_path.join(&tls.location).join("ca.pem");
let ca: RootCertStore = match load_certstore(&ca_path) {
Ok(store) => store,
Err(err) => {
error!(
":{}->{}: Failed to load root certificate '{}' for location '{}': {}",
socket_addr.port(),
address,
ca_path.display(),
tls.location,
err
);
continue;
},
};
let client: Option<(PathBuf, Vec<Certificate>, PrivateKey)> = if tls.use_client_auth {
debug!(":{}->{}: Adding client certificate...", socket_addr.port(), address);
let client_path: PathBuf = cert_path.join(&tls.location).join("client-id.pem");
match load_identity(&client_path) {
Ok((certs, key)) => Some((client_path, certs, key)),
Err(err) => {
error!(
":{}->{}: Failed to load client identity file '{}' for location '{}': {}",
socket_addr.port(),
address,
client_path.display(),
tls.location,
err
);
continue;
},
}
} else {
None
};
let config: ConfigBuilder<_, _> = ClientConfig::builder().with_safe_defaults().with_root_certificates(ca);
let config: ClientConfig = if let Some((path, certs, key)) = client {
match config.with_client_auth_cert(certs, key) {
Ok(config) => config,
Err(err) => {
error!(
":{}->{}: Failed to build client config from '{}' and '{}': {}",
socket_addr.port(),
address,
ca_path.display(),
path.display(),
err
);
continue;
},
}
} else {
config.with_no_client_auth()
};
debug!(":{}->{}: Negotiating TLS...", socket_addr.port(), address);
let connector: TlsConnector = TlsConnector::from(Arc::new(config));
let mut oconn: TlsStream<TcpStream> = match connector.connect(domain.clone(), oconn).await {
Ok(oconn) => oconn,
Err(err) => {
error!(":{}->{}: Failed to start a TLS connection with '{}': {}", socket_addr.port(), address, addr, err);
continue;
},
};
debug!(":{}->{}: Bidirectional link started", socket_addr.port(), address);
if let Err(err) = tokio::io::copy_bidirectional(&mut iconn, &mut oconn).await {
error!(":{}->{}: Bidirectional link failed: {}", socket_addr.port(), address, err);
continue;
}
debug!(":{}->{}: Bidirectional link completed", socket_addr.port(), address);
} else {
debug!(":{}->{}: Bidirectional link started", socket_addr.port(), address);
if let Err(err) = tokio::io::copy_bidirectional(&mut iconn, &mut oconn).await {
error!(":{}->{}: Bidirectional link failed: {}", socket_addr.port(), address, err);
continue;
}
debug!(":{}->{}: Bidirectional link completed", socket_addr.port(), address);
}
}
}