From 6c1b8f090f6146e614bfeaf874b1b047c8173846 Mon Sep 17 00:00:00 2001 From: c0repwn3r Date: Wed, 22 Mar 2023 14:34:06 -0400 Subject: [PATCH] [wip] thread workers --- Cargo.lock | 5 +- tfclient/Cargo.toml | 1 + tfclient/src/apiworker.rs | 11 +++ tfclient/src/config.rs | 2 +- tfclient/src/daemon.rs | 90 ++++++++++++++++++++----- tfclient/src/main.rs | 19 ++++-- tfclient/src/nebulaworker.rs | 12 +++- tfclient/src/service/codegen/mod.rs | 2 +- tfclient/src/service/codegen/systemd.rs | 40 ++++++++++- tfclient/src/service/detect.rs | 29 ++++++++ tfclient/src/service/entry.rs | 82 ++++++++++++++++++++++ tfclient/src/service/macos.rs | 22 ++++++ tfclient/src/service/mod.rs | 13 ++-- tfclient/src/service/runit.rs | 22 ++++++ tfclient/src/service/systemd.rs | 80 +++++++++++++++++++--- tfclient/src/service/windows.rs | 22 ++++++ tfclient/src/socketworker.rs | 13 ++++ tfclient/src/util.rs | 20 ++++++ 18 files changed, 444 insertions(+), 41 deletions(-) create mode 100644 tfclient/src/apiworker.rs create mode 100644 tfclient/src/service/detect.rs create mode 100644 tfclient/src/service/entry.rs create mode 100644 tfclient/src/service/macos.rs create mode 100644 tfclient/src/service/runit.rs create mode 100644 tfclient/src/service/windows.rs create mode 100644 tfclient/src/socketworker.rs diff --git a/Cargo.lock b/Cargo.lock index 9960b99..8419086 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2049,9 +2049,9 @@ dependencies = [ [[package]] name = "serde_json" -version = "1.0.91" +version = "1.0.94" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "877c235533714907a8c2464236f5c4b2a17262ef1bd71f38f35ea592c8da6883" +checksum = "1c533a59c9d8a93a09c6ab31f0fd5e5f4dd1b8fc9434804029839884765d04ea" dependencies = [ "itoa", "ryu", @@ -2399,6 +2399,7 @@ dependencies = [ "log", "reqwest", "serde", + "serde_json", "sha2", "simple_logger", "tar", diff --git a/tfclient/Cargo.toml b/tfclient/Cargo.toml index 968fc53..499e6ee 100644 --- a/tfclient/Cargo.toml +++ b/tfclient/Cargo.toml @@ -17,6 +17,7 @@ hex = "0.4.3" url = "2.3.1" toml = "0.7.3" serde = { version = "1.0.158", features = ["derive"] } +serde_json = "1.0.94" [build-dependencies] serde = { version = "1.0.157", features = ["derive"] } diff --git a/tfclient/src/apiworker.rs b/tfclient/src/apiworker.rs new file mode 100644 index 0000000..8948166 --- /dev/null +++ b/tfclient/src/apiworker.rs @@ -0,0 +1,11 @@ +use std::sync::mpsc::Receiver; +use crate::config::TFClientConfig; +use crate::daemon::ThreadMessageSender; + +pub enum APIWorkerMessage { + +} + +pub fn apiworker_main(config: TFClientConfig, transmitters: ThreadMessageSender, rx: Receiver) { + +} \ No newline at end of file diff --git a/tfclient/src/config.rs b/tfclient/src/config.rs index d9d7597..5f82572 100644 --- a/tfclient/src/config.rs +++ b/tfclient/src/config.rs @@ -7,7 +7,7 @@ use crate::dirs::{get_config_dir, get_config_file}; pub const DEFAULT_PORT: u16 = 8157; fn default_port() -> u16 { DEFAULT_PORT } -#[derive(Serialize, Deserialize)] +#[derive(Serialize, Deserialize, Clone)] pub struct TFClientConfig { #[serde(default = "default_port")] listen_port: u16 diff --git a/tfclient/src/daemon.rs b/tfclient/src/daemon.rs index e6d36e9..8918fab 100644 --- a/tfclient/src/daemon.rs +++ b/tfclient/src/daemon.rs @@ -1,25 +1,17 @@ +use std::sync::mpsc; +use std::sync::mpsc::Sender; +use std::thread; use log::{error, info, warn}; use url::Url; +use crate::apiworker::{apiworker_main, APIWorkerMessage}; use crate::config::load_config; +use crate::nebulaworker::{nebulaworker_main, NebulaWorkerMessage}; +use crate::socketworker::{socketworker_main, SocketWorkerMessage}; +use crate::util::check_server_url; pub fn daemon_main(name: String, server: String) { // Validate the `server` - info!("Checking server url..."); - let api_base = match Url::parse(&server) { - Ok(u) => u, - Err(e) => { - error!("Invalid server url `{}`: {}", server, e); - std::process::exit(1); - } - }; - match api_base.scheme() { - "http" => { warn!("HTTP api urls are not reccomended. Please switch to HTTPS if possible.") }, - "https" => (), - _ => { - error!("Unsupported protocol `{}` (expected one of http, https)", api_base.scheme()); - std::process::exit(1); - } - } + check_server_url(&server); info!("Loading config..."); let config = match load_config(&name) { @@ -29,4 +21,70 @@ pub fn daemon_main(name: String, server: String) { std::process::exit(1); } }; + + info!("Starting API thread..."); + + let (tx_api, rx_api) = mpsc::channel::(); + let (tx_socket, rx_socket) = mpsc::channel::(); + let (tx_nebula, rx_nebula) = mpsc::channel::(); + + let transmitter = ThreadMessageSender { + socket_thread: tx_socket, + api_thread: tx_api, + nebula_thread: tx_nebula + }; + + let config_api = config.clone(); + let transmitter_api = transmitter.clone(); + let api_thread = thread::spawn(move || { + apiworker_main(config_api, transmitter_api, rx_api); + }); + + info!("Starting Nebula thread..."); + let config_nebula = config.clone(); + let transmitter_nebula = transmitter.clone(); + let nebula_thread = thread::spawn(move || { + nebulaworker_main(config_nebula, transmitter_nebula, rx_nebula); + }); + + info!("Starting socket worker thread..."); + let socket_thread = thread::spawn(move || { + socketworker_main(config, transmitter, rx_socket); + }); + + info!("Waiting for socket thread to exit..."); + match socket_thread.join() { + Ok(_) => (), + Err(_) => { + error!("Error waiting for socket thread to exit."); + std::process::exit(1); + } + } + + info!("Waiting for API thread to exit..."); + match api_thread.join() { + Ok(_) => (), + Err(_) => { + error!("Error waiting for api thread to exit."); + std::process::exit(1); + } + } + + info!("Waiting for Nebula thread to exit..."); + match nebula_thread.join() { + Ok(_) => (), + Err(_) => { + error!("Error waiting for nebula thread to exit."); + std::process::exit(1); + } + } + + info!("All threads exited"); +} + +#[derive(Clone)] +pub struct ThreadMessageSender { + socket_thread: Sender, + api_thread: Sender, + nebula_thread: Sender } \ No newline at end of file diff --git a/tfclient/src/main.rs b/tfclient/src/main.rs index 8c7f9f0..49f95b8 100644 --- a/tfclient/src/main.rs +++ b/tfclient/src/main.rs @@ -21,6 +21,8 @@ pub mod nebulaworker; pub mod daemon; pub mod config; pub mod service; +pub mod apiworker; +pub mod socketworker; pub mod nebula_bin { include!(concat!(env!("OUT_DIR"), "/nebula.bin.rs")); @@ -37,6 +39,7 @@ use log::{error, info}; use simple_logger::SimpleLogger; use crate::dirs::get_data_dir; use crate::embedded_nebula::{run_embedded_nebula, run_embedded_nebula_cert}; +use crate::service::entry::{cli_install, cli_start, cli_stop, cli_uninstall}; #[derive(Parser)] #[command(author = "c0repwn3r", version, about, long_about = None)] @@ -210,10 +213,18 @@ fn main() { } } } - Commands::Install { .. } => {} - Commands::Uninstall { .. } => {} - Commands::Start { .. } => {} - Commands::Stop { .. } => {} + Commands::Install { server, name } => { + cli_install(&name, &server); + } + Commands::Uninstall { name } => { + cli_uninstall(&name); + } + Commands::Start { name } => { + cli_start(&name); + } + Commands::Stop { name } => { + cli_stop(&name); + } Commands::Run { name, server } => { daemon::daemon_main(name, server); } diff --git a/tfclient/src/nebulaworker.rs b/tfclient/src/nebulaworker.rs index 0882bf9..80a0551 100644 --- a/tfclient/src/nebulaworker.rs +++ b/tfclient/src/nebulaworker.rs @@ -1,5 +1,13 @@ -// Code to handle the command socket worker +// Code to handle the nebula worker -pub fn socketworker_main() { +use std::sync::mpsc::Receiver; +use crate::config::TFClientConfig; +use crate::daemon::ThreadMessageSender; + +pub enum NebulaWorkerMessage { + +} + +pub fn nebulaworker_main(config: TFClientConfig, transmitter: ThreadMessageSender, rx: Receiver) { } \ No newline at end of file diff --git a/tfclient/src/service/codegen/mod.rs b/tfclient/src/service/codegen/mod.rs index 547ff34..a570ad9 100644 --- a/tfclient/src/service/codegen/mod.rs +++ b/tfclient/src/service/codegen/mod.rs @@ -4,6 +4,6 @@ use std::error::Error; use std::path::PathBuf; pub trait ServiceFileGenerator { - fn create_service_files(bin_path: PathBuf, name: &str) -> Result<(), Box>; + fn create_service_files(bin_path: PathBuf, name: &str, server: &str) -> Result<(), Box>; fn delete_service_files(name: &str) -> Result<(), Box>; } \ No newline at end of file diff --git a/tfclient/src/service/codegen/systemd.rs b/tfclient/src/service/codegen/systemd.rs index 0820264..b05db5d 100644 --- a/tfclient/src/service/codegen/systemd.rs +++ b/tfclient/src/service/codegen/systemd.rs @@ -1,14 +1,48 @@ use std::error::Error; use std::path::PathBuf; +use log::debug; use crate::service::codegen::ServiceFileGenerator; +use std::fmt::Write; +use std::fs; pub struct SystemDServiceFileGenerator {} impl ServiceFileGenerator for SystemDServiceFileGenerator { - fn create_service_files(bin_path: PathBuf, name: &str) -> Result<(), Box> { - todo!() + fn create_service_files(bin_path: PathBuf, name: &str, server: &str) -> Result<(), Box> { + debug!("Generating a unit file..."); + + let mut unit_file = String::new(); + writeln!(unit_file, "[Unit]")?; + writeln!(unit_file, "Description=A client for Defined Networking compatible overlay networks (instance {})", name)?; + writeln!(unit_file, "Wants=basic.target network-online.target")?; + writeln!(unit_file, "After=basic.target network.target network-online.target")?; + writeln!(unit_file)?; + writeln!(unit_file, "[Service]")?; + writeln!(unit_file, "SyslogIdentifier=tfclient-{}", name)?; + writeln!(unit_file, "ExecStart={} run --server {} --name {}", bin_path.as_path().display(), server, name)?; + writeln!(unit_file, "Restart=always")?; + writeln!(unit_file)?; + writeln!(unit_file, "[Install]")?; + writeln!(unit_file, "WantedBy=multi-user.target")?; + + fs::write(format!("/usr/lib/systemd/system/{}.service", SystemDServiceFileGenerator::get_service_file_name(name)), unit_file)?; + + debug!("Installed unit file"); + + Ok(()) } fn delete_service_files(name: &str) -> Result<(), Box> { - todo!() + debug!("Deleting unit file..."); + + fs::remove_file(format!("/usr/lib/systemd/system/{}.service", SystemDServiceFileGenerator::get_service_file_name(name)))?; + + debug!("Removed unit file"); + + Ok(()) + } +} +impl SystemDServiceFileGenerator { + pub fn get_service_file_name(name: &str) -> String { + format!("tfclient_i-{}", name) } } \ No newline at end of file diff --git a/tfclient/src/service/detect.rs b/tfclient/src/service/detect.rs new file mode 100644 index 0000000..42a76a4 --- /dev/null +++ b/tfclient/src/service/detect.rs @@ -0,0 +1,29 @@ +use std::path::Path; +use log::info; +use crate::service::macos::OSXServiceManager; +use crate::service::runit::RunitServiceManager; +use crate::service::ServiceManager; +use crate::service::systemd::SystemDServiceManager; +use crate::service::windows::WindowsServiceManager; + +pub fn detect_service() -> Option> { + if cfg!(windows) { + return Some(Box::new(WindowsServiceManager {})); + } + if cfg!(macos) { + return Some(Box::new(OSXServiceManager {})); + } + detect_unix_service_manager() +} + +pub fn detect_unix_service_manager() -> Option> { + if Path::new("/etc/runit/1").exists() { + info!("Detected Runit service supervision (confidence: 100%, /etc/runit/1 exists)"); + return Some(Box::new(RunitServiceManager {})) + } + if Path::new("/var/lib/systemd").exists() { + info!("Detected SystemD service supervision (confidence: 100%, /var/lib/systemd exists)"); + return Some(Box::new(SystemDServiceManager {})); + } + None +} \ No newline at end of file diff --git a/tfclient/src/service/entry.rs b/tfclient/src/service/entry.rs new file mode 100644 index 0000000..65e8da9 --- /dev/null +++ b/tfclient/src/service/entry.rs @@ -0,0 +1,82 @@ +use std::env::current_exe; +use log::{error, info, warn}; +use crate::service::detect::detect_service; +use crate::util::check_server_url; + +pub fn cli_start(name: &str) { + info!("Detecting service manager..."); + let service = detect_service(); + if let Some(sm) = service { + match sm.start(name) { + Ok(_) => (), + Err(e) => { + error!("Error starting service: {}", e); + std::process::exit(1); + } + } + } else { + error!("Unable to determine which service manager to use. Could not start."); + std::process::exit(1); + } +} + +pub fn cli_stop(name: &str) { + info!("Detecting service manager..."); + let service = detect_service(); + if let Some(sm) = service { + match sm.stop(name) { + Ok(_) => (), + Err(e) => { + error!("Error starting service: {}", e); + std::process::exit(1); + } + } + } else { + error!("Unable to determine which service manager to use. Could not stop."); + std::process::exit(1); + } +} + +pub fn cli_install(name: &str, server: &str) { + info!("Checking server url..."); + check_server_url(server); + + info!("Detecting service manager..."); + let service = detect_service(); + if let Some(sm) = service { + let current_file = match current_exe() { + Ok(e) => e, + Err(e) => { + error!("Unable to get current binary: {}", e); + std::process::exit(1); + } + }; + match sm.install(current_file, name, server) { + Ok(_) => (), + Err(e) => { + error!("Error creating service files: {}", e); + std::process::exit(1); + } + } + } else { + error!("Unable to determine which service manager to use. Could not install."); + std::process::exit(1); + } +} + +pub fn cli_uninstall(name: &str) { + info!("Detecting service manager..."); + let service = detect_service(); + if let Some(sm) = service { + match sm.uninstall(name) { + Ok(_) => (), + Err(e) => { + error!("Error removing service files: {}", e); + std::process::exit(1); + } + } + } else { + error!("Unable to determine which service manager to use. Could not install."); + std::process::exit(1); + } +} \ No newline at end of file diff --git a/tfclient/src/service/macos.rs b/tfclient/src/service/macos.rs new file mode 100644 index 0000000..765ec6f --- /dev/null +++ b/tfclient/src/service/macos.rs @@ -0,0 +1,22 @@ +use std::error::Error; +use std::path::PathBuf; +use crate::service::ServiceManager; + +pub struct OSXServiceManager {} +impl ServiceManager for OSXServiceManager { + fn install(&self, bin_path: PathBuf, name: &str, server_url: &str) -> Result<(), Box> { + todo!() + } + + fn uninstall(&self, name: &str) -> Result<(), Box> { + todo!() + } + + fn start(&self, name: &str) -> Result<(), Box> { + todo!() + } + + fn stop(&self, name: &str) -> Result<(), Box> { + todo!() + } +} \ No newline at end of file diff --git a/tfclient/src/service/mod.rs b/tfclient/src/service/mod.rs index c5ff059..e00c8fb 100644 --- a/tfclient/src/service/mod.rs +++ b/tfclient/src/service/mod.rs @@ -3,10 +3,15 @@ use std::path::PathBuf; pub mod codegen; pub mod systemd; +pub mod detect; +pub mod entry; +pub mod windows; +pub mod macos; +pub mod runit; pub trait ServiceManager { - fn install(bin_path: PathBuf, name: &str) -> Result<(), Box>; - fn uninstall(name: &str) -> Result<(), Box>; - fn start(name: &str) -> Result<(), Box>; - fn stop(name: &str) -> Result<(), Box>; + fn install(&self, bin_path: PathBuf, name: &str, server_url: &str) -> Result<(), Box>; + fn uninstall(&self, name: &str) -> Result<(), Box>; + fn start(&self, name: &str) -> Result<(), Box>; + fn stop(&self, name: &str) -> Result<(), Box>; } \ No newline at end of file diff --git a/tfclient/src/service/runit.rs b/tfclient/src/service/runit.rs new file mode 100644 index 0000000..c6c4a45 --- /dev/null +++ b/tfclient/src/service/runit.rs @@ -0,0 +1,22 @@ +use std::error::Error; +use std::path::PathBuf; +use crate::service::ServiceManager; + +pub struct RunitServiceManager {} +impl ServiceManager for RunitServiceManager { + fn install(&self, bin_path: PathBuf, name: &str, server_url: &str) -> Result<(), Box> { + todo!() + } + + fn uninstall(&self, name: &str) -> Result<(), Box> { + todo!() + } + + fn start(&self, name: &str) -> Result<(), Box> { + todo!() + } + + fn stop(&self, name: &str) -> Result<(), Box> { + todo!() + } +} \ No newline at end of file diff --git a/tfclient/src/service/systemd.rs b/tfclient/src/service/systemd.rs index 42bb249..05d3cc3 100644 --- a/tfclient/src/service/systemd.rs +++ b/tfclient/src/service/systemd.rs @@ -1,22 +1,86 @@ use std::error::Error; use std::path::PathBuf; +use std::process::Command; +use log::{error, info}; +use crate::service::codegen::ServiceFileGenerator; +use crate::service::codegen::systemd::SystemDServiceFileGenerator; use crate::service::ServiceManager; pub struct SystemDServiceManager {} impl ServiceManager for SystemDServiceManager { - fn install(bin_path: PathBuf, name: &str) -> Result<(), Box> { - todo!() + fn install(&self, bin_path: PathBuf, name: &str, server_url: &str) -> Result<(), Box> { + info!("Installing for SystemD"); + + SystemDServiceFileGenerator::create_service_files(bin_path, name, server_url)?; + + info!("Enabling the SystemD service"); + + let out = Command::new("systemctl").args(["enable", &SystemDServiceFileGenerator::get_service_file_name(name)]).output()?; + if !out.status.success() { + error!("Error enabling the SystemD service (command exited with non-zero exit code)"); + error!("stdout:"); + error!("{}", String::from_utf8(out.stdout)?); + error!("stderr:"); + error!("{}", String::from_utf8(out.stderr)?); + return Err("Command exited with non-zero exit code".into()); + } + + info!("Installation successful"); + + Ok(()) } - fn uninstall(name: &str) -> Result<(), Box> { - todo!() + fn uninstall(&self, name: &str) -> Result<(), Box> { + info!("Uninstalling SystemD service files"); + + info!("Disabling the SystemD service"); + + let out = Command::new("systemctl").args(["disable", &SystemDServiceFileGenerator::get_service_file_name(name)]).output()?; + if !out.status.success() { + error!("Error disabling the SystemD service (command exited with non-zero exit code)"); + error!("stdout:"); + error!("{}", String::from_utf8(out.stdout)?); + error!("stderr:"); + error!("{}", String::from_utf8(out.stderr)?); + return Err("Command exited with non-zero exit code".into()); + } + + info!("Removing the service files"); + + SystemDServiceFileGenerator::delete_service_files(name)?; + + Ok(()) } - fn start(name: &str) -> Result<(), Box> { - todo!() + fn start(&self, name: &str) -> Result<(), Box> { + info!("Starting the SystemD service"); + + let out = Command::new("systemctl").args(["start", &SystemDServiceFileGenerator::get_service_file_name(name)]).output()?; + if !out.status.success() { + error!("Error starting the SystemD service (command exited with non-zero exit code)"); + error!("stdout:"); + error!("{}", String::from_utf8(out.stdout)?); + error!("stderr:"); + error!("{}", String::from_utf8(out.stderr)?); + return Err("Command exited with non-zero exit code".into()); + } + + Ok(()) } - fn stop(name: &str) -> Result<(), Box> { - todo!() + fn stop(&self, name: &str) -> Result<(), Box> { + info!("Stopping the SystemD service"); + + let out = Command::new("systemctl").args(["stop", &SystemDServiceFileGenerator::get_service_file_name(name)]).output()?; + if !out.status.success() { + error!("Error stopping the SystemD service (command exited with non-zero exit code)"); + error!("stdout:"); + error!("{}", String::from_utf8(out.stdout)?); + error!("stderr:"); + error!("{}", String::from_utf8(out.stderr)?); + return Err("Command exited with non-zero exit code".into()); + } + + Ok(()) } } \ No newline at end of file diff --git a/tfclient/src/service/windows.rs b/tfclient/src/service/windows.rs new file mode 100644 index 0000000..4b17651 --- /dev/null +++ b/tfclient/src/service/windows.rs @@ -0,0 +1,22 @@ +use std::error::Error; +use std::path::PathBuf; +use crate::service::ServiceManager; + +pub struct WindowsServiceManager {} +impl ServiceManager for WindowsServiceManager { + fn install(&self, bin_path: PathBuf, name: &str, server_url: &str) -> Result<(), Box> { + todo!() + } + + fn uninstall(&self, name: &str) -> Result<(), Box> { + todo!() + } + + fn start(&self, name: &str) -> Result<(), Box> { + todo!() + } + + fn stop(&self, name: &str) -> Result<(), Box> { + todo!() + } +} \ No newline at end of file diff --git a/tfclient/src/socketworker.rs b/tfclient/src/socketworker.rs new file mode 100644 index 0000000..5dbd94a --- /dev/null +++ b/tfclient/src/socketworker.rs @@ -0,0 +1,13 @@ +// Code to handle the nebula worker + +use std::sync::mpsc::Receiver; +use crate::config::TFClientConfig; +use crate::daemon::ThreadMessageSender; + +pub enum SocketWorkerMessage { + +} + +pub fn socketworker_main(config: TFClientConfig, transmitter: ThreadMessageSender, rx: Receiver) { + +} \ No newline at end of file diff --git a/tfclient/src/util.rs b/tfclient/src/util.rs index a87effb..eeecaca 100644 --- a/tfclient/src/util.rs +++ b/tfclient/src/util.rs @@ -1,9 +1,29 @@ +use log::{error, warn}; use sha2::Sha256; use sha2::Digest; +use url::Url; pub fn sha256(bytes: &[u8]) -> String { let mut hasher = Sha256::new(); hasher.update(bytes); let digest = hasher.finalize(); hex::encode(digest) +} + +pub fn check_server_url(server: &str) { + let api_base = match Url::parse(&server) { + Ok(u) => u, + Err(e) => { + error!("Invalid server url `{}`: {}", server, e); + std::process::exit(1); + } + }; + match api_base.scheme() { + "http" => { warn!("HTTP api urls are not reccomended. Please switch to HTTPS if possible.") }, + "https" => (), + _ => { + error!("Unsupported protocol `{}` (expected one of http, https)", api_base.scheme()); + std::process::exit(1); + } + } } \ No newline at end of file