[GH-ISSUE #3110] Dns resolve fail in 0.25.1 version #1134

Closed
opened 2026-03-16 01:42:11 +03:00 by kerem · 4 comments
Owner

Originally created by @vicanso on GitHub (Jul 12, 2025).
Original GitHub issue: https://github.com/hickory-dns/hickory-dns/issues/3110

Describe the bug
After I upgraded hickory-resolver from 0.24.3 to 0.25.1, the DNS resolution would fail probabilistically.

proto error: no records found for Query { name: Name("n150."), query_type: A, query_class: IN } 

/etc/resolv.conf:

# Generated by Docker Engine.
# This file can be edited; Docker Engine will not make further changes once it
# has been modified.

nameserver 100.100.100.100
nameserver 100.100.2.136
nameserver 100.100.2.138
search tail***.ts.net

# Based on host file: '/run/systemd/resolve/resolv.conf' (legacy)
# Overrides: []

To Reproduce
Steps to reproduce the behavior:

Expected behavior
A clear and concise description of what you expected to happen.

System:

  • OS: docker ubuntu
  • Architecture: x86_64
  • Version 24.04
  • rustc version: 1.86

Version:
Crate:hickory-resolver
Version:0.25.1

Additional context
My dns resolution function:

    /// Reads system DNS resolver configuration
    ///
    /// # Returns
    /// * `Result<(ResolverConfig, ResolverOpts)>` - Resolver configuration and options
    fn read_system_conf(&self) -> Result<(ResolverConfig, ResolverOpts)> {
        let (config, mut options) =
            read_system_conf().map_err(|e| Error::Resolve { source: e })?;

        options.ip_strategy = if self.ipv4_only {
            LookupIpStrategy::Ipv4Only
        } else {
            LookupIpStrategy::Ipv4AndIpv6
        };

        Ok((config, options))
    }

    /// Performs DNS lookups for configured hosts using tokio runtime
    ///
    /// # Returns
    /// * `Result<(Vec<LookupIp>, Vec<String>)>` - List of DNS lookup results and unhealthy backends
    async fn tokio_lookup_ip(&self) -> Result<(Vec<LookupIp>, Vec<String>)> {
        let provider = TokioConnectionProvider::default();
        let (config, options) = self.read_system_conf()?;
        let mut builder = Resolver::builder_with_config(config, provider);
        *builder.options_mut() = options;
        let resolver = builder.build();

        let mut lookup_ips = Vec::new();
        let mut failed_hosts = Vec::new();

        for (host, _, _) in self.hosts.iter() {
            match resolver.lookup_ip(host).await {
                Ok(lookup) => {
                    lookup_ips.push(lookup);
                },
                Err(e) => {
                    error!(
                        category = LOG_CATEGORY,
                        error = %e,
                        host,
                        "dns lookup failed"
                    );
                    failed_hosts.push(host.clone());
                },
            }
        }
        if lookup_ips.is_empty() {
            return Err(Error::Invalid {
                message: "resolve dns failed".to_string(),
            });
        }
        Ok((lookup_ips, failed_hosts))
    }

Originally created by @vicanso on GitHub (Jul 12, 2025). Original GitHub issue: https://github.com/hickory-dns/hickory-dns/issues/3110 **Describe the bug** After I upgraded hickory-resolver from 0.24.3 to 0.25.1, the DNS resolution would fail probabilistically. ``` proto error: no records found for Query { name: Name("n150."), query_type: A, query_class: IN } ``` /etc/resolv.conf: ``` # Generated by Docker Engine. # This file can be edited; Docker Engine will not make further changes once it # has been modified. nameserver 100.100.100.100 nameserver 100.100.2.136 nameserver 100.100.2.138 search tail***.ts.net # Based on host file: '/run/systemd/resolve/resolv.conf' (legacy) # Overrides: [] ``` **To Reproduce** Steps to reproduce the behavior: **Expected behavior** A clear and concise description of what you expected to happen. **System:** - OS: docker ubuntu - Architecture: x86_64 - Version 24.04 - rustc version: 1.86 **Version:** Crate:hickory-resolver Version:0.25.1 **Additional context** My dns resolution function: ```rust /// Reads system DNS resolver configuration /// /// # Returns /// * `Result<(ResolverConfig, ResolverOpts)>` - Resolver configuration and options fn read_system_conf(&self) -> Result<(ResolverConfig, ResolverOpts)> { let (config, mut options) = read_system_conf().map_err(|e| Error::Resolve { source: e })?; options.ip_strategy = if self.ipv4_only { LookupIpStrategy::Ipv4Only } else { LookupIpStrategy::Ipv4AndIpv6 }; Ok((config, options)) } /// Performs DNS lookups for configured hosts using tokio runtime /// /// # Returns /// * `Result<(Vec<LookupIp>, Vec<String>)>` - List of DNS lookup results and unhealthy backends async fn tokio_lookup_ip(&self) -> Result<(Vec<LookupIp>, Vec<String>)> { let provider = TokioConnectionProvider::default(); let (config, options) = self.read_system_conf()?; let mut builder = Resolver::builder_with_config(config, provider); *builder.options_mut() = options; let resolver = builder.build(); let mut lookup_ips = Vec::new(); let mut failed_hosts = Vec::new(); for (host, _, _) in self.hosts.iter() { match resolver.lookup_ip(host).await { Ok(lookup) => { lookup_ips.push(lookup); }, Err(e) => { error!( category = LOG_CATEGORY, error = %e, host, "dns lookup failed" ); failed_hosts.push(host.clone()); }, } } if lookup_ips.is_empty() { return Err(Error::Invalid { message: "resolve dns failed".to_string(), }); } Ok((lookup_ips, failed_hosts)) } ```
kerem closed this issue 2026-03-16 01:42:16 +03:00
Author
Owner

@djc commented on GitHub (Jul 12, 2025):

Can you show dbg!() prints of both the options and config on both 0.24.x and 0.25.x?

<!-- gh-comment-id:3064993917 --> @djc commented on GitHub (Jul 12, 2025): Can you show `dbg!()` prints of both the `options` and `config` on both 0.24.x and 0.25.x?
Author
Owner

@vicanso commented on GitHub (Jul 12, 2025):

0.24.3

&config = ResolverConfig {
    domain: None,
    search: [
        Name("tail290a2a.ts.net"),
    ],
    name_servers: NameServerConfigGroup(
        [
            NameServerConfig {
                socket_addr: 100.100.100.100:53,
                protocol: Udp,
                tls_dns_name: None,
                trust_negative_responses: false,
                bind_addr: None,
            },
            NameServerConfig {
                socket_addr: 100.100.100.100:53,
                protocol: Tcp,
                tls_dns_name: None,
                trust_negative_responses: false,
                bind_addr: None,
            },
            NameServerConfig {
                socket_addr: 100.100.2.136:53,
                protocol: Udp,
                tls_dns_name: None,
                trust_negative_responses: false,
                bind_addr: None,
            },
            NameServerConfig {
                socket_addr: 100.100.2.136:53,
                protocol: Tcp,
                tls_dns_name: None,
                trust_negative_responses: false,
                bind_addr: None,
            },
            NameServerConfig {
                socket_addr: 100.100.2.138:53,
                protocol: Udp,
                tls_dns_name: None,
                trust_negative_responses: false,
                bind_addr: None,
            },
            NameServerConfig {
                socket_addr: 100.100.2.138:53,
                protocol: Tcp,
                tls_dns_name: None,
                trust_negative_responses: false,
                bind_addr: None,
            },
        ],
    ),
}

&options = ResolverOpts {
    ndots: 1,
    timeout: 5s,
    attempts: 2,
    rotate: false,
    check_names: true,
    edns0: false,
    validate: false,
    ip_strategy: Ipv4Only,
    cache_size: 32,
    use_hosts_file: true,
    positive_min_ttl: None,
    negative_min_ttl: None,
    positive_max_ttl: None,
    negative_max_ttl: None,
    num_concurrent_reqs: 2,
    preserve_intermediates: true,
    try_tcp_on_error: false,
    server_ordering_strategy: QueryStatistics,
    recursion_desired: true,
    authentic_data: false,
    shuffle_dns_servers: false,
}

0.25.1

&config = ResolverConfig {
    domain: None,
    search: [
        Name("tail290a2a.ts.net"),
    ],
    name_servers: NameServerConfigGroup {
        servers: [
            NameServerConfig {
                socket_addr: 100.100.100.100:53,
                protocol: Udp,
                tls_dns_name: None,
                http_endpoint: None,
                trust_negative_responses: false,
                bind_addr: None,
            },
            NameServerConfig {
                socket_addr: 100.100.100.100:53,
                protocol: Tcp,
                tls_dns_name: None,
                http_endpoint: None,
                trust_negative_responses: false,
                bind_addr: None,
            },
            NameServerConfig {
                socket_addr: 100.100.2.136:53,
                protocol: Udp,
                tls_dns_name: None,
                http_endpoint: None,
                trust_negative_responses: false,
                bind_addr: None,
            },
            NameServerConfig {
                socket_addr: 100.100.2.136:53,
                protocol: Tcp,
                tls_dns_name: None,
                http_endpoint: None,
                trust_negative_responses: false,
                bind_addr: None,
            },
            NameServerConfig {
                socket_addr: 100.100.2.138:53,
                protocol: Udp,
                tls_dns_name: None,
                http_endpoint: None,
                trust_negative_responses: false,
                bind_addr: None,
            },
            NameServerConfig {
                socket_addr: 100.100.2.138:53,
                protocol: Tcp,
                tls_dns_name: None,
                http_endpoint: None,
                trust_negative_responses: false,
                bind_addr: None,
            },
        ],
    },
}

&options = ResolverOpts {
    ndots: 1,
    timeout: 5s,
    attempts: 2,
    check_names: true,
    edns0: false,
    validate: false,
    ip_strategy: Ipv4Only,
    cache_size: 32,
    use_hosts_file: Auto,
    positive_min_ttl: None,
    negative_min_ttl: None,
    positive_max_ttl: None,
    negative_max_ttl: None,
    num_concurrent_reqs: 2,
    preserve_intermediates: true,
    try_tcp_on_error: false,
    server_ordering_strategy: QueryStatistics,
    recursion_desired: true,
    avoid_local_udp_ports: {},
    os_port_selection: false,
    case_randomization: false,
    trust_anchor: None,
}
<!-- gh-comment-id:3065554957 --> @vicanso commented on GitHub (Jul 12, 2025): ## 0.24.3 ``` &config = ResolverConfig { domain: None, search: [ Name("tail290a2a.ts.net"), ], name_servers: NameServerConfigGroup( [ NameServerConfig { socket_addr: 100.100.100.100:53, protocol: Udp, tls_dns_name: None, trust_negative_responses: false, bind_addr: None, }, NameServerConfig { socket_addr: 100.100.100.100:53, protocol: Tcp, tls_dns_name: None, trust_negative_responses: false, bind_addr: None, }, NameServerConfig { socket_addr: 100.100.2.136:53, protocol: Udp, tls_dns_name: None, trust_negative_responses: false, bind_addr: None, }, NameServerConfig { socket_addr: 100.100.2.136:53, protocol: Tcp, tls_dns_name: None, trust_negative_responses: false, bind_addr: None, }, NameServerConfig { socket_addr: 100.100.2.138:53, protocol: Udp, tls_dns_name: None, trust_negative_responses: false, bind_addr: None, }, NameServerConfig { socket_addr: 100.100.2.138:53, protocol: Tcp, tls_dns_name: None, trust_negative_responses: false, bind_addr: None, }, ], ), } &options = ResolverOpts { ndots: 1, timeout: 5s, attempts: 2, rotate: false, check_names: true, edns0: false, validate: false, ip_strategy: Ipv4Only, cache_size: 32, use_hosts_file: true, positive_min_ttl: None, negative_min_ttl: None, positive_max_ttl: None, negative_max_ttl: None, num_concurrent_reqs: 2, preserve_intermediates: true, try_tcp_on_error: false, server_ordering_strategy: QueryStatistics, recursion_desired: true, authentic_data: false, shuffle_dns_servers: false, } ``` ## 0.25.1 ``` &config = ResolverConfig { domain: None, search: [ Name("tail290a2a.ts.net"), ], name_servers: NameServerConfigGroup { servers: [ NameServerConfig { socket_addr: 100.100.100.100:53, protocol: Udp, tls_dns_name: None, http_endpoint: None, trust_negative_responses: false, bind_addr: None, }, NameServerConfig { socket_addr: 100.100.100.100:53, protocol: Tcp, tls_dns_name: None, http_endpoint: None, trust_negative_responses: false, bind_addr: None, }, NameServerConfig { socket_addr: 100.100.2.136:53, protocol: Udp, tls_dns_name: None, http_endpoint: None, trust_negative_responses: false, bind_addr: None, }, NameServerConfig { socket_addr: 100.100.2.136:53, protocol: Tcp, tls_dns_name: None, http_endpoint: None, trust_negative_responses: false, bind_addr: None, }, NameServerConfig { socket_addr: 100.100.2.138:53, protocol: Udp, tls_dns_name: None, http_endpoint: None, trust_negative_responses: false, bind_addr: None, }, NameServerConfig { socket_addr: 100.100.2.138:53, protocol: Tcp, tls_dns_name: None, http_endpoint: None, trust_negative_responses: false, bind_addr: None, }, ], }, } &options = ResolverOpts { ndots: 1, timeout: 5s, attempts: 2, check_names: true, edns0: false, validate: false, ip_strategy: Ipv4Only, cache_size: 32, use_hosts_file: Auto, positive_min_ttl: None, negative_min_ttl: None, positive_max_ttl: None, negative_max_ttl: None, num_concurrent_reqs: 2, preserve_intermediates: true, try_tcp_on_error: false, server_ordering_strategy: QueryStatistics, recursion_desired: true, avoid_local_udp_ports: {}, os_port_selection: false, case_randomization: false, trust_anchor: None, } ```
Author
Owner

@marcus0x62 commented on GitHub (Jul 12, 2025):

Without more data, my initial thought is that this is probably due to #2560. @vicanso, can you post a self-contained minimal reproducer or, failing that, show what self.hosts is and how it is constructed/populated with host names? Your query object:

proto error: no records found for Query { name: Name("n150."), query_type: A, query_class: IN }

shows a request for a fully-qualified name.

<!-- gh-comment-id:3065686994 --> @marcus0x62 commented on GitHub (Jul 12, 2025): Without more data, my initial thought is that this is probably due to #2560. @vicanso, can you post a self-contained minimal reproducer or, failing that, show what self.hosts is and how it is constructed/populated with host names? Your query object: > proto error: no records found for Query { name: Name("n150."), query_type: A, query_class: IN } shows a request for a fully-qualified name.
Author
Owner

@vicanso commented on GitHub (Jul 13, 2025):

I found that if only keep the Tailscale DNS server, it will be ok.

# Generated by Docker Engine.
# This file can be edited; Docker Engine will not make further changes once it
# has been modified.

nameserver 100.100.100.100
#nameserver 100.100.2.136
#nameserver 100.100.2.138
search tail***.ts.net

# Based on host file: '/run/systemd/resolve/resolv.conf' (legacy)
# Overrides: []
<!-- gh-comment-id:3066288879 --> @vicanso commented on GitHub (Jul 13, 2025): I found that if only keep the Tailscale DNS server, it will be ok. ``` # Generated by Docker Engine. # This file can be edited; Docker Engine will not make further changes once it # has been modified. nameserver 100.100.100.100 #nameserver 100.100.2.136 #nameserver 100.100.2.138 search tail***.ts.net # Based on host file: '/run/systemd/resolve/resolv.conf' (legacy) # Overrides: [] ```
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#1134
No description provided.