From ed309b9f18e3b7abd5409312abdcf4559f055b38 Mon Sep 17 00:00:00 2001 From: Adam Ierymenko Date: Fri, 24 Mar 2023 22:45:51 -0400 Subject: [PATCH] Build fixes, add more parsing and serialization to Identity. --- {controller/src => attic}/postgresdatabase.rs | 20 +- controller/src/controller.rs | 11 +- controller/src/database.rs | 3 +- controller/src/filedatabase.rs | 34 +-- controller/src/lib.rs | 1 - controller/src/model/member.rs | 3 +- network-hypervisor/src/vl1/identity.rs | 258 ++++++++++++------ network-hypervisor/src/vl1/node.rs | 15 +- network-hypervisor/src/vl1/peer.rs | 10 +- network-hypervisor/src/vl1/rootset.rs | 6 +- network-hypervisor/src/vl1/whois.rs | 2 +- network-hypervisor/src/vl2/v1/revocation.rs | 8 +- service/src/utils.rs | 2 +- utils/src/base24.rs | 2 +- utils/src/base64.rs | 115 ++++++++ utils/src/lib.rs | 1 + vl1-service/src/datadir.rs | 38 --- vl1-service/src/vl1service.rs | 48 +--- 18 files changed, 333 insertions(+), 244 deletions(-) rename {controller/src => attic}/postgresdatabase.rs (96%) create mode 100644 utils/src/base64.rs diff --git a/controller/src/postgresdatabase.rs b/attic/postgresdatabase.rs similarity index 96% rename from controller/src/postgresdatabase.rs rename to attic/postgresdatabase.rs index 8c30a9315..1437d15eb 100644 --- a/controller/src/postgresdatabase.rs +++ b/attic/postgresdatabase.rs @@ -10,9 +10,8 @@ use tokio_postgres::types::Type; use tokio_postgres::{Client, Statement}; use zerotier_crypto::secure_eq; -use zerotier_crypto::typestate::Valid; -use zerotier_network_hypervisor::vl1::{Address, Identity, InetAddress}; +use zerotier_network_hypervisor::vl1::{Address, InetAddress}; use zerotier_network_hypervisor::vl2::rule::Rule; use zerotier_network_hypervisor::vl2::{IpRoute, NetworkId}; @@ -21,7 +20,6 @@ use zerotier_utils::tokio; use zerotier_utils::tokio::runtime::Handle; use zerotier_utils::tokio::sync::broadcast::{channel, Receiver, Sender}; use zerotier_utils::tokio::task::JoinHandle; -use zerotier_vl1_service::VL1DataStorage; use crate::database::*; use crate::model::{IpAssignmentPool, Member, Network, RequestLogItem}; @@ -130,15 +128,14 @@ impl<'a> Drop for ConnectionHolder<'a> { } pub struct PostgresDatabase { - local_controller_id_str: String, - local_identity: Valid, + local_controller: Address, connections: Mutex<(Vec>, Sender<()>)>, postgres_path: String, runtime: Handle, } impl PostgresDatabase { - pub async fn new(runtime: Handle, postgres_path: String, num_connections: usize, local_identity: Valid) -> Result, Error> { + pub async fn new(runtime: Handle, postgres_path: String, num_connections: usize) -> Result, Error> { assert!(num_connections > 0); let (sender, _) = channel(4096); let mut connections = Vec::with_capacity(num_connections); @@ -147,7 +144,6 @@ impl PostgresDatabase { } Ok(Arc::new(Self { local_controller_id_str: local_identity.address.to_string(), - local_identity, connections: Mutex::new((connections, sender)), postgres_path, runtime, @@ -177,16 +173,6 @@ impl PostgresDatabase { } } -impl VL1DataStorage for PostgresDatabase { - fn load_node_identity(&self) -> Option> { - Some(self.local_identity.clone()) - } - - fn save_node_identity(&self, _id: &Valid) -> bool { - panic!("local identity saving not supported by PostgresDatabase") - } -} - #[async_trait] impl Database for PostgresDatabase { async fn list_networks(&self) -> Result, Error> { diff --git a/controller/src/controller.rs b/controller/src/controller.rs index 467904f89..82c03da0d 100644 --- a/controller/src/controller.rs +++ b/controller/src/controller.rs @@ -9,6 +9,7 @@ use tokio::time::{Duration, Instant}; use zerotier_crypto::secure_eq; use zerotier_network_hypervisor::protocol; use zerotier_network_hypervisor::protocol::{PacketBuffer, DEFAULT_MULTICAST_LIMIT, ZEROTIER_VIRTUAL_NETWORK_DEFAULT_MTU}; +use zerotier_network_hypervisor::vl1::identity::{Identity, IdentitySecret}; use zerotier_network_hypervisor::vl1::*; use zerotier_network_hypervisor::vl2; use zerotier_network_hypervisor::vl2::multicastauthority::MulticastAuthority; @@ -37,7 +38,6 @@ pub struct Controller { reaper: Reaper, runtime: tokio::runtime::Handle, database: Arc, - local_identity: Valid, /// Handler for MULTICAST_LIKE and MULTICAST_GATHER messages. multicast_authority: MulticastAuthority, @@ -47,7 +47,7 @@ pub struct Controller { /// Recently authorized network members and when that authorization expires (in monotonic ticks). /// Note that this is not and should not be used for real authentication, just for locking up multicast info. - recently_authorized: RwLock>>, + recently_authorized: RwLock>>, } impl Controller { @@ -63,7 +63,6 @@ impl Controller { reaper: Reaper::new(&runtime), runtime, database: database.clone(), - local_identity: local_identity, multicast_authority: MulticastAuthority::new(), daemons: Mutex::new(Vec::with_capacity(2)), recently_authorized: RwLock::new(HashMap::new()), @@ -232,7 +231,7 @@ impl Controller { if member.node_id != *m { if let Some(peer) = self.service.read().unwrap().upgrade().and_then(|s| s.node().peer(*m)) { revocations.clear(); - Revocation::new(member.network_id, time_clock, member.node_id, *m, &self.local_identity, false).map(|r| revocations.push(r)); + Revocation::new(&member.network_id, time_clock, &member.node_id, m, &self.local_identity, false).map(|r| revocations.push(r)); self.send_revocations(&peer, &mut revocations); } } @@ -427,7 +426,7 @@ impl Controller { // the overhead (bandwidth and CPU) of generating these. if let Some(com) = - vl2::v1::CertificateOfMembership::new(&self.local_identity, network_id, &source_identity, time_clock, credential_ttl) + vl2::v1::CertificateOfMembership::new(&self.local_identity, &network_id, &source_identity, time_clock, credential_ttl) { let mut v1cred = V1Credentials { revision: time_clock as u64, @@ -438,7 +437,7 @@ impl Controller { }; if !nc.static_ips.is_empty() { - let mut coo = vl2::v1::CertificateOfOwnership::new(network_id, time_clock, source_identity.address); + let mut coo = vl2::v1::CertificateOfOwnership::new(&network_id, time_clock, &source_identity.address); for ip in nc.static_ips.iter() { coo.add_ip(ip); } diff --git a/controller/src/database.rs b/controller/src/database.rs index 44c873c62..f47937d1a 100644 --- a/controller/src/database.rs +++ b/controller/src/database.rs @@ -4,7 +4,6 @@ use zerotier_crypto::secure_eq; use zerotier_network_hypervisor::vl1::{Address, InetAddress}; use zerotier_network_hypervisor::vl2::NetworkId; use zerotier_utils::tokio::sync::broadcast::Receiver; -use zerotier_vl1_service::VL1DataStorage; use crate::model::*; @@ -22,7 +21,7 @@ pub enum Change { } #[async_trait] -pub trait Database: Sync + Send + VL1DataStorage + 'static { +pub trait Database: Sync + Send + 'static { async fn list_networks(&self) -> Result, Error>; async fn get_network(&self, id: NetworkId) -> Result, Error>; async fn save_network(&self, obj: Network, generate_change_notification: bool) -> Result<(), Error>; diff --git a/controller/src/filedatabase.rs b/controller/src/filedatabase.rs index f4b5aeecc..d168a14f7 100644 --- a/controller/src/filedatabase.rs +++ b/controller/src/filedatabase.rs @@ -5,7 +5,7 @@ use async_trait::async_trait; use notify::{RecursiveMode, Watcher}; use serde::de::DeserializeOwned; -use zerotier_network_hypervisor::vl1::{Address, Identity, Valid}; +use zerotier_network_hypervisor::vl1::Address; use zerotier_network_hypervisor::vl2::NetworkId; use zerotier_utils::reaper::Reaper; use zerotier_utils::tokio::fs; @@ -13,8 +13,6 @@ use zerotier_utils::tokio::runtime::Handle; use zerotier_utils::tokio::sync::broadcast::{channel, Receiver, Sender}; use zerotier_utils::tokio::task::JoinHandle; use zerotier_utils::tokio::time::{sleep, Duration, Instant}; -use zerotier_vl1_service::datadir::{load_node_identity, save_node_identity}; -use zerotier_vl1_service::VL1DataStorage; use crate::cache::Cache; use crate::database::{Change, Database, Error}; @@ -32,7 +30,7 @@ const EVENT_HANDLER_TASK_TIMEOUT: Duration = Duration::from_secs(10); /// is different from V1 so it'll need a converter to use with V1 FileDb controller data. pub struct FileDatabase { base_path: PathBuf, - local_identity: Valid, + controller_address: Address, change_sender: Sender, tasks: Reaper, cache: Cache, @@ -42,7 +40,7 @@ pub struct FileDatabase { // TODO: should cache at least hashes and detect changes in the filesystem live. impl FileDatabase { - pub async fn new>(runtime: Handle, base_path: P) -> Result, Error> { + pub async fn new>(runtime: Handle, base_path: P, controller_address: Address) -> Result, Error> { let base_path: PathBuf = base_path.as_ref().into(); let (change_sender, _) = channel(256); @@ -50,13 +48,9 @@ impl FileDatabase { let db_weak = db_weak_tmp.clone(); let runtime2 = runtime.clone(); - let local_identity = - load_node_identity(base_path.as_path()).ok_or(std::io::Error::new(std::io::ErrorKind::NotFound, "identity.secret not found"))?; - let controller_address = local_identity.address; - let db = Arc::new(Self { base_path: base_path.clone(), - local_identity, + controller_address: controller_address.clone(), change_sender, tasks: Reaper::new(&runtime2), cache: Cache::new(), @@ -246,16 +240,6 @@ impl Drop for FileDatabase { } } -impl VL1DataStorage for FileDatabase { - fn load_node_identity(&self) -> Option> { - load_node_identity(self.base_path.as_path()) - } - - fn save_node_identity(&self, id: &Valid) -> bool { - save_node_identity(self.base_path.as_path(), id) - } -} - #[async_trait] impl Database for FileDatabase { async fn list_networks(&self) -> Result, Error> { @@ -362,6 +346,7 @@ mod tests { #[allow(unused_imports)] use super::*; use std::sync::atomic::{AtomicUsize, Ordering}; + use zerotier_network_hypervisor::vl1::identity::Identity; #[allow(unused)] #[test] @@ -375,11 +360,14 @@ mod tests { println!("test filedatabase is in: {}", test_dir.as_os_str().to_str().unwrap()); let _ = std::fs::remove_dir_all(&test_dir); - let controller_id = Identity::generate(); + let controller_id = Identity::generate(false); assert!(fs::create_dir_all(&test_dir).await.is_ok()); - assert!(save_node_identity(test_dir.as_path(), &controller_id)); - let db = Arc::new(FileDatabase::new(tokio_runtime.handle().clone(), test_dir).await.expect("new db")); + let db = Arc::new( + FileDatabase::new(tokio_runtime.handle().clone(), test_dir, controller_id.public.address.clone()) + .await + .expect("new db"), + ); let change_count = Arc::new(AtomicUsize::new(0)); diff --git a/controller/src/lib.rs b/controller/src/lib.rs index 389d99ee7..0d663136d 100644 --- a/controller/src/lib.rs +++ b/controller/src/lib.rs @@ -7,6 +7,5 @@ pub(crate) mod cache; pub mod database; pub mod filedatabase; pub mod model; -pub mod postgresdatabase; pub use controller::*; diff --git a/controller/src/model/member.rs b/controller/src/model/member.rs index 940566400..d6569dd08 100644 --- a/controller/src/model/member.rs +++ b/controller/src/model/member.rs @@ -5,7 +5,8 @@ use std::hash::Hash; use serde::{Deserialize, Serialize}; -use zerotier_network_hypervisor::vl1::{Address, Identity, InetAddress}; +use zerotier_network_hypervisor::vl1::identity::Identity; +use zerotier_network_hypervisor::vl1::{Address, InetAddress}; use zerotier_network_hypervisor::vl2::NetworkId; use zerotier_utils::blob::Blob; diff --git a/network-hypervisor/src/vl1/identity.rs b/network-hypervisor/src/vl1/identity.rs index acebdcdaf..14c639f83 100644 --- a/network-hypervisor/src/vl1/identity.rs +++ b/network-hypervisor/src/vl1/identity.rs @@ -16,7 +16,7 @@ use zerotier_crypto::secret::Secret; use zerotier_crypto::typestate::Valid; use zerotier_crypto::x25519::*; use zerotier_utils::arrayvec::ArrayVec; -use zerotier_utils::base24; +use zerotier_utils::base64; use zerotier_utils::buffer::{Buffer, OutOfBoundsError}; use zerotier_utils::error::InvalidFormatError; use zerotier_utils::marshalable::{Marshalable, UnmarshalError}; @@ -123,9 +123,7 @@ impl Identity { } // Bits 40-384 of the address are filled from a SHA384 hash of all keys for a full length V2 address. - let mut address = secret.public.address.clone(); - secret.public.populate_extended_address_bits(&mut address); - secret.public.address = address; + secret.public.address = secret.public.populate_extended_address_bits(); // For V2 identities we include two self signatures to ensure that all these different key pairs // are properly bound together and can't be changed independently. @@ -148,7 +146,7 @@ impl Identity { // First, check that the full SHA384 (bits 40-384) in the address is correct. Check first since this is fast. let mut test_address = Address::new_uninitialized(); test_address.0[..PartialAddress::LEGACY_SIZE_BYTES].copy_from_slice(&self.address.0[..PartialAddress::LEGACY_SIZE_BYTES]); - self.populate_extended_address_bits(&mut test_address); + test_address = self.populate_extended_address_bits(); if self.address != test_address { return None; } @@ -190,8 +188,8 @@ impl Identity { } } - /// Populate bits 40-384 of the address with a hash of everything else. - fn populate_extended_address_bits(&self, address: &mut Address) { + /// Populate bits 40-384 of the address with a hash of everything else and return the completed address. + fn populate_extended_address_bits(&self) -> Address { let mut sha = SHA384::new(); sha.update(&self.address.0[..PartialAddress::LEGACY_SIZE_BYTES]); // include short address in full hash sha.update(&[Self::ALGORITHM_X25519 @@ -207,7 +205,9 @@ impl Identity { sha.update(p384.ecdsa.as_bytes()); } let sha = sha.finish(); + let mut address = self.address.clone(); address.0[PartialAddress::LEGACY_SIZE_BYTES..].copy_from_slice(&sha[..Address::SIZE_BYTES - PartialAddress::LEGACY_SIZE_BYTES]); + address } /// Encode for self-signing, used only with p384 keys enabled and panics otherwise. @@ -224,7 +224,6 @@ impl Identity { /// Decode a byte serialized identity. pub fn from_bytes(b: &[u8]) -> Result { let mut id; - let mut address = Address::new_uninitialized(); if b.len() == packed::V2_PUBLIC_SIZE && b[PartialAddress::LEGACY_SIZE_BYTES] == (Self::ALGORITHM_X25519 | Self::ALGORITHM_P384) { let p: &packed::V2Public = memory::cast_to_struct(b); id = Self { @@ -237,7 +236,7 @@ impl Identity { p384_self_signature: p.p384_self_signature, }), }; - address.0[..PartialAddress::LEGACY_SIZE_BYTES].copy_from_slice(&p.short_address); + id.address.0[..PartialAddress::LEGACY_SIZE_BYTES].copy_from_slice(&p.short_address); } else if b.len() == packed::V1_PUBLIC_SIZE && b[PartialAddress::LEGACY_SIZE_BYTES] == Self::ALGORITHM_X25519 { let p: &packed::V1Public = memory::cast_to_struct(b); id = Self { @@ -245,12 +244,11 @@ impl Identity { x25519: X25519 { ecdh: p.c25519, eddsa: p.ed25519 }, p384: None, }; - address.0[..PartialAddress::LEGACY_SIZE_BYTES].copy_from_slice(&p.short_address); + id.address.0[..PartialAddress::LEGACY_SIZE_BYTES].copy_from_slice(&p.short_address); } else { return Err(InvalidFormatError); } - id.populate_extended_address_bits(&mut address); - id.address = address; + id.address = id.populate_extended_address_bits(); return Ok(id); } @@ -283,23 +281,23 @@ impl ToString for Identity { fn to_string(&self) -> String { if let Some(p384) = self.p384.as_ref() { let mut s = String::with_capacity(1024); - base24::encode_into(self.address.as_bytes(), &mut s); + s.push_str(self.address.to_string().as_str()); s.push_str(":1:"); - base24::encode_into(&self.x25519.ecdh, &mut s); + base64::encode_into(&self.x25519.ecdh, &mut s); s.push(':'); - base24::encode_into(&self.x25519.eddsa, &mut s); + base64::encode_into(&self.x25519.eddsa, &mut s); s.push(':'); - base24::encode_into(p384.ecdh.as_bytes(), &mut s); + base64::encode_into(p384.ecdh.as_bytes(), &mut s); s.push(':'); - base24::encode_into(p384.ecdsa.as_bytes(), &mut s); + base64::encode_into(p384.ecdsa.as_bytes(), &mut s); s.push(':'); - base24::encode_into(&p384.ed25519_self_signature, &mut s); + base64::encode_into(&p384.ed25519_self_signature, &mut s); s.push(':'); - base24::encode_into(&p384.p384_self_signature, &mut s); + base64::encode_into(&p384.p384_self_signature, &mut s); s } else { format!( - "{}:0:{}:{}", + "{}:0:{}{}", hex::to_string(self.address.legacy_bytes()), hex::to_string(&self.x25519.ecdh), hex::to_string(&self.x25519.eddsa) @@ -314,54 +312,56 @@ impl FromStr for Identity { fn from_str(s: &str) -> Result { let ss: Vec<&str> = s.split(':').collect(); if ss.len() >= 2 { - if ss[1] == "1" && ss.len() == 8 { + if ss[1] == "1" && ss.len() >= 8 { return Ok(Self { address: Address::from_str(ss[0]).map_err(|_| InvalidFormatError)?, x25519: X25519 { - ecdh: base24::decode(ss[2].as_bytes()) + ecdh: base64::decode(ss[2].as_bytes()) .map_err(|_| InvalidFormatError)? .try_into() .map_err(|_| InvalidFormatError)?, - eddsa: base24::decode(ss[3].as_bytes()) + eddsa: base64::decode(ss[3].as_bytes()) .map_err(|_| InvalidFormatError)? .try_into() .map_err(|_| InvalidFormatError)?, }, p384: Some(P384 { - ecdh: P384PublicKey::from_bytes(base24::decode(ss[4].as_bytes()).map_err(|_| InvalidFormatError)?.as_slice()) + ecdh: P384PublicKey::from_bytes(base64::decode(ss[4].as_bytes()).map_err(|_| InvalidFormatError)?.as_slice()) .ok_or(InvalidFormatError)?, - ecdsa: P384PublicKey::from_bytes(base24::decode(ss[5].as_bytes()).map_err(|_| InvalidFormatError)?.as_slice()) + ecdsa: P384PublicKey::from_bytes(base64::decode(ss[5].as_bytes()).map_err(|_| InvalidFormatError)?.as_slice()) .ok_or(InvalidFormatError)?, - ed25519_self_signature: base24::decode(ss[6].as_bytes()) + ed25519_self_signature: base64::decode(ss[6].as_bytes()) .map_err(|_| InvalidFormatError)? .try_into() .map_err(|_| InvalidFormatError)?, - p384_self_signature: base24::decode(ss[7].as_bytes()) + p384_self_signature: base64::decode(ss[7].as_bytes()) .map_err(|_| InvalidFormatError)? .try_into() .map_err(|_| InvalidFormatError)?, }), }); - } else if ss[1] == "0" && ss.len() == 4 { - let mut address = { - let legacy_address = hex::from_string(ss[0]); - if legacy_address.len() != PartialAddress::LEGACY_SIZE_BYTES { - return Err(InvalidFormatError); - } - let mut tmp = [0u8; Address::SIZE_BYTES]; - tmp[..PartialAddress::LEGACY_SIZE_BYTES].copy_from_slice(legacy_address.as_slice()); - Address(tmp) - }; + } else if ss[1] == "0" && ss.len() >= 3 { + let ecdh_eddsa = hex::from_string(ss[2]); + if ecdh_eddsa.len() != C25519_PUBLIC_KEY_SIZE + ED25519_PUBLIC_KEY_SIZE { + return Err(InvalidFormatError); + } let mut a = Self { - address: Address::new_uninitialized(), + address: { + let legacy_address = hex::from_string(ss[0]); + if legacy_address.len() != PartialAddress::LEGACY_SIZE_BYTES { + return Err(InvalidFormatError); + } + let mut tmp = [0u8; Address::SIZE_BYTES]; + tmp[..PartialAddress::LEGACY_SIZE_BYTES].copy_from_slice(legacy_address.as_slice()); + Address(tmp) + }, x25519: X25519 { - ecdh: hex::from_string(ss[2]).try_into().map_err(|_| InvalidFormatError)?, - eddsa: hex::from_string(ss[3]).try_into().map_err(|_| InvalidFormatError)?, + ecdh: ecdh_eddsa[..C25519_PUBLIC_KEY_SIZE].try_into().unwrap(), + eddsa: ecdh_eddsa[C25519_PUBLIC_KEY_SIZE..].try_into().unwrap(), }, p384: None, }; - a.populate_extended_address_bits(&mut address); - a.address = address; + a.address = a.populate_extended_address_bits(); return Ok(a); } } @@ -401,53 +401,6 @@ impl Marshalable for Identity { } } -impl IdentitySecret { - pub fn sign(&self, data: &[u8]) -> ArrayVec { - let mut s = ArrayVec::new(); - if let Some(p384) = self.p384.as_ref() { - s.push_slice(&p384.sign(data)); - } else { - s.push_slice(&self.x25519.sign(data)); - } - s - } -} - -impl X25519Secret { - #[inline] - pub fn agree(&self, public: &Identity) -> Option> { - Some(Secret(SHA512::hash(self.ecdh.agree(&public.x25519.ecdh).as_bytes()))) - } - - /// Sign with Ed25519 using the legacy signature format used by ZeroTier V1. - /// This just means the last 32 bytes of a 96-byte signature are the first 32 bytes of the - /// SHA512 hash. This isn't used even in V1 but was once used long ago to rapidly check - /// signatures as part of a different design. Some nodes still expect it to be there though. - #[inline(always)] - pub fn sign(&self, data: &[u8]) -> [u8; 96] { - self.eddsa.sign_zt(data) - } -} - -impl P384Secret { - #[inline(always)] - pub fn sign(&self, data: &[u8]) -> [u8; P384_ECDSA_SIGNATURE_SIZE] { - self.ecdsa.sign(data) - } -} - -impl Eq for P384 {} - -impl PartialEq for P384 { - #[inline] - fn eq(&self, other: &Self) -> bool { - self.ecdh.as_bytes() == other.ecdh.as_bytes() - && self.ecdsa.as_bytes() == other.ecdsa.as_bytes() - && self.ed25519_self_signature == other.ed25519_self_signature - && self.p384_self_signature == other.p384_self_signature - } -} - impl Serialize for Identity { #[inline] fn serialize(&self, serializer: S) -> Result @@ -505,6 +458,127 @@ impl<'de> Deserialize<'de> for Identity { } } +impl Eq for P384 {} + +impl PartialEq for P384 { + #[inline] + fn eq(&self, other: &Self) -> bool { + self.ecdh.as_bytes() == other.ecdh.as_bytes() + && self.ecdsa.as_bytes() == other.ecdsa.as_bytes() + && self.ed25519_self_signature == other.ed25519_self_signature + && self.p384_self_signature == other.p384_self_signature + } +} + +impl IdentitySecret { + pub fn sign(&self, data: &[u8]) -> ArrayVec { + let mut s = ArrayVec::new(); + if let Some(p384) = self.p384.as_ref() { + s.push_slice(&p384.sign(data)); + } else { + s.push_slice(&self.x25519.sign(data)); + } + s + } +} + +impl ToString for IdentitySecret { + fn to_string(&self) -> String { + let mut s = self.public.to_string(); + if let Some(p384) = self.p384.as_ref() { + s.push(':'); + base64::encode_into(self.x25519.ecdh.secret_bytes().as_bytes(), &mut s); + s.push(':'); + base64::encode_into(self.x25519.eddsa.secret_bytes().as_bytes(), &mut s); + s.push(':'); + base64::encode_into(p384.ecdh.secret_key_bytes().as_bytes(), &mut s); + s.push(':'); + base64::encode_into(p384.ecdsa.secret_key_bytes().as_bytes(), &mut s); + } else { + s.push(':'); + s.push_str(hex::to_string(self.x25519.ecdh.secret_bytes().as_bytes()).as_str()); + s.push_str(hex::to_string(self.x25519.eddsa.secret_bytes().as_bytes()).as_str()); + } + s + } +} + +impl FromStr for IdentitySecret { + type Err = InvalidFormatError; + + fn from_str(s: &str) -> Result { + let public = Identity::from_str(s)?; + let ss: Vec<&str> = s.split(':').collect(); + if ss.len() >= 2 { + if ss[1] == "1" && ss.len() >= 12 && public.p384.is_some() { + let x25519_ecdh = X25519KeyPair::from_bytes( + &public.x25519.ecdh, + base64::decode(ss[8].as_bytes()).map_err(|_| InvalidFormatError)?.as_slice(), + ) + .ok_or(InvalidFormatError)?; + let x25519_eddsa = Ed25519KeyPair::from_bytes( + &public.x25519.ecdh, + base64::decode(ss[9].as_bytes()).map_err(|_| InvalidFormatError)?.as_slice(), + ) + .ok_or(InvalidFormatError)?; + let p384_ecdh = P384KeyPair::from_bytes( + public.p384.as_ref().unwrap().ecdh.as_bytes(), + base64::decode(ss[10].as_bytes()).map_err(|_| InvalidFormatError)?.as_slice(), + ) + .ok_or(InvalidFormatError)?; + let p384_ecdsa = P384KeyPair::from_bytes( + public.p384.as_ref().unwrap().ecdh.as_bytes(), + base64::decode(ss[11].as_bytes()).map_err(|_| InvalidFormatError)?.as_slice(), + ) + .ok_or(InvalidFormatError)?; + return Ok(Self { + public: public.validate().ok_or(InvalidFormatError)?, + x25519: X25519Secret { ecdh: x25519_ecdh, eddsa: x25519_eddsa }, + p384: Some(P384Secret { ecdh: p384_ecdh, ecdsa: p384_ecdsa }), + }); + } else if ss[1] == "0" && ss.len() >= 4 && public.p384.is_none() { + let ecdh_eddsa_secrets = hex::from_string(ss[3]); + if ecdh_eddsa_secrets.len() != C25519_SECRET_KEY_SIZE + ED25519_SECRET_KEY_SIZE { + return Err(InvalidFormatError); + } + let x25519_ecdh = + X25519KeyPair::from_bytes(&public.x25519.ecdh, &ecdh_eddsa_secrets[..C25519_SECRET_KEY_SIZE]).ok_or(InvalidFormatError)?; + let x25519_eddsa = + Ed25519KeyPair::from_bytes(&public.x25519.ecdh, &ecdh_eddsa_secrets[C25519_SECRET_KEY_SIZE..]).ok_or(InvalidFormatError)?; + return Ok(Self { + public: public.validate().ok_or(InvalidFormatError)?, + x25519: X25519Secret { ecdh: x25519_ecdh, eddsa: x25519_eddsa }, + p384: None, + }); + } + } + return Err(InvalidFormatError); + } +} + +impl X25519Secret { + #[inline] + pub fn agree(&self, public: &Identity) -> Option> { + Some(Secret(SHA512::hash(self.ecdh.agree(&public.x25519.ecdh).as_bytes()))) + } + + /// Sign with Ed25519 using the legacy signature format used by ZeroTier V1. + /// This just means the last 32 bytes of a 96-byte signature are the first 32 bytes of the + /// SHA512 hash. This isn't used even in V1 but was once used long ago to rapidly check + /// signatures as part of a different design. Some nodes still expect it to be there though. + #[inline(always)] + pub fn sign(&self, data: &[u8]) -> [u8; 96] { + self.eddsa.sign_zt(data) + } +} + +impl P384Secret { + #[inline(always)] + pub fn sign(&self, data: &[u8]) -> [u8; P384_ECDSA_SIGNATURE_SIZE] { + self.ecdsa.sign(data) + } +} + /// The actual serialization format for secret identities. #[derive(Serialize, Deserialize)] struct IdentitySecretForSerialization<'a> { @@ -572,7 +646,7 @@ impl<'de> Deserialize<'de> for IdentitySecret { let e2 = || D::Error::custom("invalid key"); let e = |_e: TryFromSliceError| e2(); Ok(IdentitySecret { - public: Valid::mark_valid(Identity { + public: Identity { address: Address::from_bytes(tmp.address).map_err(|_| e2())?, x25519: X25519 { ecdh: x25519_ecdh_public.try_into().map_err(e)?, @@ -588,7 +662,9 @@ impl<'de> Deserialize<'de> for IdentitySecret { } else { None }, - }), + } + .validate() + .ok_or_else(e2)?, x25519: X25519Secret { ecdh: X25519KeyPair::from_bytes(x25519_ecdh_public, x25519_ecdh_secret).ok_or_else(e2)?, eddsa: Ed25519KeyPair::from_bytes(x25519_eddsa_public, x25519_eddsa_secret).ok_or_else(e2)?, diff --git a/network-hypervisor/src/vl1/node.rs b/network-hypervisor/src/vl1/node.rs index 2c5c75da5..5459718af 100644 --- a/network-hypervisor/src/vl1/node.rs +++ b/network-hypervisor/src/vl1/node.rs @@ -218,7 +218,7 @@ struct BackgroundTaskIntervals { } pub struct Node { - pub(super) identity_secret: IdentitySecret, + pub identity: IdentitySecret, intervals: Mutex, paths: RwLock, Arc>>>, pub(super) peers: PeerMap, @@ -229,7 +229,7 @@ pub struct Node { impl Node { pub fn new(identity_secret: IdentitySecret) -> Self { Self { - identity_secret, + identity: identity_secret, intervals: Mutex::new(BackgroundTaskIntervals::default()), paths: RwLock::new(HashMap::new()), peers: PeerMap::new(), @@ -244,11 +244,6 @@ impl Node { } } - #[inline(always)] - pub fn identity(&self) -> &Valid { - &self.identity_secret.public - } - #[inline(always)] pub fn peer(&self, a: &Address) -> Option>> { self.peers.get_exact(a) @@ -353,7 +348,7 @@ impl Node { for (_, rs) in roots.sets.iter() { for m in rs.members.iter() { - if m.identity.eq(&self.identity_secret.public) { + if m.identity.eq(&self.identity.public) { let _ = my_root_sets .get_or_insert_with(|| Vec::new()) .write_all(rs.to_buffer::<{ RootSet::MAX_MARSHAL_SIZE }>().unwrap().as_bytes()); @@ -367,7 +362,7 @@ impl Node { if let Some(peer) = self.peers.get_exact(&m.identity.address) { new_roots.insert(peer.clone(), m.endpoints.as_ref().unwrap().iter().cloned().collect()); } else { - if let Some(peer) = Peer::new(&self.identity_secret, Valid::mark_valid(m.identity.clone()), time_ticks) { + if let Some(peer) = Peer::new(&self.identity, Valid::mark_valid(m.identity.clone()), time_ticks) { new_roots.insert(self.peers.add(Arc::new(peer)).0, m.endpoints.as_ref().unwrap().iter().cloned().collect()); } else { bad_identities.push(m.identity.clone()); @@ -584,7 +579,7 @@ impl Node { if let Ok(dest) = PartialAddress::from_legacy_address_bytes(&fragment_header.dest) { // Packet is addressed to this node. - if dest.matches(&self.identity_secret.public.address) { + if dest.matches(&self.identity.public.address) { let fragment_header = &*fragment_header; // discard mut let path = self.canonical_path(source_endpoint, source_local_socket, source_local_interface, time_ticks); path.log_receive_anything(time_ticks); diff --git a/network-hypervisor/src/vl1/peer.rs b/network-hypervisor/src/vl1/peer.rs index 4a1242afa..fc8f3bb4b 100644 --- a/network-hypervisor/src/vl1/peer.rs +++ b/network-hypervisor/src/vl1/peer.rs @@ -326,7 +326,7 @@ impl Peer { aes_gmac_siv.encrypt_init(&self.v1_proto_next_message_id().to_be_bytes()); aes_gmac_siv.encrypt_set_aad(&v1::get_packet_aad_bytes( &self.identity.address, - &node.identity().address, + &node.identity.public.address, flags_cipher_hops, )); let payload = packet.as_bytes_starting_at_mut(v1::HEADER_SIZE).unwrap(); @@ -338,7 +338,7 @@ impl Peer { let header = packet.struct_mut_at::(0).unwrap(); header.id.copy_from_slice(&tag[0..8]); header.dest = *self.identity.address.legacy_bytes(); - header.src = *node.identity().address.legacy_bytes(); + header.src = *node.identity.public.address.legacy_bytes(); header.flags_cipher_hops = flags_cipher_hops; header.mac.copy_from_slice(&tag[8..16]); } else { @@ -355,7 +355,7 @@ impl Peer { let header = packet.struct_mut_at::(0).unwrap(); header.id = self.v1_proto_next_message_id().to_be_bytes(); header.dest = *self.identity.address.legacy_bytes(); - header.src = *node.identity().address.legacy_bytes(); + header.src = *node.identity.public.address.legacy_bytes(); header.flags_cipher_hops = flags_cipher_hops; header }, @@ -413,7 +413,7 @@ impl Peer { let f: &mut (v1::PacketHeader, v1::message_component_structs::HelloFixedHeaderFields) = packet.append_struct_get_mut().unwrap(); f.0.id = message_id.to_ne_bytes(); f.0.dest = *self.identity.address.legacy_bytes(); - f.0.src = *node.identity().address.legacy_bytes(); + f.0.src = *node.identity.public.address.legacy_bytes(); f.0.flags_cipher_hops = v1::CIPHER_NOCRYPT_POLY1305; f.1.verb = message_type::VL1_HELLO; f.1.version_proto = PROTOCOL_VERSION; @@ -424,7 +424,7 @@ impl Peer { } debug_assert_eq!(packet.len(), 41); - assert!(node.identity().write_bytes(packet.as_mut(), !self.is_v2()).is_ok()); + assert!(node.identity.public.write_bytes(packet.as_mut(), !self.is_v2()).is_ok()); let (_, poly1305_key) = v1_proto_salsa_poly_create( &self.v1_proto_static_secret, diff --git a/network-hypervisor/src/vl1/rootset.rs b/network-hypervisor/src/vl1/rootset.rs index 63df75a73..d618be5e0 100644 --- a/network-hypervisor/src/vl1/rootset.rs +++ b/network-hypervisor/src/vl1/rootset.rs @@ -154,10 +154,10 @@ impl RootSet { /// /// All current members must sign whether they are disabled (witnessing) or active. The verify() /// method will return true when signing is complete. - pub fn sign(&mut self, member_identity: &Identity, member_identity_secret: &IdentitySecret) -> bool { + pub fn sign(&mut self, member_identity_secret: &IdentitySecret) -> bool { let signature = member_identity_secret.sign(self.marshal_for_signing().as_bytes()); let unsigned_entry = self.members.iter().find_map(|m| { - if m.identity.eq(member_identity) { + if m.identity.eq(&member_identity_secret.public) { Some(m.clone()) } else { None @@ -165,7 +165,7 @@ impl RootSet { }); if unsigned_entry.is_some() { let unsigned_entry = unsigned_entry.unwrap(); - self.members.retain(|m| !m.identity.eq(member_identity)); + self.members.retain(|m| !m.identity.eq(&member_identity_secret.public)); let _ = self.members.push(Root { identity: unsigned_entry.identity, endpoints: unsigned_entry.endpoints, diff --git a/network-hypervisor/src/vl1/whois.rs b/network-hypervisor/src/vl1/whois.rs index 450cd5976..33ccdaa69 100644 --- a/network-hypervisor/src/vl1/whois.rs +++ b/network-hypervisor/src/vl1/whois.rs @@ -65,7 +65,7 @@ impl Whois { } } - if let Some(peer) = node.peers.get_or_add(&node.identity_secret, &identity, time_ticks) { + if let Some(peer) = node.peers.get_or_add(&node.identity, &identity, time_ticks) { for qi in queued_items.iter() { for pkt in qi.pending_v1_packets.iter() { if let Some(source_path) = pkt.0.upgrade() { diff --git a/network-hypervisor/src/vl2/v1/revocation.rs b/network-hypervisor/src/vl2/v1/revocation.rs index 124e0ebe2..c088c241a 100644 --- a/network-hypervisor/src/vl2/v1/revocation.rs +++ b/network-hypervisor/src/vl2/v1/revocation.rs @@ -12,7 +12,7 @@ use crate::vl2::NetworkId; /// "Anti-credential" revoking a network member's permission to communicate on a network. #[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)] pub struct Revocation { - pub network_id: NetworkId, + pub network_id: u64, // legacy 64-bit network ID pub threshold: i64, pub target: u64, // legacy 40-bit address pub issued_to: u64, // legacy 40-bit address @@ -22,7 +22,7 @@ pub struct Revocation { impl Revocation { pub fn new( - network_id: NetworkId, + network_id: &NetworkId, threshold: i64, target: &Address, issued_to: &Address, @@ -31,7 +31,7 @@ impl Revocation { fast_propagate: bool, ) -> Self { let mut r = Self { - network_id, + network_id: network_id.to_legacy_u64(), threshold, target: target.legacy_u64(), issued_to: issued_to.legacy_u64(), @@ -50,7 +50,7 @@ impl Revocation { let _ = v.write_all(&[0; 4]); let _ = v.write_all(&((self.threshold as u32) ^ (self.target as u32)).to_be_bytes()); // ID is arbitrary - let _ = v.write_all(&self.network_id.to_bytes()); + let _ = v.write_all(&self.network_id.to_be_bytes()); let _ = v.write_all(&[0; 8]); let _ = v.write_all(&self.threshold.to_be_bytes()); let _ = v.write_all(&(self.fast_propagate as u64).to_be_bytes()); // 0x1 is the flag for this diff --git a/service/src/utils.rs b/service/src/utils.rs index 074e7b81f..546ffb1d7 100644 --- a/service/src/utils.rs +++ b/service/src/utils.rs @@ -3,7 +3,7 @@ use std::path::Path; use std::str::FromStr; -use zerotier_network_hypervisor::vl1::Identity; +use zerotier_network_hypervisor::vl1::identity::Identity; use zerotier_utils::io::read_limit; /// Returns true if the string starts with [yY1tT] or false for [nN0fF]. diff --git a/utils/src/base24.rs b/utils/src/base24.rs index 3ed2bce72..b61bf6bf8 100644 --- a/utils/src/base24.rs +++ b/utils/src/base24.rs @@ -105,7 +105,7 @@ mod tests { use super::*; #[test] - fn base24_encode_decode() { + fn encode_decode() { let mut tmp = [0xffu8; 256]; for _ in 0..3 { let mut s = String::with_capacity(1024); diff --git a/utils/src/base64.rs b/utils/src/base64.rs new file mode 100644 index 000000000..5fee0bb48 --- /dev/null +++ b/utils/src/base64.rs @@ -0,0 +1,115 @@ +use crate::error::InvalidParameterError; + +/// URL-safe base64 alphabet +const ALPHABET: [u8; 64] = *b"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-_"; +const ALPHABET_INV: [u8; 256] = [ + 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, + 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 62, 255, 255, 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, 255, 255, + 255, 255, 255, 255, 255, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 255, 255, 255, 255, 63, + 255, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, 255, 255, 255, 255, 255, 255, 255, + 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, + 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, + 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, + 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, + 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, +]; + +pub fn encode_into(mut b: &[u8], s: &mut String) { + while b.len() >= 3 { + let bits = (b[0] as usize) | (b[1] as usize).wrapping_shl(8) | (b[2] as usize).wrapping_shl(16); + b = &b[3..]; + let (i0, i1, i2, i3) = (bits & 63, bits.wrapping_shr(6) & 63, bits.wrapping_shr(12) & 63, bits.wrapping_shr(18)); + s.push(ALPHABET[i0] as char); + s.push(ALPHABET[i1] as char); + s.push(ALPHABET[i2] as char); + s.push(ALPHABET[i3] as char); + } + if b.len() == 2 { + let bits = (b[0] as usize) | (b[1] as usize).wrapping_shl(8); + s.push(ALPHABET[bits & 63] as char); + s.push(ALPHABET[bits.wrapping_shr(6) & 63] as char); + s.push(ALPHABET[bits.wrapping_shr(12)] as char); + } else if b.len() == 1 { + let bits = b[0] as usize; + s.push(ALPHABET[bits & 63] as char); + s.push(ALPHABET[bits.wrapping_shr(6)] as char); + } +} + +pub fn decode_into(mut s: &[u8], b: &mut Vec) -> Result<(), InvalidParameterError> { + while s.len() >= 4 { + let (i0, i1, i2, i3) = ( + ALPHABET_INV[s[0] as usize], + ALPHABET_INV[s[1] as usize], + ALPHABET_INV[s[2] as usize], + ALPHABET_INV[s[3] as usize], + ); + s = &s[4..]; + if (i0 | i1 | i2 | i3) > 64 { + return Err(InvalidParameterError("invalid base64 string")); + } + let bits = (i0 as usize) | (i1 as usize).wrapping_shl(6) | (i2 as usize).wrapping_shl(12) | (i3 as usize).wrapping_shl(18); + b.push((bits & 0xff) as u8); + b.push((bits.wrapping_shr(8) & 0xff) as u8); + b.push((bits.wrapping_shr(16) & 0xff) as u8); + } + match s.len() { + 1 => return Err(InvalidParameterError("invalid base64 string")), + 2 => { + let (i0, i1) = (ALPHABET_INV[s[0] as usize], ALPHABET_INV[s[1] as usize]); + if (i0 | i1) > 64 { + return Err(InvalidParameterError("invalid base64 string")); + } + let bits = (i0 as usize) | (i1 as usize).wrapping_shl(6); + b.push((bits & 0xff) as u8); + } + 3 => { + let (i0, i1, i2) = (ALPHABET_INV[s[0] as usize], ALPHABET_INV[s[1] as usize], ALPHABET_INV[s[2] as usize]); + if (i0 | i1 | i2) > 64 { + return Err(InvalidParameterError("invalid base64 string")); + } + let bits = (i0 as usize) | (i1 as usize).wrapping_shl(6) | (i2 as usize).wrapping_shl(12); + b.push((bits & 0xff) as u8); + b.push((bits.wrapping_shr(8) & 0xff) as u8); + } + _ => {} + } + Ok(()) +} + +pub fn encode(b: &[u8]) -> String { + let mut tmp = String::with_capacity(((b.len() / 3) * 4) + 3); + encode_into(b, &mut tmp); + tmp +} + +pub fn decode(s: &[u8]) -> Result, InvalidParameterError> { + let mut tmp = Vec::with_capacity(((s.len() / 4) * 3) + 3); + decode_into(s, &mut tmp)?; + Ok(tmp) +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn encode_decode() { + let mut tmp = [0xffu8; 256]; + for _ in 0..7 { + let mut s = String::with_capacity(1024); + let mut v: Vec = Vec::with_capacity(256); + for i in 1..256 { + s.clear(); + encode_into(&tmp[..i], &mut s); + //println!("{}", s); + v.clear(); + decode_into(s.as_str().as_bytes(), &mut v).expect("decode error"); + assert!(v.as_slice().eq(&tmp[..i])); + } + for b in tmp.iter_mut() { + *b -= 13; + } + } + } +} diff --git a/utils/src/lib.rs b/utils/src/lib.rs index 9a4c204da..dbb3ad353 100644 --- a/utils/src/lib.rs +++ b/utils/src/lib.rs @@ -8,6 +8,7 @@ pub mod arrayvec; pub mod base24; +pub mod base64; pub mod blob; pub mod buffer; pub mod cast; diff --git a/vl1-service/src/datadir.rs b/vl1-service/src/datadir.rs index d8f21bb17..52f4a5e4d 100644 --- a/vl1-service/src/datadir.rs +++ b/vl1-service/src/datadir.rs @@ -1,19 +1,15 @@ // (c) 2020-2022 ZeroTier, Inc. -- currently proprietary pending actual release and licensing. See LICENSE.md. use std::path::{Path, PathBuf}; -use std::str::FromStr; use std::sync::{Arc, Mutex, RwLock}; use serde::de::DeserializeOwned; use serde::Serialize; use zerotier_crypto::random::next_u32_secure; -use zerotier_network_hypervisor::vl1::{Identity, Valid}; use zerotier_utils::io::{fs_restrict_permissions, read_limit, DEFAULT_FILE_IO_READ_LIMIT}; use zerotier_utils::json::to_json_pretty; -use crate::vl1service::VL1DataStorage; - 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"; @@ -22,46 +18,12 @@ pub const CONFIG_FILENAME: &'static str = "local.conf"; const AUTH_TOKEN_DEFAULT_LENGTH: usize = 48; const AUTH_TOKEN_POSSIBLE_CHARS: &'static str = "0123456789abcdefghijklmnopqrstuvwxyz"; -pub fn load_node_identity(base_path: &Path) -> Option> { - let id_data = read_limit(base_path.join(IDENTITY_SECRET_FILENAME), 4096); - 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(Valid::mark_valid(id_data.unwrap())) -} - -pub fn save_node_identity(base_path: &Path, id: &Valid) -> bool { - assert!(id.secret.is_some()); - let id_secret_str = id.to_secret_string(); - let id_public_str = id.to_string(); - let secret_path = base_path.join(IDENTITY_SECRET_FILENAME); - if std::fs::write(&secret_path, id_secret_str.as_bytes()).is_err() { - return false; - } - assert!(fs_restrict_permissions(&secret_path)); - return std::fs::write(base_path.join(IDENTITY_PUBLIC_FILENAME), id_public_str.as_bytes()).is_ok(); -} - pub struct DataDir { pub base_path: PathBuf, config: RwLock>, authtoken: Mutex, } -impl VL1DataStorage for DataDir { - fn load_node_identity(&self) -> Option> { - load_node_identity(self.base_path.as_path()) - } - - fn save_node_identity(&self, id: &Valid) -> bool { - save_node_identity(self.base_path.as_path(), id) - } -} - impl DataDir { pub fn open>(path: P) -> std::io::Result { let base_path = path.as_ref().to_path_buf(); diff --git a/vl1-service/src/vl1service.rs b/vl1-service/src/vl1service.rs index 32450c8a9..9f3dd894f 100644 --- a/vl1-service/src/vl1service.rs +++ b/vl1-service/src/vl1service.rs @@ -9,6 +9,7 @@ use std::time::Duration; use zerotier_crypto::random; use zerotier_network_hypervisor::protocol::{PacketBufferFactory, PacketBufferPool}; +use zerotier_network_hypervisor::vl1::identity::IdentitySecret; use zerotier_network_hypervisor::vl1::*; use zerotier_utils::{ms_monotonic, ms_since_epoch}; @@ -20,12 +21,6 @@ use crate::LocalSocket; /// Update UDP bindings every this many seconds. const UPDATE_UDP_BINDINGS_EVERY_SECS: usize = 10; -/// Trait to implement to provide storage for VL1-related state information. -pub trait VL1DataStorage: Sync + Send { - fn load_node_identity(&self) -> Option>; - fn save_node_identity(&self, id: &Valid) -> bool; -} - /// VL1 service that connects to the physical network and hosts an inner protocol like ZeroTier VL2. /// /// This is the "outward facing" half of a full ZeroTier stack on a normal system. It binds sockets, @@ -33,11 +28,10 @@ pub trait VL1DataStorage: Sync + Send { /// 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 { + pub node: Node, state: RwLock, - vl1_data_storage: Arc, inner: Arc, buffer_pool: Arc, - node_container: Option>, // never None, set in new() } struct VL1ServiceMutableState { @@ -48,25 +42,21 @@ struct VL1ServiceMutableState { } impl VL1Service { - pub fn new(vl1_data_storage: Arc, inner: Arc, settings: VL1Settings) -> Result, Box> { - let mut service = Self { + pub fn new(identity: IdentitySecret, inner: Arc, settings: VL1Settings) -> Result, Box> { + let service = Arc::new(Self { + node: Node::::new(identity), state: RwLock::new(VL1ServiceMutableState { daemons: Vec::with_capacity(2), udp_sockets: HashMap::with_capacity(8), settings, running: true, }), - vl1_data_storage, inner, buffer_pool: Arc::new(PacketBufferPool::new( std::thread::available_parallelism().map_or(2, |c| c.get() + 2), PacketBufferFactory::new(), )), - node_container: None, - }; - - service.node_container.replace(Node::new(&service, true, false)?); - let service = Arc::new(service); + }); let mut daemons = Vec::new(); let s = service.clone(); @@ -78,12 +68,6 @@ impl VL1Service { Ok(service) } - #[inline(always)] - pub fn node(&self) -> &Node { - debug_assert!(self.node_container.is_some()); - unsafe { self.node_container.as_ref().unwrap_unchecked() } - } - pub fn bound_udp_ports(&self) -> Vec { self.state.read().unwrap().udp_sockets.keys().cloned().collect() } @@ -173,7 +157,7 @@ impl VL1Service { self.update_udp_bindings(); } udp_binding_check_every = udp_binding_check_every.wrapping_add(1); - std::thread::sleep(self.node().do_background_tasks(self.as_ref())); + std::thread::sleep(self.node.do_background_tasks(self.as_ref())); } } } @@ -187,7 +171,7 @@ impl UdpPacketHandler for VL1Servi source_address: &InetAddress, packet: zerotier_network_hypervisor::protocol::PooledPacketBuffer, ) { - self.node().handle_incoming_physical_packet( + self.node.handle_incoming_physical_packet( self.as_ref(), self.inner.as_ref(), &Endpoint::IpUdp(source_address.clone()), @@ -216,22 +200,6 @@ impl ApplicationLayer for VL1Servi socket.is_valid() } - #[inline] - fn should_respond_to(&self, _: &Valid) -> bool { - // TODO: provide a way for the user of VL1Service to control this - true - } - - #[inline] - fn load_node_identity(&self) -> Option> { - self.vl1_data_storage.load_node_identity() - } - - #[inline] - fn save_node_identity(&self, id: &Valid) -> bool { - self.vl1_data_storage.save_node_identity(id) - } - #[inline] fn get_buffer(&self) -> zerotier_network_hypervisor::protocol::PooledPacketBuffer { self.buffer_pool.get()