Skip to content

Commit

Permalink
Windows service support
Browse files Browse the repository at this point in the history
  • Loading branch information
ssrlive committed Jun 21, 2024
1 parent 3c87671 commit 6dbf58e
Show file tree
Hide file tree
Showing 6 changed files with 153 additions and 43 deletions.
7 changes: 5 additions & 2 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ futures-util = { version = "0.3", default-features = false, features = [
"sink",
"std",
] }
hickory-proto = "0.24"
http = "1"
httparse = "1"
lazy_static = "1"
Expand All @@ -41,8 +42,8 @@ rustls = { version = "0.23", default-features = false, features = [
rustls-pemfile = "2"
serde = { version = "1", features = ["derive"] }
serde_json = "1"
socks5-impl = "0.5"
socket2 = "0.5"
socks5-impl = "0.5"
thiserror = "1"
tokio = { version = "1", features = ["full"] }
tokio-rustls = { version = "0.26", default-features = false, features = [
Expand All @@ -52,7 +53,6 @@ tokio-rustls = { version = "0.26", default-features = false, features = [
] }
tokio-tungstenite = { version = "0.23", features = ["rustls-tls-webpki-roots"] }
tokio-util = "0.7"
trust-dns-proto = "0.23"
tungstenite = { version = "0.23", features = ["rustls-tls-webpki-roots"] }
url = "2"
webpki = { package = "rustls-webpki", version = "0.102", features = [
Expand All @@ -67,3 +67,6 @@ daemonize = "0.5"
[target.'cfg(target_os="android")'.dependencies]
android_logger = "0.14"
jni = { version = "0.21", default-features = false }

[target.'cfg(target_os = "windows")'.dependencies]
windows-service = "0.7"
45 changes: 8 additions & 37 deletions src/bin/overtls.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
use overtls::{run_client, run_server, BoxError, CmdOpt, Config, Error, Result};
use overtls::{async_main, BoxError, CmdOpt, Config, Result};

fn main() -> Result<(), BoxError> {
let opt = CmdOpt::parse_cmd();
Expand Down Expand Up @@ -88,43 +88,14 @@ fn main() -> Result<(), BoxError> {
let _ = daemonize.start()?;
}

let rt = tokio::runtime::Builder::new_multi_thread().enable_all().build()?;
rt.block_on(async_main(config))?;
Ok(())
}

async fn async_main(config: Config) -> Result<()> {
let shutdown_token = overtls::CancellationToken::new();
let shutdown_token_clone = shutdown_token.clone();

let main_body = async {
if config.is_server {
if config.exist_server() {
run_server(&config, shutdown_token_clone).await?;
} else {
return Err(Error::from("Config is not a server config"));
}
} else if config.exist_client() {
let callback = |addr| {
log::trace!("Listening on {}", addr);
};
run_client(&config, shutdown_token_clone, Some(callback)).await?;
} else {
return Err("Config is not a client config".into());
}

Ok(())
};

ctrlc2::set_async_handler(async move {
log::info!("Ctrl-C received, exiting...");
shutdown_token.cancel();
})
.await;

if let Err(e) = main_body.await {
log::error!("main_body error: \"{}\"", e);
#[cfg(target_os = "windows")]
if opt.daemonize {
overtls::win_svc::start_service()?;
return Ok(());
}

let shutdown_token = overtls::CancellationToken::new();
let rt = tokio::runtime::Builder::new_multi_thread().enable_all().build()?;
rt.block_on(async_main(config, true, shutdown_token))?;
Ok(())
}
1 change: 0 additions & 1 deletion src/cmdopt.rs
Original file line number Diff line number Diff line change
Expand Up @@ -100,7 +100,6 @@ pub struct CmdOpt {
#[arg(short, long, value_name = "level", value_enum, default_value = "info")]
pub verbosity: ArgVerbosity,

#[cfg(unix)]
#[arg(short, long)]
/// Daemonize for unix family.
pub daemonize: bool,
Expand Down
6 changes: 3 additions & 3 deletions src/dns.rs
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
use moka::future::Cache;
use std::{net::IpAddr, time::Duration};
use trust_dns_proto::{
use hickory_proto::{
op::{Message, Query, ResponseCode::NoError},
rr::RData,
};
use moka::future::Cache;
use std::{net::IpAddr, time::Duration};

pub(crate) fn parse_data_to_dns_message(data: &[u8], used_by_tcp: bool) -> std::io::Result<Message> {
if used_by_tcp {
Expand Down
38 changes: 38 additions & 0 deletions src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ pub(crate) mod traffic_status;
pub(crate) mod udprelay;
pub(crate) mod webapi;
pub(crate) mod weirduri;
pub mod win_svc;

pub use api::{over_tls_client_run, over_tls_client_run_with_ssr_url, over_tls_client_stop, overtls_free_string, overtls_generate_url};
use base64_wrapper::{base64_decode, base64_encode, Base64Engine};
Expand Down Expand Up @@ -63,3 +64,40 @@ pub(crate) fn b64str_to_address(s: &str, url_safe: bool) -> Result<Address> {
};
Address::try_from(&buf[..]).map_err(|e| e.into())
}

#[doc(hidden)]
pub async fn async_main(config: Config, allow_shutdown: bool, shutdown_token: CancellationToken) -> Result<()> {
if allow_shutdown {
let shutdown_token_clone = shutdown_token.clone();
ctrlc2::set_async_handler(async move {
log::info!("Ctrl-C received, exiting...");
shutdown_token_clone.cancel();
})
.await;
}

let main_body = async {
if config.is_server {
if config.exist_server() {
run_server(&config, shutdown_token).await?;
} else {
return Err(Error::from("Config is not a server config"));
}
} else if config.exist_client() {
let callback = |addr| {
log::trace!("Listening on {}", addr);
};
run_client(&config, shutdown_token, Some(callback)).await?;
} else {
return Err("Config is not a client config".into());
}

Ok(())
};

if let Err(e) = main_body.await {
log::error!("main_body error: \"{}\"", e);
}

Ok(())
}
99 changes: 99 additions & 0 deletions src/win_svc.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,99 @@
#![cfg(windows)]

const SERVICE_NAME: &str = "overtls";

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<std::ffi::OsString>) {
// 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) {
// Handle errors in some way.
}
}

fn run_service(_arguments: Vec<std::ffi::OsString>) -> 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.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 opt = crate::CmdOpt::parse_cmd();
let is_server = opt.is_server();

let mut config = if let Some(file) = opt.config {
crate::Config::from_config_file(file)?
} else if let Some(ref url_of_node) = opt.url_of_node {
let mut cfg = crate::Config::from_ssr_url(url_of_node)?;
cfg.set_listen_addr(opt.listen_addr.unwrap_or(std::net::SocketAddr::from(([127, 0, 0, 1], 1080))));
cfg
} else {
return Err("Config file or node URL is required".into());
};
config.set_cache_dns(opt.cache_dns);

if opt.generate_url {
return Err("Generate URL is not supported in Windows service".into());
}

config.check_correctness(is_server)?;

let rt = tokio::runtime::Builder::new_multi_thread().enable_all().build()?;
rt.block_on(async {
if let Err(err) = crate::async_main(config, false, shutdown_token_clone).await {
log::error!("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(())
}

0 comments on commit 6dbf58e

Please sign in to comment.