use std::net::SocketAddr;
#[cfg(feature = "tor")]
use std::path::PathBuf;
use std::time::Duration;
#[cfg(feature = "tor")]
use arti_client::DataStream;
use tokio::io::{AsyncRead, AsyncWrite};
use tokio::net::TcpStream;
use tokio::time;
use tokio_tungstenite::tungstenite::protocol::Role;
pub use tokio_tungstenite::tungstenite::Message;
pub use tokio_tungstenite::WebSocketStream;
use url::Url;
mod error;
#[cfg(feature = "socks")]
mod socks;
#[cfg(feature = "tor")]
pub mod tor;
pub use self::error::Error;
#[cfg(feature = "socks")]
use self::socks::TcpSocks5Stream;
use crate::socket::WebSocket;
use crate::ConnectionMode;
pub async fn connect(
url: &Url,
mode: &ConnectionMode,
timeout: Duration,
) -> Result<WebSocket, Error> {
match mode {
ConnectionMode::Direct => connect_direct(url, timeout).await,
#[cfg(feature = "socks")]
ConnectionMode::Proxy(proxy) => connect_proxy(url, *proxy, timeout).await,
#[cfg(feature = "tor")]
ConnectionMode::Tor { custom_path } => {
connect_tor(url, timeout, custom_path.as_ref()).await
}
}
}
const HAPPY_EYEBALLS_DELAY: Duration = Duration::from_millis(250);
async fn connect_direct(url: &Url, timeout: Duration) -> Result<WebSocket, Error> {
let host: &str = url.host_str().ok_or_else(Error::empty_host)?;
let port: u16 = url
.port_or_known_default()
.ok_or_else(Error::invalid_port)?;
let conn_fut = async {
let tcp_stream = happy_eyeballs_connect(host, port).await?;
let (stream, _) = Box::pin(tokio_tungstenite::client_async_tls(
url.as_str(),
tcp_stream,
))
.await?;
Ok::<_, Error>(stream)
};
let stream = time::timeout(timeout, conn_fut)
.await
.map_err(|_| Error::Timeout)??;
Ok(WebSocket::Tokio(stream))
}
async fn happy_eyeballs_connect(host: &str, port: u16) -> Result<TcpStream, Error> {
let addrs: Vec<SocketAddr> = tokio::net::lookup_host((host, port)).await?.collect();
if addrs.is_empty() {
return Err(std::io::Error::new(
std::io::ErrorKind::AddrNotAvailable,
"DNS resolution returned no addresses",
)
.into());
}
let mut ipv6: Vec<SocketAddr> = Vec::new();
let mut ipv4: Vec<SocketAddr> = Vec::new();
for addr in addrs {
if addr.is_ipv6() {
ipv6.push(addr);
} else {
ipv4.push(addr);
}
}
if ipv4.is_empty() {
return try_addrs_sequential(&ipv6).await;
}
if ipv6.is_empty() {
return try_addrs_sequential(&ipv4).await;
}
let ipv6_first = ipv6[0];
let ipv4_first = ipv4[0];
let ipv6_fut = TcpStream::connect(ipv6_first);
tokio::pin!(ipv6_fut);
tokio::select! {
result = &mut ipv6_fut => {
return match result {
Ok(stream) => Ok(stream),
Err(_) => try_addrs_sequential(&ipv4).await,
}
}
_ = tokio::time::sleep(HAPPY_EYEBALLS_DELAY) => {
}
}
let ipv4_fut = TcpStream::connect(ipv4_first);
tokio::pin!(ipv4_fut);
let mut ipv6_done = false;
let mut ipv4_done = false;
loop {
tokio::select! {
result = &mut ipv6_fut, if !ipv6_done => {
match result {
Ok(stream) => return Ok(stream),
Err(_) => { ipv6_done = true; }
}
}
result = &mut ipv4_fut, if !ipv4_done => {
match result {
Ok(stream) => return Ok(stream),
Err(_) => { ipv4_done = true; }
}
}
}
if ipv6_done && ipv4_done {
break;
}
}
let mut remaining = Vec::new();
let ipv4_remaining = ipv4.iter().skip(1);
let ipv6_remaining = ipv6.iter().skip(1);
let mut ipv4_iter = ipv4_remaining.peekable();
let mut ipv6_iter = ipv6_remaining.peekable();
while ipv4_iter.peek().is_some() || ipv6_iter.peek().is_some() {
if let Some(addr) = ipv4_iter.next() {
remaining.push(*addr);
}
if let Some(addr) = ipv6_iter.next() {
remaining.push(*addr);
}
}
try_addrs_sequential(&remaining).await
}
async fn try_addrs_sequential(addrs: &[SocketAddr]) -> Result<TcpStream, Error> {
let mut last_err = None;
for addr in addrs {
match TcpStream::connect(addr).await {
Ok(stream) => return Ok(stream),
Err(e) => last_err = Some(e),
}
}
Err(last_err
.unwrap_or_else(|| {
std::io::Error::new(
std::io::ErrorKind::AddrNotAvailable,
"no addresses to connect to",
)
})
.into())
}
#[cfg(feature = "socks")]
async fn connect_proxy(
url: &Url,
proxy: SocketAddr,
timeout: Duration,
) -> Result<WebSocket, Error> {
let host: &str = url.host_str().ok_or_else(Error::empty_host)?;
let port: u16 = url
.port_or_known_default()
.ok_or_else(Error::invalid_port)?;
let addr: String = format!("{host}:{port}");
let conn: TcpStream = TcpSocks5Stream::connect(proxy, addr).await?;
let (stream, _) = Box::pin(time::timeout(
timeout,
tokio_tungstenite::client_async_tls(url.as_str(), conn),
))
.await
.map_err(|_| Error::Timeout)??;
Ok(WebSocket::Tokio(stream))
}
#[cfg(feature = "tor")]
async fn connect_tor(
url: &Url,
timeout: Duration,
custom_path: Option<&PathBuf>,
) -> Result<WebSocket, Error> {
let host: &str = url.host_str().ok_or_else(Error::empty_host)?;
let port: u16 = url
.port_or_known_default()
.ok_or_else(Error::invalid_port)?;
let conn: DataStream = tor::connect(host, port, custom_path).await?;
let (stream, _) = Box::pin(time::timeout(
timeout,
tokio_tungstenite::client_async_tls(url.as_str(), conn),
))
.await
.map_err(|_| Error::Timeout)??;
Ok(WebSocket::Tor(stream))
}
#[inline]
pub async fn accept<S>(raw_stream: S) -> Result<WebSocketStream<S>, Error>
where
S: AsyncRead + AsyncWrite + Unpin,
{
Ok(tokio_tungstenite::accept_async(raw_stream).await?)
}
#[inline]
pub async fn take_upgraded<S>(raw_stream: S) -> WebSocketStream<S>
where
S: AsyncRead + AsyncWrite + Unpin,
{
WebSocketStream::from_raw_socket(raw_stream, Role::Server, None).await
}