use crate::error::{Error, Result};
use http::Uri;
use hyper::rt::{Read, ReadBufCursor, Write};
use hyper_util::client::legacy::connect::{Connected, Connection};
use std::future::Future;
use std::io;
use std::pin::Pin;
use std::sync::Arc;
use std::task::{Context, Poll};
use tokio::io::{AsyncRead, AsyncWrite, ReadBuf};
use tokio_rustls::client::TlsStream;
use tokio_rustls::TlsConnector;
use tower_service::Service;
use wireguard_netstack::{DohResolver, DohServerConfig, NetStack, TcpConnection};
#[derive(Clone)]
pub struct WgConnector {
netstack: Arc<NetStack>,
tls_connector: TlsConnector,
doh_resolver: Arc<DohResolver>,
}
impl WgConnector {
pub fn new(netstack: Arc<NetStack>) -> Self {
Self::with_dns(netstack, DohServerConfig::default())
}
pub fn with_dns(netstack: Arc<NetStack>, dns_config: DohServerConfig) -> Self {
let _ = rustls::crypto::ring::default_provider().install_default();
let root_store =
rustls::RootCertStore::from_iter(webpki_roots::TLS_SERVER_ROOTS.iter().cloned());
let tls_config = rustls::ClientConfig::builder()
.with_root_certificates(root_store)
.with_no_client_auth();
let tls_connector = TlsConnector::from(Arc::new(tls_config));
let doh_resolver = Arc::new(DohResolver::new_tunneled_with_config(netstack.clone(), dns_config));
Self {
netstack,
tls_connector,
doh_resolver,
}
}
pub fn doh_resolver(&self) -> &Arc<DohResolver> {
&self.doh_resolver
}
}
impl Service<Uri> for WgConnector {
type Response = WgTlsStream;
type Error = Error;
type Future = Pin<Box<dyn Future<Output = Result<Self::Response>> + Send>>;
fn poll_ready(&mut self, _cx: &mut Context<'_>) -> Poll<Result<()>> {
Poll::Ready(Ok(()))
}
fn call(&mut self, uri: Uri) -> Self::Future {
let netstack = self.netstack.clone();
let tls_connector = self.tls_connector.clone();
let doh_resolver = self.doh_resolver.clone();
Box::pin(async move {
let host = uri
.host()
.ok_or_else(|| Error::NoHost(uri.to_string()))?;
let is_https = uri.scheme_str() == Some("https");
let port = uri.port_u16().unwrap_or(if is_https { 443 } else { 80 });
log::info!("Connecting to {}:{} (TLS: {})", host, port, is_https);
let addr = doh_resolver.resolve_addr(host, port).await?;
log::info!("Resolved {} to {} via DoH", host, addr);
let tcp_conn = TcpConnection::connect(netstack, addr)
.await
.map_err(|e| Error::TcpConnect(e.to_string()))?;
let tcp_stream = WgStream {
conn: Arc::new(tcp_conn),
};
if is_https {
let server_name = rustls::pki_types::ServerName::try_from(host.to_string())
.map_err(|e| Error::InvalidServerName(e.to_string()))?;
log::debug!("Starting TLS handshake with {}", host);
let tls_stream = tls_connector
.connect(server_name, tcp_stream)
.await
.map_err(|e| Error::TlsHandshake(e.to_string()))?;
log::info!("TLS handshake completed with {}", host);
Ok(WgTlsStream::Tls(Box::new(tls_stream)))
} else {
Ok(WgTlsStream::Plain(tcp_stream))
}
})
}
}
pub struct WgStream {
conn: Arc<TcpConnection>,
}
pub enum WgTlsStream {
Plain(WgStream),
Tls(Box<TlsStream<WgStream>>),
}
impl Connection for WgTlsStream {
fn connected(&self) -> Connected {
Connected::new()
}
}
impl AsyncRead for WgStream {
fn poll_read(
self: Pin<&mut Self>,
cx: &mut Context<'_>,
buf: &mut ReadBuf<'_>,
) -> Poll<io::Result<()>> {
let conn = self.conn.clone();
let unfilled = buf.initialize_unfilled();
conn.netstack.poll();
let can_recv = conn.netstack.can_recv(conn.handle);
log::trace!(
"WgStream poll_read: can_recv={}, buf_len={}",
can_recv,
unfilled.len()
);
if can_recv {
match conn.netstack.recv(conn.handle, unfilled) {
Ok(n) if n > 0 => {
log::debug!("WgStream read {} bytes", n);
buf.advance(n);
return Poll::Ready(Ok(()));
}
Ok(_) => {
log::trace!("WgStream recv returned 0 bytes");
}
Err(e) => {
log::error!("WgStream recv error: {}", e);
return Poll::Ready(Err(io::Error::new(io::ErrorKind::Other, e.to_string())));
}
}
}
if !conn.netstack.may_recv(conn.handle) {
log::debug!("WgStream: connection closed (may_recv=false)");
return Poll::Ready(Ok(()));
}
let waker = cx.waker().clone();
tokio::spawn(async move {
tokio::time::sleep(std::time::Duration::from_millis(1)).await;
waker.wake();
});
Poll::Pending
}
}
impl AsyncWrite for WgStream {
fn poll_write(
self: Pin<&mut Self>,
cx: &mut Context<'_>,
buf: &[u8],
) -> Poll<io::Result<usize>> {
let conn = self.conn.clone();
conn.netstack.poll();
if conn.netstack.can_send(conn.handle) {
match conn.netstack.send(conn.handle, buf) {
Ok(n) => {
conn.netstack.poll();
return Poll::Ready(Ok(n));
}
Err(e) => {
return Poll::Ready(Err(io::Error::new(io::ErrorKind::Other, e.to_string())));
}
}
}
if !conn.netstack.may_send(conn.handle) {
return Poll::Ready(Err(io::Error::new(
io::ErrorKind::BrokenPipe,
"Connection closed",
)));
}
let waker = cx.waker().clone();
tokio::spawn(async move {
tokio::time::sleep(std::time::Duration::from_millis(1)).await;
waker.wake();
});
Poll::Pending
}
fn poll_flush(self: Pin<&mut Self>, _cx: &mut Context<'_>) -> Poll<io::Result<()>> {
self.conn.netstack.poll();
Poll::Ready(Ok(()))
}
fn poll_shutdown(self: Pin<&mut Self>, _cx: &mut Context<'_>) -> Poll<io::Result<()>> {
self.conn.shutdown();
self.conn.netstack.poll();
Poll::Ready(Ok(()))
}
}
impl AsyncRead for WgTlsStream {
fn poll_read(
self: Pin<&mut Self>,
cx: &mut Context<'_>,
buf: &mut ReadBuf<'_>,
) -> Poll<io::Result<()>> {
match self.get_mut() {
WgTlsStream::Plain(stream) => Pin::new(stream).poll_read(cx, buf),
WgTlsStream::Tls(stream) => Pin::new(stream.as_mut()).poll_read(cx, buf),
}
}
}
impl AsyncWrite for WgTlsStream {
fn poll_write(
self: Pin<&mut Self>,
cx: &mut Context<'_>,
buf: &[u8],
) -> Poll<io::Result<usize>> {
match self.get_mut() {
WgTlsStream::Plain(stream) => Pin::new(stream).poll_write(cx, buf),
WgTlsStream::Tls(stream) => Pin::new(stream.as_mut()).poll_write(cx, buf),
}
}
fn poll_flush(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<io::Result<()>> {
match self.get_mut() {
WgTlsStream::Plain(stream) => Pin::new(stream).poll_flush(cx),
WgTlsStream::Tls(stream) => Pin::new(stream.as_mut()).poll_flush(cx),
}
}
fn poll_shutdown(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<io::Result<()>> {
match self.get_mut() {
WgTlsStream::Plain(stream) => Pin::new(stream).poll_shutdown(cx),
WgTlsStream::Tls(stream) => Pin::new(stream.as_mut()).poll_shutdown(cx),
}
}
}
impl Read for WgTlsStream {
fn poll_read(
self: Pin<&mut Self>,
cx: &mut Context<'_>,
mut buf: ReadBufCursor<'_>,
) -> Poll<io::Result<()>> {
let mut temp_buf = [0u8; 8192];
let unfilled_len = unsafe { buf.as_mut().len() };
let read_len = temp_buf.len().min(unfilled_len);
let mut read_buf = ReadBuf::new(&mut temp_buf[..read_len]);
match <Self as AsyncRead>::poll_read(self, cx, &mut read_buf) {
Poll::Ready(Ok(())) => {
let filled = read_buf.filled();
if !filled.is_empty() {
unsafe {
let unfilled = buf.as_mut();
for (i, byte) in filled.iter().enumerate() {
unfilled[i].write(*byte);
}
buf.advance(filled.len());
}
}
Poll::Ready(Ok(()))
}
Poll::Ready(Err(e)) => Poll::Ready(Err(e)),
Poll::Pending => Poll::Pending,
}
}
}
impl Write for WgTlsStream {
fn poll_write(
self: Pin<&mut Self>,
cx: &mut Context<'_>,
buf: &[u8],
) -> Poll<io::Result<usize>> {
<Self as AsyncWrite>::poll_write(self, cx, buf)
}
fn poll_flush(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<io::Result<()>> {
<Self as AsyncWrite>::poll_flush(self, cx)
}
fn poll_shutdown(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<io::Result<()>> {
<Self as AsyncWrite>::poll_shutdown(self, cx)
}
}