diff --git a/network-hypervisor/src/util/dictionary.rs b/network-hypervisor/src/util/dictionary.rs index c3a3cac0a..5291a99a2 100644 --- a/network-hypervisor/src/util/dictionary.rs +++ b/network-hypervisor/src/util/dictionary.rs @@ -227,9 +227,23 @@ mod tests { type TypeMap = HashMap; use super::{Dictionary, BOOL_TRUTH}; - use crate::util::testutil::randstring; use std::collections::HashMap; + fn randstring(len: u8) -> String { + (0..len) + .map(|_| (rand::random::() % 26) + 'a' as u8) + .map(|c| { + if rand::random::() { + (c as char).to_ascii_uppercase() + } else { + c as char + } + }) + .map(|c| c.to_string()) + .collect::>() + .join("") + } + fn make_dictionary() -> (Dictionary, TypeMap) { let mut d = Dictionary::new(); let mut tm = TypeMap::new(); diff --git a/network-hypervisor/src/util/mod.rs b/network-hypervisor/src/util/mod.rs index d565a77df..1406be0e1 100644 --- a/network-hypervisor/src/util/mod.rs +++ b/network-hypervisor/src/util/mod.rs @@ -8,9 +8,6 @@ pub mod marshalable; /// A value for ticks that indicates that something never happened, and is thus very long before zero ticks. pub(crate) const NEVER_HAPPENED_TICKS: i64 = -2147483648; -#[cfg(test)] -pub mod testutil; - #[cfg(feature = "debug_events")] #[allow(unused_macros)] macro_rules! debug_event { diff --git a/network-hypervisor/src/util/testutil.rs b/network-hypervisor/src/util/testutil.rs deleted file mode 100644 index 4de683c07..000000000 --- a/network-hypervisor/src/util/testutil.rs +++ /dev/null @@ -1,17 +0,0 @@ -// (c) 2020-2022 ZeroTier, Inc. -- currently propritery pending actual release and licensing. See LICENSE.md. - -// from zeronsd -pub fn randstring(len: u8) -> String { - (0..len) - .map(|_| (rand::random::() % 26) + 'a' as u8) - .map(|c| { - if rand::random::() { - (c as char).to_ascii_uppercase() - } else { - c as char - } - }) - .map(|c| c.to_string()) - .collect::>() - .join("") -} diff --git a/network-hypervisor/src/vl1/endpoint.rs b/network-hypervisor/src/vl1/endpoint.rs index 381950983..0188ad27d 100644 --- a/network-hypervisor/src/vl1/endpoint.rs +++ b/network-hypervisor/src/vl1/endpoint.rs @@ -459,6 +459,21 @@ mod tests { }, }; + fn randstring(len: u8) -> String { + (0..len) + .map(|_| (rand::random::() % 26) + 'a' as u8) + .map(|c| { + if rand::random::() { + (c as char).to_ascii_uppercase() + } else { + c as char + } + }) + .map(|c| c.to_string()) + .collect::>() + .join("") + } + #[test] fn endpoint_default() { let e: Endpoint = Default::default(); @@ -611,7 +626,6 @@ mod tests { #[test] fn endpoint_marshal_http() { use crate::util::buffer::Buffer; - use crate::util::testutil::randstring; for _ in 0..1000 { let http = Endpoint::Http(randstring(30)); @@ -650,7 +664,6 @@ mod tests { #[test] fn endpoint_to_from_string() { - use crate::util::testutil::randstring; use std::str::FromStr; for _ in 0..1000 { diff --git a/network-hypervisor/src/vl1/mod.rs b/network-hypervisor/src/vl1/mod.rs index d16cfaec5..daf19d42e 100644 --- a/network-hypervisor/src/vl1/mod.rs +++ b/network-hypervisor/src/vl1/mod.rs @@ -24,7 +24,7 @@ pub use event::Event; pub use identity::Identity; pub use inetaddress::InetAddress; pub use mac::MAC; -pub use node::{HostSystem, InnerProtocol, Node, PathFilter, Storage}; +pub use node::{DummyInnerProtocol, DummyPathFilter, HostSystem, InnerProtocol, Node, PathFilter, Storage}; pub use path::Path; pub use peer::Peer; pub use rootset::{Root, RootSet}; diff --git a/network-hypervisor/src/vl1/node.rs b/network-hypervisor/src/vl1/node.rs index a749e393d..0b8615f9c 100644 --- a/network-hypervisor/src/vl1/node.rs +++ b/network-hypervisor/src/vl1/node.rs @@ -97,9 +97,9 @@ pub trait Storage: Sync + Send + 'static { /// Trait to be implemented to provide path hints and a filter to approve physical paths #[async_trait] -pub trait PathFilter: Sync + Send + 'static { +pub trait PathFilter: Sync + Send + 'static { /// Called to check and see if a physical address should be used for ZeroTier traffic to a node. - async fn check_path( + async fn check_path( &self, id: &Identity, endpoint: &Endpoint, @@ -108,7 +108,7 @@ pub trait PathFilter: Sync + Send + 'static { ) -> bool; /// Called to look up any statically defined or memorized paths to known nodes. - async fn get_path_hints( + async fn get_path_hints( &self, id: &Identity, ) -> Option< @@ -849,3 +849,79 @@ impl Node { } } } + +/// Dummy no-op inner protocol for debugging and testing. +#[derive(Default)] +pub struct DummyInnerProtocol; + +#[async_trait] +impl InnerProtocol for DummyInnerProtocol { + async fn handle_packet( + &self, + _source: &Peer, + _source_path: &Path, + _verb: u8, + _payload: &PacketBuffer, + ) -> bool { + false + } + + async fn handle_error( + &self, + _source: &Peer, + _source_path: &Path, + _in_re_verb: u8, + _in_re_message_id: u64, + _error_code: u8, + _payload: &PacketBuffer, + _cursor: &mut usize, + ) -> bool { + false + } + + async fn handle_ok( + &self, + _source: &Peer, + _source_path: &Path, + _in_re_verb: u8, + _in_re_message_id: u64, + _payload: &PacketBuffer, + _cursor: &mut usize, + ) -> bool { + false + } + + fn should_communicate_with(&self, _id: &Identity) -> bool { + true + } +} + +/// Dummy no-op path filter for debugging and testing. +#[derive(Default)] +pub struct DummyPathFilter; + +#[async_trait] +impl PathFilter for DummyPathFilter { + async fn check_path( + &self, + _id: &Identity, + _endpoint: &Endpoint, + _local_socket: Option<&::LocalSocket>, + _local_interface: Option<&::LocalInterface>, + ) -> bool { + true + } + + async fn get_path_hints( + &self, + _id: &Identity, + ) -> Option< + Vec<( + Endpoint, + Option<::LocalSocket>, + Option<::LocalInterface>, + )>, + > { + None + } +} diff --git a/service/src/datadir.rs b/service/src/datadir.rs index 9ab277903..a8e2a9cb6 100644 --- a/service/src/datadir.rs +++ b/service/src/datadir.rs @@ -7,10 +7,12 @@ use std::sync::Arc; use crate::localconfig::Config; use crate::utils::{read_limit, DEFAULT_FILE_IO_READ_LIMIT}; +use async_trait::async_trait; + use parking_lot::{Mutex, RwLock}; use zerotier_crypto::random::next_u32_secure; -use zerotier_network_hypervisor::vl1::Identity; +use zerotier_network_hypervisor::vl1::{Identity, Storage}; const AUTH_TOKEN_DEFAULT_LENGTH: usize = 48; const AUTH_TOKEN_POSSIBLE_CHARS: &'static str = "0123456789abcdefghijklmnopqrstuvwxyz"; @@ -26,6 +28,32 @@ pub struct DataDir { authtoken: Mutex, } +#[async_trait] +impl Storage for DataDir { + async fn load_node_identity(&self) -> Option { + let id_data = read_limit(self.base_path.join(IDENTITY_SECRET_FILENAME), 4096).await; + if id_data.is_err() { + return None; + } + let id_data = Identity::from_str(String::from_utf8_lossy(id_data.unwrap().as_slice()).as_ref()); + if id_data.is_err() { + return None; + } + Some(id_data.unwrap()) + } + + async fn save_node_identity(&self, id: &Identity) { + assert!(id.secret.is_some()); + let id_secret_str = id.to_secret_string(); + let id_public_str = id.to_string(); + let secret_path = self.base_path.join(IDENTITY_SECRET_FILENAME); + // TODO: handle errors + let _ = tokio::fs::write(&secret_path, id_secret_str.as_bytes()).await; + assert!(crate::utils::fs_restrict_permissions(&secret_path)); + let _ = tokio::fs::write(self.base_path.join(IDENTITY_PUBLIC_FILENAME), id_public_str.as_bytes()).await; + } +} + impl DataDir { pub async fn open>(path: P) -> std::io::Result { let base_path = path.as_ref().to_path_buf(); @@ -58,28 +86,6 @@ impl DataDir { return Ok(Self { base_path, config, authtoken: Mutex::new(String::new()) }); } - /// Load identity.secret from data directory. - pub async fn load_identity(&self) -> std::io::Result { - let id_data = Identity::from_str( - String::from_utf8_lossy(read_limit(self.base_path.join(IDENTITY_SECRET_FILENAME), 4096).await?.as_slice()).as_ref(), - ); - if id_data.is_err() { - return Err(std::io::Error::new(std::io::ErrorKind::InvalidData, id_data.err().unwrap())); - } - Ok(id_data.unwrap()) - } - - /// Save identity.secret and identity.public to data directory. - pub async fn save_identity(&self, id: &Identity) -> std::io::Result<()> { - assert!(id.secret.is_some()); - let id_secret_str = id.to_secret_string(); - let id_public_str = id.to_string(); - let secret_path = self.base_path.join(IDENTITY_SECRET_FILENAME); - tokio::fs::write(&secret_path, id_secret_str.as_bytes()).await?; - assert!(crate::utils::fs_restrict_permissions(&secret_path)); - tokio::fs::write(self.base_path.join(IDENTITY_PUBLIC_FILENAME), id_public_str.as_bytes()).await - } - /// Get authorization token for local API, creating and saving if it does not exist. pub async fn authtoken(&self) -> std::io::Result { let authtoken = self.authtoken.lock().clone(); diff --git a/service/src/main.rs b/service/src/main.rs index 85751cdd5..f4928ada5 100644 --- a/service/src/main.rs +++ b/service/src/main.rs @@ -10,12 +10,15 @@ pub mod utils; pub mod vnic; use std::io::Write; +use std::sync::Arc; use clap::error::{ContextKind, ContextValue}; use clap::{Arg, ArgMatches, Command}; -use zerotier_network_hypervisor::vl2::Switch; use zerotier_network_hypervisor::{VERSION_MAJOR, VERSION_MINOR, VERSION_REVISION}; +use zerotier_vl1_service::VL1Service; + +use crate::datadir::DataDir; pub fn print_help() { let h = crate::cmdline_help::make_cmdline_help(); @@ -39,6 +42,19 @@ pub struct Flags { pub auth_token_override: Option, } +async fn open_datadir(flags: &Flags) -> Arc { + let datadir = DataDir::open(flags.base_path.as_str()).await; + if datadir.is_ok() { + return Arc::new(datadir.unwrap()); + } + eprintln!( + "FATAL: unable to open data directory {}: {}", + flags.base_path, + datadir.err().unwrap().to_string() + ); + std::process::exit(exitcode::ERR_IOERR); +} + async fn async_main(flags: Flags, global_args: Box) -> i32 { #[allow(unused)] match global_args.subcommand() { @@ -59,8 +75,10 @@ async fn async_main(flags: Flags, global_args: Box) -> i32 { Some(("service", _)) => { drop(global_args); // free unnecessary heap before starting service as we're done with CLI args - /* - let svc = service::Service::new(tokio::runtime::Handle::current(), &flags.base_path, true).await; + 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).await; + let svc = VL1Service::new(datadir, test_inner, test_path_filter, zerotier_vl1_service::Settings::default()).await; if svc.is_ok() { let _ = tokio::signal::ctrl_c().await; println!("Terminate signal received, shutting down..."); @@ -69,8 +87,6 @@ async fn async_main(flags: Flags, global_args: Box) -> i32 { println!("FATAL: error launching service: {}", svc.err().unwrap().to_string()); exitcode::ERR_IOERR } - */ - todo!() } Some(("identity", cmd_args)) => todo!(), Some(("rootset", cmd_args)) => cli::rootset::cmd(flags, cmd_args).await, diff --git a/vl1-service/src/vl1service.rs b/vl1-service/src/vl1service.rs index 49cc779a9..17ca7d35e 100644 --- a/vl1-service/src/vl1service.rs +++ b/vl1-service/src/vl1service.rs @@ -24,7 +24,7 @@ use tokio::time::Duration; /// talks to the physical network, manages the vl1 node, and presents a templated interface for /// whatever inner protocol implementation is using it. This would typically be VL2 but could be /// a test harness or just the controller for a controller that runs stand-alone. -pub struct VL1Service, InnerProtocolImpl: InnerProtocol> { +pub struct VL1Service { state: tokio::sync::RwLock, storage: Arc, inner: Arc, @@ -38,7 +38,7 @@ struct VL1ServiceMutableState { settings: Settings, } -impl, InnerProtocolImpl: InnerProtocol> +impl VL1Service { pub async fn new( @@ -199,7 +199,7 @@ impl, InnerProtocolImpl: } #[async_trait] -impl, InnerProtocolImpl: InnerProtocol> HostSystem +impl HostSystem for VL1Service { type LocalSocket = crate::LocalSocket; @@ -297,7 +297,7 @@ impl, InnerProtocolImpl: } } -impl, InnerProtocolImpl: InnerProtocol> Drop +impl Drop for VL1Service { fn drop(&mut self) {