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
- Use a comma-separated list of valid name servers, e.g. --dns "8.8.8.8,1.1.1.1".
- Prefix protocols explicitly when needed: "udp://8.8.8.8:53,tcp://1.1.1.1:53,https://dns.google/dns-query".
- 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
- Pass explicit IP[:port] nameservers, comma-separated: "8.8.8.8,1.1.1.1".
- Use correct scheme prefixes (udp://, tcp://, https://) when non-default DNS is needed.
- Never pass resolver names like "systemd-resolved"; use its listener IP 127.0.0.53 instead.
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.