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

The manager service parses `PLUGIN_MODE` into the `Mode` enum (`tcp_only` / `udp_only` / `tcp_and_udp`) and panics with this message when the string doesn't match any variant. This value controls whether the SIP003 plugin handles TCP only, UDP only, or both.

Source

Thrown at src/service/manager.rs:387

            if let Some(t) = matches.get_one::<u64>("TIMEOUT") {
                manager_config.timeout = Some(Duration::from_secs(*t));
            }

            if let Some(sh) = matches.get_one::<ManagerServerHost>("SERVER_HOST").cloned() {
                manager_config.server_host = sh;
            }

            if let Some(p) = matches.get_one::<String>("PLUGIN").cloned() {
                manager_config.plugin = Some(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),
                });
            }

            #[cfg(unix)]
            if let Some(server_mode) = matches.get_one::<ManagerServerMode>("MANAGER_SERVER_MODE").cloned() {
                manager_config.server_mode = server_mode;
            }

            #[cfg(unix)]
            if let Some(server_working_directory) =
                matches.get_one::<PathBuf>("MANAGER_SERVER_WORKING_DIRECTORY").cloned()
            {
                manager_config.server_working_directory = server_working_directory;
            }
        }

View on GitHub (pinned to 8eb0f0a65b)

Solutions

  1. Use exactly one of: `tcp_only`, `udp_only`, `tcp_and_udp` (lowercase, underscores)
  2. Omit `--plugin-mode` entirely to get the default `tcp_only`
  3. Check the config file for a `plugin_mode` value and normalize it to a valid variant

Example fix

// before
--plugin-mode "TCP-AND-UDP"
// after
--plugin-mode "tcp_and_udp"
Defensive patterns

Strategy: validation

Validate before calling

const MODES: &[&str] = &["tcp_only", "udp_only", "tcp_and_udp"];
if let Some(m) = plugin_mode.as_deref() {
    assert!(MODES.contains(&m), "plugin-mode must be one of {MODES:?}");
}

Type guard

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

Try / catch

// use parse and report instead of expect
let mode: Mode = s.parse().map_err(|_| anyhow!("plugin-mode must be tcp_only|udp_only|tcp_and_udp"))?;

Prevention

When it happens

Trigger: Passing `--plugin-mode` (or equivalent) with anything other than the exact strings `tcp_only`, `udp_only`, or `tcp_and_udp`, e.g. `tcp`, `TCP_ONLY`, or `tcp-and-udp`.

Common situations: Users guessing the separator (`-` vs `_`), copying values from other proxy tools, or capitalizing the value from JSON/YAML config conventions.

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