{"record":{"id":"49ff3e33ecf06fb9","repo":"shadowsocks/shadowsocks-rust","slug":"protect-timeout","errorCode":null,"errorMessage":"protect() timeout","messagePattern":"protect\\(\\) timeout","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"crates/shadowsocks/src/net/sys/unix/linux/mod.rs","lineNumber":424,"sourceCode":"\n        Ok(())\n    }\n\n    /// Try to run VPNService#protect on Android\n    ///\n    /// https://developer.android.com/reference/android/net/VpnService#protect(java.net.Socket)\n    pub async fn vpn_protect<S>(socket: &S, opts: &ConnectOpts) -> io::Result<()>\n    where\n        S: AsRawFd + Send + Sync + 'static,\n    {\n        // shadowsocks-android uses a Unix domain socket to communicate with the VPNService#protect\n        if let Some(ref path) = opts.vpn_protect_path {\n            // RPC calls to `VpnService.protect()`\n            // Timeout in 3 seconds like shadowsocks-libev\n            match time::timeout(Duration::from_secs(3), send_vpn_protect_uds(path, socket.as_raw_fd())).await {\n                Ok(Ok(..)) => {}\n                Ok(Err(err)) => return Err(err),\n                Err(..) => return Err(io::Error::new(ErrorKind::TimedOut, \"protect() timeout\")),\n            }\n        }\n\n        // Customized SocketProtect\n        if let Some(ref protect) = opts.vpn_socket_protect {\n            protect.protect(socket.as_raw_fd())?;\n        }\n\n        Ok(())\n    }\n}\n\nstatic SUPPORT_BATCH_SEND_RECV_MSG: AtomicBool = AtomicBool::new(true);\n\nfn recvmsg_fallback<S: AsRawFd>(sock: &S, msg: &mut BatchRecvMessage<'_>) -> io::Result<()> {\n    let mut hdr: libc::msghdr = unsafe { mem::zeroed() };\n\n    let addr_storage = SockAddrStorage::zeroed();","sourceCodeStart":406,"sourceCodeEnd":442,"githubUrl":"https://github.com/shadowsocks/shadowsocks-rust/blob/8eb0f0a65b1d976ab6bed5787327ef86529b0435/crates/shadowsocks/src/net/sys/unix/linux/mod.rs#L406-L442","documentation":"When the shadowsocks local server runs on Android under a VpnService, outbound sockets must be explicitly approved via the VpnService.protect() RPC over a Unix domain socket before use. This error is thrown when that RPC does not complete within 3 seconds (matching shadowsocks-libev's timeout). It means the VPN service failed to protect the socket in time, so the connection attempt is aborted with ErrorKind::TimedOut.","triggerScenarios":"The vpn_protect_path option is set (Android VPN mode) and send_vpn_protect_uds() neither succeeds nor fails within 3 seconds — the VpnService binder side is unresponsive, the protect path points to a dead/incorrect socket file whose peer never replies, or the system is heavily loaded so the RPC stalls.","commonSituations":"Android client apps (e.g. plugins for shadowsocks-android) where the VpnService was killed or restarted and the UDS path is stale; misconfigured protect_path passed from the app; device under severe load or doze mode delaying binder responses.","solutions":["Verify the protect_path passed to vpn_protect_path is the exact UDS path exposed by the current VpnService instance (paths change when the VPN restarts)","Confirm the VpnService side is actually listening on the socket and calling protect() promptly; restart the VPN service/app","Check the device for heavy load or battery-saver/doze restrictions that delay the RPC and re-test","Increase the 3-second timeout in the source if the RPC is merely slow in your environment","Fall back to running without VPN protect if the app no longer runs under VpnService (remove vpn_protect_path from options)"],"exampleFix":"// before\nlet opts = ServerOpts { vpn_protect_path: Some(\"/data/data/com.example/stale_protect.sock\".into()), .. };\n// after\nlet opts = ServerOpts { vpn_protect_path: Some(current_vpn_service_protect_path()).into(), .. }; // path fetched from the live VpnService","handlingStrategy":"retry","validationCode":"// Before starting the server, verify the protect UDS is live\nif let Some(path) = &opts.vpn_protect_path {\n    if std::fs::metadata(path).is_err() {\n        panic!(\"protect path {} does not exist — VpnService not running?\", path);\n    }\n}","typeGuard":"fn protect_path_is_live(path: &str) -> bool {\n    std::os::unix::net::UnixStream::connect(path).is_ok()\n}","tryCatchPattern":"match server.start().await {\n    Err(e) if e.kind() == std::io::ErrorKind::TimedOut && e.to_string().contains(\"protect() timeout\") => {\n        // refresh protect path from VpnService and retry once\n        eprintln!(\"VPN protect RPC timed out; restarting VpnService and retrying\");\n        vpn_service.restart();\n        server.start().await?;\n    }\n    r => r?,\n}","preventionTips":["Always fetch the protect path from the live VpnService instance, never hard-code it","Verify the UDS exists and accepts connections before launching the server","Keep the app's VpnService foregrounded so Android does not kill it mid-session","Test on real devices under battery-saver/doze conditions, not just emulators"],"tags":["android","vpn","timeout","unix-domain-socket","network"],"backgroundTag":"request-timeout","analyzedSha":"8eb0f0a65b1d976ab6bed5787327ef86529b0435","analyzedAt":"2026-09-09T12:20:43.168Z","contentChangedAt":"2026-09-09T12:20:43.168Z","schemaVersion":2},"datasetVersion":"2026-09-14T11:17:12.474Z"}