shadowsocks/shadowsocks-rust · error

plugin-mode must be one of `tcp_only` (default), `udp_only`

Error message

plugin-mode must be one of `tcp_only` (default), `udp_only` and `tcp_and_udp`

What it means

This panic comes from an `.expect()` on `str::parse::<Mode>()` while building the plugin configuration from the `--plugin-mode` CLI flag. The string is parsed into the shadowsocks `Mode` enum, which only accepts `tcp_only`, `udp_only`, and `tcp_and_udp`; any other value panics with this message instead of returning a graceful config error.

Source

Thrown at src/service/server.rs:377

                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
                        .get_one::<String>("PLUGIN_MODE")
                        .map(|x| {
                            x.parse::<Mode>()
                                .expect("plugin-mode must be one of `tcp_only` (default), `udp_only` and `tcp_and_udp`")
                        })
                        .unwrap_or(Mode::TcpOnly),
                };

                sc.set_plugin(plugin);
            }

            // For historical reason, servers that are created from command-line have to be tcp_only.
            sc.set_mode(Mode::TcpOnly);

            if matches.get_flag("UDP_ONLY") {
                sc.set_mode(Mode::UdpOnly);
            }

            if matches.get_flag("TCP_AND_UDP") {
                sc.set_mode(Mode::TcpAndUdp);
            }

View on GitHub (pinned to 8eb0f0a65b)

Solutions

  1. Set --plugin-mode to exactly one of `tcp_only`, `udp_only`, or `tcp_and_udp` (lowercase, underscores).
  2. Omit --plugin-mode entirely to get the default `tcp_only`.
  3. If the value comes from a script/config file, validate it against the three allowed strings before launching.
  4. Wrap the parse in a checked path (e.g. `Mode::from_str` with error handling) instead of `.expect` if patching the code.

Example fix

// before
sslocal --plugin obfs --plugin-opts "obfs=http" --plugin-mode TCP_ONLY
// after
sslocal --plugin obfs --plugin-opts "obfs=http" --plugin-mode tcp_only
Defensive patterns

Strategy: validation

Validate before calling

const VALID_MODES: [&str; 3] = ["tcp_only", "udp_only", "tcp_and_udp"];
if !VALID_MODES.contains(&plugin_mode.as_str()) {
    panic!("plugin-mode must be one of tcp_only, udp_only, tcp_and_udp, got: {plugin_mode}");
}

Type guard

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

Prevention

When it happens

Trigger: Passing `--plugin-mode` (PLUGIN_OPT/PLUGIN_MODE matches in src/service/server.rs:377) with a value that is not exactly `tcp_only`, `udp_only`, or `tcp_and_udp`, e.g. `tcp`, `TCP_ONLY`, `udp`, or a typo like `tcp-and-udp`.

Common situations: Users copying plugin flags from shadowsocks-libev or other implementations that use different mode spellings (`tcp_and_udp` vs `TCP`), typos in launch scripts or systemd units, case mistakes since parsing is case-sensitive.

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/d1c499d14572a62c. Report an issue: GitHub.