use std::future::Future;
use std::io;
use std::net::SocketAddr;
use std::pin::Pin;
use std::sync::Arc;
use std::task::{ready, Context, Poll, Waker};
use std::time::Duration;
use tokio::io::{AsyncRead, AsyncWrite, ReadBuf};
use tokio::net::{TcpListener, TcpStream};
use tokio_rustls::rustls::{pki_types::ServerName, ClientConfig, ServerConfig};
use tokio_rustls::{TlsAcceptor, TlsConnector};
use super::{AcceptFuture, Acceptor, BindFuture, BoxStream, ConnectFuture, Network, NetworkError};
#[derive(Clone)]
pub struct TlsNetwork {
server: Arc<ServerConfig>,
client: Arc<ClientConfig>,
server_name: ServerName<'static>,
handshake_timeout: Duration,
}
impl std::fmt::Debug for TlsNetwork {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("TlsNetwork")
.field("server_name", &self.server_name)
.field("handshake_timeout", &self.handshake_timeout)
.finish_non_exhaustive()
}
}
impl TlsNetwork {
pub fn new(
server: Arc<ServerConfig>,
client: Arc<ClientConfig>,
server_name: ServerName<'static>,
handshake_timeout: Duration,
) -> Result<Self, NetworkError> {
if handshake_timeout.is_zero() {
return Err(NetworkError("TLS handshake timeout must be nonzero".into()));
}
Ok(Self {
server,
client,
server_name,
handshake_timeout,
})
}
pub fn server_name(&self) -> &ServerName<'static> {
&self.server_name
}
pub fn handshake_timeout(&self) -> Duration {
self.handshake_timeout
}
}
struct TlsAcceptorState {
listener: TcpListener,
acceptor: TlsAcceptor,
timeout: Duration,
}
impl Acceptor for TlsAcceptorState {
fn accept(&mut self) -> AcceptFuture<'_> {
Box::pin(async move {
let (stream, _) = self.listener.accept().await?;
if let Err(error) = stream.set_nodelay(true) {
tracing::debug!(%error, "failed to set TCP_NODELAY on accepted TLS connection");
}
let acceptor = self.acceptor.clone();
let timeout = self.timeout;
let handshake = Box::pin(async move {
tokio::time::timeout(timeout, acceptor.accept(stream))
.await
.map_err(|_| io::Error::new(io::ErrorKind::TimedOut, "TLS accept timed out"))?
});
Ok(Box::pin(InboundTlsStream {
state: InboundState::Handshaking(handshake),
waiters: Vec::new(),
}) as BoxStream)
})
}
}
impl Network for TlsNetwork {
fn bind(&self, addr: SocketAddr) -> BindFuture<'_> {
Box::pin(async move {
let listener = TcpListener::bind(addr).await?;
let bound = listener.local_addr()?;
Ok((
bound,
Box::new(TlsAcceptorState {
listener,
acceptor: TlsAcceptor::from(self.server.clone()),
timeout: self.handshake_timeout,
}) as Box<dyn Acceptor>,
))
})
}
fn connect(&self, addr: SocketAddr) -> ConnectFuture<'_> {
Box::pin(async move {
let stream = tokio::time::timeout(self.handshake_timeout, async {
let tcp = TcpStream::connect(addr).await?;
if let Err(error) = tcp.set_nodelay(true) {
tracing::debug!(%error, "failed to set TCP_NODELAY on outbound TLS connection");
}
TlsConnector::from(self.client.clone())
.connect(self.server_name.clone(), tcp)
.await
.map_err(|error| io::Error::other(error.to_string()))
})
.await
.map_err(|_| NetworkError("TLS connect timed out".into()))??;
Ok(Box::pin(stream) as BoxStream)
})
}
}
type ServerStream = tokio_rustls::server::TlsStream<TcpStream>;
type HandshakeFuture = Pin<Box<dyn Future<Output = io::Result<ServerStream>> + Send>>;
enum InboundState {
Handshaking(HandshakeFuture),
Ready(Box<ServerStream>),
Failed,
}
struct InboundTlsStream {
state: InboundState,
waiters: Vec<Waker>,
}
impl InboundTlsStream {
fn poll_stream(&mut self, cx: &mut Context<'_>) -> Poll<io::Result<&mut ServerStream>> {
if let InboundState::Handshaking(handshake) = &mut self.state {
let result = match handshake.as_mut().poll(cx) {
Poll::Pending => {
if !self.waiters.iter().any(|waker| waker.will_wake(cx.waker())) {
self.waiters.push(cx.waker().clone());
}
return Poll::Pending;
}
Poll::Ready(result) => result,
};
self.wake_waiters();
self.state = match result {
Ok(stream) => InboundState::Ready(Box::new(stream)),
Err(error) => {
self.state = InboundState::Failed;
return Poll::Ready(Err(error));
}
};
}
match &mut self.state {
InboundState::Ready(stream) => Poll::Ready(Ok(stream)),
InboundState::Failed | InboundState::Handshaking(_) => Poll::Ready(Err(
io::Error::new(io::ErrorKind::BrokenPipe, "TLS stream closed"),
)),
}
}
fn wake_waiters(&mut self) {
for waiter in self.waiters.drain(..) {
waiter.wake();
}
}
}
impl AsyncRead for InboundTlsStream {
fn poll_read(
mut self: Pin<&mut Self>,
cx: &mut Context<'_>,
buf: &mut ReadBuf<'_>,
) -> Poll<io::Result<()>> {
Pin::new(ready!(self.poll_stream(cx))?).poll_read(cx, buf)
}
}
impl AsyncWrite for InboundTlsStream {
fn poll_write(
mut self: Pin<&mut Self>,
cx: &mut Context<'_>,
buf: &[u8],
) -> Poll<io::Result<usize>> {
Pin::new(ready!(self.poll_stream(cx))?).poll_write(cx, buf)
}
fn poll_flush(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<io::Result<()>> {
Pin::new(ready!(self.poll_stream(cx))?).poll_flush(cx)
}
fn poll_shutdown(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<io::Result<()>> {
match &mut self.state {
InboundState::Ready(stream) => Pin::new(stream).poll_shutdown(cx),
InboundState::Handshaking(_) | InboundState::Failed => {
self.state = InboundState::Failed;
self.wake_waiters();
Poll::Ready(Ok(()))
}
}
}
}