diff --git a/Cargo.toml b/Cargo.toml index 05103b9..1c994e9 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -52,6 +52,9 @@ nix = { version = "0.29", default-features = false, features = [ android_logger = "0.14" jni = { version = "0.21", default-features = false } +[target.'cfg(target_os = "windows")'.dependencies] +windows-service = "0.7" + [build-dependencies] serde_json = "1" diff --git a/src/args.rs b/src/args.rs index f4f5331..d7a5539 100644 --- a/src/args.rs +++ b/src/args.rs @@ -99,6 +99,11 @@ pub struct Args { /// Verbosity level #[arg(short, long, value_name = "level", value_enum, default_value = "info")] pub verbosity: ArgVerbosity, + + /// Daemonize the process as Windows service + #[cfg(target_os = "windows")] + #[arg(long)] + pub daemonize: bool, } fn validate_tun(p: &str) -> Result { @@ -139,6 +144,8 @@ impl Default for Args { udp_timeout: 10, verbosity: ArgVerbosity::Info, virtual_dns_pool: IpCidr::from_str("198.18.0.0/15").unwrap(), + #[cfg(target_os = "windows")] + daemonize: false, } } } diff --git a/src/bin/main.rs b/src/bin/main.rs index 760835f..8d4715f 100644 --- a/src/bin/main.rs +++ b/src/bin/main.rs @@ -5,6 +5,12 @@ async fn main() -> Result<(), BoxError> { dotenvy::dotenv().ok(); let args = Args::parse_args(); + #[cfg(target_os = "windows")] + if args.daemonize { + tun2proxy::win_svc::start_service()?; + return Ok(()); + } + // let default = format!("{}={:?},trust_dns_proto=warn", module_path!(), args.verbosity); let default = format!("{:?},trust_dns_proto=warn", args.verbosity); env_logger::Builder::from_env(env_logger::Env::default().default_filter_or(default)).init(); diff --git a/src/lib.rs b/src/lib.rs index 033c542..bb19e43 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -60,6 +60,8 @@ pub mod socket_transfer; mod socks; mod traffic_status; mod virtual_dns; +#[doc(hidden)] +pub mod win_svc; const DNS_PORT: u16 = 53; diff --git a/src/win_svc.rs b/src/win_svc.rs new file mode 100644 index 0000000..44bb7bf --- /dev/null +++ b/src/win_svc.rs @@ -0,0 +1,98 @@ +#![cfg(windows)] + +const SERVICE_NAME: &str = "tun2proxy"; + +windows_service::define_windows_service!(ffi_service_main, my_service_main); + +pub fn start_service() -> Result<(), windows_service::Error> { + // Register generated `ffi_service_main` with the system and start the service, + // blocking this thread until the service is stopped. + windows_service::service_dispatcher::start(SERVICE_NAME, ffi_service_main)?; + Ok(()) +} + +fn my_service_main(arguments: Vec) { + // The entry point where execution will start on a background thread after a call to + // `service_dispatcher::start` from `main`. + + if let Err(_e) = run_service(arguments) { + log::error!("Error: {:?}", _e); + } +} + +fn run_service(_arguments: Vec) -> Result<(), crate::BoxError> { + use windows_service::service::ServiceControl; + use windows_service::service_control_handler::{self, ServiceControlHandlerResult}; + + let shutdown_token = crate::CancellationToken::new(); + let shutdown_token_clone = shutdown_token.clone(); + + let event_handler = move |control_event| -> ServiceControlHandlerResult { + match control_event { + ServiceControl::Stop => { + // Handle stop event and return control back to the system. + shutdown_token_clone.cancel(); + ServiceControlHandlerResult::NoError + } + // All services must accept Interrogate even if it's a no-op. + ServiceControl::Interrogate => ServiceControlHandlerResult::NoError, + _ => ServiceControlHandlerResult::NotImplemented, + } + }; + + // Register system service event handler + let status_handle = service_control_handler::register(SERVICE_NAME, event_handler)?; + + let mut next_status = windows_service::service::ServiceStatus { + // Should match the one from system service registry + service_type: windows_service::service::ServiceType::OWN_PROCESS, + // The new state + current_state: windows_service::service::ServiceState::Running, + // Accept stop events when running + controls_accepted: windows_service::service::ServiceControlAccept::STOP, + // Used to report an error when starting or stopping only, otherwise must be zero + exit_code: windows_service::service::ServiceExitCode::Win32(0), + // Only used for pending states, otherwise must be zero + checkpoint: 0, + // Only used for pending states, otherwise must be zero + wait_hint: std::time::Duration::default(), + // Unused for setting status + process_id: None, + }; + + // Tell the system that the service is running now + status_handle.set_service_status(next_status.clone())?; + + let args = crate::Args::parse_args(); + + let default = format!("{:?},trust_dns_proto=warn", args.verbosity); + env_logger::Builder::from_env(env_logger::Env::default().default_filter_or(default)).init(); + + let join_handle = tokio::spawn({ + async move { + unsafe extern "C" fn traffic_cb(status: *const crate::TrafficStatus, _: *mut std::ffi::c_void) { + let status = &*status; + log::debug!("Traffic: ▲ {} : ▼ {}", status.tx, status.rx); + } + unsafe { crate::tun2proxy_set_traffic_status_callback(1, Some(traffic_cb), std::ptr::null_mut()) }; + + if let Err(err) = crate::desktop_run_async(args, shutdown_token).await { + log::error!("main loop error: {}", err); + } + } + }); + + let rt = tokio::runtime::Builder::new_multi_thread().enable_all().build()?; + rt.block_on(async { + if let Err(err) = join_handle.await { + log::error!("main_entry error {}", err); + } + Ok::<(), crate::Error>(()) + })?; + + // Tell the system that the service is stopped now + next_status.current_state = windows_service::service::ServiceState::Stopped; + status_handle.set_service_status(next_status)?; + + Ok(()) +}