From a26621bbcdc2134e8685d594a67cf905e55aa4ac Mon Sep 17 00:00:00 2001 From: ssrlive <30760636+ssrlive@users.noreply.github.com> Date: Mon, 12 Feb 2024 21:36:18 +0800 Subject: [PATCH] Ffi (#89) --- apple/tun2proxy/Tun2proxyWrapper.m | 4 +- cbindgen.toml | 7 +- src/android.rs | 3 +- src/api.rs | 12 ++- src/args.rs | 52 +++++++++-- src/bin/main.rs | 5 +- src/dump_logger.rs | 9 +- src/error.rs | 2 + src/ffi.rs | 140 +++++++++++++++++++++++++++++ src/ios.rs | 5 +- src/lib.rs | 11 ++- 11 files changed, 224 insertions(+), 26 deletions(-) create mode 100644 src/ffi.rs diff --git a/apple/tun2proxy/Tun2proxyWrapper.m b/apple/tun2proxy/Tun2proxyWrapper.m index d1f8705..dd8c75d 100644 --- a/apple/tun2proxy/Tun2proxyWrapper.m +++ b/apple/tun2proxy/Tun2proxyWrapper.m @@ -18,8 +18,8 @@ dns_over_tcp:(bool)dns_over_tcp verbose:(bool)verbose { ArgDns dns_strategy = dns_over_tcp ? OverTcp : Direct; - ArgVerbosity verbosity = verbose ? Trace : Info; - tun2proxy_run(proxy_url.UTF8String, tun_fd, tun_mtu, dns_strategy, verbosity); + ArgVerbosity v = verbose ? Trace : Info; + tun2proxy_run_with_fd(proxy_url.UTF8String, tun_fd, tun_mtu, dns_strategy, v); } + (void)shutdown { diff --git a/cbindgen.toml b/cbindgen.toml index 45a8d42..c305b1e 100644 --- a/cbindgen.toml +++ b/cbindgen.toml @@ -1,5 +1,10 @@ [export] -include = ["tun2proxy_run", "tun2proxy_stop", "tun2proxy_set_log_callback"] +include = [ + "tun2proxy_run_with_fd", + "tun2proxy_run_with_name", + "tun2proxy_stop", + "tun2proxy_set_log_callback", +] exclude = [ "Java_com_github_shadowsocks_bg_Tun2proxy_run", "Java_com_github_shadowsocks_bg_Tun2proxy_stop", diff --git a/src/android.rs b/src/android.rs index b9798d0..7afee0f 100644 --- a/src/android.rs +++ b/src/android.rs @@ -37,7 +37,8 @@ pub unsafe extern "C" fn Java_com_github_shadowsocks_bg_Tun2proxy_run( let proxy_url = get_java_string(&mut env, &proxy_url).unwrap(); let proxy = ArgProxy::from_url(proxy_url).unwrap(); - let args = Args::new(Some(tun_fd), proxy, dns, verbosity); + let mut args = Args::default(); + args.proxy(proxy).tun_fd(Some(tun_fd)).dns(dns).verbosity(verbosity); crate::api::tun2proxy_internal_run(args, tun_mtu) } diff --git a/src/api.rs b/src/api.rs index 84ae904..a416773 100644 --- a/src/api.rs +++ b/src/api.rs @@ -18,10 +18,18 @@ pub(crate) fn tun2proxy_internal_run(args: Args, tun_mtu: u16) -> c_int { } let block = async move { - log::info!("Proxying {}", args.proxy); + log::info!("Proxy {} server: {}", args.proxy.proxy_type, args.proxy.addr); let mut config = tun2::Configuration::default(); - config.raw_fd(args.tun_fd.ok_or(crate::Error::from("tun_fd"))?); + + #[cfg(unix)] + if let Some(fd) = args.tun_fd { + config.raw_fd(fd); + } else { + config.name(&args.tun); + } + #[cfg(windows)] + config.name(&args.tun); let device = tun2::create_as_async(&config).map_err(std::io::Error::from)?; let join_handle = tokio::spawn(crate::run(device, tun_mtu, args, shutdown_token)); diff --git a/src/args.rs b/src/args.rs index 362c7f5..d99594a 100644 --- a/src/args.rs +++ b/src/args.rs @@ -70,14 +70,50 @@ impl Args { Self::parse() } - pub fn new(tun_fd: Option, proxy: ArgProxy, dns: ArgDns, verbosity: ArgVerbosity) -> Self { - Args { - proxy, - tun_fd, - dns, - verbosity, - ..Args::default() - } + pub fn proxy(&mut self, proxy: ArgProxy) -> &mut Self { + self.proxy = proxy; + self + } + + pub fn dns(&mut self, dns: ArgDns) -> &mut Self { + self.dns = dns; + self + } + + pub fn tun_fd(&mut self, tun_fd: Option) -> &mut Self { + self.tun_fd = tun_fd; + self + } + + pub fn verbosity(&mut self, verbosity: ArgVerbosity) -> &mut Self { + self.verbosity = verbosity; + self + } + + pub fn tun(&mut self, tun: String) -> &mut Self { + self.tun = tun; + self + } + + pub fn dns_addr(&mut self, dns_addr: IpAddr) -> &mut Self { + self.dns_addr = dns_addr; + self + } + + pub fn bypass(&mut self, bypass: IpAddr) -> &mut Self { + self.bypass.push(bypass); + self + } + + pub fn ipv6_enabled(&mut self, ipv6_enabled: bool) -> &mut Self { + self.ipv6_enabled = ipv6_enabled; + self + } + + #[cfg(target_os = "linux")] + pub fn setup(&mut self, setup: bool) -> &mut Self { + self.setup = setup; + self } } diff --git a/src/bin/main.rs b/src/bin/main.rs index 95d42d5..675faee 100644 --- a/src/bin/main.rs +++ b/src/bin/main.rs @@ -63,11 +63,10 @@ async fn main() -> Result<(), Box> { tproxy_config::tproxy_setup(&tproxy_args)?; } - log::info!("Proxying {}", args.proxy); + log::info!("Proxy {} server: {}", args.proxy.proxy_type, args.proxy.addr); let shutdown_token = CancellationToken::new(); - let cloned_token = shutdown_token.clone(); - let join_handle = tokio::spawn(tun2proxy::run(device, MTU, args, cloned_token)); + let join_handle = tokio::spawn(tun2proxy::run(device, MTU, args, shutdown_token.clone())); ctrlc2::set_async_handler(async move { log::info!("Ctrl-C received, exiting..."); diff --git a/src/dump_logger.rs b/src/dump_logger.rs index b04ff09..4eef541 100644 --- a/src/dump_logger.rs +++ b/src/dump_logger.rs @@ -6,19 +6,18 @@ use std::{ pub(crate) static DUMP_CALLBACK: Mutex> = Mutex::new(None); +pub type RawCallback = unsafe extern "C" fn(ArgVerbosity, *const c_char, *mut c_void); + /// # Safety /// /// set dump log info callback. #[no_mangle] -pub unsafe extern "C" fn tun2proxy_set_log_callback( - callback: Option, - ctx: *mut c_void, -) { +pub unsafe extern "C" fn tun2proxy_set_log_callback(callback: Option, ctx: *mut c_void) { *DUMP_CALLBACK.lock().unwrap() = Some(DumpCallback(callback, ctx)); } #[derive(Clone)] -pub struct DumpCallback(Option, *mut c_void); +pub struct DumpCallback(Option, *mut c_void); impl DumpCallback { unsafe fn call(self, dump_level: ArgVerbosity, info: *const c_char) { diff --git a/src/error.rs b/src/error.rs index caf26de..96b9732 100644 --- a/src/error.rs +++ b/src/error.rs @@ -68,4 +68,6 @@ impl From for std::io::Error { } } +pub type BoxError = Box; + pub type Result = std::result::Result; diff --git a/src/ffi.rs b/src/ffi.rs new file mode 100644 index 0000000..ac96ee2 --- /dev/null +++ b/src/ffi.rs @@ -0,0 +1,140 @@ +#![cfg(any(target_os = "windows", target_os = "macos", target_os = "linux"))] + +use crate::{ + args::{ArgDns, ArgProxy}, + ArgVerbosity, Args, +}; +use std::os::raw::{c_char, c_int}; +use tokio_util::sync::CancellationToken; +use tproxy_config::{TproxyArgs, TUN_GATEWAY, TUN_IPV4, TUN_NETMASK}; +use tun2::DEFAULT_MTU as MTU; + +static TUN_QUIT: std::sync::Mutex> = std::sync::Mutex::new(None); + +/// # Safety +/// +/// Run the tun2proxy component with some arguments. +#[no_mangle] +pub unsafe extern "C" fn tun2proxy_run_with_name( + proxy_url: *const c_char, + tun: *const c_char, + bypass: *const c_char, + dns_strategy: ArgDns, + _root_privilege: bool, + verbosity: ArgVerbosity, +) -> c_int { + let shutdown_token = CancellationToken::new(); + { + if let Ok(mut lock) = TUN_QUIT.lock() { + if lock.is_some() { + log::error!("tun2proxy already started"); + return -1; + } + *lock = Some(shutdown_token.clone()); + } else { + log::error!("tun2proxy unknown error"); + return -1; + } + } + + log::set_max_level(verbosity.into()); + log::set_boxed_logger(Box::::default()).unwrap(); + + let proxy_url = std::ffi::CStr::from_ptr(proxy_url).to_str().unwrap(); + let proxy = ArgProxy::from_url(proxy_url).unwrap(); + let tun = std::ffi::CStr::from_ptr(tun).to_str().unwrap().to_string(); + + let mut args = Args::default(); + args.proxy(proxy).tun(tun).dns(dns_strategy).verbosity(verbosity); + + #[cfg(target_os = "linux")] + args.setup(_root_privilege); + + if let Ok(bypass) = std::ffi::CStr::from_ptr(bypass).to_str() { + args.bypass(bypass.parse().unwrap()); + } + + let block = async move { + let bypass_ips = args.bypass.clone(); + + let mut config = tun2::Configuration::default(); + config.address(TUN_IPV4).netmask(TUN_NETMASK).mtu(MTU).up(); + config.destination(TUN_GATEWAY); + if let Some(tun_fd) = args.tun_fd { + config.raw_fd(tun_fd); + } else { + config.name(&args.tun); + } + + #[cfg(target_os = "linux")] + config.platform_config(|config| { + #[allow(deprecated)] + config.packet_information(true); + config.ensure_root_privileges(args.setup); + }); + + #[cfg(target_os = "windows")] + config.platform_config(|config| { + config.device_guid(Some(12324323423423434234_u128)); + }); + + #[allow(unused_variables)] + let mut tproxy_args = TproxyArgs::new() + .tun_dns(args.dns_addr) + .proxy_addr(args.proxy.addr) + .bypass_ips(&bypass_ips); + #[allow(unused_assignments)] + if args.tun_fd.is_none() { + tproxy_args = tproxy_args.tun_name(&args.tun); + } + + #[cfg(target_os = "linux")] + tproxy_config::tproxy_setup(&tproxy_args)?; + + let device = tun2::create_as_async(&config)?; + + #[cfg(any(target_os = "windows", target_os = "macos"))] + tproxy_config::tproxy_setup(&tproxy_args)?; + + log::info!("Proxy {} server: {}", args.proxy.proxy_type, args.proxy.addr); + + let join_handle = tokio::spawn(crate::run(device, MTU, args, shutdown_token)); + join_handle.await.map_err(std::io::Error::from)??; + + #[cfg(any(target_os = "linux", target_os = "windows", target_os = "macos"))] + if _root_privilege { + tproxy_config::tproxy_remove(&tproxy_args)?; + } + + Ok::<(), crate::BoxError>(()) + }; + + let exit_code = match tokio::runtime::Builder::new_multi_thread().enable_all().build() { + Err(_e) => -1, + Ok(rt) => match rt.block_on(block) { + Ok(_) => 0, + Err(_e) => -2, + }, + }; + + // release shutdown token before exit. + if let Ok(mut lock) = TUN_QUIT.lock() { + let _ = lock.take(); + } + + exit_code +} + +/// # Safety +/// +/// Shutdown the tun2proxy component. +#[no_mangle] +pub unsafe extern "C" fn tun2proxy_stop() -> c_int { + if let Ok(lock) = TUN_QUIT.lock() { + if let Some(shutdown_token) = lock.as_ref() { + shutdown_token.cancel(); + return 0; + } + } + -1 +} diff --git a/src/ios.rs b/src/ios.rs index 8322066..627c199 100644 --- a/src/ios.rs +++ b/src/ios.rs @@ -10,7 +10,7 @@ use std::os::raw::{c_char, c_int, c_ushort}; /// /// Run the tun2proxy component with some arguments. #[no_mangle] -pub unsafe extern "C" fn tun2proxy_run( +pub unsafe extern "C" fn tun2proxy_run_with_fd( proxy_url: *const c_char, tun_fd: c_int, tun_mtu: c_ushort, @@ -23,7 +23,8 @@ pub unsafe extern "C" fn tun2proxy_run( let proxy_url = std::ffi::CStr::from_ptr(proxy_url).to_str().unwrap(); let proxy = ArgProxy::from_url(proxy_url).unwrap(); - let args = Args::new(Some(tun_fd), proxy, dns_strategy, verbosity); + let mut args = Args::default(); + args.proxy(proxy).tun_fd(Some(tun_fd)).dns(dns_strategy).verbosity(verbosity); crate::api::tun2proxy_internal_run(args, tun_mtu) } diff --git a/src/lib.rs b/src/lib.rs index 7175970..5c2a415 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -14,13 +14,13 @@ use tokio::{ net::TcpStream, sync::Mutex, }; -use tokio_util::sync::CancellationToken; +pub use tokio_util::sync::CancellationToken; use tproxy_config::is_private_ip; use udp_stream::UdpStream; pub use { args::{ArgDns, ArgProxy, ArgVerbosity, Args, ProxyType}, - error::{Error, Result}, + error::{BoxError, Error, Result}, }; mod android; @@ -30,6 +30,7 @@ mod directions; mod dns; mod dump_logger; mod error; +mod ffi; mod http; mod ios; mod proxy_handler; @@ -44,6 +45,12 @@ const MAX_SESSIONS: u64 = 200; static TASK_COUNT: std::sync::atomic::AtomicU64 = std::sync::atomic::AtomicU64::new(0); use std::sync::atomic::Ordering::Relaxed; +/// Run the proxy server +/// # Arguments +/// * `device` - The network device to use +/// * `mtu` - The MTU of the network device +/// * `args` - The arguments to use +/// * `shutdown_token` - The token to exit the server pub async fn run(device: D, mtu: u16, args: Args, shutdown_token: CancellationToken) -> crate::Result<()> where D: AsyncRead + AsyncWrite + Unpin + Send + 'static,