shadowsocks/shadowsocks-rust · error
empty outbound proxy chain
Error message
empty outbound proxy chain
What it means
connect_chain dials the first hop of an outbound proxy chain; it requires at least one configured hop. When the hops list is empty there is no proxy to dial, so the function returns InvalidInput before doing any I/O. This is a configuration error surfaced at connect time.
Source
Thrown at crates/shadowsocks-service/src/net/outbound/chain.rs:45
target: &Address,
) -> io::Result<OutboundProxyStream>
where
D: TcpDialer + Sync,
{
connect_chain(self.hops(), dialer, target).await
}
}
pub(crate) async fn connect_chain<D>(
hops: &[OutboundProxyHop],
dialer: &D,
target: &Address,
) -> io::Result<OutboundProxyStream>
where
D: TcpDialer + Sync,
{
let Some(first_hop) = hops.first() else {
return Err(io::Error::new(
io::ErrorKind::InvalidInput,
"empty outbound proxy chain",
));
};
trace!("dialling first outbound proxy hop {}", first_hop.addr);
let first_tcp = dialer.dial(&first_hop.addr).await?;
let mut stream = OutboundProxyStream::from_tcp(first_tcp)?;
for (idx, hop) in hops.iter().enumerate() {
// For HTTPS hops, wrap the wire layer with TLS *before* speaking
// the application-level CONNECT verb.
if hop.is_https() {
stream = tls_wrap(stream, hop.tls_sni()).await?;
}
let next_target = hops
.get(idx + 1)View on GitHub (pinned to 8eb0f0a65b)
Solutions
- Add at least one outbound proxy hop to the configuration before starting the server/client
- Validate the hop list is non-empty at config-load time and fail fast with a clear message
- If direct (non-proxied) outbound is intended, use the direct outbound path instead of connect_chain
Example fix
// before let hops: Vec<OutboundProxyHop> = parse_hops(cfg); // empty connect_chain(dialer, hops, target).await?; // after let hops: Vec<OutboundProxyHop> = parse_hops(cfg); assert!(!hops.is_empty(), "at least one outbound proxy hop required"); connect_chain(dialer, hops, target).await?;
Defensive patterns
Strategy: validation
Validate before calling
if outbound_hops.is_empty() {
return Err(anyhow!("outbound proxy chain must contain at least one hop"));
} Type guard
fn has_hops(hops: &[OutboundProxyHop]) -> bool { !hops.is_empty() } Try / catch
match connect_chain(dialer, hops, target).await {
Err(e) if e.kind() == std::io::ErrorKind::InvalidInput && e.to_string().contains("empty outbound proxy chain") => configure_fallback_hop()?,
other => other?,
} Prevention
- Validate the proxy chain is non-empty at config load time
- Fail fast on startup rather than on first connection
- Add a unit test for the empty-hops config case
When it happens
Trigger: Calling connect_tcp or connect_chain_for_udp_associate with an outbound config that resolved to a zero-length hop list (e.g. empty `outbound_forward` / proxy chain in config).
Common situations: Config file has an empty proxy chain array; the proxy section was commented out or omitted while chaining mode is still enabled; a loader filtered out all invalid proxy entries leaving none.
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
- failed to create ServerConfig, error: {}
- missing `addr` in configuration
- 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/1733abe1ac012d08.
Report an issue: GitHub.