use std::{
future::Future,
io,
net::SocketAddr,
pin::Pin,
sync::Arc,
task::{Context, Poll},
time::Duration,
};
use async_io::{Async, Timer};
use async_net::{TcpStream, UdpSocket};
use async_trait::async_trait;
use futures_io::{AsyncRead, AsyncWrite};
use futures_util::{FutureExt, future::Either, future::select, pin_mut};
use hickory_resolver::net::runtime::{
DnsTcpStream, DnsUdpSocket, RuntimeProvider, Spawn as HickorySpawn, Time,
};
use crate::transport::Spawn;
const CONNECT_TIMEOUT: Duration = Duration::from_secs(5);
#[derive(Clone)]
pub struct AsyncIoRuntimeProvider {
spawn: Spawn,
}
impl AsyncIoRuntimeProvider {
pub(super) fn new(spawn: Spawn) -> Self {
Self { spawn }
}
}
impl RuntimeProvider for AsyncIoRuntimeProvider {
type Handle = AsyncIoHandle;
type Timer = AsyncIoTime;
type Udp = AsyncIoUdpSocket;
type Tcp = AsyncIoTcpStream;
fn create_handle(&self) -> Self::Handle {
AsyncIoHandle(self.spawn.clone())
}
fn connect_tcp(
&self,
server_addr: SocketAddr,
bind_addr: Option<SocketAddr>,
wait_for: Option<Duration>,
) -> Pin<Box<dyn Send + Future<Output = io::Result<Self::Tcp>>>> {
Box::pin(async move {
let connect = async move {
let stream = match bind_addr {
Some(bind_addr) => connect_bound(server_addr, bind_addr).await?,
None => TcpStream::connect(server_addr).await?,
};
stream.set_nodelay(true)?;
Ok(AsyncIoTcpStream(stream))
};
AsyncIoTime::timeout(wait_for.unwrap_or(CONNECT_TIMEOUT), connect).await?
})
}
fn bind_udp(
&self,
local_addr: SocketAddr,
_server_addr: SocketAddr,
) -> Pin<Box<dyn Send + Future<Output = io::Result<Self::Udp>>>> {
Box::pin(async move {
let socket = UdpSocket::bind(local_addr).await?;
Ok(AsyncIoUdpSocket(socket.into()))
})
}
}
async fn connect_bound(server_addr: SocketAddr, bind_addr: SocketAddr) -> io::Result<TcpStream> {
let domain = if server_addr.is_ipv4() {
socket2::Domain::IPV4
} else {
socket2::Domain::IPV6
};
let socket = socket2::Socket::new(domain, socket2::Type::STREAM, None)?;
socket.set_nonblocking(true)?;
socket.bind(&bind_addr.into())?;
match socket.connect(&server_addr.into()) {
Err(error) if in_progress(&error) => {}
result => result?,
}
let stream = Async::new_nonblocking(std::net::TcpStream::from(socket))?;
stream.writable().await?;
if let Some(error) = stream.get_ref().take_error()? {
return Err(error);
}
Ok(stream.into())
}
#[cfg(unix)]
fn in_progress(error: &io::Error) -> bool {
error.raw_os_error() == Some(libc::EINPROGRESS)
}
#[cfg(not(unix))]
fn in_progress(error: &io::Error) -> bool {
error.kind() == io::ErrorKind::WouldBlock
}
#[derive(Clone)]
pub struct AsyncIoHandle(Spawn);
impl HickorySpawn for AsyncIoHandle {
fn spawn_bg(&mut self, future: impl Future<Output = ()> + Send + 'static) {
(self.0)(Box::pin(future));
}
}
#[derive(Clone, Copy, Debug)]
pub struct AsyncIoTime;
#[async_trait]
impl Time for AsyncIoTime {
async fn delay_for(duration: Duration) {
Timer::after(duration).await;
}
async fn timeout<F: 'static + Future + Send>(
duration: Duration,
future: F,
) -> Result<F::Output, io::Error> {
let timer = Timer::after(duration);
pin_mut!(future);
pin_mut!(timer);
match select(future.fuse(), timer.fuse()).await {
Either::Left((output, _)) => Ok(output),
Either::Right(_) => Err(io::Error::new(
io::ErrorKind::TimedOut,
"operation timed out",
)),
}
}
}
pub struct AsyncIoUdpSocket(Arc<Async<std::net::UdpSocket>>);
#[async_trait]
impl DnsUdpSocket for AsyncIoUdpSocket {
type Time = AsyncIoTime;
fn poll_recv_from(
&self,
cx: &mut Context<'_>,
buf: &mut [u8],
) -> Poll<io::Result<(usize, SocketAddr)>> {
loop {
match self.0.get_ref().recv_from(buf) {
Err(error) if error.kind() == io::ErrorKind::WouldBlock => {
match self.0.poll_readable(cx) {
Poll::Ready(Ok(())) => {}
Poll::Ready(Err(error)) => return Poll::Ready(Err(error)),
Poll::Pending => return Poll::Pending,
}
}
result => return Poll::Ready(result),
}
}
}
fn poll_send_to(
&self,
cx: &mut Context<'_>,
buf: &[u8],
target: SocketAddr,
) -> Poll<io::Result<usize>> {
loop {
match self.0.get_ref().send_to(buf, target) {
Err(error) if error.kind() == io::ErrorKind::WouldBlock => {
match self.0.poll_writable(cx) {
Poll::Ready(Ok(())) => {}
Poll::Ready(Err(error)) => return Poll::Ready(Err(error)),
Poll::Pending => return Poll::Pending,
}
}
result => return Poll::Ready(result),
}
}
}
}
pub struct AsyncIoTcpStream(TcpStream);
impl AsyncRead for AsyncIoTcpStream {
fn poll_read(
mut self: Pin<&mut Self>,
cx: &mut Context<'_>,
buf: &mut [u8],
) -> Poll<io::Result<usize>> {
Pin::new(&mut self.0).poll_read(cx, buf)
}
}
impl AsyncWrite for AsyncIoTcpStream {
fn poll_write(
mut self: Pin<&mut Self>,
cx: &mut Context<'_>,
buf: &[u8],
) -> Poll<io::Result<usize>> {
Pin::new(&mut self.0).poll_write(cx, buf)
}
fn poll_flush(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<io::Result<()>> {
Pin::new(&mut self.0).poll_flush(cx)
}
fn poll_close(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<io::Result<()>> {
Pin::new(&mut self.0).poll_close(cx)
}
}
impl DnsTcpStream for AsyncIoTcpStream {
type Time = AsyncIoTime;
}