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

  1. 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)
  2. Confirm the VpnService side is actually listening on the socket and calling protect() promptly; restart the VPN service/app
  3. Check the device for heavy load or battery-saver/doze restrictions that delay the RPC and re-test
  4. Increase the 3-second timeout in the source if the RPC is merely slow in your environment
  5. 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

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.

Related errors


AI-assisted analysis of shadowsocks/shadowsocks-rust@8eb0f0a65b (2026-09-09). Data as JSON: /api/errors/49ff3e33ecf06fb9. Report an issue: GitHub.