shadowsocks/shadowsocks-rust · error

invalid dnsname "{domain}"

Error message

invalid dnsname "{domain}"

What it means

connect_https builds a rustls TLS connection and must construct a rustls ServerName from the target domain. If the string is not a valid DNS name (empty, contains invalid characters, is an IP literal in a form rustls rejects, or has trailing dots), it returns InvalidInput with 'invalid dnsname'.

Source

Thrown at crates/shadowsocks-service/src/net/http_stream.rs:104

                        if let Err(err) = store.add(cert) {
                            warn!("failed to add cert (native), error: {}", err);
                        }
                    }

                    store
                })
                .with_no_client_auth();

            config.alpn_protocols = vec![b"h2".to_vec(), b"http/1.1".to_vec()];
            Arc::new(config)
        });

        let connector = TlsConnector::from(TLS_CONFIG.clone());

        let host = match ServerName::try_from(domain) {
            Ok(n) => n,
            Err(_) => {
                return Err(io::Error::new(
                    io::ErrorKind::InvalidInput,
                    format!("invalid dnsname \"{domain}\""),
                ));
            }
        };

        let tls_stream = connector.connect(host.to_owned(), stream).await?;

        let (_, session) = tls_stream.get_ref();
        let negotiated_http2 = matches!(session.alpn_protocol(), Some(b"h2"));

        Ok(Self::Https(tls_stream, negotiated_http2))
    }

    pub fn negotiated_http2(&self) -> bool {
        match *self {
            Self::Http(..) => false,
            #[cfg(any(feature = "local-http-native-tls", feature = "local-http-rustls"))]

View on GitHub (pinned to 8eb0f0a65b)

Solutions

  1. Extract and pass only the bare hostname (strip scheme, port, brackets for IPv6)
  2. Convert IDN to punycode (ASCII) before connecting
  3. If the target is an IP address, upgrade to a rustls version supporting IP ServerName or use a TLS stack path that accepts IPs
  4. Validate/trim the host string before calling connect_https
  5. Log the offending domain to spot config or parsing mistakes

Example fix

// before
let domain = "https://Example.com:443";
connect_https(domain).await?;
// after
let domain = "example.com"; // bare, lowercase, punycoded host
connect_https(domain).await?;
Defensive patterns

Strategy: validation

Validate before calling

fn is_valid_dnsname(host: &str) -> bool {
    !host.is_empty() && host.len() <= 253
      && host.split('.').all(|l| !l.is_empty() && l.len() <= 63
        && l.bytes().all(|b| b.is_ascii_alphanumeric() || b == b'-'))
}

Type guard

fn parse_server_name(host: &str) -> Option<rustls::pki_types::ServerName<'static>> {
    rustls::pki_types::ServerName::try_from(host.to_string()).ok() }

Try / catch

match connect_https(domain).await { Err(e) if e.kind()==InvalidInput && e.to_string().contains("invalid dnsname") => bail!("bad host in config: {domain}"), r => r }

Prevention

When it happens

Trigger: Calling connect_https with a domain that fails ServerName::try_from: empty string, non-ASCII/IDN not pre-punycoded, embedded whitespace or control chars, IP address string (older rustls rejected IP ServerName), or names >253 chars.

Common situations: Proxy targets taken from URLs without proper host extraction (keeping scheme or port in the host), internationalized domain names not converted to punycode, IPv6 literal hosts like '[::1]' passed with brackets, or empty host from malformed config.

Understand the failure class

Background: "invalid id" errors: invalid identifier format — why libraries reject IDs before lookup, and how to fix them — this error's family across 37 libraries.

Related errors


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