[GH-ISSUE #653] trust-dns seems to leak udp connections #266

Closed
opened 2026-03-07 23:07:14 +03:00 by kerem · 4 comments
Owner

Originally created by @kpcyrd on GitHub (Jan 9, 2019).
Original GitHub issue: https://github.com/hickory-dns/hickory-dns/issues/653

Describe the bug
I might be doing something wrong here, but it seems trust-dns doesn't close udp sockets correctly:

[*] #73 resolving...
[*] #74 resolving...
[*] #75 resolving...
[*] #76 resolving...
[*] #77 resolving...
[*] #78 resolving...
thread 'main' panicked at 'Runtime::new: Os { code: 24, kind: Other, message: "Too many open files" }', libcore/result.rs:1009:5
note: Run with `RUST_BACKTRACE=1` for a backtrace.

If you add a sleep to the loop and run watch -n1 lsof -nPi you can see that the binary is opening a lot of udp sockets but never closes them.

To Reproduce
A dirty reproducer extracted from my code:

use futures::{future, Future};
use trust_dns::rr::{DNSClass, Name};
use trust_dns::rr::RecordType;
use tokio::runtime::Runtime;
use trust_dns::client::ClientHandle;
use trust_dns::client::{Client, ClientConnection, SyncClient};
use trust_dns::udp::UdpClientConnection;


fn resolve_with<T>(conn: T, name: Name, query_type: RecordType)
where
    T: ClientConnection,
{
    let client = SyncClient::new(conn);
    let (bg, mut client) = client.new_future();

    let query = future::lazy(move || {
        tokio::executor::spawn(bg);
        client
            .query(name, DNSClass::IN, query_type)
    });

    let reply = query.and_then(|_response| {
        // println!("{:?}", response);
        Ok(())
    });

    let mut rt = Runtime::new().expect("Runtime::new");
    rt.block_on(reply).expect("block_on");
}

fn main() {
    let name = "google.com".parse::<Name>().expect("Name::from_str");
    let rt = RecordType::A;

    let mut i = 0;
    loop {
        println!("[*] #{} resolving...", i);
        let addr = "1.1.1.1:53".parse().unwrap();
        let sock = UdpClientConnection::new(addr).unwrap();
        resolve_with(sock, name.clone(), rt);
        i += 1;
    }
}

Expected behavior
The program should close udp sockets after use. Creating the UdpClientConnection inside the loop is probably one of the reasons it's running out of file descriptors, but this shouldn't case a leak of the file descriptor.

System:

  • OS: Linux
  • Architecture: x86_64
  • rustc version: 1.31.1

Version:
Crate: trust-dns
Version: 0.15.0

Originally created by @kpcyrd on GitHub (Jan 9, 2019). Original GitHub issue: https://github.com/hickory-dns/hickory-dns/issues/653 **Describe the bug** I might be doing something wrong here, but it seems trust-dns doesn't close udp sockets correctly: ``` [*] #73 resolving... [*] #74 resolving... [*] #75 resolving... [*] #76 resolving... [*] #77 resolving... [*] #78 resolving... thread 'main' panicked at 'Runtime::new: Os { code: 24, kind: Other, message: "Too many open files" }', libcore/result.rs:1009:5 note: Run with `RUST_BACKTRACE=1` for a backtrace. ``` If you add a sleep to the loop and run `watch -n1 lsof -nPi` you can see that the binary is opening a lot of udp sockets but never closes them. **To Reproduce** A dirty reproducer extracted from my code: ```rust use futures::{future, Future}; use trust_dns::rr::{DNSClass, Name}; use trust_dns::rr::RecordType; use tokio::runtime::Runtime; use trust_dns::client::ClientHandle; use trust_dns::client::{Client, ClientConnection, SyncClient}; use trust_dns::udp::UdpClientConnection; fn resolve_with<T>(conn: T, name: Name, query_type: RecordType) where T: ClientConnection, { let client = SyncClient::new(conn); let (bg, mut client) = client.new_future(); let query = future::lazy(move || { tokio::executor::spawn(bg); client .query(name, DNSClass::IN, query_type) }); let reply = query.and_then(|_response| { // println!("{:?}", response); Ok(()) }); let mut rt = Runtime::new().expect("Runtime::new"); rt.block_on(reply).expect("block_on"); } fn main() { let name = "google.com".parse::<Name>().expect("Name::from_str"); let rt = RecordType::A; let mut i = 0; loop { println!("[*] #{} resolving...", i); let addr = "1.1.1.1:53".parse().unwrap(); let sock = UdpClientConnection::new(addr).unwrap(); resolve_with(sock, name.clone(), rt); i += 1; } } ``` **Expected behavior** The program should close udp sockets after use. Creating the UdpClientConnection inside the loop is probably one of the reasons it's running out of file descriptors, but this shouldn't case a leak of the file descriptor. **System:** - OS: Linux - Architecture: x86_64 - rustc version: 1.31.1 **Version:** Crate: trust-dns Version: 0.15.0
kerem 2026-03-07 23:07:14 +03:00
Author
Owner

@bluejekyll commented on GitHub (Jan 10, 2019):

Ah... The SyncClient isn't meant to be used in Futures/Tokio context, it handles all of that internally. The new_future function really should be private, but is part of the a Trait interface. I might see if that can be hidden so that it doesn't lead people down the wrong path. Sadly we don't have any examples for this, we should add some. Here are some test cases though that should better show how to use the SyncClient:

Construct: https://github.com/bluejekyll/trust-dns/blob/master/tests/integration-tests/tests/client_tests.rs#L85
Use: https://github.com/bluejekyll/trust-dns/blob/master/tests/integration-tests/tests/client_tests.rs#L102

I hope that helps!

<!-- gh-comment-id:452932409 --> @bluejekyll commented on GitHub (Jan 10, 2019): Ah... The `SyncClient` isn't meant to be used in Futures/Tokio context, it handles all of that internally. The new_future function really should be private, but is part of the a Trait interface. I might see if that can be hidden so that it doesn't lead people down the wrong path. Sadly we don't have any examples for this, we should add some. Here are some test cases though that should better show how to use the SyncClient: Construct: https://github.com/bluejekyll/trust-dns/blob/master/tests/integration-tests/tests/client_tests.rs#L85 Use: https://github.com/bluejekyll/trust-dns/blob/master/tests/integration-tests/tests/client_tests.rs#L102 I hope that helps!
Author
Owner

@kpcyrd commented on GitHub (Jan 10, 2019):

Oh, I see! After looking at the differences I think this was because I scheduled the background task on the default executor. Since the default executor is never shutdown the socket is never closed.

I think I remember why I did it this way though: I was actually looking for an async Client but couldn't find one. In addition I don't have a reference to the Runtime since I'm running the resolve inside a hyper connector. Creating a new Runtime inside a tokio Runtime causes a panic.

Is there a reason the background task is needed? It would be easier for me if creating a Client simply returns a FutureClient that I can chain with and_then, without needing a reference to Runtime or Handle.

Thanks!

<!-- gh-comment-id:452961033 --> @kpcyrd commented on GitHub (Jan 10, 2019): Oh, I see! After looking at the differences I think this was because I scheduled the background task on the default executor. Since the default executor is never shutdown the socket is never closed. I think I remember why I did it this way though: I was actually looking for an async `Client` but couldn't find one. In addition I don't have a reference to the `Runtime` since I'm running the resolve inside a hyper connector. Creating a new Runtime inside a tokio Runtime causes a panic. Is there a reason the background task is needed? It would be easier for me if creating a Client simply returns a `FutureClient` that I can chain with `and_then`, without needing a reference to `Runtime` or `Handle`. Thanks!
Author
Owner

@bluejekyll commented on GitHub (Jan 10, 2019):

If you want an Async client, here's the best example I have: https://github.com/bluejekyll/trust-dns/blob/master/tests/integration-tests/tests/client_future_tests.rs#L66

Also, there was a UdpConnection leak in the Resolver library. That may also effect the Client library, but I don't currently plan on backporting that. I'll be publishing 0.16 alpha releases soon.

<!-- gh-comment-id:452975865 --> @bluejekyll commented on GitHub (Jan 10, 2019): If you want an Async client, here's the best example I have: https://github.com/bluejekyll/trust-dns/blob/master/tests/integration-tests/tests/client_future_tests.rs#L66 Also, there was a UdpConnection leak in the Resolver library. That may also effect the Client library, but I don't currently plan on backporting that. I'll be publishing 0.16 alpha releases soon.
Author
Owner

@bluejekyll commented on GitHub (Jan 19, 2019):

FYI, I'm going to close this, 0.16.0.alpha.1 has the UDP connection leak fix from the Resolver 0.10.2 release.

<!-- gh-comment-id:455821402 --> @bluejekyll commented on GitHub (Jan 19, 2019): FYI, I'm going to close this, 0.16.0.alpha.1 has the UDP connection leak fix from the Resolver 0.10.2 release.
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#266
No description provided.