[GH-ISSUE #2456] How to use AsyncClient? #996

Closed
opened 2026-03-16 01:12:54 +03:00 by kerem · 5 comments
Owner

Originally created by @luigiminardim on GitHub (Sep 15, 2024).
Original GitHub issue: https://github.com/hickory-dns/hickory-dns/issues/2456

I'm trying to learn how to use the AsyncClient to create a DNS stub resolver server.

Here is my cargo.toml


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

[dependencies]
async-trait = "0.1.82"
hickory-client = "0.24.1"
hickory-server = "0.24.1"
tokio = "1.40.0"

And here is my main.rs


use std::net::SocketAddr;

use hickory_client::{
    client::{AsyncClient, ClientConnection, ClientHandle},
    op::Header,
    rr::Name,
    udp::UdpClientConnection,
};
use hickory_server::{
    authority::MessageResponseBuilder,
    server::{Request, RequestHandler, ResponseHandler, ResponseInfo}, ServerFuture,
};
use tokio::net::UdpSocket;

#[tokio::main]
async fn main() {
    let mut server = ServerFuture::new(StubHandler);
    let socket_address: SocketAddr = "0.0.0.0:1053".parse().unwrap();
    server.register_socket(UdpSocket::bind(socket_address).await.unwrap());
    server.block_until_done().await.expect("block_until_done");
}

struct StubHandler;

#[async_trait::async_trait]
impl RequestHandler for StubHandler {
    async fn handle_request<R: ResponseHandler>(
        &self,
        request: &Request,
        mut response_handle: R,
    ) -> ResponseInfo {
        let query = request.query();
        let conn = UdpClientConnection::new("8.8.8.8:53".parse().unwrap())
            .unwrap()
            .new_stream(None);
        let (mut client, _) = AsyncClient::connect(conn).await.unwrap();
        let client_response = client
            .query(
                Name::from(query.name()),
                query.query_class(),
                query.query_type(),
            )
            .await;
        match client_response {
            Ok(client_response) => {
                let response_builder = MessageResponseBuilder::from_message_request(request);
                let response_header = Header::response_from_request(request.header());
                let response = response_builder.build(
                    response_header,
                    client_response.answers(),
                    client_response.name_servers(),
                    &[],
                    &[],
                );
                response_handle.send_response(response).await.expect("msg")
            }
            Err(e) => {
                println!("Error: {:#?}", e); // Always reach here
                let response = ResponseInfo::from(request.header().clone());
                return response;
            }
        }
    }
}

Although the code compiles, the client never succeeds in running the query since it always prints the following error from line println!("Error: {:#?}", e); // Always reach here:

Error: Error {
    kind: Proto(
        ProtoError {
            kind: Busy,
        },
    ),
}

What am I doing wrong?

Originally created by @luigiminardim on GitHub (Sep 15, 2024). Original GitHub issue: https://github.com/hickory-dns/hickory-dns/issues/2456 I'm trying to learn how to use the AsyncClient to create a DNS stub resolver server. Here is my `cargo.toml` ```toml [package] name = "hickory_stub_handler" version = "0.1.0" edition = "2021" [dependencies] async-trait = "0.1.82" hickory-client = "0.24.1" hickory-server = "0.24.1" tokio = "1.40.0" ``` And here is my `main.rs` ```rs use std::net::SocketAddr; use hickory_client::{ client::{AsyncClient, ClientConnection, ClientHandle}, op::Header, rr::Name, udp::UdpClientConnection, }; use hickory_server::{ authority::MessageResponseBuilder, server::{Request, RequestHandler, ResponseHandler, ResponseInfo}, ServerFuture, }; use tokio::net::UdpSocket; #[tokio::main] async fn main() { let mut server = ServerFuture::new(StubHandler); let socket_address: SocketAddr = "0.0.0.0:1053".parse().unwrap(); server.register_socket(UdpSocket::bind(socket_address).await.unwrap()); server.block_until_done().await.expect("block_until_done"); } struct StubHandler; #[async_trait::async_trait] impl RequestHandler for StubHandler { async fn handle_request<R: ResponseHandler>( &self, request: &Request, mut response_handle: R, ) -> ResponseInfo { let query = request.query(); let conn = UdpClientConnection::new("8.8.8.8:53".parse().unwrap()) .unwrap() .new_stream(None); let (mut client, _) = AsyncClient::connect(conn).await.unwrap(); let client_response = client .query( Name::from(query.name()), query.query_class(), query.query_type(), ) .await; match client_response { Ok(client_response) => { let response_builder = MessageResponseBuilder::from_message_request(request); let response_header = Header::response_from_request(request.header()); let response = response_builder.build( response_header, client_response.answers(), client_response.name_servers(), &[], &[], ); response_handle.send_response(response).await.expect("msg") } Err(e) => { println!("Error: {:#?}", e); // Always reach here let response = ResponseInfo::from(request.header().clone()); return response; } } } } ``` Although the code compiles, the client never succeeds in running the query since it always prints the following error from line `println!("Error: {:#?}", e); // Always reach here`: ```sh Error: Error {     kind: Proto(         ProtoError {             kind: Busy,         },     ), } ``` What am I doing wrong?
kerem closed this issue 2026-03-16 01:12:59 +03:00
Author
Owner

@marcus0x62 commented on GitHub (Sep 17, 2024):

You need to run the background task returned by AsyncClient::connect:

let (mut client, task) = AsyncClient::connect(conn).await.unwrap();
tokio::spawn(task);

<!-- gh-comment-id:2355839271 --> @marcus0x62 commented on GitHub (Sep 17, 2024): You need to run the background task returned by AsyncClient::connect: > let (mut client, task) = AsyncClient::connect(conn).await.unwrap(); > tokio::spawn(task);
Author
Owner

@luigiminardim commented on GitHub (Sep 18, 2024):

You need to run the background task returned by AsyncClient::connect:

let (mut client, task) = AsyncClient::connect(conn).await.unwrap();
tokio::spawn(task);

Thanks, @marcus0x62! Your solution worked for me.

Although it is functioning, I think the API it's a little bit difficult to understand. I would like to get some thoughts on this topic.

<!-- gh-comment-id:2358279745 --> @luigiminardim commented on GitHub (Sep 18, 2024): > You need to run the background task returned by AsyncClient::connect: > > > let (mut client, task) = AsyncClient::connect(conn).await.unwrap(); > > tokio::spawn(task); > Thanks, @marcus0x62! Your solution worked for me. Although it is functioning, I think the API it's a little bit difficult to understand. I would like to get some thoughts on this topic.
Author
Owner

@djc commented on GitHub (Sep 18, 2024):

Although it is functioning, I think the API it's a little bit difficult to understand. I would like to get some thoughts on this topic.

connect() returns a tuple and you decided in your code to ignore the second element. The documentation says:

This returns a tuple of Self a handle to send dns messages and an optional background. The background task must be run on an executor before handle is used, if it is Some. If it is None, then another thread has already run the background.

What would you suggest doing instead? This is a decently common pattern in async Rust.

We could spawn internally instead, and require the caller to pass in a Spawner?

<!-- gh-comment-id:2358326281 --> @djc commented on GitHub (Sep 18, 2024): > Although it is functioning, I think the API it's a little bit difficult to understand. I would like to get some thoughts on this topic. `connect()` returns a tuple and you decided in your code to ignore the second element. The documentation says: > This returns a tuple of Self a handle to send dns messages and an optional background. The background task must be run on an executor before handle is used, if it is Some. If it is None, then another thread has already run the background. What would you suggest doing instead? This is a decently common pattern in async Rust. We could spawn internally instead, and require the caller to pass in a `Spawner`?
Author
Owner

@marcus0x62 commented on GitHub (Sep 18, 2024):

@luigiminardim what part was hard to understand? Was the documentation unclear? We do have simpler interfaces, but those tend to be for simpler use cases like basic clients and wouldn't be appropriate for building a stub resolver server.

<!-- gh-comment-id:2359263403 --> @marcus0x62 commented on GitHub (Sep 18, 2024): @luigiminardim what part was hard to understand? Was the documentation unclear? We do have simpler interfaces, but those tend to be for simpler use cases like basic clients and wouldn't be appropriate for building a stub resolver server.
Author
Owner

@luigiminardim commented on GitHub (Sep 19, 2024):

We could spawn internally instead, and require the caller to pass in a Spawner?

@djc, Thanks for clarifying the documentation. Since I'm new to Rust, I don't know the common practices of the language. But I do think that requiring the caller to pass in a Spawner is a good idea since the user would know at compile time that he is committing a mistake. Of course, if it is possible, in my opinion, the best option is if this task management is more transparent to the user.

Also answering @marcus0x62, I think that the thing that is missing in the documentation is an example. Even in the source code test, I didn't find AsyncClient usage with a UDP connection.

<!-- gh-comment-id:2359689481 --> @luigiminardim commented on GitHub (Sep 19, 2024): > We could spawn internally instead, and require the caller to pass in a `Spawner`? @djc, Thanks for clarifying the documentation. Since I'm new to Rust, I don't know the common practices of the language. But I do think that requiring the caller to pass in a Spawner is a good idea since the user would know at compile time that he is committing a mistake. Of course, if it is possible, in my opinion, the best option is if this task management is more transparent to the user. Also answering @marcus0x62, I think that the thing that is missing in the documentation is an example. Even in the source code test, I didn't find AsyncClient usage with a UDP connection.
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#996
No description provided.