[GH-ISSUE #1669] Using a custom UdpSocket will put lookup_ip in a busy loop #733

Closed
opened 2026-03-16 00:02:55 +03:00 by kerem · 3 comments
Owner

Originally created by @spacemeowx2 on GitHub (Mar 25, 2022).
Original GitHub issue: https://github.com/hickory-dns/hickory-dns/issues/1669

Describe the bug
In my scenario I need to use a custom UdpSocket to make it pass through a proxy, requesting a DNS server.

I found that I can't use await in trust_dns_proto::udp::UdpSocket::bind, otherwise the program will get stuck.

To Reproduce
Cargo.toml

[package]
name = "example"
version = "0.1.0"
edition = "2021"

[dependencies]
trust-dns-resolver = "0.21"
tokio = { version = "1.0", features = ["full"] }
async-trait = "0.1"
futures = "0.3"

lib/main.rs

use std::{future::Future, net::SocketAddr, task::Poll};

use async_trait::async_trait;
use futures::ready;
use tokio::task::yield_now;
use trust_dns_resolver::{
    config::{ResolverConfig, ResolverOpts},
    name_server::{GenericConnection, GenericConnectionProvider, RuntimeProvider, Spawn},
    proto::{error::ProtoError, iocompat::AsyncIoTokioAsStd, udp::UdpSocket, TokioTime},
    AsyncResolver,
};

#[tokio::main]
async fn main() {
    let resolver: AsyncResolver<MyConnection, MyConnectionProvider> =
        AsyncResolver::new(ResolverConfig::google(), ResolverOpts::default(), MyHandle).unwrap();

    let ips = resolver.lookup_ip("www.google.com").await.unwrap();
    println!("IPs: {:?}", ips);
}

pub struct MyUdpSocket(tokio::net::UdpSocket);

#[async_trait]
impl UdpSocket for MyUdpSocket {
    type Time = TokioTime;

    async fn bind(addr: SocketAddr) -> std::io::Result<Self> {
        // comment this will get the result
        yield_now().await;

        Ok(MyUdpSocket(tokio::net::UdpSocket::bind(addr).await?))
    }

    fn poll_recv_from(
        &self,
        cx: &mut std::task::Context<'_>,
        buf: &mut [u8],
    ) -> Poll<std::io::Result<(usize, SocketAddr)>> {
        let mut buf = tokio::io::ReadBuf::new(buf);
        let addr = ready!(self.0.poll_recv_from(cx, &mut buf))?;
        let len = buf.filled().len();

        Poll::Ready(Ok((len, addr)))
    }

    fn poll_send_to(
        &self,
        cx: &mut std::task::Context<'_>,
        buf: &[u8],
        target: SocketAddr,
    ) -> Poll<std::io::Result<usize>> {
        self.0.poll_send_to(cx, buf, target)
    }
}

#[derive(Clone)]
pub struct MyHandle;
impl Spawn for MyHandle {
    fn spawn_bg<F>(&mut self, future: F)
    where
        F: Future<Output = Result<(), ProtoError>> + Send + 'static,
    {
        let _join = tokio::spawn(future);
    }
}

#[derive(Clone, Copy)]
pub struct MyRuntime;
impl RuntimeProvider for MyRuntime {
    type Handle = MyHandle;
    type Tcp = AsyncIoTokioAsStd<tokio::net::TcpStream>;
    type Timer = TokioTime;
    type Udp = MyUdpSocket;
}
pub type MyConnection = GenericConnection;
pub type MyConnectionProvider = GenericConnectionProvider<MyRuntime>;

Expected behavior
I should be able to use await in trust_dns_proto::udp::UdpSocket::bind

System:

  • OS: macOS
  • Architecture: arm64
  • Version 12.3
  • rustc version: 1.59.0

Version:
Crate: resolver
Version: 0.21.1

Additional context

I think the root cause is here:

github.com/bluejekyll/trust-dns@b470913512/crates/proto/src/udp/udp_stream.rs (L238-L240)

It assumes that Udp::bind is done immediately, so each return of Poll::Pending causes the loop to restart and Future to be recreated, so the state is lost

Originally created by @spacemeowx2 on GitHub (Mar 25, 2022). Original GitHub issue: https://github.com/hickory-dns/hickory-dns/issues/1669 **Describe the bug** In my scenario I need to use a custom UdpSocket to make it pass through a proxy, requesting a DNS server. I found that I can't use `await` in [`trust_dns_proto::udp::UdpSocket::bind`](https://docs.rs/trust-dns-proto/latest/trust_dns_proto/udp/trait.UdpSocket.html#tymethod.bind), otherwise the program will get stuck. **To Reproduce** `Cargo.toml` ```toml [package] name = "example" version = "0.1.0" edition = "2021" [dependencies] trust-dns-resolver = "0.21" tokio = { version = "1.0", features = ["full"] } async-trait = "0.1" futures = "0.3" ``` `lib/main.rs` ```rust use std::{future::Future, net::SocketAddr, task::Poll}; use async_trait::async_trait; use futures::ready; use tokio::task::yield_now; use trust_dns_resolver::{ config::{ResolverConfig, ResolverOpts}, name_server::{GenericConnection, GenericConnectionProvider, RuntimeProvider, Spawn}, proto::{error::ProtoError, iocompat::AsyncIoTokioAsStd, udp::UdpSocket, TokioTime}, AsyncResolver, }; #[tokio::main] async fn main() { let resolver: AsyncResolver<MyConnection, MyConnectionProvider> = AsyncResolver::new(ResolverConfig::google(), ResolverOpts::default(), MyHandle).unwrap(); let ips = resolver.lookup_ip("www.google.com").await.unwrap(); println!("IPs: {:?}", ips); } pub struct MyUdpSocket(tokio::net::UdpSocket); #[async_trait] impl UdpSocket for MyUdpSocket { type Time = TokioTime; async fn bind(addr: SocketAddr) -> std::io::Result<Self> { // comment this will get the result yield_now().await; Ok(MyUdpSocket(tokio::net::UdpSocket::bind(addr).await?)) } fn poll_recv_from( &self, cx: &mut std::task::Context<'_>, buf: &mut [u8], ) -> Poll<std::io::Result<(usize, SocketAddr)>> { let mut buf = tokio::io::ReadBuf::new(buf); let addr = ready!(self.0.poll_recv_from(cx, &mut buf))?; let len = buf.filled().len(); Poll::Ready(Ok((len, addr))) } fn poll_send_to( &self, cx: &mut std::task::Context<'_>, buf: &[u8], target: SocketAddr, ) -> Poll<std::io::Result<usize>> { self.0.poll_send_to(cx, buf, target) } } #[derive(Clone)] pub struct MyHandle; impl Spawn for MyHandle { fn spawn_bg<F>(&mut self, future: F) where F: Future<Output = Result<(), ProtoError>> + Send + 'static, { let _join = tokio::spawn(future); } } #[derive(Clone, Copy)] pub struct MyRuntime; impl RuntimeProvider for MyRuntime { type Handle = MyHandle; type Tcp = AsyncIoTokioAsStd<tokio::net::TcpStream>; type Timer = TokioTime; type Udp = MyUdpSocket; } pub type MyConnection = GenericConnection; pub type MyConnectionProvider = GenericConnectionProvider<MyRuntime>; ``` **Expected behavior** I should be able to use `await` in `trust_dns_proto::udp::UdpSocket::bind` **System:** - OS: macOS - Architecture: arm64 - Version 12.3 - rustc version: 1.59.0 **Version:** Crate: resolver Version: 0.21.1 **Additional context** I think the root cause is here: https://github.com/bluejekyll/trust-dns/blob/b470913512655abc0e6c5a360eb7390e3a50e3c0/crates/proto/src/udp/udp_stream.rs#L238-L240 It assumes that `Udp::bind` is done immediately, so each return of `Poll::Pending` causes the loop to restart and `Future` to be recreated, so the state is lost
kerem 2026-03-16 00:02:55 +03:00
Author
Owner

@spacemeowx2 commented on GitHub (Mar 25, 2022):

I found that NextRandomUdpSocket is always immediately await or boxed by Box::new, can this Future be rewritten to async fn to solve this issue?

<!-- gh-comment-id:1079382841 --> @spacemeowx2 commented on GitHub (Mar 25, 2022): I found that `NextRandomUdpSocket` is always immediately `await` or boxed by `Box::new`, can this `Future` be rewritten to `async fn` to solve this issue?
Author
Owner

@bluejekyll commented on GitHub (Mar 25, 2022):

I think there are two questions here:

  1. how do we get rid of the eager poll?

We need to add an additional "state" of "connect" to the Future. And this would be easier to do with an async fn

  1. can we use async fn?

Yes, absolutely, it's just old code. I haven't looked, but we might need to use async_trait to allow that. If that's a problem, we can always box a dyn Future for that use case, if the async_trait change spider-webs out significantly.

<!-- gh-comment-id:1079385812 --> @bluejekyll commented on GitHub (Mar 25, 2022): I think there are two questions here: 1) how do we get rid of the eager poll? We need to add an additional "state" of "connect" to the Future. And this would be easier to do with an `async fn` 2) can we use `async fn`? Yes, absolutely, it's just old code. I haven't looked, but we might need to use `async_trait` to allow that. If that's a problem, we can always box a dyn Future for that use case, if the `async_trait` change spider-webs out significantly.
Author
Owner

@divergentdave commented on GitHub (Nov 21, 2024):

This was fixed by #2464. If the bind future returns Pending, it will now be saved and polled until ready. I think this can be closed.

<!-- gh-comment-id:2491985821 --> @divergentdave commented on GitHub (Nov 21, 2024): This was fixed by #2464. If the bind future returns `Pending`, it will now be saved and polled until ready. I think this can be closed.
Sign in to join this conversation.
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set.

Reference
starred/hickory-dns#733
No description provided.