jpochyla/psst · error · io::Error

could not resolve to any addresses

Error message

could not resolve to any addresses

What it means

This io::Error (ErrorKind::InvalidInput) is produced by `stream_without_proxy` when `ap.to_socket_addrs()` resolves successfully but yields zero addresses, so the TCP connect loop never runs and no per-attempt error was recorded. It means the access-point hostname/port string could not be turned into any usable SocketAddr. It is the fallback branch (psst-core/src/connection/mod.rs:214-219) that normally would carry the last connect failure instead.

Solutions

  1. Verify the access-point strings passed to `connect` are non-empty, valid `host:port` pairs (run `nslookup <host>` to confirm they resolve to at least one address).
  2. Check DNS resolution on the machine (e.g. `dig <ap-host>`); fix resolv.conf/VPN/corporate DNS filtering if records are missing or filtered.
  3. Confirm the AP resolve endpoint (AP_RESOLVE_ENDPOINT) is reachable and returning data; if resolution of the AP list itself failed, the list may contain garbage entries.
  4. Try setting a SOCKS5 proxy_url so `stream_through_proxy` is used instead of direct resolution, bypassing local DNS.
  5. Filter the ap_list before calling connect, dropping entries that fail `tokio::net::lookup_host` or std `to_socket_addrs`.

Example fix

// before
let aps: Vec<String> = resolve_data.ap_list;
Connection::connect(&aps, None)?

// after
let aps: Vec<String> = resolve_data
    .ap_list
    .into_iter()
    .filter(|ap| ap.to_socket_addrs().map(|mut a| a.next().is_some()).unwrap_or(false))
    .collect();
if aps.is_empty() {
    return Err(Error::ConnectionFailed);
}
Connection::connect(&aps, None)?
Defensive patterns

Strategy: validation

Validate before calling

fn ap_is_resolvable(ap: &str) -> bool {
    use std::net::ToSocketAddrs;
    ap.to_socket_addrs().map(|mut a| a.next().is_some()).unwrap_or(false)
}
let aps: Vec<String> = ap_list.into_iter().filter(|ap| ap_is_resolvable(ap)).collect();
assert!(!aps.is_empty(), "no resolvable access points");

Type guard

fn is_valid_ap(ap: &str) -> bool {
    use std::net::ToSocketAddrs;
    !ap.trim().is_empty()
        && ap.to_socket_addrs().map(|mut it| it.next().is_some()).unwrap_or(false)
}

Try / catch

match Connection::connect(&aps, None) {
    Ok(conn) => conn,
    Err(Error::ConnectionFailed) => {
        // every AP failed; check DNS/network and retry or surface config problem
        eprintln!("no access point reachable: verify DNS and AP list");
        return Err(Error::ConnectionFailed);
    }
    Err(e) => return Err(e),
}

Prevention

When it happens

Trigger: Calling `Connection::connect(ap_list, None)` (which calls `stream_without_proxy`) with an access point string that DNS-resolves to an empty address list — e.g. an empty or whitespace hostname, a hostname with no DNS records, or a malformed `host:port` value where to_socket_addrs() still succeeds but returns no addresses.

Common situations: A stale or misconfigured AP resolver endpoint returning empty hostnames; DNS misconfiguration (a record type exists but yields no A/AAAA results on the platform); IPv6-only or IPv4-only environments where the resolver filters out all addresses; corporate networks blocking DNS resolution of Spotify access points so the list contains unresolvable entries; a bug or version change upstream in how the AP list is fetched (resolve_ap/resolve_spclient) leaving blank strings in the list.

Understand the failure class

Background: "Must be a positive integer", "Invalid value", "Unsupported": the invalid-argument-value error family, when a library rejects the value you pass — this error's family across 35 libraries.


AI-assisted analysis of jpochyla/psst@3c3621aa79 (2026-09-11). Data as JSON: /api/errors/a1555eb0e1d91124. Report an issue: GitHub.

Appendix: source

Thrown at psst-core/src/connection/mod.rs:215

        }
        log::error!("failed to connect to any access point");
        Err(Error::ConnectionFailed)
    }

    fn stream_without_proxy(ap: &str) -> Result<TcpStream, io::Error> {
        let mut last_err = None;
        for addr in ap.to_socket_addrs()? {
            match TcpStream::connect_timeout(&addr, NET_CONNECT_TIMEOUT) {
                Ok(stream) => {
                    return Ok(stream);
                }
                Err(err) => {
                    last_err.replace(err);
                }
            }
        }
        Err(last_err.unwrap_or_else(|| {
            io::Error::new(
                io::ErrorKind::InvalidInput,
                "could not resolve to any addresses",
            )
        }))
    }

    fn stream_through_proxy(ap: &str, url: &str) -> Result<TcpStream, Error> {
        match Url::parse(url) {
            Ok(url) if url.scheme() == "socks" || url.scheme() == "socks5" => {
                // Currently we only support SOCKS5 proxies.
                Self::stream_through_socks5_proxy(ap, &url)
            }
            _ => {
                // Proxy URL failed to parse or has unsupported scheme.
                Err(Error::ProxyUrlInvalid)
            }
        }
    }

View on GitHub (pinned to 3c3621aa79)