shadowsocks/shadowsocks-rust · error

malformed `mode`, must be one of `tcp_only`, `udp_only` and

Error message

malformed `mode`, must be one of `tcp_only`, `udp_only` and `tcp_and_udp`

What it means

While parsing the server configuration (config.rs, mode section), the `mode` string is parsed into Mode. If it is not exactly one of `tcp_only`, `udp_only`, or `tcp_and_udp`, the parse fails and the library returns ErrorKind::Malformed. This keeps invalid global modes from silently defaulting to TCP-only.

Source

Thrown at crates/shadowsocks-service/src/config.rs:1788

                    // by checking all its remote servers if all of them supports IPv6.
                    let ip = if ipv6_first {
                        Ipv6Addr::LOCALHOST.into()
                    } else {
                        Ipv4Addr::LOCALHOST.into()
                    };

                    ServerAddr::from(SocketAddr::new(ip, local_port))
                }
            }
        }

        // Mode
        let mut global_mode = Mode::TcpOnly;
        if let Some(m) = config.mode {
            match m.parse::<Mode>() {
                Ok(xm) => global_mode = xm,
                Err(..) => {
                    let e = Error::new(
                        ErrorKind::Malformed,
                        "malformed `mode`, must be one of `tcp_only`, `udp_only` and `tcp_and_udp`",
                        None,
                    );
                    return Err(e);
                }
            }
        }

        match config_type {
            ConfigType::Local => {
                // Standard config
                if config.local_address.is_some() && config.local_port.unwrap_or(0) == 0 {
                    let err = Error::new(ErrorKind::MissingField, "missing `local_port`", None);
                    return Err(err);
                }

                if let Some(local_port) = config.local_port {

View on GitHub (pinned to 8eb0f0a65b)

Solutions

  1. Change `mode` in the config to exactly one of "tcp_only", "udp_only", or "tcp_and_udp" (all lowercase, with underscores).
  2. Remove the `mode` key entirely to accept the default (Mode::TcpOnly).
  3. Check for typos/casing when the value comes from an environment variable or template substitution.

Example fix

// before
{ "mode": "TCP-ONLY" }
// after
{ "mode": "tcp_only" }
Defensive patterns

Strategy: validation

Validate before calling

const MODES: [&str; 3] = ["tcp_only", "udp_only", "tcp_and_udp"];
if let Some(m) = raw.get("mode") {
    if !MODES.contains(&m.as_str().unwrap_or("")) {
        return Err(format!("invalid mode: {m}; must be one of tcp_only, udp_only, tcp_and_udp"));
    }
}

Type guard

fn is_valid_mode(s: &str) -> bool {
    matches!(s, "tcp_only" | "udp_only" | "tcp_and_udp")
}

Try / catch

match Config::load(path) {
    Ok(cfg) => start(cfg),
    Err(e) if e.to_string().contains("malformed `mode`") => eprintln!("mode must be tcp_only, udp_only, or tcp_and_udp (lowercase)"),
    Err(e) => return Err(e),
}

Prevention

When it happens

Trigger: Loading a config whose JSON `mode` field contains a string that Mode::from_str rejects, e.g. "tcp", "TCP_ONLY", "tcp-and-udp", or any typo; typically hit via Config::load / Config::from_str on a JSON config.

Common situations: Hand-written configs using wrong casing or abbreviations; older configs using legacy mode spellings; generated configs from third-party tools emitting non-canonical mode values.

Understand the failure class

Background: Invalid enum value errors: "Unknown type", "Invalid scope", "must be one of" — when a string is not on the library's allowed list — this error's family across 23 libraries.

Related errors


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