You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
213 lines
7.4 KiB
213 lines
7.4 KiB
use crate::Args;
|
|
use std::os::raw::{c_char, c_int, c_ushort};
|
|
|
|
/// # Safety
|
|
/// Run the tun2proxy component with command line arguments
|
|
/// Parameters:
|
|
/// - cli_args: The command line arguments,
|
|
/// e.g. `tun2proxy-bin --setup --proxy socks5://127.0.0.1:1080 --bypass 98.76.54.0/24 --dns over-tcp --verbosity trace`
|
|
/// - tun_mtu: The MTU of the TUN device, e.g. 1500
|
|
/// - packet_information: Whether exists packet information in packet from TUN device
|
|
#[unsafe(no_mangle)]
|
|
pub unsafe extern "C" fn tun2proxy_run_with_cli_args(cli_args: *const c_char, tun_mtu: c_ushort, packet_information: bool) -> c_int {
|
|
let Ok(cli_args) = unsafe { std::ffi::CStr::from_ptr(cli_args) }.to_str() else {
|
|
log::error!("Failed to convert CLI arguments to string");
|
|
return -5;
|
|
};
|
|
let Some(args) = shlex::split(cli_args) else {
|
|
log::error!("Failed to split CLI arguments");
|
|
return -6;
|
|
};
|
|
let args = <Args as ::clap::Parser>::parse_from(args);
|
|
general_run_for_api(args, tun_mtu, packet_information)
|
|
}
|
|
|
|
static TUN_QUIT: std::sync::Mutex<Option<tokio_util::sync::CancellationToken>> = std::sync::Mutex::new(None);
|
|
|
|
pub(crate) fn tun2proxy_stop_internal() -> c_int {
|
|
if let Ok(mut lock) = TUN_QUIT.lock() {
|
|
if let Some(shutdown_token) = lock.take() {
|
|
shutdown_token.cancel();
|
|
return 0;
|
|
}
|
|
}
|
|
-1
|
|
}
|
|
|
|
pub fn general_run_for_api(args: Args, tun_mtu: u16, packet_information: bool) -> c_int {
|
|
log::set_max_level(args.verbosity.into());
|
|
if let Err(err) = log::set_boxed_logger(Box::<crate::dump_logger::DumpLogger>::default()) {
|
|
log::debug!("set logger error: {err}");
|
|
}
|
|
|
|
let shutdown_token = tokio_util::sync::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!("failed to lock tun2proxy quit token");
|
|
return -2;
|
|
}
|
|
|
|
let Ok(rt) = tokio::runtime::Builder::new_multi_thread().enable_all().build() else {
|
|
log::error!("failed to create tokio runtime with");
|
|
return -3;
|
|
};
|
|
let args_clone = args.clone();
|
|
let res = rt.block_on(async move {
|
|
let ret = general_run_async(args_clone, tun_mtu, packet_information, shutdown_token).await;
|
|
// Spawn a std thread to force exit after timeout so it isn't cancelled
|
|
// when the tokio runtime is dropped.
|
|
let _h = std::thread::spawn(move || {
|
|
// Delay some seconds then try to exit current process if not exited yet, normally this case should not happen
|
|
std::thread::sleep(crate::FORCE_EXIT_TIMEOUT);
|
|
log::info!("Forcing exit now.");
|
|
std::process::exit(-1);
|
|
});
|
|
tokio::time::sleep(std::time::Duration::from_micros(100)).await;
|
|
ret
|
|
});
|
|
|
|
let res = match res {
|
|
Ok(sessions) => {
|
|
log::debug!("tun2proxy exited normally, current session count: {sessions}");
|
|
0
|
|
}
|
|
Err(e) => {
|
|
log::error!("failed to run tun2proxy with error: {e:?}");
|
|
-4
|
|
}
|
|
};
|
|
|
|
if let Ok(mut lock) = TUN_QUIT.lock() {
|
|
lock.take();
|
|
}
|
|
|
|
res
|
|
}
|
|
|
|
/// Run the tun2proxy component with some arguments.
|
|
pub async fn general_run_async(
|
|
args: Args,
|
|
tun_mtu: u16,
|
|
_packet_information: bool,
|
|
shutdown_token: tokio_util::sync::CancellationToken,
|
|
) -> std::io::Result<usize> {
|
|
let mut tun_config = tun::Configuration::default();
|
|
|
|
#[cfg(any(target_os = "linux", target_os = "windows", target_os = "macos"))]
|
|
{
|
|
use tproxy_config::{TUN_GATEWAY, TUN_IPV4, TUN_NETMASK};
|
|
tun_config.address(TUN_IPV4).netmask(TUN_NETMASK).mtu(tun_mtu).up();
|
|
tun_config.destination(TUN_GATEWAY);
|
|
}
|
|
|
|
#[cfg(unix)]
|
|
if let Some(fd) = args.tun_fd {
|
|
tun_config.raw_fd(fd);
|
|
if let Some(v) = args.close_fd_on_drop {
|
|
tun_config.close_fd_on_drop(v);
|
|
};
|
|
} else if let Some(ref tun) = args.tun {
|
|
tun_config.tun_name(tun);
|
|
}
|
|
#[cfg(windows)]
|
|
if let Some(ref tun) = args.tun {
|
|
tun_config.tun_name(tun);
|
|
}
|
|
|
|
#[cfg(target_os = "linux")]
|
|
tun_config.platform_config(|cfg| {
|
|
#[allow(deprecated)]
|
|
cfg.packet_information(true);
|
|
cfg.ensure_root_privileges(args.setup);
|
|
});
|
|
|
|
#[cfg(target_os = "windows")]
|
|
tun_config.platform_config(|cfg| {
|
|
cfg.device_guid(12324323423423434234_u128);
|
|
});
|
|
|
|
#[cfg(any(target_os = "ios", target_os = "macos"))]
|
|
tun_config.platform_config(|cfg| {
|
|
cfg.packet_information(_packet_information);
|
|
});
|
|
|
|
#[cfg(any(target_os = "linux", target_os = "windows", target_os = "macos"))]
|
|
#[allow(unused_variables)]
|
|
let mut tproxy_args = tproxy_config::TproxyArgs::new()
|
|
.tun_dns(args.dns_addr)
|
|
.proxy_addr(args.proxy.addr)
|
|
.bypass_ips(&args.bypass)
|
|
.ipv6_default_route(args.ipv6_enabled);
|
|
|
|
let device = tun::create_as_async(&tun_config)?;
|
|
|
|
#[cfg(any(target_os = "linux", target_os = "windows", target_os = "macos"))]
|
|
if let Ok(tun_name) = tun::AbstractDevice::tun_name(&*device) {
|
|
// Above line is equivalent to: `use tun::AbstractDevice; if let Ok(tun_name) = device.tun_name() {`
|
|
tproxy_args = tproxy_args.tun_name(&tun_name);
|
|
}
|
|
|
|
// TproxyState implements the Drop trait to restore network configuration,
|
|
// so we need to assign it to a variable, even if it is not used.
|
|
#[cfg(any(target_os = "linux", target_os = "windows", target_os = "macos"))]
|
|
let mut restore: Option<tproxy_config::TproxyState> = None;
|
|
|
|
#[cfg(any(target_os = "linux", target_os = "windows", target_os = "macos"))]
|
|
if args.setup {
|
|
restore = Some(tproxy_config::tproxy_setup(&tproxy_args).await?);
|
|
}
|
|
|
|
#[cfg(target_os = "linux")]
|
|
{
|
|
let mut admin_command_args = args.admin_command.iter();
|
|
if let Some(command) = admin_command_args.next() {
|
|
let child = tokio::process::Command::new(command)
|
|
.args(admin_command_args)
|
|
.kill_on_drop(true)
|
|
.spawn();
|
|
|
|
match child {
|
|
Err(err) => {
|
|
log::warn!("Failed to start admin process: {err}");
|
|
}
|
|
Ok(mut child) => {
|
|
tokio::spawn(async move {
|
|
if let Err(err) = child.wait().await {
|
|
log::warn!("Admin process terminated: {err}");
|
|
}
|
|
});
|
|
}
|
|
};
|
|
}
|
|
}
|
|
|
|
let join_handle = tokio::spawn(crate::run(device, tun_mtu, args.clone(), shutdown_token.clone()));
|
|
|
|
match join_handle.await? {
|
|
Ok(sessions) => {
|
|
#[cfg(any(target_os = "linux", target_os = "windows", target_os = "macos"))]
|
|
tproxy_config::tproxy_remove(restore).await?;
|
|
|
|
let max_sessions = args.max_sessions;
|
|
if args.exit_on_fatal_error && sessions >= max_sessions {
|
|
let info = format!("Forced exit due to max sessions reached ({sessions}/{max_sessions})");
|
|
return Err(std::io::Error::other(info));
|
|
}
|
|
Ok(sessions)
|
|
}
|
|
Err(err) => Err(std::io::Error::from(err)),
|
|
}
|
|
}
|
|
|
|
/// # Safety
|
|
///
|
|
/// Shutdown the tun2proxy component.
|
|
#[unsafe(no_mangle)]
|
|
pub unsafe extern "C" fn tun2proxy_stop() -> c_int {
|
|
tun2proxy_stop_internal()
|
|
}
|
|
|