mirror of
https://github.com/shadowsocks/shadowsocks-rust.git
synced 2026-02-09 01:59:16 +08:00
feat(shadowsocks): making android::SocketProtect sealed (#1974)
This commit is contained in:
2
.gitignore
vendored
2
.gitignore
vendored
@@ -1,4 +1,4 @@
|
||||
/target
|
||||
target
|
||||
/build/release
|
||||
/build/target
|
||||
/build/install
|
||||
|
||||
18
Cargo.lock
generated
18
Cargo.lock
generated
@@ -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]]
|
||||
|
||||
@@ -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 = [
|
||||
|
||||
@@ -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::{
|
||||
|
||||
@@ -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<std::path::PathBuf>,
|
||||
/// 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<std::sync::Arc<Box<dyn android::SocketProtect + Send + Sync>>>,
|
||||
|
||||
@@ -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<F>(&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<dyn android::SocketProtect + Send + Sync>;
|
||||
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<F> {
|
||||
pub f: F,
|
||||
f: F,
|
||||
}
|
||||
|
||||
#[sealed]
|
||||
impl<F> SocketProtect for SocketProtectFn<F>
|
||||
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<F> Clone for SocketProtectFn<Arc<F>> {
|
||||
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<F> MakeSocketProtect for F
|
||||
where
|
||||
F: Fn(RawFd) -> io::Result<()> + Send + Sync + 'static,
|
||||
{
|
||||
type SocketProtect = SocketProtectFn<F>;
|
||||
type SocketProtectType = SocketProtectFn<F>;
|
||||
|
||||
fn make_socket_protect(self) -> Self::SocketProtect {
|
||||
fn make_socket_protect(self) -> Self::SocketProtectType {
|
||||
SocketProtectFn { f: self }
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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<S: AsRawFd>(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<P: AsRef<Path>>(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<P: AsRef<Path>>(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<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(())
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user