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.
159 lines
6.2 KiB
159 lines
6.2 KiB
use tun2proxy::{ArgVerbosity, Args, BoxError};
|
|
|
|
fn main() -> Result<(), BoxError> {
|
|
dotenvy::dotenv().ok();
|
|
let args = Args::parse_args();
|
|
|
|
#[cfg(unix)]
|
|
if args.daemonize {
|
|
let stdout = std::fs::File::create("/tmp/tun2proxy.out")?;
|
|
let stderr = std::fs::File::create("/tmp/tun2proxy.err")?;
|
|
let daemonize = daemonize::Daemonize::new()
|
|
.working_directory("/tmp")
|
|
.umask(0o777)
|
|
.stdout(stdout)
|
|
.stderr(stderr)
|
|
.privileged_action(|| "Executed before drop privileges");
|
|
let _ = daemonize.start()?;
|
|
}
|
|
|
|
#[cfg(target_os = "windows")]
|
|
if args.daemonize {
|
|
tun2proxy::win_svc::start_service()?;
|
|
return Ok(());
|
|
}
|
|
|
|
let rt = tokio::runtime::Builder::new_multi_thread().enable_all().build()?;
|
|
rt.block_on(async move {
|
|
let res = main_async(args).await;
|
|
// Start a timer to force exit after FORCE_EXIT_TIMEOUT seconds. Use a std thread
|
|
// so the timer is not cancelled when the tokio runtime is being shut down.
|
|
let _h = std::thread::spawn(move || {
|
|
log::info!("Starting {:?} exit timer", tun2proxy::FORCE_EXIT_TIMEOUT);
|
|
// Delay some seconds then try to exit current process if not exited yet, normally this case should not happen
|
|
std::thread::sleep(tun2proxy::FORCE_EXIT_TIMEOUT);
|
|
log::info!("Forcing exit now.");
|
|
std::process::exit(-1);
|
|
});
|
|
|
|
log::info!("Runtime.block_on exiting...");
|
|
tokio::time::sleep(std::time::Duration::from_micros(100)).await;
|
|
|
|
res
|
|
})
|
|
}
|
|
|
|
fn setup_logging(args: &Args) {
|
|
let avoid_trace = match args.verbosity {
|
|
ArgVerbosity::Trace => ArgVerbosity::Debug,
|
|
_ => args.verbosity,
|
|
};
|
|
let default = format!(
|
|
"{:?},hickory_proto=warn,ipstack={:?},netlink_proto={:?},netlink_sys={:?}",
|
|
args.verbosity, avoid_trace, avoid_trace, avoid_trace
|
|
);
|
|
env_logger::Builder::from_env(env_logger::Env::default().default_filter_or(default)).init();
|
|
}
|
|
|
|
async fn main_async(args: Args) -> Result<(), BoxError> {
|
|
setup_logging(&args);
|
|
|
|
let shutdown_token = tokio_util::sync::CancellationToken::new();
|
|
let main_loop_handle = tokio::spawn({
|
|
let args = args.clone();
|
|
let shutdown_token = shutdown_token.clone();
|
|
async move {
|
|
#[cfg(target_os = "linux")]
|
|
if args.unshare && args.socket_transfer_fd.is_none() {
|
|
if let Err(err) = namespace_proxy_main(args, shutdown_token).await {
|
|
log::error!("namespace proxy error: {err}");
|
|
}
|
|
return Ok(0);
|
|
}
|
|
|
|
unsafe extern "C" fn traffic_cb(status: *const tun2proxy::TrafficStatus, _: *mut std::ffi::c_void) {
|
|
let status = unsafe { &*status };
|
|
log::debug!("Traffic: ▲ {} : ▼ {}", status.tx, status.rx);
|
|
}
|
|
unsafe { tun2proxy::tun2proxy_set_traffic_status_callback(1, Some(traffic_cb), std::ptr::null_mut()) };
|
|
|
|
let ret = tun2proxy::general_run_async(args, tun::DEFAULT_MTU, cfg!(target_os = "macos"), shutdown_token).await;
|
|
if let Err(err) = &ret {
|
|
log::error!("main loop error: {err}");
|
|
}
|
|
ret
|
|
}
|
|
});
|
|
|
|
let ctrlc_fired = std::sync::Arc::new(std::sync::atomic::AtomicBool::new(false));
|
|
let ctrlc_fired_clone = ctrlc_fired.clone();
|
|
let ctrlc_handel = ctrlc2::AsyncCtrlC::new(move || {
|
|
log::info!("Ctrl-C received, exiting...");
|
|
ctrlc_fired_clone.store(true, std::sync::atomic::Ordering::SeqCst);
|
|
shutdown_token.cancel();
|
|
true
|
|
})?;
|
|
|
|
let _tasks = main_loop_handle.await??;
|
|
|
|
if ctrlc_fired.load(std::sync::atomic::Ordering::SeqCst) {
|
|
log::info!("Ctrl-C fired, waiting the handler to finish...");
|
|
match tokio::time::timeout(std::time::Duration::from_secs(1), ctrlc_handel).await {
|
|
Ok(Ok(())) => log::info!("Ctrl-C handler finished"),
|
|
Ok(Err(e)) => log::warn!("Ctrl-C handler error: {e}"),
|
|
Err(_) => log::warn!("Ctrl-C handler timeout, continuing..."),
|
|
}
|
|
}
|
|
|
|
Ok(())
|
|
}
|
|
|
|
#[cfg(target_os = "linux")]
|
|
async fn namespace_proxy_main(
|
|
_args: Args,
|
|
_shutdown_token: tokio_util::sync::CancellationToken,
|
|
) -> Result<std::process::ExitStatus, tun2proxy::Error> {
|
|
use nix::fcntl::{OFlag, open};
|
|
use nix::sys::stat::Mode;
|
|
use std::os::fd::AsRawFd;
|
|
|
|
let (socket, remote_fd) = tun2proxy::socket_transfer::create_transfer_socket_pair().await?;
|
|
|
|
let fd = open("/proc/self/exe", OFlag::O_PATH, Mode::empty())?;
|
|
|
|
let child = tokio::process::Command::new("unshare")
|
|
.args("--user --map-current-user --net --mount --keep-caps --kill-child --fork".split(' '))
|
|
.arg(format!("/proc/self/fd/{}", fd.as_raw_fd()))
|
|
.arg("--socket-transfer-fd")
|
|
.arg(remote_fd.as_raw_fd().to_string())
|
|
.args(std::env::args().skip(1))
|
|
.kill_on_drop(true)
|
|
.spawn();
|
|
|
|
let mut child = match child {
|
|
Err(err) if err.kind() == std::io::ErrorKind::NotFound => {
|
|
log::error!("`unshare(1)` executable wasn't located in PATH.");
|
|
log::error!("Consider installing linux utils package: `apt install util-linux`");
|
|
log::error!("Or similar for your distribution.");
|
|
return Err(err.into());
|
|
}
|
|
child => child?,
|
|
};
|
|
|
|
let unshare_pid = child.id().unwrap_or(0);
|
|
log::info!("The tun proxy is running in unprivileged mode. See `namespaces(7)`.");
|
|
log::info!("");
|
|
log::info!("If you need to run a process that relies on root-like capabilities (e.g. `openvpn`)");
|
|
log::info!("Use `tun2proxy-bin --unshare --setup [...] -- openvpn --config [...]`");
|
|
log::info!("");
|
|
log::info!("To run a new process in the created namespace (e.g. a flatpak app)");
|
|
log::info!("Use `nsenter --preserve-credentials --user --net --mount --target {unshare_pid} /bin/sh`");
|
|
log::info!("");
|
|
if let Some(pidfile) = _args.unshare_pidfile.as_ref() {
|
|
log::info!("Writing unshare pid to {pidfile}");
|
|
std::fs::write(pidfile, unshare_pid.to_string()).ok();
|
|
}
|
|
tokio::spawn(async move { tun2proxy::socket_transfer::process_socket_requests(&socket).await });
|
|
|
|
Ok(child.wait().await?)
|
|
}
|
|
|