{"record":{"id":"78f14346a11d5555","repo":"shadowsocks/shadowsocks-rust","slug":"outbound-udp-relay-requires-every-hop-to-be-socks5","errorCode":null,"errorMessage":"outbound UDP relay requires every hop to be SOCKS5","messagePattern":"outbound UDP relay requires every hop to be SOCKS5","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"error","filePath":"crates/shadowsocks-service/src/net/outbound/udp.rs","lineNumber":111,"sourceCode":"        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()?;\n        trace!(\"outbound udp local socket bound to {}\", local_udp_addr);\n\n        let mut relays: Vec<Socks5UdpRelay> = Vec::with_capacity(hops.len());\n\n        // The \"announce\" address tells the next hop the source it should\n        // expect datagrams from. For the first hop this is the local UDP\n        // socket; for later hops it is the relay address granted by the","sourceCodeStart":93,"sourceCodeEnd":129,"githubUrl":"https://github.com/shadowsocks/shadowsocks-rust/blob/8eb0f0a65b1d976ab6bed5787327ef86529b0435/crates/shadowsocks-service/src/net/outbound/udp.rs#L93-L129","documentation":"UDP relay through the outbound chain is implemented only for SOCKS5 (via UDP ASSOCIATE). If any hop in the chain is a different kind (HTTP, HTTPS, shadowsocks, etc.), associate rejects the whole chain with Unsupported since intermediate protocols cannot carry UDP.","triggerScenarios":"OutboundUdpProxy::associate receives a hop list where at least one hop is not OutboundProxyKind::Socks5 — e.g. an http hop in the middle of the chain, or a pure-https chain used for UDP.","commonSituations":"Sharing one proxy chain config between TCP and UDP paths; assuming HTTP CONNECT proxies forward UDP; adding an https hop for encryption without realizing UDP needs SOCKS5.","solutions":["Make every hop in the UDP chain OutboundProxyKind::Socks5","Use a separate TCP-only chain for non-SOCKS5 proxies and a SOCKS5-only chain for UDP","Drop the non-SOCKS5 hop or terminate UDP locally instead of relaying"],"exampleFix":"// before\nhops: vec![http_hop, socks5_hop] // used for UDP\n// after\nhops: vec![socks5_hop_1, socks5_hop_2] // all socks5 for UDP relay","handlingStrategy":"validation","validationCode":"fn all_socks5(hops: &[OutboundProxyHop]) -> bool {\n    !hops.is_empty() && hops.iter().all(|h| matches!(h.kind, OutboundProxyKind::Socks5{..}))\n}\nif !all_socks5(&hops) { return Err(anyhow!(\"udp relay chain must be all socks5\")); }","typeGuard":"fn is_socks5(k: &OutboundProxyKind) -> bool { matches!(k, OutboundProxyKind::Socks5{..}) }","tryCatchPattern":"match associate(client).await {\n    Err(e) if e.kind() == io::ErrorKind::Unsupported && e.to_string().contains(\"SOCKS5\") => {\n        eprintln!(\"use socks5-only chain for udp\");\n    }\n    other => other?,\n}","preventionTips":["Keep separate chain configs for TCP and UDP outbound","Validate every hop kind before enabling UDP relay","Document that UDP relay requires end-to-end SOCKS5 support"],"tags":["udp","socks5","unsupported"],"backgroundTag":"unsupported-operation","analyzedSha":"8eb0f0a65b1d976ab6bed5787327ef86529b0435","analyzedAt":"2026-09-09T12:20:43.168Z","contentChangedAt":"2026-09-09T12:20:43.168Z","schemaVersion":2},"datasetVersion":"2026-09-17T15:17:12.973Z"}