Bring service back to simple working state to end-to-end test.

This commit is contained in:
Adam Ierymenko 2022-09-16 15:27:24 -04:00
parent 0552d587a0
commit 2017dcf746
No known key found for this signature in database
GPG key ID: C8877CF2D7A5D7F3
9 changed files with 164 additions and 59 deletions

View file

@ -227,9 +227,23 @@ mod tests {
type TypeMap = HashMap<String, Type>;
use super::{Dictionary, BOOL_TRUTH};
use crate::util::testutil::randstring;
use std::collections::HashMap;
fn randstring(len: u8) -> String {
(0..len)
.map(|_| (rand::random::<u8>() % 26) + 'a' as u8)
.map(|c| {
if rand::random::<bool>() {
(c as char).to_ascii_uppercase()
} else {
c as char
}
})
.map(|c| c.to_string())
.collect::<Vec<String>>()
.join("")
}
fn make_dictionary() -> (Dictionary, TypeMap) {
let mut d = Dictionary::new();
let mut tm = TypeMap::new();

View file

@ -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 {

View file

@ -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::<u8>() % 26) + 'a' as u8)
.map(|c| {
if rand::random::<bool>() {
(c as char).to_ascii_uppercase()
} else {
c as char
}
})
.map(|c| c.to_string())
.collect::<Vec<String>>()
.join("")
}

View file

@ -459,6 +459,21 @@ mod tests {
},
};
fn randstring(len: u8) -> String {
(0..len)
.map(|_| (rand::random::<u8>() % 26) + 'a' as u8)
.map(|c| {
if rand::random::<bool>() {
(c as char).to_ascii_uppercase()
} else {
c as char
}
})
.map(|c| c.to_string())
.collect::<Vec<String>>()
.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 {

View file

@ -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};

View file

@ -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<HostSystemImpl: HostSystem>: 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<HostSystemImpl: HostSystem>(
&self,
id: &Identity,
endpoint: &Endpoint,
@ -108,7 +108,7 @@ pub trait PathFilter<HostSystemImpl: HostSystem>: 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<HostSystemImpl: HostSystem>(
&self,
id: &Identity,
) -> Option<
@ -849,3 +849,79 @@ impl<HostSystemImpl: HostSystem> Node<HostSystemImpl> {
}
}
}
/// Dummy no-op inner protocol for debugging and testing.
#[derive(Default)]
pub struct DummyInnerProtocol;
#[async_trait]
impl InnerProtocol for DummyInnerProtocol {
async fn handle_packet<HostSystemImpl: HostSystem>(
&self,
_source: &Peer<HostSystemImpl>,
_source_path: &Path<HostSystemImpl>,
_verb: u8,
_payload: &PacketBuffer,
) -> bool {
false
}
async fn handle_error<HostSystemImpl: HostSystem>(
&self,
_source: &Peer<HostSystemImpl>,
_source_path: &Path<HostSystemImpl>,
_in_re_verb: u8,
_in_re_message_id: u64,
_error_code: u8,
_payload: &PacketBuffer,
_cursor: &mut usize,
) -> bool {
false
}
async fn handle_ok<HostSystemImpl: HostSystem>(
&self,
_source: &Peer<HostSystemImpl>,
_source_path: &Path<HostSystemImpl>,
_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<HostSystemImpl: HostSystem>(
&self,
_id: &Identity,
_endpoint: &Endpoint,
_local_socket: Option<&<HostSystemImpl as HostSystem>::LocalSocket>,
_local_interface: Option<&<HostSystemImpl as HostSystem>::LocalInterface>,
) -> bool {
true
}
async fn get_path_hints<HostSystemImpl: HostSystem>(
&self,
_id: &Identity,
) -> Option<
Vec<(
Endpoint,
Option<<HostSystemImpl as HostSystem>::LocalSocket>,
Option<<HostSystemImpl as HostSystem>::LocalInterface>,
)>,
> {
None
}
}

View file

@ -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<String>,
}
#[async_trait]
impl Storage for DataDir {
async fn load_node_identity(&self) -> Option<Identity> {
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<P: AsRef<Path>>(path: P) -> std::io::Result<Self> {
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<Identity> {
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<String> {
let authtoken = self.authtoken.lock().clone();

View file

@ -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<String>,
}
async fn open_datadir(flags: &Flags) -> Arc<DataDir> {
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<ArgMatches>) -> i32 {
#[allow(unused)]
match global_args.subcommand() {
@ -59,8 +75,10 @@ async fn async_main(flags: Flags, global_args: Box<ArgMatches>) -> 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<ArgMatches>) -> 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,

View file

@ -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<StorageImpl: Storage, PathFilterImpl: PathFilter<Self>, InnerProtocolImpl: InnerProtocol> {
pub struct VL1Service<StorageImpl: Storage, PathFilterImpl: PathFilter, InnerProtocolImpl: InnerProtocol> {
state: tokio::sync::RwLock<VL1ServiceMutableState>,
storage: Arc<StorageImpl>,
inner: Arc<InnerProtocolImpl>,
@ -38,7 +38,7 @@ struct VL1ServiceMutableState {
settings: Settings,
}
impl<StorageImpl: Storage, PathFilterImpl: PathFilter<Self>, InnerProtocolImpl: InnerProtocol>
impl<StorageImpl: Storage, PathFilterImpl: PathFilter, InnerProtocolImpl: InnerProtocol>
VL1Service<StorageImpl, PathFilterImpl, InnerProtocolImpl>
{
pub async fn new(
@ -199,7 +199,7 @@ impl<StorageImpl: Storage, PathFilterImpl: PathFilter<Self>, InnerProtocolImpl:
}
#[async_trait]
impl<StorageImpl: Storage, PathFilterImpl: PathFilter<Self>, InnerProtocolImpl: InnerProtocol> HostSystem
impl<StorageImpl: Storage, PathFilterImpl: PathFilter, InnerProtocolImpl: InnerProtocol> HostSystem
for VL1Service<StorageImpl, PathFilterImpl, InnerProtocolImpl>
{
type LocalSocket = crate::LocalSocket;
@ -297,7 +297,7 @@ impl<StorageImpl: Storage, PathFilterImpl: PathFilter<Self>, InnerProtocolImpl:
}
}
impl<StorageImpl: Storage, PathFilterImpl: PathFilter<Self>, InnerProtocolImpl: InnerProtocol> Drop
impl<StorageImpl: Storage, PathFilterImpl: PathFilter, InnerProtocolImpl: InnerProtocol> Drop
for VL1Service<StorageImpl, PathFilterImpl, InnerProtocolImpl>
{
fn drop(&mut self) {