diff --git a/controller/Cargo.toml b/controller/Cargo.toml index c824b4107..572780ba2 100644 --- a/controller/Cargo.toml +++ b/controller/Cargo.toml @@ -17,3 +17,7 @@ parking_lot = { version = "^0", features = [], default-features = false } serde = { version = "^1", features = ["derive"], default-features = false } serde_json = { version = "^1", features = ["std"], default-features = false } clap = { version = "^3", features = ["std", "suggestions"], default-features = false } + +[target."cfg(not(windows))".dependencies] +signal-hook = "^0" +libc = "^0" diff --git a/controller/src/main.rs b/controller/src/main.rs index cde385cba..0621ea5d7 100644 --- a/controller/src/main.rs +++ b/controller/src/main.rs @@ -1,3 +1,45 @@ // (c) 2020-2022 ZeroTier, Inc. -- currently propritery pending actual release and licensing. See LICENSE.md. -fn main() {} +use std::sync::atomic::{AtomicBool, Ordering}; +use std::sync::Arc; + +use std::time::Duration; + +use zerotier_utils::exitcode; +use zerotier_vl1_service::VL1Service; + +fn main() { + std::process::exit( + if let Ok(_tokio_runtime) = zerotier_utils::tokio::runtime::Builder::new_multi_thread().enable_all().build() { + let test_inner = Arc::new(zerotier_network_hypervisor::vl1::DummyInnerProtocol::default()); + let test_path_filter = Arc::new(zerotier_network_hypervisor::vl1::DummyPathFilter::default()); + let datadir = open_datadir(&flags); + let svc = VL1Service::new(datadir, test_inner, test_path_filter, zerotier_vl1_service::VL1Settings::default()); + if svc.is_ok() { + let svc = svc.unwrap(); + svc.node().init_default_roots(); + + // Wait for kill signal on Unix-like platforms. + #[cfg(unix)] + { + let term = Arc::new(AtomicBool::new(false)); + let _ = signal_hook::flag::register(libc::SIGINT, term.clone()); + let _ = signal_hook::flag::register(libc::SIGTERM, term.clone()); + let _ = signal_hook::flag::register(libc::SIGQUIT, term.clone()); + while !term.load(Ordering::Relaxed) { + std::thread::sleep(Duration::from_secs(1)); + } + } + + println!("Terminate signal received, shutting down..."); + exitcode::OK + } else { + eprintln!("FATAL: error launching service: {}", svc.err().unwrap().to_string()); + exitcode::ERR_IOERR + } + } else { + eprintln!("FATAL: error launching service: can't start async runtime"); + exitcode::ERR_IOERR + }, + ); +} diff --git a/service/src/main.rs b/service/src/main.rs index c06c15ae7..4a94ef5e1 100644 --- a/service/src/main.rs +++ b/service/src/main.rs @@ -2,7 +2,6 @@ pub mod cli; pub mod cmdline_help; -pub mod datadir; pub mod localconfig; pub mod utils; pub mod vnic; @@ -18,9 +17,10 @@ use clap::{Arg, ArgMatches, Command}; use zerotier_network_hypervisor::{VERSION_MAJOR, VERSION_MINOR, VERSION_REVISION}; use zerotier_utils::exitcode; +use zerotier_vl1_service::datadir::DataDir; use zerotier_vl1_service::VL1Service; -use crate::datadir::DataDir; +use crate::localconfig::Config; pub fn print_help() { let h = crate::cmdline_help::make_cmdline_help(); @@ -44,7 +44,7 @@ pub struct Flags { pub auth_token_override: Option, } -fn open_datadir(flags: &Flags) -> Arc { +fn open_datadir(flags: &Flags) -> Arc> { let datadir = DataDir::open(flags.base_path.as_str()); if datadir.is_ok() { return Arc::new(datadir.unwrap()); diff --git a/service/src/utils.rs b/service/src/utils.rs index c071107e6..52979dff7 100644 --- a/service/src/utils.rs +++ b/service/src/utils.rs @@ -1,42 +1,10 @@ // (c) 2020-2022 ZeroTier, Inc. -- currently propritery pending actual release and licensing. See LICENSE.md. -use std::fs::File; use std::io::Read; use std::path::Path; -use std::str::FromStr; use zerotier_network_hypervisor::vl1::Identity; - -/// Default sanity limit parameter for read_limit() used throughout the service. -pub const DEFAULT_FILE_IO_READ_LIMIT: usize = 1048576; - -/// Convenience function to read up to limit bytes from a file. -/// -/// If the file is larger than limit, the excess is not read. -pub fn read_limit>(path: P, limit: usize) -> std::io::Result> { - let mut f = File::open(path)?; - let bytes = f.metadata()?.len().min(limit as u64) as usize; - let mut v: Vec = Vec::with_capacity(bytes); - v.resize(bytes, 0); - f.read_exact(v.as_mut_slice())?; - Ok(v) -} - -/// Set permissions on a file or directory to be most restrictive (visible only to the service's user). -#[cfg(unix)] -pub fn fs_restrict_permissions>(path: P) -> bool { - unsafe { - let c_path = std::ffi::CString::new(path.as_ref().to_str().unwrap()).unwrap(); - libc::chmod( - c_path.as_ptr(), - if path.as_ref().is_dir() { - 0o700 - } else { - 0o600 - }, - ) == 0 - } -} +use zerotier_utils::io::read_limit; /// Returns true if the string starts with [yY1tT] or false for [nN0fF]. pub fn parse_bool(v: &str) -> Result { diff --git a/utils/Cargo.toml b/utils/Cargo.toml index fa69621be..a66eaa8ae 100644 --- a/utils/Cargo.toml +++ b/utils/Cargo.toml @@ -14,3 +14,9 @@ parking_lot = { version = "^0", features = [], default-features = false } serde = { version = "^1", features = ["derive"], default-features = false } serde_json = { version = "^1", features = ["std"], default-features = false } tokio = { version = "^1", default-features = false, features = ["fs", "io-util", "io-std", "net", "parking_lot", "process", "rt", "rt-multi-thread", "signal", "sync", "time"], optional = true } + +[target."cfg(windows)".dependencies] +winapi = { version = "^0", features = ["handleapi", "ws2ipdef", "ws2tcpip"] } + +[target."cfg(not(windows))".dependencies] +libc = "^0" diff --git a/utils/src/io.rs b/utils/src/io.rs new file mode 100644 index 000000000..9a48e85c2 --- /dev/null +++ b/utils/src/io.rs @@ -0,0 +1,36 @@ +// (c) 2020-2022 ZeroTier, Inc. -- currently propritery pending actual release and licensing. See LICENSE.md. + +use std::fs::File; +use std::io::Read; +use std::path::Path; + +/// Default sanity limit parameter for read_limit() used throughout the service. +pub const DEFAULT_FILE_IO_READ_LIMIT: usize = 1048576; + +/// Convenience function to read up to limit bytes from a file. +/// +/// If the file is larger than limit, the excess is not read. +pub fn read_limit>(path: P, limit: usize) -> std::io::Result> { + let mut f = File::open(path)?; + let bytes = f.metadata()?.len().min(limit as u64) as usize; + let mut v: Vec = Vec::with_capacity(bytes); + v.resize(bytes, 0); + f.read_exact(v.as_mut_slice())?; + Ok(v) +} + +/// Set permissions on a file or directory to be most restrictive (visible only to the service's user). +#[cfg(unix)] +pub fn fs_restrict_permissions>(path: P) -> bool { + unsafe { + let c_path = std::ffi::CString::new(path.as_ref().to_str().unwrap()).unwrap(); + libc::chmod( + c_path.as_ptr(), + if path.as_ref().is_dir() { + 0o700 + } else { + 0o600 + }, + ) == 0 + } +} diff --git a/utils/src/lib.rs b/utils/src/lib.rs index 2e7cb0ff8..499476591 100644 --- a/utils/src/lib.rs +++ b/utils/src/lib.rs @@ -10,6 +10,7 @@ pub mod exitcode; pub mod gate; pub mod gatherarray; pub mod hex; +pub mod io; pub mod json; pub mod marshalable; pub mod memory; diff --git a/service/src/datadir.rs b/vl1-service/src/datadir.rs similarity index 83% rename from service/src/datadir.rs rename to vl1-service/src/datadir.rs index 08aef4bb4..92b8d26d9 100644 --- a/service/src/datadir.rs +++ b/vl1-service/src/datadir.rs @@ -1,32 +1,33 @@ // (c) 2020-2022 ZeroTier, Inc. -- currently propritery pending actual release and licensing. See LICENSE.md. -use crate::localconfig::Config; -use crate::utils::{read_limit, DEFAULT_FILE_IO_READ_LIMIT}; use std::path::{Path, PathBuf}; use std::str::FromStr; use std::sync::Arc; use parking_lot::{Mutex, RwLock}; +use serde::de::DeserializeOwned; +use serde::Serialize; use zerotier_crypto::random::next_u32_secure; use zerotier_network_hypervisor::vl1::{Identity, NodeStorage}; +use zerotier_utils::io::{fs_restrict_permissions, read_limit, DEFAULT_FILE_IO_READ_LIMIT}; use zerotier_utils::json::to_json_pretty; +pub const AUTH_TOKEN_FILENAME: &'static str = "authtoken.secret"; +pub const IDENTITY_PUBLIC_FILENAME: &'static str = "identity.public"; +pub const IDENTITY_SECRET_FILENAME: &'static str = "identity.secret"; +pub const CONFIG_FILENAME: &'static str = "local.conf"; + const AUTH_TOKEN_DEFAULT_LENGTH: usize = 48; const AUTH_TOKEN_POSSIBLE_CHARS: &'static str = "0123456789abcdefghijklmnopqrstuvwxyz"; -const AUTH_TOKEN_FILENAME: &'static str = "authtoken.secret"; -const IDENTITY_PUBLIC_FILENAME: &'static str = "identity.public"; -const IDENTITY_SECRET_FILENAME: &'static str = "identity.secret"; -const CONFIG_FILENAME: &'static str = "local.conf"; -/// Abstraction around ZeroTier's home data directory. -pub struct DataDir { +pub struct DataDir { pub base_path: PathBuf, config: RwLock>, authtoken: Mutex, } -impl NodeStorage for DataDir { +impl NodeStorage for DataDir { fn load_node_identity(&self) -> Option { let id_data = read_limit(self.base_path.join(IDENTITY_SECRET_FILENAME), 4096); if id_data.is_err() { @@ -46,12 +47,12 @@ impl NodeStorage for DataDir { let secret_path = self.base_path.join(IDENTITY_SECRET_FILENAME); // TODO: handle errors let _ = std::fs::write(&secret_path, id_secret_str.as_bytes()); - assert!(crate::utils::fs_restrict_permissions(&secret_path)); + assert!(fs_restrict_permissions(&secret_path)); let _ = std::fs::write(self.base_path.join(IDENTITY_PUBLIC_FILENAME), id_public_str.as_bytes()); } } -impl DataDir { +impl DataDir { pub fn open>(path: P) -> std::io::Result { let base_path = path.as_ref().to_path_buf(); if !base_path.is_dir() { @@ -95,7 +96,7 @@ impl DataDir { tmp.push(AUTH_TOKEN_POSSIBLE_CHARS.as_bytes()[(next_u32_secure() as usize) % AUTH_TOKEN_POSSIBLE_CHARS.len()] as char); } std::fs::write(&authtoken_path, tmp.as_bytes())?; - assert!(crate::utils::fs_restrict_permissions(&authtoken_path)); + assert!(fs_restrict_permissions(&authtoken_path)); *self.authtoken.lock() = tmp; } else { *self.authtoken.lock() = String::from_utf8_lossy(authtoken_bytes.unwrap().as_slice()).into(); diff --git a/vl1-service/src/lib.rs b/vl1-service/src/lib.rs index fe7cb0dee..356e91a93 100644 --- a/vl1-service/src/lib.rs +++ b/vl1-service/src/lib.rs @@ -6,6 +6,7 @@ mod settings; mod vl1service; pub mod constants; +pub mod datadir; pub mod sys; pub use localinterface::LocalInterface;