shadowsocks/shadowsocks-rust · error · io::Error

resolve empty

Error message

resolve empty

What it means

In the local DNS resolver service, lookup() retries lookup_inner up to `attempts` times and, if every attempt fails, returns the last error. When no attempts succeed at all, the initial placeholder io::Error "resolve empty" (InvalidData) is returned — meaning the resolver could not obtain any DNS response from the configured nameserver.

Source

Thrown at crates/shadowsocks-service/src/local/dns/dns_resolver.rs:53

            connect_opts: ConnectOpts::default(),
            attempts: 2,
        }
    }

    pub fn set_mode(&mut self, mode: Mode) {
        self.mode = mode;
    }

    pub fn set_ipv6_first(&mut self, ipv6_first: bool) {
        self.ipv6_first = ipv6_first;
    }

    pub fn set_connect_opts(&mut self, connect_opts: ConnectOpts) {
        self.connect_opts = connect_opts;
    }

    async fn lookup(&self, msg: Message) -> io::Result<Message> {
        let mut last_err = io::Error::new(ErrorKind::InvalidData, "resolve empty");

        for _ in 0..self.attempts {
            match self.lookup_inner(msg.clone()).await {
                Ok(m) => return Ok(m),
                Err(err) => last_err = err,
            }
        }

        Err(last_err)
    }

    async fn lookup_inner(&self, msg: Message) -> io::Result<Message> {
        match self.ns {
            NameServerAddr::SocketAddr(ns) => {
                let mut last_err = io::Error::new(ErrorKind::InvalidData, "resolve empty");

                // Query UDP then TCP
                if self.mode.enable_udp() {

View on GitHub (pinned to 8eb0f0a65b)

Solutions

  1. Verify the configured DNS nameserver address is reachable (test with dig/nslookup against it)
  2. Check network connectivity and firewall rules for UDP/TCP port 53 to the resolver
  3. Increase the `attempts`/timeout settings in the local DNS server config if responses are slow
  4. Check connect_opts — binding to the wrong interface or proxy path breaks queries
Defensive patterns

Strategy: retry

Validate before calling

// preflight: ensure the nameserver answers before relying on the resolver
// dig +time=2 +tries=1 @<ns> example.com

Try / catch

match resolver.resolve(addr, port).await {
    Err(e) if e.kind() == std::io::ErrorKind::InvalidData && e.to_string().contains("resolve empty") => {
        // fall back to system resolver or retry after checking connectivity
    }
    r => r?,
}

Prevention

When it happens

Trigger: Calling DnsResolver::resolve/lookup when the configured nameserver is unreachable, all UDP/TCP query attempts fail or time out, and `attempts` iterations all return Err so last_err stays the initial placeholder.

Common situations: DNS server address misconfigured or firewalled; local resolver down; no network connectivity; connect_opts (e.g. outbound interface) pointing at a broken path.

Understand the failure class

Background: EmptyResultError / "no results found": when an API or scraper succeeds but returns zero rows — this error's family across 9 libraries.

Related errors


AI-assisted analysis of shadowsocks/shadowsocks-rust@8eb0f0a65b (2026-09-09). Data as JSON: /api/errors/c8656b6204b441fa. Report an issue: GitHub.