shadowsocks/shadowsocks-rust · error

server-addr

Error message

server-addr

What it means

The `SERVER_ADDR` CLI value is parsed with `svr_addr.parse::<ServerAddr>().expect("server-addr")`. The panic means the string is not a valid SIP002 server address — it must be a `host:port` (or `hostname:port`) pair. Invalid hosts, missing port, or malformed IPv6 literals all fail this parse.

Source

Thrown at src/service/server.rs:355

                .expect("`method` is required");

            let password = match matches.get_one::<String>("PASSWORD") {
                Some(pwd) => read_variable_field_value(pwd).into(),
                None => {
                    // NOTE: svr_addr should have been checked by crate::vparser
                    if method.is_none() {
                        // If method doesn't need a key (none, plain), then we can leave it empty
                        String::new()
                    } else {
                        match crate::password::read_server_password(svr_addr) {
                            Ok(pwd) => pwd,
                            Err(..) => panic!("`password` is required for server {svr_addr}"),
                        }
                    }
                }
            };

            let svr_addr = svr_addr.parse::<ServerAddr>().expect("server-addr");
            let timeout = matches.get_one::<u64>("TIMEOUT").map(|x| Duration::from_secs(*x));

            let mut sc = match ServerConfig::new(svr_addr, password, method) {
                Ok(sc) => sc,
                Err(err) => {
                    panic!("failed to create ServerConfig, error: {}", err);
                }
            };
            if let Some(timeout) = timeout {
                sc.set_timeout(timeout);
            }

            if let Some(p) = matches.get_one::<String>("PLUGIN").cloned() {
                let plugin = PluginConfig {
                    plugin: p,
                    plugin_opts: matches.get_one::<String>("PLUGIN_OPT").cloned(),
                    plugin_args: Vec::new(),
                    plugin_mode: matches

View on GitHub (pinned to 8eb0f0a65b)

Solutions

  1. Supply a valid `host:port` pair, e.g. `-s "0.0.0.0:8388"`
  2. Bracket IPv6 literals: `-s "[::]:8388"`
  3. Strip any scheme prefix and trim whitespace from the value
  4. Use a config file with a properly validated `server` field for complex setups

Example fix

// before
ssserver -s "0.0.0.0" -p "pass" -m aes-256-gcm
// after
ssserver -s "0.0.0.0:8388" -p "pass" -m aes-256-gcm
Defensive patterns

Strategy: validation

Validate before calling

use std::net::ToSocketAddrs;
fn valid_server_addr(s: &str) -> bool {
    s.rsplit_once(':').map(|(h, p)| p.parse::<u16>().is_ok() && !h.is_empty()).unwrap_or(false)
        && s.to_socket_addrs().is_ok()
}

Type guard

fn looks_like_host_port(s: &str) -> bool {
    s.rsplit_once(':').map(|(h, p)| !h.is_empty() && p.parse::<u16>().is_ok()).unwrap_or(false)
}

Try / catch

// parse explicitly with guidance
let addr: ServerAddr = s.parse().map_err(|_| anyhow!("-s must be host:port (IPv6 needs brackets: [::1]:8388)"))?;

Prevention

When it happens

Trigger: Running `ssserver`/managers with `-s` values like `0.0.0.0` (no port), `:8388` (no host), `example.com` (no port), or unbracketed IPv6 like `-s ::1:8388`.

Common situations: Forgetting the port; writing IPv6 addresses without brackets (`[::1]:8388` is required); trailing whitespace or protocol prefixes (`udp://host:port`).

Understand the failure class

Background: "Invalid ... format", "must be in format X", "does not look like a ..." — invalid argument format errors across CLI tools and libraries — this error's family across 17 libraries.

Related errors


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