use std::{io, sync::Arc};
use shadowsocks::{
context::SharedContext,
net::ConnectOpts,
relay::socks5::Address,
};
use crate::config::{OutboundProxy, OutboundProxyProtocol};
pub mod auth;
pub mod chain;
#[cfg(feature = "local-http")]
pub mod http_connect;
pub mod socks5;
pub mod stream;
pub mod tls;
pub mod udp;
pub use auth::{HttpProxyAuth, Socks5Auth};
pub use socks5::Socks5Negotiator;
pub use stream::OutboundProxyStream;
pub use udp::OutboundProxyDatagram;
#[cfg(feature = "local-http")]
pub use http_connect::{HttpConnectClient, HttpConnectTunnel};
#[trait_variant::make(Send)]
pub trait TcpDialer {
async fn dial(&self, addr: &Address) -> io::Result<shadowsocks::net::TcpStream>;
}
#[derive(Debug, Clone)]
pub struct OutboundProxyHop {
pub addr: Address,
pub kind: OutboundProxyKind,
}
impl OutboundProxyHop {
fn is_https(&self) -> bool {
matches!(self.kind, OutboundProxyKind::Https { .. })
}
#[cfg(any(feature = "local-http-native-tls", feature = "local-http-rustls"))]
fn tls_sni(&self) -> &str {
match &self.kind {
OutboundProxyKind::Https { sni, .. } => sni,
_ => unreachable!("tls_sni called on non-HTTPS hop"),
}
}
#[cfg(not(any(feature = "local-http-native-tls", feature = "local-http-rustls")))]
fn tls_sni(&self) -> &str {
""
}
fn supports_udp(&self) -> bool {
matches!(self.kind, OutboundProxyKind::Socks5 { .. })
}
}
#[derive(Debug, Clone)]
#[non_exhaustive]
pub enum OutboundProxyKind {
Socks5 { auth: Socks5Auth },
Http { auth: HttpProxyAuth },
Https {
auth: HttpProxyAuth,
sni: String,
},
}
#[derive(Debug, Clone)]
pub struct OutboundProxyClient {
hops: Arc<[OutboundProxyHop]>,
}
impl OutboundProxyClient {
pub fn from_config(proxies: &[OutboundProxy]) -> Self {
let hops: Vec<OutboundProxyHop> = proxies.iter().map(hop_from_config).collect();
Self { hops: hops.into() }
}
pub fn hops(&self) -> &[OutboundProxyHop] {
&self.hops
}
pub fn len(&self) -> usize {
self.hops.len()
}
pub fn is_empty(&self) -> bool {
self.hops.is_empty()
}
pub fn supports_udp(&self) -> bool {
!self.hops.is_empty() && self.hops.iter().all(OutboundProxyHop::supports_udp)
}
pub async fn associate_udp<D>(
&self,
context: &SharedContext,
dialer: &D,
connect_opts: &ConnectOpts,
target: Address,
) -> io::Result<OutboundProxyDatagram>
where
D: TcpDialer + Sync,
{
OutboundProxyDatagram::associate(self, context, dialer, connect_opts, target).await
}
}
fn hop_from_config(proxy: &OutboundProxy) -> OutboundProxyHop {
let addr = proxy.address();
let kind = match proxy.protocol {
OutboundProxyProtocol::Socks5 => {
let auth = match proxy.auth.as_ref() {
Some(a) => Socks5Auth::username_password(a.username.as_bytes(), a.password.as_bytes()),
None => Socks5Auth::None,
};
OutboundProxyKind::Socks5 { auth }
}
OutboundProxyProtocol::Http => OutboundProxyKind::Http {
auth: http_auth_from_config(proxy),
},
OutboundProxyProtocol::Https => OutboundProxyKind::Https {
auth: http_auth_from_config(proxy),
sni: proxy.host.clone(),
},
};
OutboundProxyHop { addr, kind }
}
fn http_auth_from_config(proxy: &OutboundProxy) -> HttpProxyAuth {
match proxy.auth.as_ref() {
Some(a) => HttpProxyAuth::basic(a.username.clone(), a.password.clone()),
None => HttpProxyAuth::None,
}
}