shadowsocks/shadowsocks-rust · error
empty outbound proxy chain
Error message
empty outbound proxy chain
What it means
OutboundUdpProxy::associate builds a UDP relay over the proxy chain, but UDP relaying is only possible when at least one hop is configured and (per the next check) all hops are SOCKS5. An empty hop list yields InvalidInput with this message before any networking occurs.
Source
Thrown at crates/shadowsocks-service/src/net/outbound/udp.rs:104
/// Establish UDP relays through every hop of `client`.
///
/// * `target` is the inner-most destination (typically the ss-server's
/// UDP external address).
/// * `dialer` is used to dial all TCP control connections.
/// * `connect_opts` configures the local UDP socket.
pub async fn associate<D>(
client: &OutboundProxyClient,
context: &SharedContext,
dialer: &D,
connect_opts: &ConnectOpts,
target: Address,
) -> io::Result<Self>
where
D: TcpDialer + Sync,
{
let hops = client.hops();
if hops.is_empty() {
return Err(io::Error::new(
io::ErrorKind::InvalidInput,
"empty outbound proxy chain",
));
}
for hop in hops {
if !matches!(hop.kind, OutboundProxyKind::Socks5 { .. }) {
return Err(io::Error::new(
io::ErrorKind::Unsupported,
"outbound UDP relay requires every hop to be SOCKS5",
));
}
}
// Bind the local UDP socket using the shadowsocks helper so
// `ConnectOpts` (bind address, fwmark, ...) is honoured.
let socket = ShadowUdpSocket::connect_any_with_opts(AddrFamily::Ipv4, connect_opts).await?;
let local_udp_addr = socket.local_addr()?;View on GitHub (pinned to 8eb0f0a65b)
Solutions
- Configure at least one SOCKS5 proxy hop before creating the UDP associate
- Validate hops().is_empty() at config load and reject the configuration early
- Use the direct UDP socket path if no proxy is intended
Example fix
// before
if client.hops().is_empty() { return Err(...); } // hit at runtime
// after
// at config load:
ensure!(!cfg.udp_hops.is_empty(), "udp outbound requires at least one socks5 hop"); Defensive patterns
Strategy: validation
Validate before calling
if client.hops().is_empty() {
return Err(anyhow!("UDP outbound needs at least one socks5 hop"));
} Type guard
fn udp_capable(hops: &[OutboundProxyHop]) -> bool { !hops.is_empty() } Try / catch
match OutboundUdpProxy::associate(client, peer).await {
Err(e) if e.kind() == io::ErrorKind::InvalidInput && e.to_string().contains("empty outbound proxy chain") => configure_udp_hops()?,
other => other?,
} Prevention
- Validate UDP hops at startup, not on first associate
- Share one validated chain builder between TCP and UDP paths
- Log a config-error and exit early instead of failing per-connection
When it happens
Trigger: Calling OutboundUdpProxy::associate with a client whose hops() vector is empty — the same misconfiguration as error 100 but on the UDP path.
Common situations: Empty or missing UDP forward-proxy config; all proxy entries rejected at parse time; the user expects direct UDP forwarding but supplied no proxies.
Understand the failure class
Background: "must not be empty", "cannot be empty" — required-field validation errors across open-source libraries — this error's family across 41 libraries.
Related errors
- unexpected response from 8.8.8.8:53
- not supported udp transparent proxy type
- empty outbound proxy chain
- all plugins are exited. all connections may fail, check your
- unsupported syslog facility: {}
AI-assisted analysis of shadowsocks/shadowsocks-rust@8eb0f0a65b (2026-09-09).
Data as JSON: /api/errors/0593709c36cbb153.
Report an issue: GitHub.