diff --git a/.gitignore b/.gitignore index 6e6dbd43..e5de06ff 100644 --- a/.gitignore +++ b/.gitignore @@ -1,4 +1,4 @@ -/target +target /build/release /build/target /build/install diff --git a/Cargo.lock b/Cargo.lock index 246b01a4..954d429c 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -888,7 +888,7 @@ dependencies = [ "libc", "option-ext", "redox_users", - "windows-sys 0.59.0", + "windows-sys 0.60.2", ] [[package]] @@ -1979,7 +1979,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "07033963ba89ebaf1584d767badaa2e8fcec21aedea6b8c0346d487d49c28667" dependencies = [ "cfg-if", - "windows-targets 0.48.5", + "windows-targets 0.53.2", ] [[package]] @@ -3160,6 +3160,17 @@ version = "1.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49" +[[package]] +name = "sealed" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "22f968c5ea23d555e670b449c1c5e7b2fc399fdaec1d304a17cd48e288abc107" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.102", +] + [[package]] name = "sec1" version = "0.7.3" @@ -3347,6 +3358,7 @@ dependencies = [ "percent-encoding", "pin-project", "rand 0.9.1", + "sealed", "sendfd", "serde", "serde_json", @@ -4370,7 +4382,7 @@ version = "0.1.9" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "cf221c93e13a30d793f7645a0e7762c55d169dbb0a49671918a2319d289b10bb" dependencies = [ - "windows-sys 0.48.0", + "windows-sys 0.59.0", ] [[package]] diff --git a/crates/shadowsocks/Cargo.toml b/crates/shadowsocks/Cargo.toml index c4227638..08cc0bf5 100644 --- a/crates/shadowsocks/Cargo.toml +++ b/crates/shadowsocks/Cargo.toml @@ -72,6 +72,7 @@ percent-encoding = "2.1" futures = "0.3" trait-variant = "0.1" dynosaur = "0.2.0" +sealed = "0.6" socket2 = { version = "0.5", features = ["all"] } tokio = { version = "1.9.0", features = [ diff --git a/crates/shadowsocks/src/net/mod.rs b/crates/shadowsocks/src/net/mod.rs index 16dcb820..c8c824b8 100644 --- a/crates/shadowsocks/src/net/mod.rs +++ b/crates/shadowsocks/src/net/mod.rs @@ -2,8 +2,6 @@ use std::net::SocketAddr; -#[cfg(target_os = "android")] -pub use self::option::android::{SocketProtect, SocketProtectFn}; #[cfg(unix)] pub use self::sys::uds::{UnixListener, UnixStream}; pub use self::{ diff --git a/crates/shadowsocks/src/net/option.rs b/crates/shadowsocks/src/net/option.rs index 98afe656..3e9d903b 100644 --- a/crates/shadowsocks/src/net/option.rs +++ b/crates/shadowsocks/src/net/option.rs @@ -59,6 +59,9 @@ pub struct ConnectOpts { /// This is an [Android shadowsocks implementation](https://github.com/shadowsocks/shadowsocks-android) specific feature #[cfg(target_os = "android")] pub vpn_protect_path: Option, + /// A customizable socket protect implementation for Android for calling `VpnService.protect(fd)` + /// + /// see [`ConnectOpts::set_vpn_socket_protect`] #[cfg(target_os = "android")] pub vpn_socket_protect: Option>>, @@ -93,67 +96,77 @@ pub struct AcceptOpts { #[cfg(target_os = "android")] impl ConnectOpts { /// Set `vpn_protect_path` for Android VPNService.protect implementation + /// + /// Example: + /// + /// ```rust + /// // Sync function for calling `VpnService.protect(fd)` + /// opts.set_vpn_socket_protect(|fd| { + /// // Your implementation here + /// // For example, using `jni` to call Android's VpnService.protect(fd) + /// Ok(()) + /// }); + /// ``` pub fn set_vpn_socket_protect(&mut self, f: F) where - F: self::android::MakeSocketProtect + Send + Sync + 'static, + F: android::MakeSocketProtect + Send + Sync + 'static, + F::SocketProtectType: android::SocketProtect + Send + Sync + 'static, { - let protect_fn = Box::new(f.make_socket_protect()) as Box; - self.vpn_socket_protect = Some(std::sync::Arc::new(protect_fn)) + self.vpn_socket_protect = Some(std::sync::Arc::new(Box::new(f.make_socket_protect()))); } } /// Android specific features #[cfg(target_os = "android")] pub mod android { - use std::{fmt, io, os::unix::io::RawFd, sync::Arc}; + use sealed::sealed; + use std::{fmt, io, os::unix::io::RawFd}; /// Android VPN socket protect implemetation + #[sealed] pub trait SocketProtect { /// Protects the socket file descriptor by calling `VpnService.protect(fd)` fn protect(&self, fd: RawFd) -> io::Result<()>; } + impl fmt::Debug for dyn SocketProtect + Send + Sync { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + f.debug_struct("SocketProtect").finish_non_exhaustive() + } + } + /// Creating an instance of `SocketProtect` + #[sealed] pub trait MakeSocketProtect { - type SocketProtect: SocketProtect + Send + Sync; + type SocketProtectType: SocketProtect; /// Creates an instance of `SocketProtect` - fn make_socket_protect(self) -> Self::SocketProtect; + fn make_socket_protect(self) -> Self::SocketProtectType; } /// A function that implements `SocketProtect` trait pub struct SocketProtectFn { - pub f: F, + f: F, } + #[sealed] impl SocketProtect for SocketProtectFn where - F: Fn(RawFd) -> io::Result<()>, + F: Fn(RawFd) -> io::Result<()> + Send + Sync + 'static, { fn protect(&self, fd: RawFd) -> io::Result<()> { (self.f)(fd) } } - impl Clone for SocketProtectFn> { - fn clone(&self) -> Self { - Self { f: self.f.clone() } - } - } - - impl fmt::Debug for dyn SocketProtect + Send + Sync { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - f.debug_struct("dyn SocketProtect + Send + Sync").finish() - } - } - + #[sealed] impl MakeSocketProtect for F where F: Fn(RawFd) -> io::Result<()> + Send + Sync + 'static, { - type SocketProtect = SocketProtectFn; + type SocketProtectType = SocketProtectFn; - fn make_socket_protect(self) -> Self::SocketProtect { + fn make_socket_protect(self) -> Self::SocketProtectType { SocketProtectFn { f: self } } } diff --git a/crates/shadowsocks/src/net/sys/unix/linux/mod.rs b/crates/shadowsocks/src/net/sys/unix/linux/mod.rs index 932270aa..9ad71ba5 100644 --- a/crates/shadowsocks/src/net/sys/unix/linux/mod.rs +++ b/crates/shadowsocks/src/net/sys/unix/linux/mod.rs @@ -9,7 +9,6 @@ use std::{ task::{self, Poll}, }; -use cfg_if::cfg_if; use log::{debug, error, warn}; use pin_project::pin_project; use socket2::{Domain, Protocol, SockAddr, Socket, Type}; @@ -56,22 +55,7 @@ impl TcpStream { // This is a workaround for VPNService #[cfg(target_os = "android")] if !addr.ip().is_loopback() { - use std::time::Duration; - use tokio::time; - - 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), vpn_protect(path, socket.as_raw_fd())).await { - Ok(Ok(..)) => {} - Ok(Err(err)) => return Err(err), - Err(..) => return Err(io::Error::new(ErrorKind::TimedOut, "protect() timeout")), - } - } - - if let Some(ref protect) = opts.vpn_socket_protect { - protect.protect(socket.as_raw_fd())?; - } + android::vpn_protect(&socket, opts).await?; } // Set SO_MARK for mark-based routing on Linux (since 2.6.25) @@ -335,24 +319,7 @@ pub async fn bind_outbound_udp_socket(bind_addr: &SocketAddr, config: &ConnectOp // Any traffic except localhost should be protected // This is a workaround for VPNService #[cfg(target_os = "android")] - { - use std::time::Duration; - use tokio::time; - - if let Some(ref path) = config.vpn_protect_path { - // RPC calls to `VpnService.protect()` - // Timeout in 3 seconds like shadowsocks-libev - match time::timeout(Duration::from_secs(3), vpn_protect(path, socket.as_raw_fd())).await { - Ok(Ok(..)) => {} - Ok(Err(err)) => return Err(err), - Err(..) => return Err(io::Error::new(ErrorKind::TimedOut, "protect() timeout")), - } - } - - if let Some(ref protect) = config.vpn_socket_protect { - protect.protect(socket.as_raw_fd())?; - } - } + android::vpn_protect(&socket, config).await?; // Set SO_MARK for mark-based routing on Linux (since 2.6.25) // NOTE: This will require CAP_NET_ADMIN capability (root in most cases) @@ -403,36 +370,67 @@ fn set_bindtodevice(socket: &S, iface: &str) -> io::Result<()> { Ok(()) } -cfg_if! { - if #[cfg(target_os = "android")] { - use std::path::Path; - use tokio::io::AsyncReadExt; +#[cfg(target_os = "android")] +mod android { + use std::{ + io::{self, ErrorKind}, + os::unix::io::{AsRawFd, RawFd}, + path::Path, + time::Duration, + }; + use tokio::{io::AsyncReadExt, time}; - use super::uds::UnixStream; + use super::super::uds::UnixStream; + use super::ConnectOpts; - /// This is a RPC for Android to `protect()` socket for connecting to remote servers - /// - /// https://developer.android.com/reference/android/net/VpnService#protect(java.net.Socket) - /// - /// More detail could be found in [shadowsocks-android](https://github.com/shadowsocks/shadowsocks-android) project. - async fn vpn_protect>(protect_path: P, fd: RawFd) -> io::Result<()> { - let mut stream = UnixStream::connect(protect_path).await?; + /// This is a RPC for Android to `protect()` socket for connecting to remote servers + /// + /// https://developer.android.com/reference/android/net/VpnService#protect(java.net.Socket) + /// + /// More detail could be found in [shadowsocks-android](https://github.com/shadowsocks/shadowsocks-android) project. + async fn send_vpn_protect_uds>(protect_path: P, fd: RawFd) -> io::Result<()> { + let mut stream = UnixStream::connect(protect_path).await?; - // send fds - let dummy: [u8; 1] = [1]; - let fds: [RawFd; 1] = [fd]; - stream.send_with_fd(&dummy, &fds).await?; + // send fds + let dummy: [u8; 1] = [1]; + let fds: [RawFd; 1] = [fd]; + stream.send_with_fd(&dummy, &fds).await?; - // receive the return value - let mut response = [0; 1]; - stream.read_exact(&mut response).await?; + // receive the return value + let mut response = [0; 1]; + stream.read_exact(&mut response).await?; - if response[0] == 0xFF { - return Err(io::Error::other("protect() failed")); - } - - Ok(()) + if response[0] == 0xFF { + return Err(io::Error::other("protect() failed")); } + + 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(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(()) } }