From 19d973cfd46c629c24b38df382a1bf055458d588 Mon Sep 17 00:00:00 2001 From: Adam Ierymenko Date: Wed, 16 Nov 2022 21:41:08 -0500 Subject: [PATCH] (1) Implement typestate Verified for identity, (2) FileDatabase in controller now works. --- controller/src/controller.rs | 25 +- controller/src/database.rs | 2 +- controller/src/filedatabase.rs | 47 +- controller/src/model/member.rs | 8 +- controller/src/model/mod.rs | 8 +- controller/src/model/network.rs | 34 +- crypto/Cargo.toml | 5 +- crypto/README.md | 2 +- crypto/src/poly1305.rs | 3 +- crypto/src/verified.rs | 98 ++- network-hypervisor/src/vl1/address.rs | 10 +- network-hypervisor/src/vl1/identity.rs | 53 +- network-hypervisor/src/vl1/inetaddress.rs | 14 +- network-hypervisor/src/vl1/mac.rs | 12 +- network-hypervisor/src/vl1/mod.rs | 2 + network-hypervisor/src/vl1/node.rs | 35 +- network-hypervisor/src/vl1/peer.rs | 5 +- network-hypervisor/src/vl1/rootset.rs | 2 +- .../src/vl2/multicastauthority.rs | 1 - network-hypervisor/src/vl2/multicastgroup.rs | 15 + network-hypervisor/src/vl2/networkconfig.rs | 8 +- network-hypervisor/src/vl2/networkid.rs | 8 + network-hypervisor/src/vl2/rule.rs | 759 ++++++++++++++++-- .../src/vl2/v1/certificateofmembership.rs | 4 +- .../src/vl2/v1/certificateofownership.rs | 4 +- network-hypervisor/src/vl2/v1/mod.rs | 1 + network-hypervisor/src/vl2/v1/revocation.rs | 2 +- network-hypervisor/src/vl2/v1/tag.rs | 2 +- service/src/utils.rs | 4 +- utils/src/arrayvec.rs | 15 + utils/src/blob.rs | 9 + vl1-service/src/datadir.rs | 8 +- vl1-service/src/vl1service.rs | 8 +- 33 files changed, 994 insertions(+), 219 deletions(-) diff --git a/controller/src/controller.rs b/controller/src/controller.rs index f80291776..d1e348421 100644 --- a/controller/src/controller.rs +++ b/controller/src/controller.rs @@ -35,7 +35,7 @@ pub struct Controller { reaper: Reaper, runtime: tokio::runtime::Handle, database: Arc, - local_identity: Identity, + local_identity: Verified, /// Handler for MULTICAST_LIKE and MULTICAST_GATHER messages. multicast_authority: MulticastAuthority, @@ -61,7 +61,7 @@ impl Controller { reaper: Reaper::new(&runtime), runtime, database: database.clone(), - local_identity, + local_identity: local_identity, multicast_authority: MulticastAuthority::new(), daemons: Mutex::new(Vec::with_capacity(2)), recently_authorized: RwLock::new(HashMap::new()), @@ -76,9 +76,8 @@ impl Controller { /// Set the service and HostSystem implementation for this controller and start daemons. /// /// This must be called once the service that uses this handler is up or the controller - /// won't actually do anything. The reference the handler holds is weak to prevent - /// a circular reference, so if the VL1Service is dropped this must be called again to - /// tell the controller handler about a new instance. + /// won't actually do anything. The controller holds a weak reference to VL1Service so + /// be sure it's not dropped. pub async fn start(&self, service: &Arc>) { *self.service.write().unwrap() = Arc::downgrade(service); @@ -106,7 +105,7 @@ impl Controller { // Create background task to expire multicast subscriptions and recent authorizations. let self2 = self.self_ref.clone(); self.daemons.lock().unwrap().push(self.runtime.spawn(async move { - let sleep_duration = Duration::from_millis((protocol::VL2_DEFAULT_MULTICAST_LIKE_EXPIRE / 2).min(5000) as u64); + let sleep_duration = Duration::from_millis((protocol::VL2_DEFAULT_MULTICAST_LIKE_EXPIRE / 2).min(2500) as u64); loop { tokio::time::sleep(sleep_duration).await; @@ -234,7 +233,7 @@ impl Controller { /// reason is returned with None or an acceptance reason with a network configuration is returned. async fn get_network_config( self: &Arc, - source_identity: &Identity, + source_identity: &Verified, network_id: NetworkId, now: i64, ) -> Result<(AuthorizationResult, Option, Option>), Box> { @@ -287,7 +286,7 @@ impl Controller { if !member_authorized { if member.is_none() { if network.learn_members.unwrap_or(true) { - let _ = member.insert(Member::new_with_identity(source_identity.clone(), network_id)); + let _ = member.insert(Member::new_with_identity(source_identity.as_ref().clone(), network_id)); member_changed = true; } else { return Ok((AuthorizationResult::Rejected, None, None)); @@ -407,8 +406,10 @@ impl Controller { } else { return Ok((AuthorizationResult::RejectedDueToError, None, None)); } - } else { - // TODO: create V2 type credential for V2-only networks + } + + if source_identity.p384.is_some() { + // TODO: create V2 type credential for V2 nodes } // Log this member in the recently authorized cache, which is currently just used to filter whether we should @@ -578,14 +579,14 @@ impl InnerProtocol for Controller { impl VL1AuthProvider for Controller { #[inline(always)] - fn should_respond_to(&self, _: &Identity) -> bool { + fn should_respond_to(&self, _: &Verified) -> bool { // Controllers always have to establish sessions to process requests. We don't really know if // a member is relevant until we have looked up both the network and the member, since whether // or not to "learn" unknown members is a network level option. true } - fn has_trust_relationship(&self, id: &Identity) -> bool { + fn has_trust_relationship(&self, id: &Verified) -> bool { self.recently_authorized .read() .unwrap() diff --git a/controller/src/database.rs b/controller/src/database.rs index 3390197c0..6bb722048 100644 --- a/controller/src/database.rs +++ b/controller/src/database.rs @@ -9,7 +9,7 @@ use zerotier_utils::tokio::sync::broadcast::Receiver; use crate::model::*; /// Database change relevant to the controller and that was NOT initiated by the controller. -#[derive(Clone)] +#[derive(Clone, Debug)] pub enum Change { NetworkCreated(Network), NetworkChanged(Network, Network), diff --git a/controller/src/filedatabase.rs b/controller/src/filedatabase.rs index 01a04832e..40212fa43 100644 --- a/controller/src/filedatabase.rs +++ b/controller/src/filedatabase.rs @@ -8,11 +8,9 @@ use async_trait::async_trait; use notify::{RecursiveMode, Watcher}; use serde::de::DeserializeOwned; -use zerotier_network_hypervisor::vl1::{Address, Identity, NodeStorage}; +use zerotier_network_hypervisor::vl1::{Address, Identity, NodeStorage, Verified}; use zerotier_network_hypervisor::vl2::NetworkId; - use zerotier_utils::io::{fs_restrict_permissions, read_limit}; - use zerotier_utils::reaper::Reaper; use zerotier_utils::tokio::fs; use zerotier_utils::tokio::runtime::Handle; @@ -60,11 +58,6 @@ impl FileDatabase { tasks: Reaper::new(&runtime2), cache: Cache::new(), daemon: runtime2.spawn(async move { - while db_weak.lock().unwrap().upgrade().is_none() { - // Wait for parent to finish constructing and start up, then create watcher. - sleep(Duration::from_millis(10)).await; - } - let mut watcher = notify::recommended_watcher(move |event: notify::Result| { if let Ok(event) = event { match event.kind { @@ -121,7 +114,6 @@ impl FileDatabase { } if deleted.is_some() { - println!("DELETED: {}", deleted.unwrap().as_os_str().to_string_lossy()); match record_type { RecordType::Network => { if let Some((network, mut members)) = @@ -147,7 +139,6 @@ impl FileDatabase { } if let Some(changed) = changed { - println!("CHANGED: {}", changed.as_os_str().to_string_lossy()); match record_type { RecordType::Network => { if let Ok(Some(new_network)) = @@ -258,7 +249,7 @@ impl FileDatabase { /// Get record type and also the number after it: network number or address. fn record_type_from_path(controller_address: Address, p: &Path) -> Option<(RecordType, NetworkId, Option
)> { - let parent = p.parent()?.to_string_lossy(); + let parent = p.parent()?.file_name()?.to_string_lossy(); if parent.len() == 7 && (parent.starts_with("N") || parent.starts_with('n')) { let network_id = NetworkId::from_controller_and_network_no(controller_address, u64::from_str_radix(&parent[1..], 16).ok()?)?; if let Some(file_name) = p.file_name().map(|p| p.to_string_lossy().to_lowercase()) { @@ -284,7 +275,7 @@ impl Drop for FileDatabase { } impl NodeStorage for FileDatabase { - fn load_node_identity(&self) -> Option { + fn load_node_identity(&self) -> Option> { let id_data = read_limit(self.base_path.join(IDENTITY_SECRET_FILENAME), 16384); if id_data.is_err() { return None; @@ -293,10 +284,10 @@ impl NodeStorage for FileDatabase { if id_data.is_err() { return None; } - Some(id_data.unwrap()) + Some(Verified::assume_verified(id_data.unwrap())) } - fn save_node_identity(&self, id: &Identity) { + fn save_node_identity(&self, id: &Verified) { assert!(id.secret.is_some()); let id_secret_str = id.to_secret_string(); let secret_path = self.base_path.join(IDENTITY_SECRET_FILENAME); @@ -422,17 +413,35 @@ 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 db = Arc::new(FileDatabase::new(tokio_runtime.handle().clone(), test_dir).await.expect("new db")); + db.save_node_identity(&controller_id); + assert!(db.load_node_identity().is_some()); + + let db2 = db.clone(); + tokio_runtime.spawn(async move { + let mut change_receiver = db2.changes().await.unwrap(); + loop { + if let Ok(change) = change_receiver.recv().await { + //println!("[FileDatabase] {:#?}", change); + } else { + break; + } + } + }); + + let mut test_network = Network::new(network_id); + db.save_network(test_network.clone()).await.expect("network save error"); - let db = FileDatabase::new(tokio_runtime.handle().clone(), test_dir).await.expect("new db"); let mut test_member = Member::new_without_identity(node_id, network_id); - for x in 0..3 { test_member.name = x.to_string(); - db.save_member(test_member.clone()).await.expect("member save ok"); + db.save_member(test_member.clone()).await.expect("member save error"); + + sleep(Duration::from_millis(100)).await; let test_member2 = db.get_member(network_id, node_id).await.unwrap().unwrap(); - //println!("{}", test_member.to_string()); - //println!("{}", test_member2.to_string()); assert!(test_member == test_member2); } }); diff --git a/controller/src/model/member.rs b/controller/src/model/member.rs index 63483a39e..558ebb1f8 100644 --- a/controller/src/model/member.rs +++ b/controller/src/model/member.rs @@ -9,7 +9,7 @@ use zerotier_network_hypervisor::vl1::{Address, Identity, InetAddress}; use zerotier_network_hypervisor::vl2::NetworkId; use zerotier_utils::blob::Blob; -#[derive(Clone, PartialEq, Eq, Serialize, Deserialize)] +#[derive(Clone, PartialEq, Eq, Serialize, Deserialize, Debug)] pub struct Member { #[serde(rename = "address")] pub node_id: Address, @@ -119,9 +119,3 @@ impl Hash for Member { self.network_id.hash(state); } } - -impl ToString for Member { - fn to_string(&self) -> String { - zerotier_utils::json::to_json_pretty(self) - } -} diff --git a/controller/src/model/mod.rs b/controller/src/model/mod.rs index c3320c8cf..f21d98b64 100644 --- a/controller/src/model/mod.rs +++ b/controller/src/model/mod.rs @@ -15,7 +15,7 @@ use zerotier_network_hypervisor::vl2::networkconfig::NetworkConfig; use zerotier_network_hypervisor::vl2::NetworkId; use zerotier_utils::blob::Blob; -#[derive(Clone, PartialEq, Eq, Serialize, Deserialize)] +#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] pub enum RecordType { Network, Member, @@ -23,13 +23,13 @@ pub enum RecordType { } /// A complete network with all member configuration information for import/export or blob storage. -#[derive(Clone, PartialEq, Eq, Serialize, Deserialize)] +#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] pub struct NetworkExport { pub network: Network, pub members: HashMap, } -#[derive(Clone, PartialEq, Eq, Serialize, Deserialize)] +#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] #[repr(u8)] pub enum AuthorizationResult { #[serde(rename = "r")] @@ -87,7 +87,7 @@ impl ToString for AuthorizationResult { } } -#[derive(Clone, PartialEq, Eq, Serialize, Deserialize)] +#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] pub struct RequestLogItem { #[serde(rename = "nw")] pub network_id: NetworkId, diff --git a/controller/src/model/network.rs b/controller/src/model/network.rs index 595caa6cb..c33cbdec4 100644 --- a/controller/src/model/network.rs +++ b/controller/src/model/network.rs @@ -15,12 +15,12 @@ use crate::model::Member; pub const CREDENTIAL_WINDOW_SIZE_DEFAULT: u64 = 1000 * 60 * 60; -#[derive(Clone, PartialEq, Eq, Serialize, Deserialize, Default)] +#[derive(Clone, PartialEq, Eq, Serialize, Deserialize, Default, Debug)] pub struct Ipv4AssignMode { pub zt: bool, } -#[derive(Clone, PartialEq, Eq, Serialize, Deserialize, Default)] +#[derive(Clone, PartialEq, Eq, Serialize, Deserialize, Default, Debug)] pub struct Ipv6AssignMode { pub zt: bool, pub rfc4193: bool, @@ -28,7 +28,7 @@ pub struct Ipv6AssignMode { pub _6plane: bool, } -#[derive(Clone, PartialEq, Eq, Serialize, Deserialize, Hash)] +#[derive(Clone, PartialEq, Eq, Serialize, Deserialize, Hash, Debug)] pub struct IpAssignmentPool { #[serde(rename = "ipRangeStart")] ip_range_start: InetAddress, @@ -40,7 +40,7 @@ pub struct IpAssignmentPool { /// /// This contains only fields of relevance to the controller. Other fields can be tracked by various /// database implementations such as row last modified, creation time, ownership in an admin panel, etc. -#[derive(Clone, PartialEq, Eq, Serialize, Deserialize)] +#[derive(Clone, PartialEq, Eq, Serialize, Deserialize, Debug)] pub struct Network { pub id: NetworkId, @@ -134,18 +134,32 @@ impl Hash for Network { } } -impl ToString for Network { - fn to_string(&self) -> String { - zerotier_utils::json::to_json_pretty(self) - } -} - #[inline(always)] fn troo() -> bool { true } impl Network { + pub fn new(id: NetworkId) -> Self { + Network { + id, + name: String::new(), + multicast_limit: None, + enable_broadcast: None, + v4_assign_mode: None, + v6_assign_mode: None, + ip_assignment_pools: HashSet::new(), + ip_routes: HashSet::new(), + dns: HashMap::new(), + rules: Vec::new(), + credential_ttl: None, + min_supported_version: None, + mtu: None, + private: true, + learn_members: None, + } + } + /// Check member IP assignments and return 'true' if IP assignments were created or modified. pub async fn assign_ip_addresses(&self, database: &DatabaseImpl, member: &mut Member) -> bool { let mut modified = false; diff --git a/crypto/Cargo.toml b/crypto/Cargo.toml index d2af1d188..feea1fd91 100644 --- a/crypto/Cargo.toml +++ b/crypto/Cargo.toml @@ -11,7 +11,7 @@ ed25519-dalek = { version = "1.0.1", features = ["std", "u64_backend"], default- foreign-types = "0.3.1" lazy_static = "^1" openssl = { version = "^0", features = [], default-features = false } -poly1305 = { version = "0.7.2", features = [], default-features = false } +poly1305 = { version = "0.8.0", features = [], default-features = false } pqc_kyber = { path = "../third_party/kyber", features = ["kyber1024", "reference"], default-features = false } #pqc_kyber = { version = "^0", features = ["kyber1024", "reference"], default-features = false } rand_core = "0.5.1" @@ -19,9 +19,6 @@ rand_core_062 = { package = "rand_core", version = "0.6.2" } subtle = "2.4.1" x25519-dalek = { version = "1.2.0", features = ["std", "u64_backend"], default-features = false } -[target."cfg(not(any(target_os = \"macos\", target_os = \"ios\")))".dependencies] -openssl = "^0" - [dev-dependencies] criterion = "0.3" sha2 = "^0" diff --git a/crypto/README.md b/crypto/README.md index 701698254..527e7d50a 100644 --- a/crypto/README.md +++ b/crypto/README.md @@ -4,4 +4,4 @@ Most of this library is just glue to provide a simple safe API around things like OpenSSL or OS-specific crypto APIs. -It also contains ZSSP, the V2 ZeroTier Secure Session Protocol. +It also contains [ZSSP](ZSSP.md), the V2 ZeroTier Secure Session Protocol. diff --git a/crypto/src/poly1305.rs b/crypto/src/poly1305.rs index 13b27f91a..2922321cc 100644 --- a/crypto/src/poly1305.rs +++ b/crypto/src/poly1305.rs @@ -1,6 +1,6 @@ // (c) 2020-2022 ZeroTier, Inc. -- currently propritery pending actual release and licensing. See LICENSE.md. -use poly1305::universal_hash::NewUniversalHash; +use poly1305::universal_hash::KeyInit; /// The poly1305 message authentication function. pub struct Poly1305(poly1305::Poly1305, [u8; 16], usize); @@ -12,7 +12,6 @@ pub const POLY1305_MAC_SIZE: usize = 16; pub fn compute(one_time_key: &[u8], message: &[u8]) -> [u8; POLY1305_MAC_SIZE] { poly1305::Poly1305::new(poly1305::Key::from_slice(one_time_key)) .compute_unpadded(message) - .into_bytes() .into() } diff --git a/crypto/src/verified.rs b/crypto/src/verified.rs index d25710ed6..7d87e4df4 100644 --- a/crypto/src/verified.rs +++ b/crypto/src/verified.rs @@ -1,15 +1,32 @@ // (c) 2020-2022 ZeroTier, Inc. -- currently propritery pending actual release and licensing. See LICENSE.md. +use std::fmt::Debug; +use std::hash::Hash; use std::ops::{Deref, DerefMut}; -/// A zero-overhead wrapper that signals that a credential is verified. +/// A zero-overhead typestate indicating that a credential has been verified as valid. /// -/// This is used when a function expects to receive an object that is already verified to -/// make code more self-documenting and make it semantically harder to accidentally use -/// an untrusted object. -#[derive(Clone, PartialEq, Eq, PartialOrd, Ord)] +/// What this means is obviously specific to the credential. +/// +/// The purpose of this is to make code more self-documenting and make it harder to accidentally +/// use an unverified/unvalidated credential (or other security critical object) where a verified +/// one is required. #[repr(transparent)] -pub struct Verified(pub T); +pub struct Verified(T); + +impl AsRef for Verified { + #[inline(always)] + fn as_ref(&self) -> &T { + &self.0 + } +} + +impl AsMut for Verified { + #[inline(always)] + fn as_mut(&mut self) -> &mut T { + &mut self.0 + } +} impl Deref for Verified { type Target = T; @@ -27,9 +44,78 @@ impl DerefMut for Verified { } } +impl Clone for Verified +where + T: Clone, +{ + #[inline(always)] + fn clone(&self) -> Self { + Self(self.0.clone()) + } +} + +impl PartialEq for Verified +where + T: PartialEq, +{ + #[inline(always)] + fn eq(&self, other: &Self) -> bool { + self.0.eq(&other.0) + } +} + +impl Eq for Verified where T: Eq {} + +impl Ord for Verified +where + T: Ord, +{ + #[inline(always)] + fn cmp(&self, other: &Self) -> std::cmp::Ordering { + self.0.cmp(&other.0) + } +} + +impl PartialOrd for Verified +where + T: PartialOrd, +{ + #[inline(always)] + fn partial_cmp(&self, other: &Self) -> Option { + self.0.partial_cmp(&other.0) + } +} + +impl Hash for Verified +where + T: Hash, +{ + #[inline(always)] + fn hash(&self, state: &mut H) { + self.0.hash(state); + } +} + +impl Debug for Verified +where + T: Debug, +{ + #[inline] + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + f.debug_tuple("Verified").field(&self.0).finish() + } +} + impl Verified { + /// Strip the Verified typestate off this object. #[inline(always)] pub fn unwrap(self) -> T { self.0 } + + /// Set Verified typestate on an object. + #[inline(always)] + pub fn assume_verified(o: T) -> Self { + Self(o) + } } diff --git a/network-hypervisor/src/vl1/address.rs b/network-hypervisor/src/vl1/address.rs index 0f897915c..d283423ec 100644 --- a/network-hypervisor/src/vl1/address.rs +++ b/network-hypervisor/src/vl1/address.rs @@ -1,5 +1,6 @@ // (c) 2020-2022 ZeroTier, Inc. -- currently propritery pending actual release and licensing. See LICENSE.md. +use std::fmt::Debug; use std::hash::{Hash, Hasher}; use std::num::NonZeroU64; use std::str::FromStr; @@ -12,7 +13,7 @@ use zerotier_utils::error::InvalidFormatError; use zerotier_utils::hex; /// A unique address on the global ZeroTier VL1 network. -#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)] +#[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord)] #[repr(transparent)] pub struct Address(NonZeroU64); @@ -92,6 +93,13 @@ impl Hash for Address { } } +impl Debug for Address { + #[inline] + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + f.write_str(self.to_string().as_str()) + } +} + impl Serialize for Address { fn serialize(&self, serializer: S) -> Result where diff --git a/network-hypervisor/src/vl1/identity.rs b/network-hypervisor/src/vl1/identity.rs index 2e22e57ae..d9cb44ed8 100644 --- a/network-hypervisor/src/vl1/identity.rs +++ b/network-hypervisor/src/vl1/identity.rs @@ -2,6 +2,7 @@ use std::cmp::Ordering; use std::convert::TryInto; +use std::fmt::Debug; use std::hash::{Hash, Hasher}; use std::io::Write; use std::str::FromStr; @@ -22,6 +23,7 @@ use zerotier_utils::marshalable::{Marshalable, UnmarshalError}; use crate::protocol::{ADDRESS_SIZE, ADDRESS_SIZE_STRING, IDENTITY_POW_THRESHOLD}; use crate::vl1::Address; +use crate::vl1::Verified; /// Current maximum size for an identity signature. pub const IDENTITY_MAX_SIGNATURE_SIZE: usize = P384_ECDSA_SIGNATURE_SIZE + 1; @@ -164,7 +166,7 @@ impl Identity { const FLAG_INCLUDES_SECRETS: u8 = 0x80; /// Generate a new identity. - pub fn generate() -> Self { + pub fn generate() -> Verified { // First generate an identity with just x25519 keys and derive its address. let mut sha = SHA512::new(); let ed25519 = Ed25519KeyPair::generate(); @@ -204,7 +206,7 @@ impl Identity { assert!(id.upgrade().is_ok()); assert!(id.p384.is_some() && id.secret.as_ref().unwrap().p384.is_some()); - id + Verified::assume_verified(id) } /// Upgrade older x25519-only identities to hybrid identities with both x25519 and NIST P-384 curves. @@ -288,7 +290,7 @@ impl Identity { /// Locally check the validity of this identity. /// /// This is somewhat time consuming due to the memory-intensive work algorithm. - pub fn validate_identity(&self) -> bool { + pub fn validate(self) -> Option> { if let Some(p384) = self.p384.as_ref() { let mut self_sign_buf: Vec = Vec::with_capacity( ADDRESS_SIZE + 4 + C25519_PUBLIC_KEY_SIZE + ED25519_PUBLIC_KEY_SIZE + P384_PUBLIC_KEY_SIZE + P384_PUBLIC_KEY_SIZE, @@ -301,12 +303,12 @@ impl Identity { let _ = self_sign_buf.write_all(p384.ecdsa.as_bytes()); if !p384.ecdsa.verify(self_sign_buf.as_slice(), &p384.ecdsa_self_signature) { - return false; + return None; } let _ = self_sign_buf.write_all(&p384.ecdsa_self_signature); if !ed25519_verify(&self.ed25519, &p384.ed25519_self_signature, self_sign_buf.as_slice()) { - return false; + return None; } } @@ -318,7 +320,11 @@ impl Identity { let mut digest = sha.finish(); zt_address_derivation_work_function(&mut digest); - return digest[0] < IDENTITY_POW_THRESHOLD && Address::from_bytes(&digest[59..64]).map_or(false, |a| a == self.address); + return if digest[0] < IDENTITY_POW_THRESHOLD && Address::from_bytes(&digest[59..64]).map_or(false, |a| a == self.address) { + Some(Verified::assume_verified(self)) + } else { + None + }; } /// Returns true if this identity was upgraded from another older version. @@ -337,7 +343,7 @@ impl Identity { /// For new identities with P-384 keys a hybrid agreement is performed using both X25519 and NIST P-384 ECDH. /// The final key is derived as HMAC(x25519 secret, p-384 secret) to yield a FIPS-compliant key agreement with /// the X25519 secret being used as a "salt" as far as FIPS is concerned. - pub fn agree(&self, other: &Identity) -> Option> { + pub fn agree(&self, other: &Verified) -> Option> { if let Some(secret) = self.secret.as_ref() { let c25519_secret: Secret<64> = Secret(SHA512::hash(&secret.x25519.agree(&other.x25519).0)); @@ -822,6 +828,13 @@ impl Hash for Identity { } } +impl Debug for Identity { + #[inline] + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + f.write_str(self.to_string().as_str()) + } +} + impl Serialize for Identity { fn serialize(&self, serializer: S) -> Result where @@ -887,8 +900,7 @@ mod tests { let id = Identity::from_str( "728efdb79d:0:3077ed0084d8d48a3ac628af6b45d9351e823bff34bc4376cddfc77a3d73a966c7d347bdcc1244d0e99e1b9c961ff5e963092e90ca43b47ff58c114d2d699664:2afaefcd1dca336ed59957eb61919b55009850b0b7088af3ee142672b637d1d49cc882b30a006f9eee42f2211ef8fe1cbe99a16a4436737fc158ce2243c15f12", ) - .unwrap(); - assert!(id.validate_identity()); + .unwrap().validate().unwrap(); let self_agree = id.agree(&id).unwrap(); assert!(self_agree_expected.as_slice().eq(&self_agree.as_bytes()[..48])); @@ -920,7 +932,6 @@ mod tests { fn marshal_unmarshal_sign_verify_agree() { let gen = Identity::generate(); assert!(gen.agree(&gen).is_some()); - assert!(gen.validate_identity()); let bytes = gen.to_secret_bytes().unwrap(); let string = gen.to_secret_string(); assert!(Identity::from_str(string.as_str()).unwrap().eq(&gen)); @@ -938,57 +949,53 @@ mod tests { assert!(Identity::from_str(string.as_str()).unwrap().secret.is_some()); let gen2 = Identity::generate(); - assert!(gen2.validate_identity()); assert!(gen2.agree(&gen).unwrap().eq(&gen.agree(&gen2).unwrap())); for id_str in GOOD_V0_IDENTITIES { - let mut id = Identity::from_str(id_str).unwrap(); + let mut id = Identity::from_str(id_str).unwrap().validate().unwrap(); assert_eq!(id.to_secret_string().as_str(), id_str); - assert!(id.validate_identity()); assert!(id.p384.is_none()); let idb = id.to_secret_bytes().unwrap(); - let id_unmarshal = Identity::from_bytes(idb.as_bytes()).unwrap(); + let id_unmarshal = Identity::from_bytes(idb.as_bytes()).unwrap().validate().unwrap(); assert!(id == id_unmarshal); assert!(id_unmarshal.secret.is_some()); let idb2 = id_unmarshal.to_bytes(); - let id_unmarshal2 = Identity::from_bytes(&idb2).unwrap(); + let id_unmarshal2 = Identity::from_bytes(&idb2).unwrap().validate().unwrap(); assert!(id_unmarshal2 == id_unmarshal); assert!(id_unmarshal2 == id); assert!(id_unmarshal2.secret.is_none()); let ids = id.to_string(); - assert!(Identity::from_str(ids.as_str()).unwrap() == id); + assert!(Identity::from_str(ids.as_str()).unwrap() == *id); assert!(id.upgrade().is_ok()); - assert!(id.validate_identity()); assert!(id.p384.is_some()); assert!(id.secret.as_ref().unwrap().p384.is_some()); let ids = id.to_string(); - assert!(Identity::from_str(ids.as_str()).unwrap() == id); + assert!(Identity::from_str(ids.as_str()).unwrap() == *id); } for id_str in GOOD_V1_IDENTITIES { - let id = Identity::from_str(id_str).unwrap(); + let id = Identity::from_str(id_str).unwrap().validate().unwrap(); assert_eq!(id.to_secret_string().as_str(), id_str); - assert!(id.validate_identity()); assert!(id.p384.is_some()); assert!(id.secret.as_ref().unwrap().p384.is_some()); let idb = id.to_secret_bytes().unwrap(); - let id_unmarshal = Identity::from_bytes(idb.as_bytes()).unwrap(); + let id_unmarshal = Identity::from_bytes(idb.as_bytes()).unwrap().validate().unwrap(); assert!(id == id_unmarshal); let idb2 = id_unmarshal.to_bytes(); - let id_unmarshal2 = Identity::from_bytes(&idb2).unwrap(); + let id_unmarshal2 = Identity::from_bytes(&idb2).unwrap().validate().unwrap(); assert!(id_unmarshal2 == id_unmarshal); assert!(id_unmarshal2 == id); let ids = id.to_string(); - assert!(Identity::from_str(ids.as_str()).unwrap() == id); + assert!(Identity::from_str(ids.as_str()).unwrap() == *id); } } } diff --git a/network-hypervisor/src/vl1/inetaddress.rs b/network-hypervisor/src/vl1/inetaddress.rs index 2d6157da4..7d16dbdde 100644 --- a/network-hypervisor/src/vl1/inetaddress.rs +++ b/network-hypervisor/src/vl1/inetaddress.rs @@ -222,7 +222,7 @@ impl TryInto for InetAddress { impl TryInto for &InetAddress { type Error = InvalidParameterError; - #[inline(always)] + #[inline] fn try_into(self) -> Result { unsafe { match self.sa.sa_family { @@ -239,7 +239,7 @@ impl TryInto for &InetAddress { } impl From<&IpAddr> for InetAddress { - #[inline(always)] + #[inline] fn from(ip: &IpAddr) -> Self { match ip { IpAddr::V4(ip4) => Self::from(ip4), @@ -284,7 +284,7 @@ impl From for InetAddress { } impl From<&SocketAddr> for InetAddress { - #[inline(always)] + #[inline] fn from(sa: &SocketAddr) -> Self { match sa { SocketAddr::V4(sa4) => Self::from(sa4), @@ -353,6 +353,7 @@ impl std::fmt::Debug for InetAddress { const TEMP_SERIALIZE_BUFFER_SIZE: usize = 24; impl Serialize for InetAddress { + #[inline] fn serialize(&self, serializer: S) -> Result where S: Serializer, @@ -372,10 +373,12 @@ struct InetAddressVisitor; impl<'de> serde::de::Visitor<'de> for InetAddressVisitor { type Value = InetAddress; + #[inline] fn expecting(&self, formatter: &mut std::fmt::Formatter) -> std::fmt::Result { formatter.write_str("an InetAddress") } + #[inline] fn visit_bytes(self, v: &[u8]) -> Result where E: serde::de::Error, @@ -390,6 +393,7 @@ impl<'de> serde::de::Visitor<'de> for InetAddressVisitor { } } + #[inline] fn visit_str(self, v: &str) -> Result where E: serde::de::Error, @@ -399,6 +403,7 @@ impl<'de> serde::de::Visitor<'de> for InetAddressVisitor { } impl<'de> Deserialize<'de> for InetAddress { + #[inline] fn deserialize(deserializer: D) -> Result where D: Deserializer<'de>, @@ -420,7 +425,7 @@ impl InetAddress { /// Construct from IP and port. /// If the IP is not either 4 or 16 bytes in length, a nil/0 InetAddress is returned. - #[inline(always)] + #[inline] pub fn from_ip_port(ip: &[u8], port: u16) -> InetAddress { unsafe { let mut c = MaybeUninit::::uninit().assume_init(); // gets zeroed in set() @@ -570,6 +575,7 @@ impl InetAddress { } /// Get the IP port for this InetAddress. + #[inline] pub fn port(&self) -> u16 { unsafe { u16::from_be(match self.sa.sa_family as AddressFamilyType { diff --git a/network-hypervisor/src/vl1/mac.rs b/network-hypervisor/src/vl1/mac.rs index 72dc7934b..3e799a7e6 100644 --- a/network-hypervisor/src/vl1/mac.rs +++ b/network-hypervisor/src/vl1/mac.rs @@ -17,12 +17,6 @@ use zerotier_utils::marshalable::{Marshalable, UnmarshalError}; #[repr(transparent)] pub struct MAC(NonZeroU64); -impl Debug for MAC { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - f.write_str(&self.to_string()) - } -} - impl MAC { #[inline(always)] pub fn from_u64(i: u64) -> Option { @@ -122,6 +116,12 @@ impl Hash for MAC { } } +impl Debug for MAC { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + f.write_str(&self.to_string()) + } +} + impl Serialize for MAC { fn serialize(&self, serializer: S) -> Result where diff --git a/network-hypervisor/src/vl1/mod.rs b/network-hypervisor/src/vl1/mod.rs index 6725397ef..a41ee818c 100644 --- a/network-hypervisor/src/vl1/mod.rs +++ b/network-hypervisor/src/vl1/mod.rs @@ -24,6 +24,8 @@ pub use path::Path; pub use peer::Peer; pub use rootset::{Root, RootSet}; +pub use zerotier_crypto::verified::Verified; + #[cfg(feature = "debug_events")] #[allow(unused_macros)] #[macro_export] diff --git a/network-hypervisor/src/vl1/node.rs b/network-hypervisor/src/vl1/node.rs index e4a7227bd..00e1774a4 100644 --- a/network-hypervisor/src/vl1/node.rs +++ b/network-hypervisor/src/vl1/node.rs @@ -17,9 +17,9 @@ use crate::vl1::identity::Identity; use crate::vl1::path::{Path, PathServiceResult}; use crate::vl1::peer::Peer; use crate::vl1::rootset::RootSet; +use crate::vl1::Verified; use zerotier_crypto::random; -use zerotier_crypto::verified::Verified; use zerotier_utils::error::InvalidParameterError; use zerotier_utils::gate::IntervalGate; use zerotier_utils::hex; @@ -36,14 +36,14 @@ pub trait VL1AuthProvider: Sync + Send { /// /// If this returns false, the node simply drops messages on the floor and refuses /// to init V2 sessions. - fn should_respond_to(&self, id: &Identity) -> bool; + fn should_respond_to(&self, id: &Verified) -> bool; /// Check if this node has any trust relationship with the provided identity. /// /// This should return true if there is any special trust relationship such as mutual /// membership in a network or for controllers the peer's membership in any network /// they control. - fn has_trust_relationship(&self, id: &Identity) -> bool; + fn has_trust_relationship(&self, id: &Verified) -> bool; } /// Trait to be implemented by outside code to provide object storage to VL1 @@ -52,10 +52,10 @@ pub trait VL1AuthProvider: Sync + Send { /// implementers of HostSystem to break this out and allow a user to specify it. pub trait NodeStorage: Sync + Send { /// Load this node's identity from the data store. - fn load_node_identity(&self) -> Option; + fn load_node_identity(&self) -> Option>; /// Save this node's identity to the data store. - fn save_node_identity(&self, id: &Identity); + fn save_node_identity(&self, id: &Verified); } /// Trait implemented by external code to handle events and provide an interface to the system or application. @@ -112,7 +112,7 @@ pub trait HostSystem: VL1AuthProvider + NodeStorage + 'static { #[allow(unused_variables)] fn should_use_physical_path( &self, - id: &Identity, + id: &Verified, endpoint: &Endpoint, local_socket: Option<&HostSystemImpl::LocalSocket>, local_interface: Option<&HostSystemImpl::LocalInterface>, @@ -126,7 +126,7 @@ pub trait HostSystem: VL1AuthProvider + NodeStorage + 'static { #[allow(unused_variables)] fn get_path_hints( &self, - id: &Identity, + id: &Verified, ) -> Option< Vec<( Endpoint, @@ -272,7 +272,7 @@ pub struct Node { pub instance_id: [u8; 16], /// This node's identity and permanent keys. - pub identity: Identity, + pub identity: Verified, /// Interval latches for periodic background tasks. intervals: Mutex, @@ -306,7 +306,7 @@ impl Node { return Err(InvalidParameterError("no identity found and auto-generate not enabled")); } else { let id = Identity::generate(); - host_system.event(Event::IdentityAutoGenerated(id.clone())); + host_system.event(Event::IdentityAutoGenerated(id.as_ref().clone())); host_system.storage().save_node_identity(&id); id } @@ -319,7 +319,7 @@ impl Node { let old = id.clone(); if id.upgrade()? { host_system.storage().save_node_identity(&id); - host_system.event(Event::IdentityAutoUpgraded(old, id.clone())); + host_system.event(Event::IdentityAutoUpgraded(old.unwrap(), id.as_ref().clone())); } } @@ -477,7 +477,7 @@ impl Node { let (mut old_root_identities, address_collisions, new_roots, bad_identities, my_root_sets) = { let roots = self.roots.read().unwrap(); - let old_root_identities: Vec = roots.roots.iter().map(|(p, _)| p.identity.clone()).collect(); + let old_root_identities: Vec = roots.roots.iter().map(|(p, _)| p.identity.as_ref().clone()).collect(); let mut new_roots = HashMap::new(); let mut bad_identities = Vec::new(); let mut my_root_sets: Option> = None; @@ -497,7 +497,7 @@ impl Node { .read() .unwrap() .get(&m.identity.address) - .map_or(false, |p| !p.identity.eq(&m.identity)) + .map_or(false, |p| !p.identity.as_ref().eq(&m.identity)) || address_collision_check .insert(m.identity.address, &m.identity) .map_or(false, |old_id| !old_id.eq(&m.identity)) @@ -522,7 +522,8 @@ impl Node { if let Some(peer) = peers.get(&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, m.identity.clone(), time_ticks) { + if let Some(peer) = Peer::new(&self.identity, Verified::assume_verified(m.identity.clone()), time_ticks) + { drop(peers); new_roots.insert( self.peers @@ -557,7 +558,7 @@ impl Node { ))); } - let mut new_root_identities: Vec = new_roots.iter().map(|(p, _)| p.identity.clone()).collect(); + let mut new_root_identities: Vec = new_roots.iter().map(|(p, _)| p.identity.as_ref().clone()).collect(); old_root_identities.sort_unstable(); new_root_identities.sort_unstable(); @@ -991,7 +992,7 @@ impl Node { authoritative: bool, ) { if authoritative { - if received_identity.validate_identity() { + if let Some(received_identity) = received_identity.validate() { let mut whois_queue = self.whois_queue.lock().unwrap(); if let Some(qi) = whois_queue.get_mut(&received_identity.address) { let address = received_identity.address; @@ -1124,12 +1125,12 @@ impl InnerProtocol for DummyInnerProtocol {} impl VL1AuthProvider for DummyInnerProtocol { #[inline(always)] - fn should_respond_to(&self, id: &Identity) -> bool { + fn should_respond_to(&self, _: &Verified) -> bool { true } #[inline(always)] - fn has_trust_relationship(&self, id: &Identity) -> bool { + fn has_trust_relationship(&self, _: &Verified) -> bool { true } } diff --git a/network-hypervisor/src/vl1/peer.rs b/network-hypervisor/src/vl1/peer.rs index 16a2a20a1..9957f6403 100644 --- a/network-hypervisor/src/vl1/peer.rs +++ b/network-hypervisor/src/vl1/peer.rs @@ -18,13 +18,14 @@ use crate::protocol::*; use crate::vl1::address::Address; use crate::vl1::debug_event; use crate::vl1::node::*; +use crate::vl1::Verified; use crate::vl1::{Endpoint, Identity, Path}; use crate::{VERSION_MAJOR, VERSION_MINOR, VERSION_REVISION}; pub(crate) const SERVICE_INTERVAL_MS: i64 = 10000; pub struct Peer { - pub identity: Identity, + pub identity: Verified, v1_proto_static_secret: v1::SymmetricSecret, paths: Mutex>, @@ -61,7 +62,7 @@ impl Peer { /// /// This only returns None if this_node_identity does not have its secrets or if some /// fatal error occurs performing key agreement between the two identities. - pub(crate) fn new(this_node_identity: &Identity, id: Identity, time_ticks: i64) -> Option { + pub(crate) fn new(this_node_identity: &Verified, id: Verified, time_ticks: i64) -> Option { this_node_identity.agree(&id).map(|static_secret| -> Self { Self { identity: id, diff --git a/network-hypervisor/src/vl1/rootset.rs b/network-hypervisor/src/vl1/rootset.rs index 6c78ad279..7fbeb823f 100644 --- a/network-hypervisor/src/vl1/rootset.rs +++ b/network-hypervisor/src/vl1/rootset.rs @@ -119,7 +119,7 @@ impl RootSet { } } - return Some(Verified(self)); + return Some(Verified::assume_verified(self)); } /// Add a member to this definition, replacing any current entry with this address. diff --git a/network-hypervisor/src/vl2/multicastauthority.rs b/network-hypervisor/src/vl2/multicastauthority.rs index 6a3f47409..be5805eab 100644 --- a/network-hypervisor/src/vl2/multicastauthority.rs +++ b/network-hypervisor/src/vl2/multicastauthority.rs @@ -21,7 +21,6 @@ pub struct MulticastAuthority { } impl MulticastAuthority { - #[inline] pub fn new() -> Self { Self { subscriptions: RwLock::new(HashMap::new()) } } diff --git a/network-hypervisor/src/vl2/multicastgroup.rs b/network-hypervisor/src/vl2/multicastgroup.rs index f5cdcf0e2..ad0f86a02 100644 --- a/network-hypervisor/src/vl2/multicastgroup.rs +++ b/network-hypervisor/src/vl2/multicastgroup.rs @@ -1,5 +1,6 @@ // (c) 2020-2022 ZeroTier, Inc. -- currently propritery pending actual release and licensing. See LICENSE.md. +use std::fmt::Debug; use std::hash::{Hash, Hasher}; use std::str::FromStr; @@ -34,6 +35,20 @@ impl Hash for MulticastGroup { state.write_u32(self.adi); } } + +impl ToString for MulticastGroup { + fn to_string(&self) -> String { + format!("{}/{}", self.mac.to_string(), self.adi.to_string()) + } +} + +impl Debug for MulticastGroup { + #[inline] + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + f.write_str(self.to_string().as_str()) + } +} + impl Serialize for MulticastGroup { fn serialize(&self, serializer: S) -> Result where diff --git a/network-hypervisor/src/vl2/networkconfig.rs b/network-hypervisor/src/vl2/networkconfig.rs index d48f81fb7..a2bcaa130 100644 --- a/network-hypervisor/src/vl2/networkconfig.rs +++ b/network-hypervisor/src/vl2/networkconfig.rs @@ -17,7 +17,7 @@ use zerotier_utils::error::InvalidParameterError; use zerotier_utils::marshalable::Marshalable; /// Network configuration object sent to nodes by network controllers. -#[derive(Clone, Serialize, Deserialize, PartialEq, Eq)] +#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)] pub struct NetworkConfig { /// Network ID pub network_id: NetworkId, @@ -420,7 +420,7 @@ mod proto_v1_field_name { } /// SSO authentication configuration object. -#[derive(Clone, Serialize, Deserialize, PartialEq, Eq)] +#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)] pub struct SSOAuthConfiguration { pub version: u32, pub authentication_url: String, @@ -435,7 +435,7 @@ pub struct SSOAuthConfiguration { /// /// These are also handed out to V2 nodes to use when communicating with V1 nodes on /// networks that support older protocol versions. -#[derive(Clone, Serialize, Deserialize, PartialEq, Eq)] +#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)] pub struct V1Credentials { pub revision: u64, pub max_delta: u64, @@ -447,7 +447,7 @@ pub struct V1Credentials { } /// Statically pushed L3 IP routes included with a network configuration. -#[derive(Clone, Serialize, Deserialize, PartialEq, Eq, Hash)] +#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq, Hash)] pub struct IpRoute { pub target: InetAddress, #[serde(skip_serializing_if = "Option::is_none")] diff --git a/network-hypervisor/src/vl2/networkid.rs b/network-hypervisor/src/vl2/networkid.rs index 38c4a3948..c84db8727 100644 --- a/network-hypervisor/src/vl2/networkid.rs +++ b/network-hypervisor/src/vl2/networkid.rs @@ -1,5 +1,6 @@ // (c) 2020-2022 ZeroTier, Inc. -- currently propritery pending actual release and licensing. See LICENSE.md. +use std::fmt::Debug; use std::hash::{Hash, Hasher}; use std::num::NonZeroU64; use std::str::FromStr; @@ -97,6 +98,13 @@ impl ToString for NetworkId { } } +impl Debug for NetworkId { + #[inline] + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + f.write_str(self.to_string().as_str()) + } +} + impl FromStr for NetworkId { type Err = InvalidFormatError; diff --git a/network-hypervisor/src/vl2/rule.rs b/network-hypervisor/src/vl2/rule.rs index 024d7661f..73391080c 100644 --- a/network-hypervisor/src/vl2/rule.rs +++ b/network-hypervisor/src/vl2/rule.rs @@ -1,4 +1,6 @@ +use std::fmt::Debug; use std::mem::{size_of, zeroed}; +use std::net::Ipv6Addr; use serde::{Deserialize, Deserializer, Serialize, Serializer}; @@ -159,44 +161,44 @@ union RuleValue { /// Trait to implement in order to evaluate rules. pub trait RuleVisitor { - fn action_drop(self) -> bool; - fn action_accept(self) -> bool; - fn action_tee(self, address: Address, flags: u32, length: u16) -> bool; - fn action_watch(self, address: Address, flags: u32, length: u16) -> bool; - fn action_redirect(self, address: Address, flags: u32, length: u16) -> bool; - fn action_break(self) -> bool; - fn action_priority(self, qos_bucket: u8) -> bool; + fn action_drop(&mut self) -> bool; + fn action_accept(&mut self) -> bool; + fn action_tee(&mut self, address: Address, flags: u32, length: u16) -> bool; + fn action_watch(&mut self, address: Address, flags: u32, length: u16) -> bool; + fn action_redirect(&mut self, address: Address, flags: u32, length: u16) -> bool; + fn action_break(&mut self) -> bool; + fn action_priority(&mut self, qos_bucket: u8) -> bool; - fn invalid_rule(self) -> bool; + fn invalid_rule(&mut self) -> bool; - fn match_source_zerotier_address(self, not: bool, or: bool, address: Address); - fn match_dest_zerotier_address(self, not: bool, or: bool, address: Address); - fn match_vlan_id(self, not: bool, or: bool, id: u16); - fn match_vlan_pcp(self, not: bool, or: bool, pcp: u8); - fn match_vlan_dei(self, not: bool, or: bool, dei: u8); - fn match_mac_source(self, not: bool, or: bool, mac: MAC); - fn match_mac_dest(self, not: bool, or: bool, mac: MAC); - fn match_ipv4_source(self, not: bool, or: bool, ip: &[u8; 4], mask: u8); - fn match_ipv4_dest(self, not: bool, or: bool, ip: &[u8; 4], mask: u8); - fn match_ipv6_source(self, not: bool, or: bool, ip: &[u8; 16], mask: u8); - fn match_ipv6_dest(self, not: bool, or: bool, ip: &[u8; 16], mask: u8); - fn match_ip_tos(self, not: bool, or: bool, mask: u8, start: u8, end: u8); - fn match_ip_protocol(self, not: bool, or: bool, protocol: u8); - fn match_ethertype(self, not: bool, or: bool, ethertype: u16); - fn match_icmp(self, not: bool, or: bool, _type: u8, code: u8, flags: u8); - fn match_ip_source_port_range(self, not: bool, or: bool, start: u16, end: u16); - fn match_ip_dest_port_range(self, not: bool, or: bool, start: u16, end: u16); - fn match_characteristics(self, not: bool, or: bool, characteristics: u64); - fn match_frame_size_range(self, not: bool, or: bool, start: u16, end: u16); - fn match_random(self, not: bool, or: bool, probability: u32); - fn match_tags_difference(self, not: bool, or: bool, id: u32, value: u32); - fn match_tags_bitwise_and(self, not: bool, or: bool, id: u32, value: u32); - fn match_tags_bitwise_or(self, not: bool, or: bool, id: u32, value: u32); - fn match_tags_bitwise_xor(self, not: bool, or: bool, id: u32, value: u32); - fn match_tags_equal(self, not: bool, or: bool, id: u32, value: u32); - fn match_tag_sender(self, not: bool, or: bool, id: u32, value: u32); - fn match_tag_receiver(self, not: bool, or: bool, id: u32, value: u32); - fn match_integer_range(self, not: bool, or: bool, start: u64, end: u64, idx: u16, format: u8); + fn match_source_zerotier_address(&mut self, not: bool, or: bool, address: Address); + fn match_dest_zerotier_address(&mut self, not: bool, or: bool, address: Address); + fn match_vlan_id(&mut self, not: bool, or: bool, id: u16); + fn match_vlan_pcp(&mut self, not: bool, or: bool, pcp: u8); + fn match_vlan_dei(&mut self, not: bool, or: bool, dei: u8); + fn match_mac_source(&mut self, not: bool, or: bool, mac: MAC); + fn match_mac_dest(&mut self, not: bool, or: bool, mac: MAC); + fn match_ipv4_source(&mut self, not: bool, or: bool, ip: &[u8; 4], mask: u8); + fn match_ipv4_dest(&mut self, not: bool, or: bool, ip: &[u8; 4], mask: u8); + fn match_ipv6_source(&mut self, not: bool, or: bool, ip: &[u8; 16], mask: u8); + fn match_ipv6_dest(&mut self, not: bool, or: bool, ip: &[u8; 16], mask: u8); + fn match_ip_tos(&mut self, not: bool, or: bool, mask: u8, start: u8, end: u8); + fn match_ip_protocol(&mut self, not: bool, or: bool, protocol: u8); + fn match_ethertype(&mut self, not: bool, or: bool, ethertype: u16); + fn match_icmp(&mut self, not: bool, or: bool, _type: u8, code: u8, flags: u8); + fn match_ip_source_port_range(&mut self, not: bool, or: bool, start: u16, end: u16); + fn match_ip_dest_port_range(&mut self, not: bool, or: bool, start: u16, end: u16); + fn match_characteristics(&mut self, not: bool, or: bool, characteristics: u64); + fn match_frame_size_range(&mut self, not: bool, or: bool, start: u16, end: u16); + fn match_random(&mut self, not: bool, or: bool, probability: u32); + fn match_tags_difference(&mut self, not: bool, or: bool, id: u32, value: u32); + fn match_tags_bitwise_and(&mut self, not: bool, or: bool, id: u32, value: u32); + fn match_tags_bitwise_or(&mut self, not: bool, or: bool, id: u32, value: u32); + fn match_tags_bitwise_xor(&mut self, not: bool, or: bool, id: u32, value: u32); + fn match_tags_equal(&mut self, not: bool, or: bool, id: u32, value: u32); + fn match_tag_sender(&mut self, not: bool, or: bool, id: u32, value: u32); + fn match_tag_receiver(&mut self, not: bool, or: bool, id: u32, value: u32); + fn match_integer_range(&mut self, not: bool, or: bool, start: u64, end: u64, idx: u16, format: u8); } #[repr(C, packed)] @@ -220,8 +222,8 @@ impl Rule { } /// Execute the visitor, returning the result of action methods and true for condition methods. - #[inline(always)] - pub fn visit(&self, v: V) -> bool { + #[inline] + pub fn visit(&self, v: &mut V) -> bool { unsafe { let t = self.t; let not = (t & 0x80) != 0; @@ -576,6 +578,7 @@ impl Marshalable for Rule { } impl PartialEq for Rule { + #[inline] fn eq(&self, other: &Self) -> bool { unsafe { (*(self as *const Self).cast::<[u8; size_of::()]>()).eq(&*(other as *const Self).cast::<[u8; size_of::()]>()) } } @@ -583,6 +586,21 @@ impl PartialEq for Rule { impl Eq for Rule {} +impl ToString for Rule { + fn to_string(&self) -> String { + let mut s = RuleStringer(String::with_capacity(32)); + self.visit(&mut s); + s.0 + } +} + +impl Debug for Rule { + #[inline] + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + f.write_str(self.to_string().as_str()) + } +} + impl Serialize for Rule { fn serialize(&self, serializer: S) -> Result where @@ -675,7 +693,7 @@ static HR_NAME_TO_RULE_TYPE: phf::Map<&'static str, u8> = phf_map! { "MATCH_TAGS_EQUAL" => match_cond::TAGS_EQUAL, "MATCH_TAG_SENDER" => match_cond::TAG_SENDER, "MATCH_TAG_RECEIVER" => match_cond::TAG_RECEIVER, - "INTEGER_RANGE" => match_cond::INTEGER_RANGE, + "MATCH_INTEGER_RANGE" => match_cond::INTEGER_RANGE, }; /// A "bag of fields" used to serialize/deserialize rules in human readable form e.g. JSON. @@ -860,21 +878,22 @@ impl<'a> HumanReadableRule<'a> { fn from_rule(r: &Rule) -> Self { let mut hr = HumanReadableRule::default(); - r.visit(MakeHumanReadable(&mut hr)); + r.visit(&mut MakeHumanReadable(&mut hr)); hr } } +#[repr(transparent)] struct MakeHumanReadable<'a>(&'a mut HumanReadableRule<'static>); impl<'a> MakeHumanReadable<'a> { - fn do_tag(self, t: &'static str, not: bool, or: bool, id: u32, value: u32) { + fn do_tag(&mut self, t: &'static str, not: bool, or: bool, id: u32, value: u32) { let _ = self.0.id.insert(id); let _ = self.0.value.insert(value); self.do_cond(t, not, or); } - fn do_cond(self, t: &'static str, not: bool, or: bool) { + fn do_cond(&mut self, t: &'static str, not: bool, or: bool) { self.0._type = t; if not { let _ = self.0.not.insert(not); @@ -887,19 +906,19 @@ impl<'a> MakeHumanReadable<'a> { impl<'a> RuleVisitor for MakeHumanReadable<'a> { #[inline(always)] - fn action_drop(self) -> bool { + fn action_drop(&mut self) -> bool { self.0._type = "ACTION_DROP"; true } #[inline(always)] - fn action_accept(self) -> bool { + fn action_accept(&mut self) -> bool { self.0._type = "ACTION_ACCEPT"; true } #[inline(always)] - fn action_tee(self, address: Address, flags: u32, length: u16) -> bool { + fn action_tee(&mut self, address: Address, flags: u32, length: u16) -> bool { self.0._type = "ACTION_TEE"; let _ = self.0.address.insert(address); let _ = self.0.flags.insert(flags); @@ -908,7 +927,7 @@ impl<'a> RuleVisitor for MakeHumanReadable<'a> { } #[inline(always)] - fn action_watch(self, address: Address, flags: u32, length: u16) -> bool { + fn action_watch(&mut self, address: Address, flags: u32, length: u16) -> bool { self.0._type = "ACTION_WATCH"; let _ = self.0.address.insert(address); let _ = self.0.flags.insert(flags); @@ -917,7 +936,7 @@ impl<'a> RuleVisitor for MakeHumanReadable<'a> { } #[inline(always)] - fn action_redirect(self, address: Address, flags: u32, length: u16) -> bool { + fn action_redirect(&mut self, address: Address, flags: u32, length: u16) -> bool { self.0._type = "ACTION_REDIRECT"; let _ = self.0.address.insert(address); let _ = self.0.flags.insert(flags); @@ -926,91 +945,91 @@ impl<'a> RuleVisitor for MakeHumanReadable<'a> { } #[inline(always)] - fn action_break(self) -> bool { + fn action_break(&mut self) -> bool { self.0._type = "ACTION_BREAK"; true } #[inline(always)] - fn action_priority(self, qos_bucket: u8) -> bool { + fn action_priority(&mut self, qos_bucket: u8) -> bool { self.0._type = "ACTION_PRIORITY"; let _ = self.0.qosBucket.insert(qos_bucket); true } #[inline(always)] - fn invalid_rule(self) -> bool { + fn invalid_rule(&mut self) -> bool { false } #[inline(always)] - fn match_source_zerotier_address(self, not: bool, or: bool, address: Address) { + fn match_source_zerotier_address(&mut self, not: bool, or: bool, address: Address) { let _ = self.0.zt.insert(address); self.do_cond("MATCH_SOURCE_ZEROTIER_ADDRESS", not, or); } #[inline(always)] - fn match_dest_zerotier_address(self, not: bool, or: bool, address: Address) { + fn match_dest_zerotier_address(&mut self, not: bool, or: bool, address: Address) { let _ = self.0.zt.insert(address); self.do_cond("MATCH_DEST_ZEROTIER_ADDRESS", not, or); } #[inline(always)] - fn match_vlan_id(self, not: bool, or: bool, id: u16) { + fn match_vlan_id(&mut self, not: bool, or: bool, id: u16) { let _ = self.0.vlanId.insert(id); self.do_cond("MATCH_VLAN_ID", not, or); } #[inline(always)] - fn match_vlan_pcp(self, not: bool, or: bool, pcp: u8) { + fn match_vlan_pcp(&mut self, not: bool, or: bool, pcp: u8) { let _ = self.0.vlanPcp.insert(pcp); self.do_cond("MATCH_VLAN_PCP", not, or); } #[inline(always)] - fn match_vlan_dei(self, not: bool, or: bool, dei: u8) { + fn match_vlan_dei(&mut self, not: bool, or: bool, dei: u8) { let _ = self.0.vlanDei.insert(dei); self.do_cond("MATCH_VLAN_DEI", not, or); } #[inline(always)] - fn match_mac_source(self, not: bool, or: bool, mac: MAC) { + fn match_mac_source(&mut self, not: bool, or: bool, mac: MAC) { let _ = self.0.mac.insert(mac); self.do_cond("MATCH_MAC_SOURCE", not, or); } #[inline(always)] - fn match_mac_dest(self, not: bool, or: bool, mac: MAC) { + fn match_mac_dest(&mut self, not: bool, or: bool, mac: MAC) { let _ = self.0.mac.insert(mac); self.do_cond("MATCH_MAC_DEST", not, or); } #[inline(always)] - fn match_ipv4_source(self, not: bool, or: bool, ip: &[u8; 4], mask: u8) { + fn match_ipv4_source(&mut self, not: bool, or: bool, ip: &[u8; 4], mask: u8) { let _ = self.0.ip.insert(InetAddress::from_ip_port(ip, mask as u16)); self.do_cond("MATCH_IPV4_SOURCE", not, or); } #[inline(always)] - fn match_ipv4_dest(self, not: bool, or: bool, ip: &[u8; 4], mask: u8) { + fn match_ipv4_dest(&mut self, not: bool, or: bool, ip: &[u8; 4], mask: u8) { let _ = self.0.ip.insert(InetAddress::from_ip_port(ip, mask as u16)); self.do_cond("MATCH_IPV4_DEST", not, or); } #[inline(always)] - fn match_ipv6_source(self, not: bool, or: bool, ip: &[u8; 16], mask: u8) { + fn match_ipv6_source(&mut self, not: bool, or: bool, ip: &[u8; 16], mask: u8) { let _ = self.0.ip.insert(InetAddress::from_ip_port(ip, mask as u16)); self.do_cond("MATCH_IPV6_SOURCE", not, or); } #[inline(always)] - fn match_ipv6_dest(self, not: bool, or: bool, ip: &[u8; 16], mask: u8) { + fn match_ipv6_dest(&mut self, not: bool, or: bool, ip: &[u8; 16], mask: u8) { let _ = self.0.ip.insert(InetAddress::from_ip_port(ip, mask as u16)); self.do_cond("MATCH_IPV6_DEST", not, or); } #[inline(always)] - fn match_ip_tos(self, not: bool, or: bool, mask: u8, start: u8, end: u8) { + fn match_ip_tos(&mut self, not: bool, or: bool, mask: u8, start: u8, end: u8) { let _ = self.0.mask.insert(mask as u64); let _ = self.0.start.insert(start as u64); let _ = self.0.end.insert(end as u64); @@ -1018,19 +1037,19 @@ impl<'a> RuleVisitor for MakeHumanReadable<'a> { } #[inline(always)] - fn match_ip_protocol(self, not: bool, or: bool, protocol: u8) { + fn match_ip_protocol(&mut self, not: bool, or: bool, protocol: u8) { let _ = self.0.ipProtocol.insert(protocol); self.do_cond("MATCH_IP_PROTOCOL", not, or); } #[inline(always)] - fn match_ethertype(self, not: bool, or: bool, ethertype: u16) { + fn match_ethertype(&mut self, not: bool, or: bool, ethertype: u16) { let _ = self.0.etherType.insert(ethertype); self.do_cond("MATCH_ETHERTYPE", not, or); } #[inline(always)] - fn match_icmp(self, not: bool, or: bool, _type: u8, code: u8, flags: u8) { + fn match_icmp(&mut self, not: bool, or: bool, _type: u8, code: u8, flags: u8) { let _ = self.0.icmpType.insert(_type); if (flags & 0x01) != 0 { let _ = self.0.icmpCode.insert(code); @@ -1039,80 +1058,662 @@ impl<'a> RuleVisitor for MakeHumanReadable<'a> { } #[inline(always)] - fn match_ip_source_port_range(self, not: bool, or: bool, start: u16, end: u16) { + fn match_ip_source_port_range(&mut self, not: bool, or: bool, start: u16, end: u16) { let _ = self.0.start.insert(start as u64); let _ = self.0.end.insert(end as u64); self.do_cond("MATCH_IP_SOURCE_PORT_RANGE", not, or); } #[inline(always)] - fn match_ip_dest_port_range(self, not: bool, or: bool, start: u16, end: u16) { + fn match_ip_dest_port_range(&mut self, not: bool, or: bool, start: u16, end: u16) { let _ = self.0.start.insert(start as u64); let _ = self.0.end.insert(end as u64); self.do_cond("MATCH_IP_DEST_PORT_RANGE", not, or); } #[inline(always)] - fn match_characteristics(self, not: bool, or: bool, characteristics: u64) { + fn match_characteristics(&mut self, not: bool, or: bool, characteristics: u64) { let _ = self.0.mask.insert(characteristics); self.do_cond("MATCH_CHARACTERISTICS", not, or); } #[inline(always)] - fn match_frame_size_range(self, not: bool, or: bool, start: u16, end: u16) { + fn match_frame_size_range(&mut self, not: bool, or: bool, start: u16, end: u16) { let _ = self.0.start.insert(start as u64); let _ = self.0.end.insert(end as u64); self.do_cond("MATCH_FRAME_SIZE_RANGE", not, or); } #[inline(always)] - fn match_random(self, not: bool, or: bool, probability: u32) { + fn match_random(&mut self, not: bool, or: bool, probability: u32) { let _ = self.0.probability.insert(probability); self.do_cond("MATCH_RANDOM", not, or); } #[inline(always)] - fn match_tags_difference(self, not: bool, or: bool, id: u32, value: u32) { + fn match_tags_difference(&mut self, not: bool, or: bool, id: u32, value: u32) { self.do_tag("MATCH_TAGS_DIFFERENCE", not, or, id, value); } #[inline(always)] - fn match_tags_bitwise_and(self, not: bool, or: bool, id: u32, value: u32) { + fn match_tags_bitwise_and(&mut self, not: bool, or: bool, id: u32, value: u32) { self.do_tag("MATCH_TAGS_BITWISE_AND", not, or, id, value); } #[inline(always)] - fn match_tags_bitwise_or(self, not: bool, or: bool, id: u32, value: u32) { + fn match_tags_bitwise_or(&mut self, not: bool, or: bool, id: u32, value: u32) { self.do_tag("MATCH_TAGS_BITWISE_OR", not, or, id, value); } #[inline(always)] - fn match_tags_bitwise_xor(self, not: bool, or: bool, id: u32, value: u32) { + fn match_tags_bitwise_xor(&mut self, not: bool, or: bool, id: u32, value: u32) { self.do_tag("MATCH_TAGS_BITWISE_XOR", not, or, id, value); } #[inline(always)] - fn match_tags_equal(self, not: bool, or: bool, id: u32, value: u32) { + fn match_tags_equal(&mut self, not: bool, or: bool, id: u32, value: u32) { self.do_tag("MATCH_TAGS_EQUAL", not, or, id, value); } #[inline(always)] - fn match_tag_sender(self, not: bool, or: bool, id: u32, value: u32) { + fn match_tag_sender(&mut self, not: bool, or: bool, id: u32, value: u32) { self.do_tag("MATCH_TAG_SENDER", not, or, id, value); } #[inline(always)] - fn match_tag_receiver(self, not: bool, or: bool, id: u32, value: u32) { + fn match_tag_receiver(&mut self, not: bool, or: bool, id: u32, value: u32) { self.do_tag("MATCH_TAG_RECEIVER", not, or, id, value); } #[inline(always)] - fn match_integer_range(self, not: bool, or: bool, start: u64, end: u64, idx: u16, format: u8) { + fn match_integer_range(&mut self, not: bool, or: bool, start: u64, end: u64, idx: u16, format: u8) { let _ = self.0.start.insert(start); let _ = self.0.end.insert(end); let _ = self.0.idx.insert(idx); let _ = self.0.little.insert((format & 0x80) != 0); let _ = self.0.bits.insert((format & 63) + 1); - self.do_cond("INTEGER_RANGE", not, or); + self.do_cond("MATCH_INTEGER_RANGE", not, or); + } +} + +#[repr(transparent)] +struct RuleStringer(String); + +impl RuleVisitor for RuleStringer { + #[inline(always)] + fn action_drop(&mut self) -> bool { + self.0.push_str("ACTION_DROP"); + true + } + + #[inline(always)] + fn action_accept(&mut self) -> bool { + self.0.push_str("ACTION_ACCEPT"); + true + } + + #[inline(always)] + fn action_tee(&mut self, address: Address, flags: u32, length: u16) -> bool { + self.0 = format!("ACTION_TEE({}, {}, {})", address.to_string(), flags, length); + true + } + + #[inline(always)] + fn action_watch(&mut self, address: Address, flags: u32, length: u16) -> bool { + self.0 = format!("ACTION_WATCH({}, {}, {})", address.to_string(), flags, length); + true + } + + #[inline(always)] + fn action_redirect(&mut self, address: Address, flags: u32, length: u16) -> bool { + self.0 = format!("ACTION_REDIRECT({}, {}, {})", address.to_string(), flags, length); + true + } + + #[inline(always)] + fn action_break(&mut self) -> bool { + self.0.push_str("ACTION_BREAK"); + true + } + + #[inline(always)] + fn action_priority(&mut self, qos_bucket: u8) -> bool { + self.0 = format!("ACTION_PRIORITY({})", qos_bucket); + true + } + + #[inline(always)] + fn invalid_rule(&mut self) -> bool { + self.0.push_str("(INVALID RULE)"); + false + } + + #[inline(always)] + fn match_source_zerotier_address(&mut self, not: bool, or: bool, address: Address) { + self.0 = format!( + "MATCH_SOURCE_ZEROTIER_ADDRESS({}{}{})", + if or { + "|" + } else { + "" + }, + if not { + "!" + } else { + "" + }, + address.to_string() + ); + } + + #[inline(always)] + fn match_dest_zerotier_address(&mut self, not: bool, or: bool, address: Address) { + self.0 = format!( + "MATCH_DEST_ZEROTIER_ADDRESS({}{}{})", + if or { + "|" + } else { + "" + }, + if not { + "!" + } else { + "" + }, + address.to_string() + ); + } + + #[inline(always)] + fn match_vlan_id(&mut self, not: bool, or: bool, id: u16) { + self.0 = format!( + "MATCH_VLAN_ID({}{}{})", + if or { + "|" + } else { + "" + }, + if not { + "!" + } else { + "" + }, + id + ); + } + + #[inline(always)] + fn match_vlan_pcp(&mut self, not: bool, or: bool, pcp: u8) { + self.0 = format!( + "MATCH_VLAN_PCP({}{}{})", + if or { + "|" + } else { + "" + }, + if not { + "!" + } else { + "" + }, + pcp + ); + } + + #[inline(always)] + fn match_vlan_dei(&mut self, not: bool, or: bool, dei: u8) { + self.0 = format!( + "MATCH_VLAN_DEI({}{}{})", + if or { + "|" + } else { + "" + }, + if not { + "!" + } else { + "" + }, + dei + ); + } + + #[inline(always)] + fn match_mac_source(&mut self, not: bool, or: bool, mac: MAC) { + self.0 = format!( + "MATCH_MAC_SOURCE({}{}{})", + if or { + "|" + } else { + "" + }, + if not { + "!" + } else { + "" + }, + mac.to_string() + ); + } + + #[inline(always)] + fn match_mac_dest(&mut self, not: bool, or: bool, mac: MAC) { + self.0 = format!( + "MATCH_MAC_DEST({}{}{})", + if or { + "|" + } else { + "" + }, + if not { + "!" + } else { + "" + }, + mac.to_string() + ); + } + + #[inline(always)] + fn match_ipv4_source(&mut self, not: bool, or: bool, ip: &[u8; 4], mask: u8) { + self.0 = format!( + "MATCH_IPV4_SOURCE({}{}{}.{}.{}.{}/{})", + if or { + "|" + } else { + "" + }, + if not { + "!" + } else { + "" + }, + ip[0], + ip[1], + ip[2], + ip[3], + mask + ); + } + + #[inline(always)] + fn match_ipv4_dest(&mut self, not: bool, or: bool, ip: &[u8; 4], mask: u8) { + self.0 = format!( + "MATCH_IPV4_DEST({}{}{}.{}.{}.{}/{})", + if or { + "|" + } else { + "" + }, + if not { + "!" + } else { + "" + }, + ip[0], + ip[1], + ip[2], + ip[3], + mask + ); + } + + fn match_ipv6_source(&mut self, not: bool, or: bool, ip: &[u8; 16], mask: u8) { + self.0 = format!( + "MATCH_IPV6_SOURCE({}{}{}/{})", + if or { + "|" + } else { + "" + }, + if not { + "!" + } else { + "" + }, + Ipv6Addr::from(*ip).to_string(), + mask + ); + } + + fn match_ipv6_dest(&mut self, not: bool, or: bool, ip: &[u8; 16], mask: u8) { + self.0 = format!( + "MATCH_IPV6_DEST({}{}{}/{})", + if or { + "|" + } else { + "" + }, + if not { + "!" + } else { + "" + }, + Ipv6Addr::from(*ip).to_string(), + mask + ); + } + + #[inline(always)] + fn match_ip_tos(&mut self, not: bool, or: bool, mask: u8, start: u8, end: u8) { + self.0 = format!( + "MATCH_IP_TOS({}{}{}&{}-{})", + if or { + "|" + } else { + "" + }, + if not { + "!" + } else { + "" + }, + mask, + start, + end + ); + } + + #[inline(always)] + fn match_ip_protocol(&mut self, not: bool, or: bool, protocol: u8) { + self.0 = format!( + "MATCH_IP_PROTOCOL({}{}{})", + if or { + "|" + } else { + "" + }, + if not { + "!" + } else { + "" + }, + protocol + ); + } + + #[inline(always)] + fn match_ethertype(&mut self, not: bool, or: bool, ethertype: u16) { + self.0 = format!( + "MATCH_ETHERTYPE({}{}{})", + if or { + "|" + } else { + "" + }, + if not { + "!" + } else { + "" + }, + ethertype + ); + } + + #[inline(always)] + fn match_icmp(&mut self, not: bool, or: bool, _type: u8, code: u8, flags: u8) { + self.0 = format!( + "MATCH_ICMP({}{} {}, {}, {})", + if or { + "|" + } else { + "" + }, + if not { + "!" + } else { + "" + }, + _type, + code, + flags + ); + } + + #[inline(always)] + fn match_ip_source_port_range(&mut self, not: bool, or: bool, start: u16, end: u16) { + self.0 = format!( + "MATCH_IP_SOURCE_PORT_RANGE({}{}{}-{})", + if or { + "|" + } else { + "" + }, + if not { + "!" + } else { + "" + }, + start, + end + ); + } + + #[inline(always)] + fn match_ip_dest_port_range(&mut self, not: bool, or: bool, start: u16, end: u16) { + self.0 = format!( + "MATCH_IP_DEST_PORT_RANGE({}{}{}-{})", + if or { + "|" + } else { + "" + }, + if not { + "!" + } else { + "" + }, + start, + end + ); + } + + #[inline(always)] + fn match_characteristics(&mut self, not: bool, or: bool, characteristics: u64) { + self.0 = format!( + "MATCH_IP_CHARACTERISTICS({}{}{})", + if or { + "|" + } else { + "" + }, + if not { + "!" + } else { + "" + }, + zerotier_utils::hex::to_string_u64(characteristics, false) + ); + } + + #[inline(always)] + fn match_frame_size_range(&mut self, not: bool, or: bool, start: u16, end: u16) { + self.0 = format!( + "MATCH_FRAME_SIZE_RANGE({}{}{}-{})", + if or { + "|" + } else { + "" + }, + if not { + "!" + } else { + "" + }, + start, + end + ); + } + + #[inline(always)] + fn match_random(&mut self, not: bool, or: bool, probability: u32) { + self.0 = format!( + "MATCH_RANDOM({}{}{})", + if or { + "|" + } else { + "" + }, + if not { + "!" + } else { + "" + }, + probability + ); + } + + #[inline(always)] + fn match_tags_difference(&mut self, not: bool, or: bool, id: u32, value: u32) { + self.0 = format!( + "MATCH_TAGS_DIFFERENCE({}{}{}<>{})", + if or { + "|" + } else { + "" + }, + if not { + "!" + } else { + "" + }, + id, + value + ); + } + + #[inline(always)] + fn match_tags_bitwise_and(&mut self, not: bool, or: bool, id: u32, value: u32) { + self.0 = format!( + "MATCH_TAGS_BITWISE_AND({}{}{}&{}!=0)", + if or { + "|" + } else { + "" + }, + if not { + "!" + } else { + "" + }, + id, + value + ); + } + + #[inline(always)] + fn match_tags_bitwise_or(&mut self, not: bool, or: bool, id: u32, value: u32) { + self.0 = format!( + "MATCH_TAGS_BITWISE_OR({}{}{}|{}!=0)", + if or { + "|" + } else { + "" + }, + if not { + "!" + } else { + "" + }, + id, + value + ); + } + + #[inline(always)] + fn match_tags_bitwise_xor(&mut self, not: bool, or: bool, id: u32, value: u32) { + self.0 = format!( + "MATCH_TAGS_BITWISE_XOR({}{}{}^{}!=0)", + if or { + "|" + } else { + "" + }, + if not { + "!" + } else { + "" + }, + id, + value + ); + } + + #[inline(always)] + fn match_tags_equal(&mut self, not: bool, or: bool, id: u32, value: u32) { + self.0 = format!( + "MATCH_TAGS_EQUAL({}{}{}=={})", + if or { + "|" + } else { + "" + }, + if not { + "!" + } else { + "" + }, + id, + value + ); + } + + #[inline(always)] + fn match_tag_sender(&mut self, not: bool, or: bool, id: u32, value: u32) { + self.0 = format!( + "MATCH_TAG_SENDER({}{}{}=={})", + if or { + "|" + } else { + "" + }, + if not { + "!" + } else { + "" + }, + id, + value + ); + } + + #[inline(always)] + fn match_tag_receiver(&mut self, not: bool, or: bool, id: u32, value: u32) { + self.0 = format!( + "MATCH_TAG_RECEIVER({}{}{}=={})", + if or { + "|" + } else { + "" + }, + if not { + "!" + } else { + "" + }, + id, + value + ); + } + + #[inline(always)] + fn match_integer_range(&mut self, not: bool, or: bool, start: u64, end: u64, idx: u16, format: u8) { + self.0 = format!( + "MATCH_INTEGER_RANGE({}{}({}, {}, {}, {}))", + if or { + "|" + } else { + "" + }, + if not { + "!" + } else { + "" + }, + start, + end, + idx, + format + ); } } diff --git a/network-hypervisor/src/vl2/v1/certificateofmembership.rs b/network-hypervisor/src/vl2/v1/certificateofmembership.rs index 53c2db80b..3e18ae309 100644 --- a/network-hypervisor/src/vl2/v1/certificateofmembership.rs +++ b/network-hypervisor/src/vl2/v1/certificateofmembership.rs @@ -21,7 +21,7 @@ use zerotier_utils::memory; /// This was done to permit some things such as geo-fencing that were never implemented, so it's /// a bit of a case of YAGNI. In V2 this is deprecated in favor of a more standard sort of /// certificate. -#[derive(Clone, Serialize, Deserialize, PartialEq, Eq)] +#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)] pub struct CertificateOfMembership { pub network_id: NetworkId, pub timestamp: i64, @@ -173,7 +173,7 @@ impl CertificateOfMembership { pub fn verify(self, issuer: &Identity, expect_issued_to: &Identity) -> Option> { if Self::v1_proto_issued_to_fingerprint(expect_issued_to).eq(&self.issued_to_fingerprint.as_bytes()[..32]) { if issuer.verify(&self.v1_proto_get_qualifier_bytes(), self.signature.as_bytes()) { - return Some(Verified(self)); + return Some(Verified::assume_verified(self)); } } return None; diff --git a/network-hypervisor/src/vl2/v1/certificateofownership.rs b/network-hypervisor/src/vl2/v1/certificateofownership.rs index 717fe25ae..05f975982 100644 --- a/network-hypervisor/src/vl2/v1/certificateofownership.rs +++ b/network-hypervisor/src/vl2/v1/certificateofownership.rs @@ -9,7 +9,7 @@ use serde::{Deserialize, Serialize}; use zerotier_utils::arrayvec::ArrayVec; use zerotier_utils::error::InvalidParameterError; -#[derive(Clone, Serialize, Deserialize, PartialEq, Eq, Hash)] +#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq, Hash)] pub enum Thing { Ipv4([u8; 4]), Ipv6([u8; 16]), @@ -27,7 +27,7 @@ impl Thing { } } -#[derive(Clone, Serialize, Deserialize, PartialEq, Eq)] +#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)] pub struct CertificateOfOwnership { pub network_id: NetworkId, pub timestamp: i64, diff --git a/network-hypervisor/src/vl2/v1/mod.rs b/network-hypervisor/src/vl2/v1/mod.rs index 9813a08e8..5c92fed0c 100644 --- a/network-hypervisor/src/vl2/v1/mod.rs +++ b/network-hypervisor/src/vl2/v1/mod.rs @@ -3,6 +3,7 @@ mod certificateofownership; mod revocation; mod tag; +#[derive(Clone, Copy, Debug, PartialEq, Eq)] #[repr(u8)] pub enum CredentialType { Null = 0u8, diff --git a/network-hypervisor/src/vl2/v1/revocation.rs b/network-hypervisor/src/vl2/v1/revocation.rs index d9c16c535..7566e52aa 100644 --- a/network-hypervisor/src/vl2/v1/revocation.rs +++ b/network-hypervisor/src/vl2/v1/revocation.rs @@ -10,7 +10,7 @@ use crate::vl1::{Address, Identity}; use crate::vl2::v1::CredentialType; use crate::vl2::NetworkId; -#[derive(Clone, Serialize, Deserialize, PartialEq, Eq)] +#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)] pub struct Revocation { pub id: u32, pub network_id: NetworkId, diff --git a/network-hypervisor/src/vl2/v1/tag.rs b/network-hypervisor/src/vl2/v1/tag.rs index 4d0082ab3..3ff0cd0e7 100644 --- a/network-hypervisor/src/vl2/v1/tag.rs +++ b/network-hypervisor/src/vl2/v1/tag.rs @@ -10,7 +10,7 @@ use zerotier_utils::arrayvec::ArrayVec; use zerotier_utils::blob::Blob; use zerotier_utils::error::InvalidParameterError; -#[derive(Clone, Serialize, Deserialize, PartialEq, Eq)] +#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)] pub struct Tag { pub network_id: NetworkId, pub timestamp: i64, diff --git a/service/src/utils.rs b/service/src/utils.rs index 095232379..dd1d8d6c5 100644 --- a/service/src/utils.rs +++ b/service/src/utils.rs @@ -42,8 +42,10 @@ pub fn parse_cli_identity(input: &str, validate: bool) -> Result From<[T; S]> for ArrayVec { } } +impl ToString for ArrayVec { + #[inline] + fn to_string(&self) -> String { + crate::hex::to_string(self.as_bytes()) + } +} + +impl Debug for ArrayVec { + #[inline] + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + f.write_str(self.to_string().as_str()) + } +} + impl Write for ArrayVec { #[inline] fn write(&mut self, buf: &[u8]) -> std::io::Result { diff --git a/utils/src/blob.rs b/utils/src/blob.rs index e1b17926b..424b17add 100644 --- a/utils/src/blob.rs +++ b/utils/src/blob.rs @@ -1,5 +1,7 @@ // (c) 2020-2022 ZeroTier, Inc. -- currently propritery pending actual release and licensing. See LICENSE.md. +use std::fmt::Debug; + use serde::ser::SerializeTuple; use serde::{Deserialize, Deserializer, Serialize, Serializer}; @@ -64,6 +66,13 @@ impl ToString for Blob { } } +impl Debug for Blob { + #[inline] + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + f.write_str(self.to_string().as_str()) + } +} + impl Serialize for Blob { #[inline] fn serialize(&self, serializer: S) -> Result diff --git a/vl1-service/src/datadir.rs b/vl1-service/src/datadir.rs index cac09d468..d7a066e4a 100644 --- a/vl1-service/src/datadir.rs +++ b/vl1-service/src/datadir.rs @@ -8,7 +8,7 @@ use serde::de::DeserializeOwned; use serde::Serialize; use zerotier_crypto::random::next_u32_secure; -use zerotier_network_hypervisor::vl1::{Identity, NodeStorage}; +use zerotier_network_hypervisor::vl1::{Identity, NodeStorage, Verified}; use zerotier_utils::io::{fs_restrict_permissions, read_limit, DEFAULT_FILE_IO_READ_LIMIT}; use zerotier_utils::json::to_json_pretty; @@ -27,7 +27,7 @@ pub struct DataDir NodeStorage for DataDir { - fn load_node_identity(&self) -> Option { + fn load_node_identity(&self) -> Option> { let id_data = read_limit(self.base_path.join(IDENTITY_SECRET_FILENAME), 4096); if id_data.is_err() { return None; @@ -36,10 +36,10 @@ impl) { assert!(id.secret.is_some()); let id_secret_str = id.to_secret_string(); let id_public_str = id.to_string(); diff --git a/vl1-service/src/vl1service.rs b/vl1-service/src/vl1service.rs index 70eafc6a8..335e8b1e3 100644 --- a/vl1-service/src/vl1service.rs +++ b/vl1-service/src/vl1service.rs @@ -328,12 +328,12 @@ impl< > NodeStorage for VL1Service { #[inline(always)] - fn load_node_identity(&self) -> Option { + fn load_node_identity(&self) -> Option> { self.storage.load_node_identity() } #[inline(always)] - fn save_node_identity(&self, id: &Identity) { + fn save_node_identity(&self, id: &Verified) { self.storage.save_node_identity(id) } } @@ -345,12 +345,12 @@ impl< > VL1AuthProvider for VL1Service { #[inline(always)] - fn should_respond_to(&self, id: &Identity) -> bool { + fn should_respond_to(&self, id: &Verified) -> bool { self.vl1_auth_provider.should_respond_to(id) } #[inline(always)] - fn has_trust_relationship(&self, id: &Identity) -> bool { + fn has_trust_relationship(&self, id: &Verified) -> bool { self.vl1_auth_provider.has_trust_relationship(id) } }