shadowsocks/shadowsocks-rust · error

dns

Error message

dns

What it means

The DNS value from --dns is passed to config.set_dns_formatted, which parses a comma/space-separated list of name servers (and optional protocol prefixes like udp://, tcp://, https://); malformed input makes the .expect("dns") panic. This must run under the local-dns feature and accepts formats like 8.8.8.8 or 8.8.8.8:53.

Source

Thrown at src/service/local.rs:900

        #[cfg(all(unix, not(target_os = "android")))]
        match matches.get_one::<u64>("NOFILE") {
            Some(nofile) => config.nofile = Some(*nofile),
            None => {
                if config.nofile.is_none() {
                    crate::sys::adjust_nofile();
                }
            }
        }

        if let Some(acl_file) = matches.get_one::<String>("ACL") {
            let acl = AccessControl::load_from_file(acl_file)
                .map_err(|err| ShadowsocksError::LoadAclFailure(format!("loading ACL \"{acl_file}\", {err}")))?;
            config.acl = Some(acl);
        }

        if let Some(dns) = matches.get_one::<String>("DNS") {
            config.set_dns_formatted(dns).expect("dns");
        }

        if let Some(dns_cache_size) = matches.get_one::<usize>("DNS_CACHE_SIZE") {
            config.dns_cache_size = Some(*dns_cache_size);
        }

        if matches.get_flag("IPV6_FIRST") {
            config.ipv6_first = true;
        }

        if let Some(udp_timeout) = matches.get_one::<u64>("UDP_TIMEOUT") {
            config.udp_timeout = Some(Duration::from_secs(*udp_timeout));
        }

        if let Some(udp_max_assoc) = matches.get_one::<usize>("UDP_MAX_ASSOCIATIONS") {
            config.udp_max_associations = Some(*udp_max_assoc);
        }

View on GitHub (pinned to 8eb0f0a65b)

Solutions

  1. Use a comma-separated list of valid name servers, e.g. --dns "8.8.8.8,1.1.1.1".
  2. Prefix protocols explicitly when needed: "udp://8.8.8.8:53,tcp://1.1.1.1:53,https://dns.google/dns-query".
  3. Pre-validate each token parses as a NameServerAddr (or fix scheme/port typos) before invoking.

Example fix

// before
sslocal --dns "systemd-resolved" ...

// after
sslocal --dns "udp://127.0.0.53:53,8.8.8.8" ...
Defensive patterns

Strategy: validation

Validate before calling

fn validate_dns_arg(dns: &str) -> Result<(), String> {
    for ns in dns.split(',').map(str::trim).filter(|s| !s.is_empty()) {
        let ns = ns.strip_prefix("udp://").or_else(|| ns.strip_prefix("tcp://"))
            .unwrap_or(ns);
        if ns.parse::<std::net::SocketAddr>().is_err()
            && ns.parse::<std::net::IpAddr>().is_err() {
            return Err(format!("invalid dns nameserver: {}", ns));
        }
    }
    Ok(())
}

Try / catch

config.set_dns_formatted(dns).unwrap_or_else(|e| {
    eprintln!("invalid --dns value '{}': {}", dns, e);
    std::process::exit(2);
});

Prevention

When it happens

Trigger: Running sslocal with --dns set to a malformed nameserver list: missing port with a bad token ("8.8.8.8:dnsgarbage"), empty entries ("8.8.8.8,,1.1.1.1"), unsupported scheme (ftp://8.8.8.8), or a value like "systemd" instead of a real address.

Common situations: Pasting resolv.conf-style content; using "systemd-resolved" as a value; extra whitespace/typos in addresses; scheme typos (htps://); trying "local" instead of an IP.

Understand the failure class

Background: "Invalid value" and "allowed values are" config errors: what your library rejected and how to fix it — this error's family across 41 libraries.

Related errors


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