{"record":{"id":"0593709c36cbb153","repo":"shadowsocks/shadowsocks-rust","slug":"empty-outbound-proxy-chain-059370","errorCode":null,"errorMessage":"empty outbound proxy chain","messagePattern":"empty outbound proxy chain","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"error","filePath":"crates/shadowsocks-service/src/net/outbound/udp.rs","lineNumber":104,"sourceCode":"    /// Establish UDP relays through every hop of `client`.\n    ///\n    /// * `target` is the inner-most destination (typically the ss-server's\n    ///   UDP external address).\n    /// * `dialer` is used to dial all TCP control connections.\n    /// * `connect_opts` configures the local UDP socket.\n    pub async fn associate<D>(\n        client: &OutboundProxyClient,\n        context: &SharedContext,\n        dialer: &D,\n        connect_opts: &ConnectOpts,\n        target: Address,\n    ) -> io::Result<Self>\n    where\n        D: TcpDialer + Sync,\n    {\n        let hops = client.hops();\n        if hops.is_empty() {\n            return Err(io::Error::new(\n                io::ErrorKind::InvalidInput,\n                \"empty outbound proxy chain\",\n            ));\n        }\n        for hop in hops {\n            if !matches!(hop.kind, OutboundProxyKind::Socks5 { .. }) {\n                return Err(io::Error::new(\n                    io::ErrorKind::Unsupported,\n                    \"outbound UDP relay requires every hop to be SOCKS5\",\n                ));\n            }\n        }\n\n        // Bind the local UDP socket using the shadowsocks helper so\n        // `ConnectOpts` (bind address, fwmark, ...) is honoured.\n        let socket = ShadowUdpSocket::connect_any_with_opts(AddrFamily::Ipv4, connect_opts).await?;\n\n        let local_udp_addr = socket.local_addr()?;","sourceCodeStart":86,"sourceCodeEnd":122,"githubUrl":"https://github.com/shadowsocks/shadowsocks-rust/blob/8eb0f0a65b1d976ab6bed5787327ef86529b0435/crates/shadowsocks-service/src/net/outbound/udp.rs#L86-L122","documentation":"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.","triggerScenarios":"Calling OutboundUdpProxy::associate with a client whose hops() vector is empty — the same misconfiguration as error 100 but on the UDP path.","commonSituations":"Empty or missing UDP forward-proxy config; all proxy entries rejected at parse time; the user expects direct UDP forwarding but supplied no proxies.","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"],"exampleFix":"// before\nif client.hops().is_empty() { return Err(...); } // hit at runtime\n// after\n// at config load:\nensure!(!cfg.udp_hops.is_empty(), \"udp outbound requires at least one socks5 hop\");","handlingStrategy":"validation","validationCode":"if client.hops().is_empty() {\n    return Err(anyhow!(\"UDP outbound needs at least one socks5 hop\"));\n}","typeGuard":"fn udp_capable(hops: &[OutboundProxyHop]) -> bool { !hops.is_empty() }","tryCatchPattern":"match OutboundUdpProxy::associate(client, peer).await {\n    Err(e) if e.kind() == io::ErrorKind::InvalidInput && e.to_string().contains(\"empty outbound proxy chain\") => configure_udp_hops()?,\n    other => other?,\n}","preventionTips":["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"],"tags":["udp","proxy","configuration"],"backgroundTag":"empty-required-field","analyzedSha":"8eb0f0a65b1d976ab6bed5787327ef86529b0435","analyzedAt":"2026-09-09T12:20:43.168Z","contentChangedAt":"2026-09-09T12:20:43.168Z","schemaVersion":2},"datasetVersion":"2026-09-14T16:17:12.679Z"}