shadowsocks/shadowsocks-rust · error
protect() timeout
Error message
protect() timeout
What it means
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.
Source
Thrown at crates/shadowsocks/src/net/sys/unix/linux/mod.rs:424
Ok(())
}
/// Try to run VPNService#protect on Android
///
/// https://developer.android.com/reference/android/net/VpnService#protect(java.net.Socket)
pub async fn vpn_protect<S>(socket: &S, opts: &ConnectOpts) -> io::Result<()>
where
S: AsRawFd + Send + Sync + 'static,
{
// shadowsocks-android uses a Unix domain socket to communicate with the VPNService#protect
if let Some(ref path) = opts.vpn_protect_path {
// RPC calls to `VpnService.protect()`
// Timeout in 3 seconds like shadowsocks-libev
match time::timeout(Duration::from_secs(3), send_vpn_protect_uds(path, socket.as_raw_fd())).await {
Ok(Ok(..)) => {}
Ok(Err(err)) => return Err(err),
Err(..) => return Err(io::Error::new(ErrorKind::TimedOut, "protect() timeout")),
}
}
// Customized SocketProtect
if let Some(ref protect) = opts.vpn_socket_protect {
protect.protect(socket.as_raw_fd())?;
}
Ok(())
}
}
static SUPPORT_BATCH_SEND_RECV_MSG: AtomicBool = AtomicBool::new(true);
fn recvmsg_fallback<S: AsRawFd>(sock: &S, msg: &mut BatchRecvMessage<'_>) -> io::Result<()> {
let mut hdr: libc::msghdr = unsafe { mem::zeroed() };
let addr_storage = SockAddrStorage::zeroed();View on GitHub (pinned to 8eb0f0a65b)
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)
Example fix
// before
let opts = ServerOpts { vpn_protect_path: Some("/data/data/com.example/stale_protect.sock".into()), .. };
// after
let opts = ServerOpts { vpn_protect_path: Some(current_vpn_service_protect_path()).into(), .. }; // path fetched from the live VpnService Defensive patterns
Strategy: retry
Validate before calling
// Before starting the server, verify the protect UDS is live
if let Some(path) = &opts.vpn_protect_path {
if std::fs::metadata(path).is_err() {
panic!("protect path {} does not exist — VpnService not running?", path);
}
} Type guard
fn protect_path_is_live(path: &str) -> bool {
std::os::unix::net::UnixStream::connect(path).is_ok()
} Try / catch
match server.start().await {
Err(e) if e.kind() == std::io::ErrorKind::TimedOut && e.to_string().contains("protect() timeout") => {
// refresh protect path from VpnService and retry once
eprintln!("VPN protect RPC timed out; restarting VpnService and retrying");
vpn_service.restart();
server.start().await?;
}
r => r?,
} Prevention
- 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
When it happens
Trigger: 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.
Common situations: 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.
Understand the failure class
Background: Request timed out: what client-side request timeouts mean across libraries (Request timed out, TIMED_OUT, APITimeoutError) — this error's family across 39 libraries.
- Timeouts: ETIMEDOUT, deadlines, and hung requests — what actually expires when a request times out.
Related errors
- resolve empty
- connect {} timeout
- invalid outbound_bind_addr
- resolve empty
- unexpected response from http://clients3.google.com/generate
AI-assisted analysis of shadowsocks/shadowsocks-rust@8eb0f0a65b (2026-09-09).
Data as JSON: /api/errors/49ff3e33ecf06fb9.
Report an issue: GitHub.