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

When --plugin is configured, the optional PLUGIN_MODE value is parsed into the Mode enum (TcpOnly/UdpOnly/TcpAndUdp); an unknown string makes .parse fail and the expect panics with this message. It restricts plugin-mode to the three documented values, defaulting to tcp_only when the flag is absent.

Source

Thrown at src/service/local.rs:675

                Err(err) => {
                    panic!("failed to create ServerConfig, error: {}", err);
                }
            };
            sc.set_source(ServerSource::CommandLine);
            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);
            }

            config.server.push(ServerInstanceConfig::with_server_config(sc));
        }

        if let Some(mut svr_addr) = matches.get_one::<ServerConfig>("SERVER_URL").cloned() {
            svr_addr.set_source(ServerSource::CommandLine);
            config.server.push(ServerInstanceConfig::with_server_config(svr_addr));
        }

        #[cfg(feature = "local-flow-stat")]
        {
            use shadowsocks_service::config::LocalFlowStatAddress;

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 to get the tcp_only default.
  3. Check the Mode enum's FromStr for the accepted spellings of your binary version.

Example fix

// before
sslocal --plugin v2ray-plugin --plugin-mode both ...

// after
sslocal --plugin v2ray-plugin --plugin-mode tcp_and_udp ...
Defensive patterns

Strategy: validation

Validate before calling

const VALID_MODES: [&str; 3] = ["tcp_only", "udp_only", "tcp_and_udp"];
if let Some(m) = matches.get_one::<String>("PLUGIN_MODE") {
    if !VALID_MODES.contains(&m.as_str()) {
        return Err(format!("invalid plugin-mode: {}", m));
    }
}

Try / catch

let mode = m.parse::<Mode>().unwrap_or_else(|_| {
    eprintln!("plugin-mode must be tcp_only, udp_only or tcp_and_udp");
    std::process::exit(2);
});

Prevention

When it happens

Trigger: Running sslocal with --plugin and --plugin-mode set to something other than tcp_only, udp_only, or tcp_and_udp — e.g. "both", "tcp", "TCP_ONLY", or a mode supported only by ssserver.

Common situations: Case sensitivity mistakes; inventing mode names; copying server-side mode values that don't exist for the local plugin path; config generators emitting wrong keys.

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