From 810a1fb229cea663207fb00a8967987b55036b32 Mon Sep 17 00:00:00 2001 From: Adam Ierymenko Date: Mon, 6 Dec 2021 09:40:41 -0500 Subject: [PATCH] Just a whole bucket of Rust. --- zerotier-core-crypto/src/hash.rs | 22 + .../src/networkhypervisor.rs | 2 +- .../src/util/buffer.rs | 55 ++- zerotier-network-hypervisor/src/util/mod.rs | 128 +---- .../src/vl1/address.rs | 26 +- .../src/vl1/endpoint.rs | 3 +- .../src/vl1/ephemeral.rs | 2 +- .../src/vl1/fragmentedpacket.rs | 7 +- .../src/vl1/identity.rs | 20 +- .../src/vl1/inetaddress.rs | 3 +- zerotier-network-hypervisor/src/vl1/mac.rs | 2 +- zerotier-network-hypervisor/src/vl1/mod.rs | 6 +- zerotier-network-hypervisor/src/vl1/node.rs | 10 +- zerotier-network-hypervisor/src/vl1/path.rs | 22 +- zerotier-network-hypervisor/src/vl1/peer.rs | 458 +++++++++--------- .../src/vl1/protocol.rs | 55 +-- zerotier-system-service/Cargo.lock | 1 + zerotier-system-service/Cargo.toml | 1 + zerotier-system-service/src/fastudpsocket.rs | 100 ++-- zerotier-system-service/src/localconfig.rs | 8 +- zerotier-system-service/src/log.rs | 16 +- zerotier-system-service/src/main.rs | 11 +- zerotier-system-service/src/service.rs | 69 ++- zerotier-system-service/src/store.rs | 61 +-- zerotier-system-service/src/utils.rs | 23 +- zerotier-system-service/src/vnic/common.rs | 10 +- 26 files changed, 536 insertions(+), 585 deletions(-) diff --git a/zerotier-core-crypto/src/hash.rs b/zerotier-core-crypto/src/hash.rs index 4d22db3f3..15e09f17a 100644 --- a/zerotier-core-crypto/src/hash.rs +++ b/zerotier-core-crypto/src/hash.rs @@ -32,6 +32,17 @@ impl SHA512 { h } + pub fn hmac_multipart(key: &[u8], msg: &[&[u8]]) -> [u8; SHA512_HASH_SIZE] { + let mut m = gcrypt::mac::Mac::new(gcrypt::mac::Algorithm::HmacSha512).unwrap(); + m.set_key(key).expect("FATAL: invalid HMAC-SHA512 key"); + for msg_part in msg.iter() { + m.update(*msg_part).expect("FATAL: HMAC-SHA512 failed"); + } + let mut h = [0_u8; SHA512_HASH_SIZE]; + m.get_mac(&mut h).expect("FATAL: HMAC-SHA512 failed"); + h + } + #[inline(always)] pub fn new() -> Self { Self(gcrypt::digest::MessageDigest::new(gcrypt::digest::Algorithm::Sha512).unwrap()) } @@ -84,6 +95,17 @@ impl SHA384 { h } + pub fn hmac_multipart(key: &[u8], msg: &[&[u8]]) -> [u8; SHA384_HASH_SIZE] { + let mut m = gcrypt::mac::Mac::new(gcrypt::mac::Algorithm::HmacSha384).unwrap(); + m.set_key(key).expect("FATAL: invalid HMAC-SHA512 key"); + for msg_part in msg.iter() { + m.update(*msg_part).expect("FATAL: HMAC-SHA512 failed"); + } + let mut h = [0_u8; SHA384_HASH_SIZE]; + m.get_mac(&mut h).expect("FATAL: HMAC-SHA512 failed"); + h + } + #[inline(always)] pub fn new() -> Self { Self(gcrypt::digest::MessageDigest::new(gcrypt::digest::Algorithm::Sha384).unwrap()) } diff --git a/zerotier-network-hypervisor/src/networkhypervisor.rs b/zerotier-network-hypervisor/src/networkhypervisor.rs index a01fabfa6..27c47fd0e 100644 --- a/zerotier-network-hypervisor/src/networkhypervisor.rs +++ b/zerotier-network-hypervisor/src/networkhypervisor.rs @@ -23,7 +23,7 @@ pub struct NetworkHypervisor { } impl NetworkHypervisor { - pub fn new(ci: &CI, auto_generate_identity_type: Option) -> Result { + pub fn new(ci: &CI, auto_generate_identity_type: Option) -> Result { Ok(NetworkHypervisor { vl1: Node::new(ci, auto_generate_identity_type)?, vl2: Switch::new(), diff --git a/zerotier-network-hypervisor/src/util/buffer.rs b/zerotier-network-hypervisor/src/util/buffer.rs index 9ad206795..b54428bc9 100644 --- a/zerotier-network-hypervisor/src/util/buffer.rs +++ b/zerotier-network-hypervisor/src/util/buffer.rs @@ -7,7 +7,7 @@ */ use std::io::Write; -use std::mem::size_of; +use std::mem::{MaybeUninit, size_of}; use crate::util::pool::PoolFactory; @@ -39,6 +39,10 @@ impl Buffer { #[inline(always)] pub fn new() -> Self { Self(0, [0_u8; L]) } + /// Create a zero size buffer without zeroing its actual memory. + #[inline(always)] + pub unsafe fn new_nozero() -> Self { Self(0, MaybeUninit::uninit().assume_init()) } + /// Get a Buffer initialized with a copy of a byte slice. #[inline(always)] pub fn from_bytes(b: &[u8]) -> std::io::Result { @@ -82,6 +86,19 @@ impl Buffer { self.1[0..prev_len].fill(0); } + /// Load array into buffer. + /// This will panic if the array is larger than L. + #[inline(always)] + pub fn set_to(&mut self, b: &[u8]) { + let prev_len = self.0; + let len = b.len(); + self.0 = len; + self.1[0..len].copy_from_slice(b); + if len < prev_len { + self.1[len..prev_len].fill(0); + } + } + #[inline(always)] pub fn len(&self) -> usize { self.0 } @@ -109,48 +126,39 @@ impl Buffer { #[inline(always)] pub unsafe fn get_unchecked(&self, i: usize) -> u8 { *self.1.get_unchecked(i) } - /// Append a packed structure and call a function to initialize it in place. - /// Anything not initialized will be zero. + /// Append a structure and return a mutable reference to its memory. #[inline(always)] - pub fn append_and_init_struct R>(&mut self, initializer: F) -> std::io::Result { + pub fn append_struct_get_mut(&mut self) -> std::io::Result<&mut T> { let ptr = self.0; let end = ptr + size_of::(); if end <= L { self.0 = end; - unsafe { - Ok(initializer(&mut *self.1.as_mut_ptr().cast::().offset(ptr as isize).cast::())) - } + Ok(unsafe { &mut *self.1.as_mut_ptr().add(ptr).cast() }) } else { Err(std::io::Error::new(std::io::ErrorKind::UnexpectedEof, OVERFLOW_ERR_MSG)) } } - /// Append and initialize a byte array with a fixed size set at compile time. - /// This is more efficient than setting a size at runtime as it may allow the compiler to - /// skip some bounds checking. Any bytes not initialized will be zero. + /// Append a fixed size array and return a mutable reference to its memory. #[inline(always)] - pub fn append_and_init_bytes_fixed R, const N: usize>(&mut self, initializer: F) -> std::io::Result { + pub fn append_bytes_fixed_get_mut(&mut self) -> std::io::Result<&mut [u8; S]> { let ptr = self.0; - let end = ptr + N; + let end = ptr + S; if end <= L { self.0 = end; - unsafe { - Ok(initializer(&mut *self.1.as_mut_ptr().cast::().offset(ptr as isize).cast::<[u8; N]>())) - } + Ok(unsafe { &mut *self.1.as_mut_ptr().add(ptr).cast() }) } else { Err(std::io::Error::new(std::io::ErrorKind::UnexpectedEof, OVERFLOW_ERR_MSG)) } } - /// Append and initialize a slice with a size that is set at runtime. - /// Any bytes not initialized will be zero. - #[inline(always)] - pub fn append_and_init_bytes R>(&mut self, l: usize, initializer: F) -> std::io::Result { + /// Append a runtime sized array and return a mutable reference to its memory. + pub fn append_bytes_get_mut(&mut self, s: usize) -> std::io::Result<&mut [u8]> { let ptr = self.0; let end = ptr + l; if end <= L { self.0 = end; - Ok(initializer(&mut self.1[ptr..end])) + Ok(&mut self.1[ptr..end]) } else { Err(std::io::Error::new(std::io::ErrorKind::UnexpectedEof, OVERFLOW_ERR_MSG)) } @@ -171,7 +179,6 @@ impl Buffer { } /// Append a fixed length byte array (copy into buffer). - /// Use append_and_init_ functions if possible as these avoid extra copies. #[inline(always)] pub fn append_bytes_fixed(&mut self, buf: &[u8; S]) -> std::io::Result<()> { let ptr = self.0; @@ -364,7 +371,7 @@ impl Buffer { debug_assert!(end <= L); if end <= self.0 { *cursor = end; - Ok(crate::util::load_u16_be(&self.1[ptr..end])) + Ok(u16::from_be_bytes(unsafe { *self.1.as_ptr().add(ptr).cast() })) } else { Err(std::io::Error::new(std::io::ErrorKind::UnexpectedEof, OVERFLOW_ERR_MSG)) } @@ -378,7 +385,7 @@ impl Buffer { debug_assert!(end <= L); if end <= self.0 { *cursor = end; - Ok(crate::util::load_u32_be(&self.1[ptr..end])) + Ok(u32::from_be_bytes(unsafe { *self.1.as_ptr().add(ptr).cast() })) } else { Err(std::io::Error::new(std::io::ErrorKind::UnexpectedEof, OVERFLOW_ERR_MSG)) } @@ -392,7 +399,7 @@ impl Buffer { debug_assert!(end <= L); if end <= self.0 { *cursor = end; - Ok(crate::util::load_u64_be(&self.1[ptr..end])) + Ok(u64::from_be_bytes(unsafe { *self.1.as_ptr().add(ptr).cast() })) } else { Err(std::io::Error::new(std::io::ErrorKind::UnexpectedEof, OVERFLOW_ERR_MSG)) } diff --git a/zerotier-network-hypervisor/src/util/mod.rs b/zerotier-network-hypervisor/src/util/mod.rs index 04df92966..de818db5d 100644 --- a/zerotier-network-hypervisor/src/util/mod.rs +++ b/zerotier-network-hypervisor/src/util/mod.rs @@ -15,140 +15,38 @@ pub use zerotier_core_crypto::varint; pub(crate) const ZEROES: [u8; 64] = [0_u8; 64]; +/// Obtain a reference to a sub-array within an existing array. +/// Attempts to violate array bounds will panic or fail to compile. #[inline(always)] -pub(crate) unsafe fn equal_ptr(a: *const u8, b: *const u8, l: usize) -> bool { - for i in 0..l { - if *a.offset(i as isize) != *b.offset(i as isize) { - return false; - } - } - true +pub(crate) fn array_range(a: &[T; A]) -> &[T; LEN] { + assert!((START + LEN) <= A); + unsafe { &*a.as_ptr().add(std::mem::size_of::() * start_index).cast::<[T; LEN]>() } } -#[cfg(any(target_arch = "x86_64", target_arch = "x86", target_arch = "aarch64"))] +/// Cast a u64 reference to a byte array in place. +/// Going the other direction is not safe on some architectures, but this should be safe everywhere. #[inline(always)] -pub(crate) fn store_u16_be(i: u16, d: &mut [u8]) { - unsafe { *d.as_mut_ptr().cast::() = i.to_be() }; -} - -#[cfg(not(any(target_arch = "x86_64", target_arch = "x86", target_arch = "aarch64")))] -#[inline(always)] -pub(crate) fn store_u16_be(i: u16, d: &mut [u8]) { - d[0] = (i >> 8) as u8; - d[1] = i as u8; -} - -#[cfg(any(target_arch = "x86_64", target_arch = "x86", target_arch = "aarch64"))] -#[inline(always)] -pub(crate) fn store_u32_be(i: u32, d: &mut [u8]) { - unsafe { *d.as_mut_ptr().cast::() = i.to_be() }; -} - -#[cfg(not(any(target_arch = "x86_64", target_arch = "x86", target_arch = "aarch64")))] -#[inline(always)] -pub(crate) fn store_u32_be(i: u32, d: &mut [u8]) { - d[0] = (i >> 24) as u8; - d[1] = (i >> 16) as u8; - d[2] = (i >> 8) as u8; - d[3] = i as u8; -} - -#[cfg(any(target_arch = "x86_64", target_arch = "x86", target_arch = "aarch64"))] -#[inline(always)] -pub(crate) fn store_u64_be(i: u64, d: &mut [u8]) { - unsafe { *d.as_mut_ptr().cast::() = i.to_be() }; -} - -#[cfg(not(any(target_arch = "x86_64", target_arch = "x86", target_arch = "aarch64")))] -#[inline(always)] -pub(crate) fn store_u64_be(i: u64, d: &mut [u8]) { - d[0] = (i >> 56) as u8; - d[1] = (i >> 48) as u8; - d[2] = (i >> 40) as u8; - d[3] = (i >> 32) as u8; - d[4] = (i >> 24) as u8; - d[5] = (i >> 16) as u8; - d[6] = (i >> 8) as u8; - d[7] = i as u8; -} - -#[cfg(any(target_arch = "x86_64", target_arch = "x86", target_arch = "aarch64"))] -#[inline(always)] -pub(crate) fn load_u16_be(d: &[u8]) -> u16 { - u16::from_be(unsafe { *d.as_ptr().cast::() }) -} - -#[cfg(not(any(target_arch = "x86_64", target_arch = "x86", target_arch = "aarch64")))] -#[inline(always)] -pub(crate) fn load_u16_be(d: &[u8]) -> u16 { - (d[0] as u16) << 8 | (d[1] as u16) -} - -#[cfg(any(target_arch = "x86_64", target_arch = "x86", target_arch = "aarch64"))] -#[inline(always)] -pub(crate) fn load_u32_be(d: &[u8]) -> u32 { - u32::from_be(unsafe { *d.as_ptr().cast::() }) -} - -#[cfg(not(any(target_arch = "x86_64", target_arch = "x86", target_arch = "aarch64")))] -#[inline(always)] -pub(crate) fn load_u32_be(d: &[u8]) -> u32 { - (d[0] as u32) << 24 | (d[1] as u32) << 16 | (d[2] as u32) << 8 | (d[3] as u32) -} - -#[cfg(any(target_arch = "x86_64", target_arch = "x86", target_arch = "aarch64"))] -#[inline(always)] -pub(crate) fn load_u64_be(d: &[u8]) -> u64 { - u64::from_be(unsafe { *d.as_ptr().cast::() }) -} - -#[cfg(not(any(target_arch = "x86_64", target_arch = "x86", target_arch = "aarch64")))] -#[inline(always)] -pub(crate) fn load_u64_be(d: &[u8]) -> u64 { - (d[0] as u64) << 56 | (d[1] as u64) << 48 | (d[2] as u64) << 40 | (d[3] as u64) << 32 | (d[4] as u64) << 24 | (d[5] as u64) << 16 | (d[6] as u64) << 8 | (d[7] as u64) -} - -/// Mix bits in a 64-bit integer. -/// https://nullprogram.com/blog/2018/07/31/ -#[inline(always)] -pub(crate) fn hash64(mut x: u64) -> u64 { - x ^= x.wrapping_shr(30); - x = x.wrapping_mul(0xbf58476d1ce4e5b9); - x ^= x.wrapping_shr(27); - x = x.wrapping_mul(0x94d049bb133111eb); - x ^ x.wrapping_shr(31) -} - -/// Mix bits in 32-bit integer. -/// https://nullprogram.com/blog/2018/07/31/ -#[inline(always)] -pub(crate) fn hash32(mut x: u32) -> u32 { - x ^= x.wrapping_shr(16); - x = x.wrapping_mul(0x7feb352d); - x ^= x.wrapping_shr(15); - x = x.wrapping_mul(0x846ca68b); - x ^ x.wrapping_shr(16) -} +pub(crate) fn u64_as_bytes(i: &u64) -> &[u8; 8] { unsafe { &*(i as *const u64).cast() } } /// A hasher for maps that just returns u64 values as-is. /// /// This should be used only for things like ZeroTier addresses that are already random /// and that aren't vulnerable to malicious crafting of identifiers. #[derive(Copy, Clone)] -pub(crate) struct U64PassThroughHasher(u64); +pub struct U64NoOpHasher(u64); -impl U64PassThroughHasher { +impl U64NoOpHasher { #[inline(always)] pub fn new() -> Self { Self(0) } } -impl std::hash::Hasher for U64PassThroughHasher { +impl std::hash::Hasher for U64NoOpHasher { #[inline(always)] fn finish(&self) -> u64 { self.0 } #[inline(always)] fn write(&mut self, _: &[u8]) { - panic!("U64PassThroughHasher can only be used with u64 and i64"); + panic!("U64NoOpHasher should only be used with u64 and i64 types"); } #[inline(always)] @@ -158,7 +56,7 @@ impl std::hash::Hasher for U64PassThroughHasher { fn write_i64(&mut self, i: i64) { self.0 += i as u64; } } -impl std::hash::BuildHasher for U64PassThroughHasher { +impl std::hash::BuildHasher for U64NoOpHasher { type Hasher = Self; #[inline(always)] diff --git a/zerotier-network-hypervisor/src/vl1/address.rs b/zerotier-network-hypervisor/src/vl1/address.rs index 1d263270b..524a4d379 100644 --- a/zerotier-network-hypervisor/src/vl1/address.rs +++ b/zerotier-network-hypervisor/src/vl1/address.rs @@ -24,22 +24,19 @@ impl Address { #[inline(always)] pub fn from_u64(mut i: u64) -> Option
{ i &= 0xffffffffff; - if i != 0 && (i >> 32) != ADDRESS_RESERVED_PREFIX as u64 { - Some(Address(unsafe { NonZeroU64::new_unchecked(i) })) - } else { - None - } + NonZeroU64::new(i).and_then(|ii| { + if (i >> 32) != ADDRESS_RESERVED_PREFIX as u64 { + Some(Address(ii)) + } else { + None + } + }) } #[inline(always)] pub fn from_bytes(b: &[u8]) -> Option
{ if b.len() >= ADDRESS_SIZE { - let i = (b[0] as u64) << 32 | (b[1] as u64) << 24 | (b[2] as u64) << 16 | (b[3] as u64) << 8 | b[4] as u64; - if i != 0 && (i >> 32) != ADDRESS_RESERVED_PREFIX as u64 { - Some(Address(unsafe { NonZeroU64::new_unchecked(i) })) - } else { - None - } + Self::from_u64((b[0] as u64) << 32 | (b[1] as u64) << 24 | (b[2] as u64) << 16 | (b[3] as u64) << 8 | b[4] as u64) } else { None } @@ -47,12 +44,7 @@ impl Address { #[inline(always)] pub fn from_bytes_fixed(b: &[u8; ADDRESS_SIZE]) -> Option
{ - let i = (b[0] as u64) << 32 | (b[1] as u64) << 24 | (b[2] as u64) << 16 | (b[3] as u64) << 8 | b[4] as u64; - if i != 0 && (i >> 32) != ADDRESS_RESERVED_PREFIX as u64 { - Some(Address(unsafe { NonZeroU64::new_unchecked(i) })) - } else { - None - } + Self::from_u64((b[0] as u64) << 32 | (b[1] as u64) << 24 | (b[2] as u64) << 16 | (b[3] as u64) << 8 | b[4] as u64) } #[inline(always)] diff --git a/zerotier-network-hypervisor/src/vl1/endpoint.rs b/zerotier-network-hypervisor/src/vl1/endpoint.rs index 679698283..da638c934 100644 --- a/zerotier-network-hypervisor/src/vl1/endpoint.rs +++ b/zerotier-network-hypervisor/src/vl1/endpoint.rs @@ -147,7 +147,7 @@ impl Endpoint { let b: &[u8; 18] = buf.read_bytes_fixed(cursor)?; Ok(Endpoint::IpUdp(InetAddress::from_ip_port(&b[0..16], crate::util::load_u16_be(&b[16..18])))) } else { - Ok(Endpoint::Nil) + Err(std::io::Error::new(std::io::ErrorKind::InvalidData, "unrecognized endpoint type in stream")) } } else { let read_mac = |buf: &Buffer, cursor: &mut usize| { @@ -158,6 +158,7 @@ impl Endpoint { Err(std::io::Error::new(std::io::ErrorKind::InvalidData, "invalid MAC address")) } }; + match type_byte - 16 { TYPE_NIL => Ok(Endpoint::Nil), TYPE_ZEROTIER => { diff --git a/zerotier-network-hypervisor/src/vl1/ephemeral.rs b/zerotier-network-hypervisor/src/vl1/ephemeral.rs index 1688304d1..ee7a17684 100644 --- a/zerotier-network-hypervisor/src/vl1/ephemeral.rs +++ b/zerotier-network-hypervisor/src/vl1/ephemeral.rs @@ -247,7 +247,7 @@ impl EphemeralKeyPairSet { /// Symmetric secret representing a step in the ephemeral keying ratchet. pub struct EphemeralSymmetricSecret { - secret: SymmetricSecret, + pub secret: SymmetricSecret, ratchet_state: [u8; 16], rekey_time: i64, expire_time: i64, diff --git a/zerotier-network-hypervisor/src/vl1/fragmentedpacket.rs b/zerotier-network-hypervisor/src/vl1/fragmentedpacket.rs index 9d378dbb3..91de3a24d 100644 --- a/zerotier-network-hypervisor/src/vl1/fragmentedpacket.rs +++ b/zerotier-network-hypervisor/src/vl1/fragmentedpacket.rs @@ -16,7 +16,7 @@ use crate::vl1::protocol::*; /// compiler to optimize out any additional state in Option. pub(crate) struct FragmentedPacket { pub ts_ticks: i64, - pub frags: [Option; FRAGMENT_COUNT_MAX], + pub frags: [Option; PACKET_FRAGMENT_COUNT_MAX], pub have: u8, pub expecting: u8, } @@ -33,14 +33,13 @@ impl FragmentedPacket { } /// Add a fragment to this fragment set and return true if the packet appears complete. - /// This will panic if 'no' is out of bounds. #[inline(always)] pub fn add_fragment(&mut self, frag: PacketBuffer, no: u8, expecting: u8) -> bool { - if no < FRAGMENT_COUNT_MAX as u8 { + if no < PACKET_FRAGMENT_COUNT_MAX as u8 { if self.frags[no as usize].replace(frag).is_none() { self.have = self.have.wrapping_add(1); self.expecting |= expecting; // in valid streams expecting is either 0 or the (same) total - return self.have == self.expecting && self.have < FRAGMENT_COUNT_MAX as u8; + return self.have == self.expecting && self.have < PACKET_FRAGMENT_COUNT_MAX as u8; } } false diff --git a/zerotier-network-hypervisor/src/vl1/identity.rs b/zerotier-network-hypervisor/src/vl1/identity.rs index 077f1fa07..2edb0b35a 100644 --- a/zerotier-network-hypervisor/src/vl1/identity.rs +++ b/zerotier-network-hypervisor/src/vl1/identity.rs @@ -85,7 +85,7 @@ fn concat_v1_secret_keys(c25519: &[u8], ed25519: &[u8], p521_ecdh: &[u8], p521_e #[derive(Copy, Clone)] #[repr(u8)] -pub enum Type { +pub enum IdentityType { /// Curve25519 / Ed25519 identity (type 0) C25519 = 0, /// Dual NIST P-521 ECDH / ECDSA + Curve25519 / Ed25519 (type 1) @@ -220,10 +220,10 @@ impl Identity { /// take tens to hundreds of milliseconds on a typical 2020 system, while V1 identites /// take about 500ms. Generation can take a lot longer on low power devices, but only /// has to be done once. - pub fn generate(id_type: Type) -> Identity { + pub fn generate(id_type: IdentityType) -> Identity { match id_type { - Type::C25519 => Self::generate_c25519(), - Type::P521 => Self::generate_p521() + IdentityType::C25519 => Self::generate_c25519(), + IdentityType::P521 => Self::generate_p521() } } @@ -330,7 +330,7 @@ impl Identity { /// Get this identity's type. #[inline(always)] - pub fn id_type(&self) -> Type { if self.v1.is_some() { Type::P521 } else { Type::C25519 } } + pub fn id_type(&self) -> IdentityType { if self.v1.is_some() { IdentityType::P521 } else { IdentityType::C25519 } } /// Returns true if this identity also holds its secret keys. #[inline(always)] @@ -390,7 +390,7 @@ impl Identity { let addr = addr.unwrap(); let id_type = buf.read_u8(cursor)?; - if id_type == Type::C25519 as u8 { + if id_type == IdentityType::C25519 as u8 { let c25519_public_bytes = buf.read_bytes_fixed::<{ C25519_PUBLIC_KEY_SIZE }>(cursor)?; let ed25519_public_bytes = buf.read_bytes_fixed::<{ ED25519_PUBLIC_KEY_SIZE }>(cursor)?; @@ -421,7 +421,7 @@ impl Identity { std::io::Result::Err(std::io::Error::new(std::io::ErrorKind::InvalidData, "unrecognized secret key length (type 0)")) } - } else if id_type == Type::P521 as u8 { + } else if id_type == IdentityType::P521 as u8 { let c25519_public_bytes = buf.read_bytes_fixed::<{ C25519_PUBLIC_KEY_SIZE }>(cursor)?; let ed25519_public_bytes = buf.read_bytes_fixed::<{ ED25519_PUBLIC_KEY_SIZE }>(cursor)?; @@ -638,11 +638,11 @@ impl Hash for Identity { mod tests { use std::str::FromStr; - use crate::vl1::identity::{Identity, Type}; + use crate::vl1::identity::{Identity, IdentityType}; #[test] fn type0() { - let id = Identity::generate(Type::C25519); + let id = Identity::generate(IdentityType::C25519); //println!("V0: {}", id.to_string()); if !id.locally_validate() { panic!("new V0 identity validation failed"); @@ -674,7 +674,7 @@ mod tests { #[test] fn type1() { let start = std::time::SystemTime::now(); - let id = Identity::generate(Type::P521); + let id = Identity::generate(IdentityType::P521); let end = std::time::SystemTime::now(); println!("V1 generate: {}ms {}", end.duration_since(start).unwrap().as_millis(), id.to_string()); if !id.locally_validate() { diff --git a/zerotier-network-hypervisor/src/vl1/inetaddress.rs b/zerotier-network-hypervisor/src/vl1/inetaddress.rs index 5f8ccfc51..9ae9a4096 100644 --- a/zerotier-network-hypervisor/src/vl1/inetaddress.rs +++ b/zerotier-network-hypervisor/src/vl1/inetaddress.rs @@ -17,7 +17,6 @@ use std::str::FromStr; use winapi::um::winsock2 as winsock2; use crate::error::InvalidFormatError; -use crate::util::equal_ptr; use crate::util::buffer::Buffer; #[allow(non_camel_case_types)] @@ -463,7 +462,7 @@ impl PartialEq for InetAddress { AF_INET => { self.sin.sin_port == other.sin.sin_port && self.sin.sin_addr.s_addr == other.sin.sin_addr.s_addr } AF_INET6 => { if self.sin6.sin6_port == other.sin6.sin6_port { - equal_ptr((&(self.sin6.sin6_addr) as *const in6_addr).cast(), (&(other.sin6.sin6_addr) as *const in6_addr).cast(), 16) + (*(&(self.sin6.sin6_addr) as *const in6_addr).cast::<[u8; 16]>()).eq(&*(&(other.sin6.sin6_addr) as *const in6_addr).cast::<[u8; 16]>()) } else { false } diff --git a/zerotier-network-hypervisor/src/vl1/mac.rs b/zerotier-network-hypervisor/src/vl1/mac.rs index f08c18173..4dd1075a6 100644 --- a/zerotier-network-hypervisor/src/vl1/mac.rs +++ b/zerotier-network-hypervisor/src/vl1/mac.rs @@ -19,7 +19,7 @@ pub struct MAC(NonZeroU64); impl MAC { #[inline(always)] - pub fn from_u64(i: u64) -> Option { NonZeroU64::new(i & 0xffffffffffff).map_or(None, |i| Some(MAC(i))) } + pub fn from_u64(i: u64) -> Option { NonZeroU64::new(i & 0xffffffffffff).map(|i| MAC(i)) } #[inline(always)] pub fn from_bytes(b: &[u8]) -> Option { diff --git a/zerotier-network-hypervisor/src/vl1/mod.rs b/zerotier-network-hypervisor/src/vl1/mod.rs index f27ca2c75..0f7e452de 100644 --- a/zerotier-network-hypervisor/src/vl1/mod.rs +++ b/zerotier-network-hypervisor/src/vl1/mod.rs @@ -6,12 +6,12 @@ * https://www.zerotier.com/ */ -pub mod identity; pub mod inetaddress; pub mod endpoint; pub mod rootset; #[allow(unused)] +pub(crate) mod identity; pub(crate) mod protocol; pub(crate) mod node; pub(crate) mod path; @@ -26,10 +26,12 @@ pub(crate) mod symmetricsecret; pub use address::Address; pub use mac::MAC; -pub use identity::Identity; +pub use identity::{Identity, IdentityType, IDENTITY_TYPE_0_SIGNATURE_SIZE, IDENTITY_TYPE_1_SIGNATURE_SIZE}; pub use endpoint::Endpoint; pub use dictionary::Dictionary; pub use inetaddress::InetAddress; pub use peer::Peer; pub use path::Path; pub use node::{Node, NodeInterface}; + +pub use protocol::{PACKET_SIZE_MAX, PACKET_FRAGMENT_COUNT_MAX}; diff --git a/zerotier-network-hypervisor/src/vl1/node.rs b/zerotier-network-hypervisor/src/vl1/node.rs index 65ca33b93..dc71fa490 100644 --- a/zerotier-network-hypervisor/src/vl1/node.rs +++ b/zerotier-network-hypervisor/src/vl1/node.rs @@ -21,7 +21,7 @@ use crate::error::InvalidParameterError; use crate::util::gate::IntervalGate; use crate::util::pool::{Pool, Pooled}; use crate::util::buffer::Buffer; -use crate::vl1::{Address, Endpoint, Identity}; +use crate::vl1::{Address, Endpoint, Identity, IdentityType}; use crate::vl1::path::Path; use crate::vl1::peer::Peer; use crate::vl1::protocol::*; @@ -46,7 +46,7 @@ pub trait NodeInterface { fn event_user_message(&self, source: &Identity, message_type: u64, message: &[u8]); /// Load this node's identity from the data store. - fn load_node_identity(&self) -> Option<&[u8]>; + fn load_node_identity(&self) -> Option>; /// Save this node's identity. /// Note that this is only called on first startup (after up) and after identity_changed. @@ -115,7 +115,7 @@ pub trait VL1PacketHandler { #[derive(Default)] struct BackgroundTaskIntervals { whois: IntervalGate<{ WhoisQueue::INTERVAL }>, - paths: IntervalGate<{ Path::INTERVAL }>, + paths: IntervalGate<{ Path::CALL_EVERY_INTERVAL_INTERVAL }>, peers: IntervalGate<{ Peer::INTERVAL }>, } @@ -137,7 +137,7 @@ impl Node { /// /// If the auto-generate identity type is not None, a new identity will be generated if /// no identity is currently stored in the data store. - pub fn new(ci: &I, auto_generate_identity_type: Option) -> Result { + pub fn new(ci: &I, auto_generate_identity_type: Option) -> Result { let id = { let id_str = ci.load_node_identity(); if id_str.is_none() { @@ -149,7 +149,7 @@ impl Node { id } } else { - let id_str = String::from_utf8_lossy(id_str.unwrap()); + let id_str = String::from_utf8_lossy(id_str.as_ref().unwrap().as_slice()); let id = Identity::from_str(id_str.as_ref()); if id.is_err() { return Err(InvalidParameterError("invalid identity")); diff --git a/zerotier-network-hypervisor/src/vl1/path.rs b/zerotier-network-hypervisor/src/vl1/path.rs index 1cd317dba..b888caaef 100644 --- a/zerotier-network-hypervisor/src/vl1/path.rs +++ b/zerotier-network-hypervisor/src/vl1/path.rs @@ -13,7 +13,7 @@ use std::sync::atomic::{AtomicI64, Ordering}; use parking_lot::Mutex; use crate::PacketBuffer; -use crate::util::U64PassThroughHasher; +use crate::util::U64NoOpHasher; use crate::vl1::Endpoint; use crate::vl1::fragmentedpacket::FragmentedPacket; use crate::vl1::node::NodeInterface; @@ -32,12 +32,10 @@ pub struct Path { local_interface: Option, last_send_time_ticks: AtomicI64, last_receive_time_ticks: AtomicI64, - fragmented_packets: Mutex>, + fragmented_packets: Mutex>, } impl Path { - pub(crate) const INTERVAL: i64 = PATH_KEEPALIVE_INTERVAL; - #[inline(always)] pub fn new(endpoint: Endpoint, local_socket: Option, local_interface: Option) -> Self { Self { @@ -46,7 +44,7 @@ impl Path { local_interface, last_send_time_ticks: AtomicI64::new(0), last_receive_time_ticks: AtomicI64::new(0), - fragmented_packets: Mutex::new(HashMap::with_capacity_and_hasher(4, U64PassThroughHasher::new())), + fragmented_packets: Mutex::new(HashMap::with_capacity_and_hasher(4, U64NoOpHasher::new())), } } @@ -71,10 +69,10 @@ impl Path { pub(crate) fn receive_fragment(&self, packet_id: PacketID, fragment_no: u8, fragment_expecting_count: u8, packet: PacketBuffer, time_ticks: i64) -> Option { let mut fp = self.fragmented_packets.lock(); - // This is mostly a defense against denial of service attacks or broken peers. It will - // trim off about 1/3 of waiting packets if the total is over the limit. + // Discard some old waiting packets if the total incoming fragments for a path exceeds a + // sanity limit. This is to prevent memory exhaustion DOS attacks. let fps = fp.len(); - if fps > FRAGMENT_MAX_INBOUND_PACKETS_PER_PATH { + if fps > PACKET_FRAGMENT_MAX_INBOUND_PACKETS_PER_PATH { let mut entries: Vec<(i64, u64)> = Vec::new(); entries.reserve(fps); for f in fp.iter() { @@ -82,10 +80,11 @@ impl Path { } entries.sort_unstable_by(|a, b| (*a).0.cmp(&(*b).0)); for i in 0..(fps / 3) { - let _ = fp.remove(&(*unsafe { entries.get_unchecked(i) }).1); + let _ = fp.remove(&(*entries.get(i).unwrap()).1); } } + // This is optimized for the fragmented case because that's the most common when transferring data. if fp.entry(packet_id).or_insert_with(|| FragmentedPacket::new(time_ticks)).add_fragment(packet, fragment_no, fragment_expecting_count) { fp.remove(&packet_id) } else { @@ -103,9 +102,12 @@ impl Path { self.last_send_time_ticks.store(time_ticks, Ordering::Relaxed); } + /// Desired period between calls to call_every_interval(). + pub(crate) const CALL_EVERY_INTERVAL_INTERVAL: i64 = PATH_KEEPALIVE_INTERVAL; + /// Called every INTERVAL during background tasks. #[inline(always)] pub(crate) fn call_every_interval(&self, ct: &CI, time_ticks: i64) { - self.fragmented_packets.lock().retain(|packet_id, frag| (time_ticks - frag.ts_ticks) < FRAGMENT_EXPIRATION); + self.fragmented_packets.lock().retain(|packet_id, frag| (time_ticks - frag.ts_ticks) < PACKET_FRAGMENT_EXPIRATION); } } diff --git a/zerotier-network-hypervisor/src/vl1/peer.rs b/zerotier-network-hypervisor/src/vl1/peer.rs index 370f46709..98e3bd71d 100644 --- a/zerotier-network-hypervisor/src/vl1/peer.rs +++ b/zerotier-network-hypervisor/src/vl1/peer.rs @@ -7,6 +7,8 @@ */ use std::convert::TryInto; +use std::intrinsics::try; +use std::mem::MaybeUninit; use std::num::NonZeroI64; use std::ptr::copy_nonoverlapping; use std::sync::Arc; @@ -28,9 +30,12 @@ use crate::{VERSION_MAJOR, VERSION_MINOR, VERSION_PROTO, VERSION_REVISION, Packe use crate::defaults::UDP_DEFAULT_MTU; use crate::util::pool::{Pool, PoolFactory}; use crate::util::buffer::Buffer; +use crate::util::{array_range, u64_as_bytes}; use crate::vl1::{Dictionary, Endpoint, Identity, InetAddress, Path}; +use crate::vl1::ephemeral::EphemeralSymmetricSecret; use crate::vl1::node::*; use crate::vl1::protocol::*; +use crate::vl1::symmetricsecret::SymmetricSecret; /// Interval for servicing and background operations on peers. pub(crate) const PEER_SERVICE_INTERVAL: i64 = 30000; @@ -45,6 +50,7 @@ impl PoolFactory for AesGmacSivPoolFactory { fn reset(&self, obj: &mut AesGmacSiv) { obj.reset(); } } +/// A secret key with all its derived forms and initialized ciphers. struct PeerSecret { // Time secret was created in ticks for ephemeral secrets, or -1 for static secrets. create_time_ticks: i64, @@ -60,14 +66,6 @@ struct PeerSecret { aes: Pool, } -struct EphemeralKeyPair { - // Time ephemeral key pair was created. - create_time_ticks: i64, - - // SHA384(c25519 public | p521 public) - public_keys_hash: [u8; 48], -} - /// A remote peer known to this node. /// Sending-related and receiving-related fields are locked separately since concurrent /// send/receive is not uncommon. @@ -76,7 +74,7 @@ pub struct Peer { identity: Identity, // Static shared secret computed from agreement with identity. - static_secret: PeerSecret, + static_secret: SymmetricSecret, // Derived static secret (in initialized cipher) used to encrypt the dictionary part of HELLO. static_secret_hello_dictionary: Mutex, @@ -85,10 +83,7 @@ pub struct Peer { static_secret_packet_hmac: Secret<48>, // Latest ephemeral secret acknowledged with OK(HELLO). - ephemeral_secret: Mutex>>, - - // Either None or the current ephemeral key pair whose public keys are on offer. - ephemeral_pair: Mutex>, + ephemeral_secret: Mutex>>, // Paths sorted in descending order of quality / preference. paths: Mutex>>, @@ -106,8 +101,8 @@ pub struct Peer { total_bytes_received_indirect: AtomicU64, total_bytes_forwarded: AtomicU64, - // Counter for assigning packet IV's a.k.a. PacketIDs. - packet_id_counter: AtomicU64, + // Counter for assigning sequential message IDs. + message_id_counter: AtomicU64, // Remote peer version information. remote_version: AtomicU64, @@ -137,14 +132,84 @@ fn salsa_derive_per_packet_key(key: &Secret<48>, header: &PacketHeader, packet_s /// Create initialized instances of Salsa20/12 and Poly1305 for a packet. #[inline(always)] -fn salsa_poly_create(secret: &PeerSecret, header: &PacketHeader, packet_size: usize) -> (Salsa, Poly1305) { - let key = salsa_derive_per_packet_key(&secret.secret, header, packet_size); +fn salsa_poly_create(secret: &SymmetricSecret, header: &PacketHeader, packet_size: usize) -> (Salsa, Poly1305) { + let key = salsa_derive_per_packet_key(&secret.key, header, packet_size); let mut salsa = Salsa::new(&key.0[0..32], header.id_bytes(), true).unwrap(); let mut poly1305_key = [0_u8; 32]; salsa.crypt_in_place(&mut poly1305_key); (salsa, Poly1305::new(&poly1305_key).unwrap()) } +/// Attempt AEAD packet encryption and MAC validation. +fn try_aead_decrypt(secret: &SymmetricSecret, packet_frag0_payload_bytes: &[u8], header: &PacketHeader, fragments: &[Option], payload: &mut Buffer, message_id: &mut u64) -> bool { + packet_frag0_payload_bytes.get(0).map_or(false, |verb| { + match header.cipher() { + CIPHER_NOCRYPT_POLY1305 => { + if (verb & VERB_MASK) == VERB_VL1_HELLO { + let _ = payload.append_bytes(packet_frag0_payload_bytes); + for f in fragments.iter() { + let _ = f.as_ref().map(|f| f.as_bytes_starting_at(FRAGMENT_HEADER_SIZE).map(|f| payload.append_bytes(f))); + } + let (_, mut poly) = salsa_poly_create(secret, header, packet.len()); + poly.update(payload.as_bytes()); + if poly.finish()[0..8].eq(&header.mac) { + *message_id = u64::from_ne_bytes(header.id); + true + } else { + false + } + } else { + // Only HELLO is permitted without payload encryption. Drop other packet types if sent this way. + false + } + } + + CIPHER_SALSA2012_POLY1305 => { + let (mut salsa, mut poly) = salsa_poly_create(secret, header, packet.len()); + poly.update(packet_frag0_payload_bytes); + let _ = payload.append_bytes_get_mut(packet_frag0_payload_bytes.len()).map(|b| salsa.crypt(packet_frag0_payload_bytes, b)); + for f in fragments.iter() { + let _ = f.as_ref().map(|f| f.as_bytes_starting_at(FRAGMENT_HEADER_SIZE).map(|f| { + poly.update(f); + let _ = payload.append_bytes_get_mut(f.len()).map(|b| salsa.crypt(f, b)); + })); + } + if poly.finish()[0..8].eq(&header.mac) { + *message_id = u64::from_ne_bytes(header.id); + true + } else { + false + } + } + + CIPHER_AES_GMAC_SIV => { + let mut aes = secret.aes_gmac_siv.get(); + aes.decrypt_init(&header.aes_gmac_siv_tag()); + aes.decrypt_set_aad(&header.aad_bytes()); + // NOTE: if there are somehow missing fragments this part will silently fail, + // but the packet will fail MAC check in decrypt_finish() so meh. + let _ = payload.append_bytes_get_mut(packet_frag0_payload_bytes.len()).map(|b| aes.decrypt(packet_frag0_payload_bytes, b)); + for f in fragments.iter() { + f.as_ref().map(|f| { + f.as_bytes_starting_at(FRAGMENT_HEADER_SIZE).map(|f| { + let _ = payload.append_bytes_get_mut(f.len()).map(|b| aes.decrypt(f, b)); + }) + }); + } + aes.decrypt_finish().map_or_else(false, |tag| { + // AES-GMAC-SIV encrypts the packet ID too as part of its computation of a single + // opaque 128-bit tag, so to get the original packet ID we have to grab it from the + // decrypted tag. + *mesasge_id = u64::from_ne_bytes(*array_range::(tag)); + true + }) + } + + _ => false, + } + }) +} + impl Peer { pub(crate) const INTERVAL: i64 = PEER_SERVICE_INTERVAL; @@ -153,23 +218,12 @@ impl Peer { /// fatal error occurs performing key agreement between the two identities. pub(crate) fn new(this_node_identity: &Identity, id: Identity) -> Option { this_node_identity.agree(&id).map(|static_secret| { - let aes_factory = AesGmacSivPoolFactory( - zt_kbkdf_hmac_sha384(&static_secret.0, KBKDF_KEY_USAGE_LABEL_AES_GMAC_SIV_K0, 0, 0), - zt_kbkdf_hmac_sha384(&static_secret.0, KBKDF_KEY_USAGE_LABEL_AES_GMAC_SIV_K1, 0, 0)); - let static_secret_hello_dictionary = zt_kbkdf_hmac_sha384(&static_secret.0, KBKDF_KEY_USAGE_LABEL_HELLO_DICTIONARY_ENCRYPT, 0, 0); - let static_secret_packet_hmac = zt_kbkdf_hmac_sha384(&static_secret.0, KBKDF_KEY_USAGE_LABEL_PACKET_HMAC, 0, 0); Peer { identity: id, - static_secret: PeerSecret { - create_time_ticks: -1, - encrypt_count: AtomicU64::new(0), - secret: static_secret, - aes: Pool::new(4, aes_factory), - }, + static_secret: SymmetricSecret::new(static_secret), static_secret_hello_dictionary: Mutex::new(AesCtr::new(&static_secret_hello_dictionary.0[0..32])), static_secret_packet_hmac, ephemeral_secret: Mutex::new(None), - ephemeral_pair: Mutex::new(None), paths: Mutex::new(Vec::new()), reported_local_ip: Mutex::new(None), last_send_time_ticks: AtomicI64::new(0), @@ -180,207 +234,146 @@ impl Peer { total_bytes_received: AtomicU64::new(0), total_bytes_received_indirect: AtomicU64::new(0), total_bytes_forwarded: AtomicU64::new(0), - packet_id_counter: AtomicU64::new(next_u64_secure()), + message_id_counter: AtomicU64::new(next_u64_secure()), remote_version: AtomicU64::new(0), remote_protocol_version: AtomicU8::new(0), } }) } - /// Get the next packet ID / IV. + /// Get the next message ID. #[inline(always)] - pub(crate) fn next_packet_id(&self) -> PacketID { self.packet_id_counter.fetch_add(1, Ordering::Relaxed) } + pub(crate) fn next_message_id(&self) -> u64 { self.message_id_counter.fetch_add(1, Ordering::Relaxed) } /// Receive, decrypt, authenticate, and process an incoming packet from this peer. /// If the packet comes in multiple fragments, the fragments slice should contain all /// those fragments after the main packet header and first chunk. pub(crate) fn receive(&self, node: &Node, ci: &CI, ph: &PH, time_ticks: i64, source_path: &Arc, header: &PacketHeader, packet: &Buffer<{ PACKET_SIZE_MAX }>, fragments: &[Option]) { let _ = packet.as_bytes_starting_at(PACKET_VERB_INDEX).map(|packet_frag0_payload_bytes| { - let mut payload = node.get_packet_buffer(); - - let mut forward_secrecy = true; // set to false below if ephemeral fails - let mut packet_id = header.id as u64; - let cipher = header.cipher(); - let ephemeral_secret = self.ephemeral_secret.lock().clone(); - for secret in [ephemeral_secret.as_ref().map_or(&self.static_secret, |s| s.as_ref()), &self.static_secret] { - match cipher { - CIPHER_NOCRYPT_POLY1305 => { - if (packet_frag0_payload_bytes[0] & VERB_MASK) == VERB_VL1_HELLO { - let _ = payload.append_bytes(packet_frag0_payload_bytes); - for f in fragments.iter() { - let _ = f.as_ref().map(|f| f.as_bytes_starting_at(FRAGMENT_HEADER_SIZE).map(|f| payload.append_bytes(f))); - } - - // FIPS note: for FIPS purposes the HMAC-SHA384 tag at the end of V2 HELLOs - // will be considered the "real" handshake authentication. This authentication - // is technically deprecated in V2. - let (_, mut poly) = salsa_poly_create(secret, header, packet.len()); - poly.update(payload.as_bytes()); - - if poly.finish()[0..8].eq(&header.message_auth) { - break; - } - } else { - // Only HELLO is permitted without payload encryption. Drop other packet types if sent this way. - return; - } - } - - CIPHER_SALSA2012_POLY1305 => { - let (mut salsa, mut poly) = salsa_poly_create(secret, header, packet.len()); - poly.update(packet_frag0_payload_bytes); - let _ = payload.append_and_init_bytes(packet_frag0_payload_bytes.len(), |b| salsa.crypt(packet_frag0_payload_bytes, b)); - for f in fragments.iter() { - let _ = f.as_ref().map(|f| f.as_bytes_starting_at(FRAGMENT_HEADER_SIZE).map(|f| { - poly.update(f); - let _ = payload.append_and_init_bytes(f.len(), |b| salsa.crypt(f, b)); - })); - } - if poly.finish()[0..8].eq(&header.message_auth) { - break; - } - } - - CIPHER_AES_GMAC_SIV => { - let mut aes = secret.aes.get(); - aes.decrypt_init(&header.aes_gmac_siv_tag()); - aes.decrypt_set_aad(&header.aad_bytes()); - let _ = payload.append_and_init_bytes(packet_frag0_payload_bytes.len(), |b| aes.decrypt(packet_frag0_payload_bytes, b)); - for f in fragments.iter() { - let _ = f.as_ref().map(|f| f.as_bytes_starting_at(FRAGMENT_HEADER_SIZE).map(|f| payload.append_and_init_bytes(f.len(), |b| aes.decrypt(f, b)))); - } - let tag = aes.decrypt_finish(); - if tag.is_some() { - // For AES-GMAC-SIV we need to grab the original packet ID from the decrypted tag. - let tag = tag.unwrap(); - unsafe { copy_nonoverlapping(tag.as_ptr(), (&mut packet_id as *mut u64).cast(), 8) }; - break; - } - } - - _ => { - return; - } - } - - if (secret as *const PeerSecret) == (&self.static_secret as *const PeerSecret) { - // If the static secret failed to authenticate it means we either didn't have an - // ephemeral key or the ephemeral also failed (as it's tried first). + let mut payload: Buffer = unsafe { Buffer::new_nozero() }; + let mut message_id = 0_u64; + let mut forward_secrecy = true; + let ephemeral_secret: Option> = self.ephemeral_secret.lock().clone(); + if !ephemeral_secret.map_or(false, |ephemeral_secret| try_aead_decrypt(&ephemeral_secret.secret, packet_frag0_payload_bytes, header, fragments, &mut payload, &mut message_id)) { + unsafe { payload.set_size_unchecked(0); } + if !try_aead_decrypt(&self.static_secret, packet_frag0_payload_bytes, header, fragments, &mut payload, &mut message_id) { return; - } else { - // If ephemeral failed, static secret will be tried. Set forward secrecy to false. - forward_secrecy = false; - payload.clear(); } + forward_secrecy = false; } - drop(ephemeral_secret); - - // If decryption and authentication succeeded, the code above will break out of the - // for loop and end up here. Otherwise it returns from the whole function. self.last_receive_time_ticks.store(time_ticks, Ordering::Relaxed); self.total_bytes_received.fetch_add((payload.len() + PACKET_HEADER_SIZE) as u64, Ordering::Relaxed); - let _ = payload.u8_at(0).map(|mut verb| { - let extended_authentication = (verb & VERB_FLAG_EXTENDED_AUTHENTICATION) != 0; - if extended_authentication { - let auth_bytes = payload.as_bytes(); - if auth_bytes.len() >= (1 + SHA384_HASH_SIZE) { - let packet_hmac_start = auth_bytes.len() - SHA384_HASH_SIZE; - if !SHA384::hmac(self.static_secret_packet_hmac.as_ref(), &auth_bytes[1..packet_hmac_start]).eq(&auth_bytes[packet_hmac_start..]) { - return; - } - let new_len = payload.len() - SHA384_HASH_SIZE; - payload.set_size(new_len); - } else { + debug_assert!(!payload.is_empty()); // should be impossible since this fails in try_aead_decrypt() + let mut verb = payload.as_bytes()[0]; + + // If this flag is set, the end of the payload is a full HMAC-SHA384 authentication + // tag for much stronger authentication. + let extended_authentication = (verb & VERB_FLAG_EXTENDED_AUTHENTICATION) != 0; + if extended_authentication { + if payload.len() >= (1 + SHA384_HASH_SIZE) { + let actual_end_of_payload = payload.len() - SHA384_HASH_SIZE; + let hmac = SHA384::hmac_multipart(self.static_secret_packet_hmac.as_ref(), &[u64_as_bytes(&message_id), payload.as_bytes()]); + if !hmac.eq(&(payload.as_bytes()[actual_end_of_payload..])) { return; } + payload.set_size(actual_end_of_payload); + } else { + return; } + } - if (verb & VERB_FLAG_COMPRESSED) != 0 { - let mut decompressed_payload = node.get_packet_buffer(); - let _ = decompressed_payload.append_u8(verb); - let dlen = lz4_flex::block::decompress_into(&payload.as_bytes()[1..], &mut decompressed_payload.as_bytes_mut()); - if dlen.is_ok() { - decompressed_payload.set_size(dlen.unwrap()); - payload = decompressed_payload; - } else { - return; - } + if (verb & VERB_FLAG_COMPRESSED) != 0 { + let mut decompressed_payload: [u8; PACKET_SIZE_MAX] = unsafe { MaybeUninit::uninit().assume_init() }; + decompressed_payload[0] = verb; + let dlen = lz4_flex::block::decompress_into(&payload.as_bytes()[1..], &mut decompressed_payload[1..]); + if dlen.is_ok() { + payload.set_to(&decompressed_payload[0..(dlen.unwrap() + 1)]); + } else { + return; } + } - // For performance reasons we let VL2 handle packets first. It returns false - // if it didn't handle the packet, in which case it's handled at VL1. This is - // because the most performance critical path is the handling of the ???_FRAME - // verbs, which are in VL2. - verb &= VERB_MASK; - if !ph.handle_packet(self, source_path, forward_secrecy, extended_authentication, verb, payload.as_ref()) { - match verb { - //VERB_VL1_NOP => {} - VERB_VL1_HELLO => self.receive_hello(ci, node, time_ticks, source_path, payload.as_ref()), - VERB_VL1_ERROR => self.receive_error(ci, ph, node, time_ticks, source_path, forward_secrecy, extended_authentication, payload.as_ref()), - VERB_VL1_OK => self.receive_ok(ci, ph, node, time_ticks, source_path, forward_secrecy, extended_authentication, payload.as_ref()), - VERB_VL1_WHOIS => self.receive_whois(ci, node, time_ticks, source_path, payload.as_ref()), - VERB_VL1_RENDEZVOUS => self.receive_rendezvous(ci, node, time_ticks, source_path, payload.as_ref()), - VERB_VL1_ECHO => self.receive_echo(ci, node, time_ticks, source_path, payload.as_ref()), - VERB_VL1_PUSH_DIRECT_PATHS => self.receive_push_direct_paths(ci, node, time_ticks, source_path, payload.as_ref()), - VERB_VL1_USER_MESSAGE => self.receive_user_message(ci, node, time_ticks, source_path, payload.as_ref()), - _ => {} - } + // For performance reasons we let VL2 handle packets first. It returns false + // if it didn't handle the packet, in which case it's handled at VL1. This is + // because the most performance critical path is the handling of the ???_FRAME + // verbs, which are in VL2. + verb &= VERB_MASK; + if !ph.handle_packet(self, source_path, forward_secrecy, extended_authentication, verb, &payload) { + match verb { + //VERB_VL1_NOP => {} + VERB_VL1_HELLO => self.receive_hello(ci, node, time_ticks, source_path, &payload), + VERB_VL1_ERROR => self.receive_error(ci, ph, node, time_ticks, source_path, forward_secrecy, extended_authentication, &payload), + VERB_VL1_OK => self.receive_ok(ci, ph, node, time_ticks, source_path, forward_secrecy, extended_authentication, &payload), + VERB_VL1_WHOIS => self.receive_whois(ci, node, time_ticks, source_path, &payload), + VERB_VL1_RENDEZVOUS => self.receive_rendezvous(ci, node, time_ticks, source_path, &payload), + VERB_VL1_ECHO => self.receive_echo(ci, node, time_ticks, source_path, &payload), + VERB_VL1_PUSH_DIRECT_PATHS => self.receive_push_direct_paths(ci, node, time_ticks, source_path, &payload), + VERB_VL1_USER_MESSAGE => self.receive_user_message(ci, node, time_ticks, source_path, &payload), + _ => {} } - }); + } }); } - fn send_to_endpoint(&self, ci: &CI, endpoint: &Endpoint, local_socket: Option, local_interface: Option, packet_id: PacketID, packet: &Buffer<{ PACKET_SIZE_MAX }>) -> bool { + fn send_to_endpoint(&self, ci: &CI, endpoint: &Endpoint, local_socket: Option, local_interface: Option, packet: &Buffer<{ PACKET_SIZE_MAX }>) -> bool { debug_assert!(packet.len() <= PACKET_SIZE_MAX); - if matches!(endpoint, Endpoint::IpUdp(_)) { - let packet_size = packet.len(); - if packet_size > UDP_DEFAULT_MTU { - let bytes = packet.as_bytes(); - if !ci.wire_send(endpoint, local_socket, local_interface, &[&bytes[0..UDP_DEFAULT_MTU]], 0) { - return false; - } - - let mut pos = UDP_DEFAULT_MTU; - - let fragment_count = (((packet_size - UDP_DEFAULT_MTU) as u32) / ((UDP_DEFAULT_MTU - FRAGMENT_HEADER_SIZE) as u32)) + ((((packet_size - UDP_DEFAULT_MTU) as u32) % ((UDP_DEFAULT_MTU - FRAGMENT_HEADER_SIZE) as u32)) != 0) as u32; - debug_assert!(fragment_count <= FRAGMENT_COUNT_MAX as u32); - - let mut header = FragmentHeader { - id: packet_id, - dest: bytes[PACKET_DESTINATION_INDEX..PACKET_DESTINATION_INDEX + ADDRESS_SIZE].try_into().unwrap(), - fragment_indicator: FRAGMENT_INDICATOR, - total_and_fragment_no: ((fragment_count + 1) << 4) as u8, - reserved_hops: 0, - }; - - let mut chunk_size = (packet_size - pos).min(UDP_DEFAULT_MTU - FRAGMENT_HEADER_SIZE); - loop { - header.total_and_fragment_no += 1; - let next_pos = pos + chunk_size; - if !ci.wire_send(endpoint, local_socket, local_interface, &[header.as_bytes(), &bytes[pos..next_pos]], 0) { + debug_assert!(packet.len() >= PACKET_SIZE_MIN); + match endpoint { + Endpoint::Ip(_) | Endpoint::IpUdp(_) | Endpoint::Ethernet(_) | Endpoint::Bluetooth(_) | Endpoint::WifiDirect(_) => { + let packet_size = packet.len(); + if packet_size > UDP_DEFAULT_MTU { + let bytes = packet.as_bytes(); + if !ci.wire_send(endpoint, local_socket, local_interface, &[&bytes[0..UDP_DEFAULT_MTU]], 0) { return false; } - pos = next_pos; - if pos < packet_size { - chunk_size = (packet_size - pos).min(UDP_DEFAULT_MTU - FRAGMENT_HEADER_SIZE); - } else { - return true; + + let mut pos = UDP_DEFAULT_MTU; + + let overrun_size = (packet_size - UDP_DEFAULT_MTU) as u32; + let fragment_count = (overrun_size / (UDP_DEFAULT_MTU - FRAGMENT_HEADER_SIZE) as u32) + (((overrun_size % (UDP_DEFAULT_MTU - FRAGMENT_HEADER_SIZE) as u32) != 0) as u32); + debug_assert!(fragment_count <= PACKET_FRAGMENT_COUNT_MAX as u32); + + let mut header = FragmentHeader { + id: unsafe { *packet.as_bytes().as_ptr().cast::<[u8; 8]>() }, + dest: bytes[PACKET_DESTINATION_INDEX..PACKET_DESTINATION_INDEX + ADDRESS_SIZE].try_into().unwrap(), + fragment_indicator: PACKET_FRAGMENT_INDICATOR, + total_and_fragment_no: ((fragment_count + 1) << 4) as u8, + reserved_hops: 0, + }; + + let mut chunk_size = (packet_size - pos).min(UDP_DEFAULT_MTU - FRAGMENT_HEADER_SIZE); + loop { + header.total_and_fragment_no += 1; + let next_pos = pos + chunk_size; + if !ci.wire_send(endpoint, local_socket, local_interface, &[header.as_bytes(), &bytes[pos..next_pos]], 0) { + return false; + } + pos = next_pos; + if pos < packet_size { + chunk_size = (packet_size - pos).min(UDP_DEFAULT_MTU - FRAGMENT_HEADER_SIZE); + } else { + return true; + } } + } else { + return ci.wire_send(endpoint, local_socket, local_interface, &[packet.as_bytes()], 0); } } + _ => { + return ci.wire_send(endpoint, local_socket, local_interface, &[packet.as_bytes()], 0); + } } - return ci.wire_send(endpoint, local_socket, local_interface, &[packet.as_bytes()], 0); } /// Send a packet to this peer. /// /// This will go directly if there is an active path, or otherwise indirectly /// via a root or some other route. - pub(crate) fn send(&self, ci: &CI, node: &Node, time_ticks: i64, packet_id: PacketID, packet: &Buffer<{ PACKET_SIZE_MAX }>) -> bool { + pub(crate) fn send(&self, ci: &CI, node: &Node, time_ticks: i64, packet: &Buffer<{ PACKET_SIZE_MAX }>) -> bool { self.path(node).map_or(false, |path| { - if self.send_to_endpoint(ci, path.endpoint(), path.local_socket(), path.local_interface(), packet_id, packet) { + if self.send_to_endpoint(ci, path.endpoint(), path.local_socket(), path.local_interface(), packet) { self.last_send_time_ticks.store(time_ticks, Ordering::Relaxed); self.total_bytes_sent.fetch_add(packet.len() as u64, Ordering::Relaxed); true @@ -422,84 +415,70 @@ impl Peer { let mut packet: Buffer<{ PACKET_SIZE_MAX }> = Buffer::new(); let time_ticks = ci.time_ticks(); - let packet_id = self.next_packet_id(); - debug_assert!(packet.append_and_init_struct(|header: &mut PacketHeader| { - header.id = packet_id; - header.dest = self.identity.address().to_bytes(); - header.src = node.address().to_bytes(); - header.flags_cipher_hops = CIPHER_NOCRYPT_POLY1305; - }).is_ok()); - debug_assert!(packet.append_and_init_struct(|header: &mut message_component_structs::HelloFixedHeaderFields| { - header.verb = VERB_VL1_HELLO | VERB_FLAG_EXTENDED_AUTHENTICATION; - header.version_proto = VERSION_PROTO; - header.version_major = VERSION_MAJOR; - header.version_minor = VERSION_MINOR; - header.version_revision = (VERSION_REVISION as u16).to_be(); - header.timestamp = (time_ticks as u64).to_be(); - }).is_ok()); + let message_id = self.next_message_id(); + let packet_header: &mut PacketHeader = packet.append_struct_get_mut().unwrap(); + let hello_fixed_headers: &mut message_component_structs::HelloFixedHeaderFields = packet.append_struct_get_mut().unwrap(); + packet_header.id = message_id.to_ne_bytes(); // packet ID and message ID are the same when Poly1305 MAC is used + packet_header.dest = self.identity.address().to_bytes(); + packet_header.src = node.address().to_bytes(); + packet_header.flags_cipher_hops = CIPHER_NOCRYPT_POLY1305; + hello_fixed_headers.verb = VERB_VL1_HELLO | VERB_FLAG_EXTENDED_AUTHENTICATION; + hello_fixed_headers.version_proto = VERSION_PROTO; + hello_fixed_headers.version_major = VERSION_MAJOR; + hello_fixed_headers.version_minor = VERSION_MINOR; + hello_fixed_headers.version_revision = (VERSION_REVISION as u16).to_be(); + hello_fixed_headers.timestamp = (time_ticks as u64).to_be(); debug_assert!(self.identity.marshal(&mut packet, false).is_ok()); debug_assert!(endpoint.marshal(&mut packet).is_ok()); + // Write an IV for AES-CTR encryption of the dictionary and allocate two more + // bytes for reserved legacy use below. let aes_ctr_iv_position = packet.len(); - debug_assert!(packet.append_and_init_bytes_fixed(|iv: &mut [u8; 18]| { - zerotier_core_crypto::random::fill_bytes_secure(&mut iv[0..16]); - iv[12] &= 0x7f; // mask off MSB of counter in iv to play nice with some AES-CTR implementations + let aes_ctr_iv: &mut [u8; 18] = packet.append_bytes_fixed_get_mut().unwrap(); + zerotier_core_crypto::random::fill_bytes_secure(&mut aes_ctr_iv[0..16]); + aes_ctr_iv[12] &= 0x7f; // mask off MSB of counter in iv to play nice with some AES-CTR implementations - // LEGACY: create a 16-bit encrypted field that specifies zero moons. This is ignored by v2 - // but causes v1 nodes to be able to parse this packet properly. This is not significant in - // terms of encryption or authentication. - let mut salsa_iv = packet_id.to_ne_bytes(); - salsa_iv[7] &= 0xf8; - Salsa::new(&self.static_secret.secret.0[0..32], &salsa_iv, true).unwrap().crypt(&[0_u8, 0_u8], &mut salsa_iv[16..18]); - }).is_ok()); + // LEGACY: create a 16-bit encrypted field that specifies zero "moons." This is ignored now + // but causes old nodes to be able to parse this packet properly. This is not significant in + // terms of encryption or authentication and can disappear once old versions are dead. Newer + // versions ignore these bytes. + let mut salsa_iv = message_id.to_ne_bytes(); + salsa_iv[7] &= 0xf8; + Salsa::new(&self.static_secret.secret.0[0..32], &salsa_iv, true).unwrap().crypt(&[0_u8, 0_u8], &mut aes_ctr_iv[16..18]); + // Create dictionary that contains extended HELLO fields. let dict_start_position = packet.len(); let mut dict = Dictionary::new(); dict.set_u64(HELLO_DICT_KEY_INSTANCE_ID, node.instance_id); dict.set_u64(HELLO_DICT_KEY_CLOCK, ci.time_clock() as u64); - if node.is_peer_root(self) { - // If the peer is a root we include some extra information for diagnostic and statistics - // purposes such as the CPU type, bits, and OS info. This is not sent to other peers. - dict.set_str(HELLO_DICT_KEY_SYS_ARCH, std::env::consts::ARCH); - #[cfg(target_pointer_width = "32")] { - dict.set_u64(HELLO_DICT_KEY_SYS_BITS, 32); - } - #[cfg(target_pointer_width = "64")] { - dict.set_u64(HELLO_DICT_KEY_SYS_BITS, 64); - } - dict.set_str(HELLO_DICT_KEY_OS_NAME, std::env::consts::OS); - } - let mut flags = String::new(); - // TODO - //if node.fips_mode() { - // flags.push('F'); - //} - dict.set_str(HELLO_DICT_KEY_FLAGS, flags.as_str()); debug_assert!(dict.write_to(&mut packet).is_ok()); + // Encrypt extended fields with AES-CTR. let mut dict_aes = self.static_secret_hello_dictionary.lock(); dict_aes.init(&packet.as_bytes()[aes_ctr_iv_position..aes_ctr_iv_position + 16]); dict_aes.crypt_in_place(&mut packet.as_bytes_mut()[dict_start_position..]); drop(dict_aes); - debug_assert!(packet.append_u16(0).is_ok()); - - debug_assert!(packet.append_bytes_fixed(&SHA384::hmac(self.static_secret_packet_hmac.as_ref(), packet.as_bytes_starting_at(PACKET_HEADER_SIZE).unwrap())).is_ok()); + // Append extended authentication HMAC. + debug_assert!(packet.append_bytes_fixed(&SHA384::hmac_multipart(self.static_secret_packet_hmac.as_ref(), &[u64_as_bytes(&message_id), &packet.as_bytes()[PACKET_HEADER_SIZE..]])).is_ok()); + // Set outer packet MAC. We use legacy poly1305 for HELLO for backward + // compatibility, but note that newer nodes and roots will check the full + // HMAC-SHA384 above. let (_, mut poly) = salsa_poly_create(&self.static_secret, packet.struct_at::(0).unwrap(), packet.len()); poly.update(packet.as_bytes_starting_at(PACKET_HEADER_SIZE).unwrap()); - packet.as_bytes_mut()[HEADER_MAC_FIELD_INDEX..HEADER_MAC_FIELD_INDEX + 8].copy_from_slice(&poly.finish()[0..8]); + packet_header.mac.copy_from_slice(&poly.finish()[0..8]); self.static_secret.encrypt_count.fetch_add(1, Ordering::Relaxed); self.last_send_time_ticks.store(time_ticks, Ordering::Relaxed); self.total_bytes_sent.fetch_add(packet.len() as u64, Ordering::Relaxed); path.as_ref().map_or_else(|| { - self.send_to_endpoint(ci, endpoint, None, None, packet_id, &packet) + self.send_to_endpoint(ci, endpoint, None, None, &packet) }, |path| { path.log_send(time_ticks); - self.send_to_endpoint(ci, endpoint, path.local_socket(), path.local_interface(), packet_id, &packet) + self.send_to_endpoint(ci, endpoint, path.local_socket(), path.local_interface(), &packet) }) }) } @@ -516,7 +495,7 @@ impl Peer { let mut cursor: usize = 0; let _ = payload.read_struct::(&mut cursor).map(|error_header| { let in_re_packet_id = error_header.in_re_packet_id; - let current_packet_id_counter = self.packet_id_counter.load(Ordering::Relaxed); + let current_packet_id_counter = self.message_id_counter.load(Ordering::Relaxed); if current_packet_id_counter.checked_sub(in_re_packet_id).map_or_else(|| { (!in_re_packet_id).wrapping_add(current_packet_id_counter) < PACKET_RESPONSE_COUNTER_DELTA_MAX }, |packets_ago| { @@ -536,7 +515,7 @@ impl Peer { let mut cursor: usize = 0; let _ = payload.read_struct::(&mut cursor).map(|ok_header| { let in_re_packet_id = ok_header.in_re_packet_id; - let current_packet_id_counter = self.packet_id_counter.load(Ordering::Relaxed); + let current_packet_id_counter = self.message_id_counter.load(Ordering::Relaxed); if current_packet_id_counter.checked_sub(in_re_packet_id).map_or_else(|| { (!in_re_packet_id).wrapping_add(current_packet_id_counter) < PACKET_RESPONSE_COUNTER_DELTA_MAX }, |packets_ago| { @@ -571,7 +550,10 @@ impl Peer { fn receive_user_message(&self, ci: &CI, node: &Node, time_ticks: i64, source_path: &Arc, payload: &Buffer<{ PACKET_SIZE_MAX }>) {} /// Get current best path or None if there are no direct paths to this peer. - pub fn direct_path(&self) -> Option> { self.paths.lock().first().map(|p| p.clone()) } + #[inline(always)] + pub fn direct_path(&self) -> Option> { + self.paths.lock().first().map(|p| p.clone()) + } /// Get either the current best direct path or an indirect path. pub fn path(&self, node: &Node) -> Option> { diff --git a/zerotier-network-hypervisor/src/vl1/protocol.rs b/zerotier-network-hypervisor/src/vl1/protocol.rs index b19a3b9de..3bf322c28 100644 --- a/zerotier-network-hypervisor/src/vl1/protocol.rs +++ b/zerotier-network-hypervisor/src/vl1/protocol.rs @@ -24,16 +24,6 @@ pub const VERB_VL1_USER_MESSAGE: u8 = 0x14; pub const HELLO_DICT_KEY_INSTANCE_ID: &'static str = "I"; pub const HELLO_DICT_KEY_CLOCK: &'static str = "C"; -pub const HELLO_DICT_KEY_EPHEMERAL_PUBLIC: &'static str = "E"; -pub const HELLO_DICT_KEY_EPHEMERAL_ACK: &'static str = "e"; -pub const HELLO_DICT_KEY_HELLO_ORIGIN: &'static str = "@"; -pub const HELLO_DICT_KEY_SYS_ARCH: &'static str = "Sa"; -pub const HELLO_DICT_KEY_SYS_BITS: &'static str = "Sb"; -pub const HELLO_DICT_KEY_OS_NAME: &'static str = "So"; -pub const HELLO_DICT_KEY_OS_VERSION: &'static str = "Sv"; -pub const HELLO_DICT_KEY_OS_VARIANT: &'static str = "St"; -pub const HELLO_DICT_KEY_VENDOR: &'static str = "V"; -pub const HELLO_DICT_KEY_FLAGS: &'static str = "+"; /// KBKDF usage label indicating a key used to encrypt the dictionary inside HELLO. pub const KBKDF_KEY_USAGE_LABEL_HELLO_DICTIONARY_ENCRYPT: u8 = b'H'; @@ -126,26 +116,26 @@ pub const CIPHER_AES_GMAC_SIV: u8 = 0x30; pub const HEADER_FLAG_FRAGMENTED: u8 = 0x40; /// Minimum size of a fragment. -pub const FRAGMENT_SIZE_MIN: usize = 16; +pub const PACKET_FRAGMENT_SIZE_MIN: usize = 16; /// Size of fragment header after which data begins. pub const FRAGMENT_HEADER_SIZE: usize = 16; /// Maximum allowed number of fragments. -pub const FRAGMENT_COUNT_MAX: usize = 8; +pub const PACKET_FRAGMENT_COUNT_MAX: usize = 8; /// Time after which an incomplete fragmented packet expires. -pub const FRAGMENT_EXPIRATION: i64 = 1500; +pub const PACKET_FRAGMENT_EXPIRATION: i64 = 1500; /// Maximum number of inbound fragmented packets to handle at once per path. /// This is a sanity limit to prevent memory exhaustion due to DOS attacks or broken peers. -pub const FRAGMENT_MAX_INBOUND_PACKETS_PER_PATH: usize = 256; +pub const PACKET_FRAGMENT_MAX_INBOUND_PACKETS_PER_PATH: usize = 256; /// Index of packet fragment indicator byte to detect fragments. -pub const FRAGMENT_INDICATOR_INDEX: usize = 13; +pub const PACKET_FRAGMENT_INDICATOR_INDEX: usize = 13; /// Byte found at FRAGMENT_INDICATOR_INDEX to indicate a fragment. -pub const FRAGMENT_INDICATOR: u8 = 0xff; +pub const PACKET_FRAGMENT_INDICATOR: u8 = 0xff; /// Verb (inner) flag indicating that the packet's payload (after the verb) is LZ4 compressed. pub const VERB_FLAG_COMPRESSED: u8 = 0x80; @@ -232,33 +222,17 @@ pub fn compress_packet(src: &[u8], dest: &mut Buffer<{ PACKET_SIZE_MAX }>) -> bo return false; } -/// Add HMAC-SHA384 to the end of a packet and set verb flag. -#[inline(always)] -pub fn add_extended_auth(pkt: &mut Buffer<{ PACKET_SIZE_MAX }>, hmac_secret_key: &[u8]) -> std::io::Result<()> { - pkt.append_bytes_fixed(&zerotier_core_crypto::hash::SHA384::hmac(hmac_secret_key, pkt.as_bytes_starting_at(PACKET_VERB_INDEX + 1)?))?; - pkt.as_bytes_mut()[PACKET_VERB_INDEX] |= VERB_FLAG_EXTENDED_AUTHENTICATION; - Ok(()) -} - -/// A unique packet identifier, also the cryptographic nonce. -/// -/// Packet IDs are stored as u64s for efficiency but they should be treated as -/// [u8; 8] fields in that their endianness is "wire" endian. If for some reason -/// packet IDs need to be portably compared or shared across systems they should -/// be treated as bytes not integers. -pub type PacketID = u64; - /// ZeroTier unencrypted outer packet header /// /// This is the header for a complete packet. If the fragmented flag is set, it will /// arrive with one or more fragments that must be assembled to complete it. #[repr(packed)] pub struct PacketHeader { - pub id: PacketID, + pub id: [u8; 8], pub dest: [u8; 5], pub src: [u8; 5], pub flags_cipher_hops: u8, - pub message_auth: [u8; 8], + pub mac: [u8; 8], } unsafe impl RawObject for PacketHeader {} @@ -281,9 +255,6 @@ impl PacketHeader { #[inline(always)] pub fn is_fragmented(&self) -> bool { (self.flags_cipher_hops & HEADER_FLAG_FRAGMENTED) != 0 } - #[inline(always)] - pub fn id_bytes(&self) -> &[u8; 8] { unsafe { &*(self as *const Self).cast::<[u8; 8]>() } } - #[inline(always)] pub fn as_bytes(&self) -> &[u8; PACKET_HEADER_SIZE] { unsafe { &*(self as *const Self).cast::<[u8; PACKET_HEADER_SIZE]>() } } @@ -300,7 +271,7 @@ impl PacketHeader { pub fn aes_gmac_siv_tag(&self) -> [u8; 16] { let mut id = unsafe { MaybeUninit::<[u8; 16]>::uninit().assume_init() }; id[0..8].copy_from_slice(self.id_bytes()); - id[8..16].copy_from_slice(&self.message_auth); + id[8..16].copy_from_slice(&self.mac); id } } @@ -313,7 +284,7 @@ impl PacketHeader { /// bit set and remaining fragments being these. #[repr(packed)] pub struct FragmentHeader { - pub id: PacketID, // packet ID + pub id: [u8; 8], // (outer) packet ID pub dest: [u8; 5], // destination address pub fragment_indicator: u8, // always 0xff in fragments pub total_and_fragment_no: u8, // TTTTNNNN (fragment number, total fragments) @@ -324,7 +295,7 @@ unsafe impl RawObject for FragmentHeader {} impl FragmentHeader { #[inline(always)] - pub fn is_fragment(&self) -> bool { self.fragment_indicator == FRAGMENT_INDICATOR } + pub fn is_fragment(&self) -> bool { self.fragment_indicator == PACKET_FRAGMENT_INDICATOR } #[inline(always)] pub fn total_fragments(&self) -> u8 { self.total_and_fragment_no >> 4 } @@ -412,11 +383,11 @@ mod tests { } let bar = PacketHeader{ - id: 0x0102030405060708_u64.to_be(), + id: [1_u8, 2, 3, 4, 5, 6, 7, 8], dest: [0_u8; 5], src: [0_u8; 5], flags_cipher_hops: 0, - message_auth: [0_u8; 8], + mac: [0_u8; 8], }; assert_eq!(bar.id_bytes().clone(), [1_u8, 2, 3, 4, 5, 6, 7, 8]); } diff --git a/zerotier-system-service/Cargo.lock b/zerotier-system-service/Cargo.lock index b8fde2dab..afc629700 100644 --- a/zerotier-system-service/Cargo.lock +++ b/zerotier-system-service/Cargo.lock @@ -2236,6 +2236,7 @@ dependencies = [ "digest_auth", "libc", "mach", + "num-traits", "num_cpus", "parking_lot", "serde", diff --git a/zerotier-system-service/Cargo.toml b/zerotier-system-service/Cargo.toml index 089359863..f8c281099 100644 --- a/zerotier-system-service/Cargo.toml +++ b/zerotier-system-service/Cargo.toml @@ -12,6 +12,7 @@ codegen-units = 1 panic = 'abort' [dependencies] +num-traits = "^0" zerotier-network-hypervisor = { path = "../zerotier-network-hypervisor" } zerotier-core-crypto = { path = "../zerotier-core-crypto" } serde = { version = "^1", features = ["derive"], default-features = false } diff --git a/zerotier-system-service/src/fastudpsocket.rs b/zerotier-system-service/src/fastudpsocket.rs index 78778c776..705b630f1 100644 --- a/zerotier-system-service/src/fastudpsocket.rs +++ b/zerotier-system-service/src/fastudpsocket.rs @@ -6,7 +6,16 @@ * https://www.zerotier.com/ */ +/* + * This is a threaded UDP socket listener for high performance. The fastest way to receive UDP + * (without heroic efforts like kernel bypass) on most platforms is to create a separate socket + * for each thread using options like SO_REUSEPORT and concurrent packet listening. + */ + +use std::mem::{MaybeUninit, transmute, zeroed}; +use std::num::NonZeroI64; use std::os::raw::c_int; +use std::ptr::null_mut; use std::sync::Arc; use std::sync::atomic::{AtomicBool, Ordering}; @@ -14,18 +23,11 @@ use num_traits::cast::AsPrimitive; use zerotier_network_hypervisor::vl1::InetAddress; use zerotier_network_hypervisor::{PacketBuffer, PacketBufferPool}; +use crate::debug; -/* - * This is a threaded UDP socket listener for high performance. The fastest way to receive UDP - * (without heroic efforts like kernel bypass) on most platforms is to create a separate socket - * for each thread using options like SO_REUSEPORT and concurrent packet listening. - */ +const FAST_UDP_SOCKET_MAX_THREADS: usize = 4; -#[cfg(windows)] -pub(crate) type FastUDPRawOsSocket = winsock2::SOCKET; - -#[cfg(unix)] -pub(crate) type FastUDPRawOsSocket = c_int; +pub(crate) type FastUDPRawOsSocket = NonZeroI64; #[cfg(unix)] fn bind_udp_socket(_device_name: &str, address: &InetAddress) -> Result { @@ -39,7 +41,7 @@ fn bind_udp_socket(_device_name: &str, address: &InetAddress) -> Result Result i64 { (*socket) as i64 } - -#[inline(always)] -pub(crate) fn fast_udp_socket_from_i64(socket: i64) -> Option { - if socket >= 0 { - Some(socket as FastUDPRawOsSocket) - } else { - None - } + unsafe { libc::close(socket.get().as_()); } } /// Send to a raw UDP socket with optional packet TTL. /// If the packet_ttl option is <=0, packet is sent with the default TTL. TTL setting is only used /// in ZeroTier right now to do escalating TTL probes for IPv4 NAT traversal. #[cfg(unix)] -#[inline(always)] -pub(crate) fn fast_udp_socket_sendto(socket: &FastUDPRawOsSocket, to_address: &InetAddress, data: &[u8], packet_ttl: i32) { +pub(crate) fn fast_udp_socket_sendto(socket: &FastUDPRawOsSocket, to_address: &InetAddress, data: &[&[u8]], packet_ttl: u8) { unsafe { - if packet_ttl <= 0 { - libc::sendto(*socket, data.as_ptr().cast(), data.len().as_(), 0, (to_address as *const InetAddress).cast(), std::mem::size_of::().as_()); + debug_assert!(zerotier_network_hypervisor::vl1::PACKET_FRAGMENT_COUNT_MAX < 16); + debug_assert!(data.len() <= 16); + let mut iov: [libc::iovec; 16] = MaybeUninit::uninit().assume_init(); + let data_len = data.len() & 15; + let mut data_ptr = 0; + while data_ptr < data_len { + let v = iov.get_unchecked_mut(data_ptr); + let d = *data.get_unchecked(data_ptr); + data_ptr += 1; + v.iov_base = transmute(d.as_ptr()); // iov_base is mut even though data is not changed + debug_assert!(d.len() > 0); + v.iov_len = d.len(); + } + + let mhdr = libc::msghdr { + msg_name: transmute(to_address as *const InetAddress), // also mut even though it's not modified + msg_namelen: std::mem::size_of::().as_(), + msg_iov: iov.as_mut_ptr(), + msg_iovlen: data_len.as_(), + msg_control: null_mut(), + msg_controllen: 0, + msg_flags: 0 + }; + + if packet_ttl == 0 || to_address.is_ipv6() { + let _ = libc::sendmsg(socket.get().as_(), transmute(&mhdr as *const libc::msghdr), 0); } else { let mut ttl = packet_ttl as c_int; - libc::setsockopt(*socket, libc::IPPROTO_IP.as_(), libc::IP_TTL.as_(), (&mut ttl as *mut c_int).cast(), std::mem::size_of::().as_()); - libc::sendto(*socket, data.as_ptr().cast(), data.len().as_(), 0, (to_address as *const InetAddress).cast(), std::mem::size_of::().as_()); + libc::setsockopt(socket.get().as_(), libc::IPPROTO_IP.as_(), libc::IP_TTL.as_(), (&mut ttl as *mut c_int).cast(), std::mem::size_of::().as_()); + let _ = libc::sendmsg(socket.get().as_(), transmute(&mhdr as *const libc::msghdr), 0); ttl = 255; - libc::setsockopt(*socket, libc::IPPROTO_IP.as_(), libc::IP_TTL.as_(), (&mut ttl as *mut c_int).cast(), std::mem::size_of::().as_()); + libc::setsockopt(socket.get().as_(), libc::IPPROTO_IP.as_(), libc::IP_TTL.as_(), (&mut ttl as *mut c_int).cast(), std::mem::size_of::().as_()); } } } @@ -172,7 +185,7 @@ pub(crate) fn fast_udp_socket_sendto(socket: &FastUDPRawOsSocket, to_address: &I fn fast_udp_socket_recvfrom(socket: &FastUDPRawOsSocket, buf: &mut PacketBuffer, from_address: &mut InetAddress) -> isize { unsafe { let mut addrlen = std::mem::size_of::() as libc::socklen_t; - let s = libc::recvfrom(*socket, buf.as_mut_ptr().cast(), buf.capacity().as_(), 0, (from_address as *mut InetAddress).cast(), &mut addrlen) as isize; + let s = libc::recvfrom(socket.get().as_(), buf.as_mut_ptr().cast(), buf.capacity().as_(), 0, (from_address as *mut InetAddress).cast(), &mut addrlen) as isize; if s > 0 { buf.set_size_unchecked(s as usize); } @@ -182,7 +195,7 @@ fn fast_udp_socket_recvfrom(socket: &FastUDPRawOsSocket, buf: &mut PacketBuffer, impl FastUDPSocket { pub fn new(device_name: &str, address: &InetAddress, packet_buffer_pool: &Arc, handler: F) -> Result { - let thread_count = num_cpus::get_physical().clamp(1, 4); + let thread_count = num_cpus::get_physical().clamp(1, FAST_UDP_SOCKET_MAX_THREADS); let mut s = Self { thread_run: Arc::new(AtomicBool::new(true)), @@ -230,19 +243,16 @@ impl FastUDPSocket { Ok(s) } - #[inline(always)] - pub fn all_sockets(&self) -> &[FastUDPRawOsSocket] { - self.sockets.as_slice() - } - #[inline(always)] pub fn send(&self, to_address: &InetAddress, data: &[u8], packet_ttl: i32) { - fast_udp_socket_sendto(self.sockets.get(0).unwrap(), to_address, data, packet_ttl); + debug_assert!(!self.sockets.is_empty()); + fast_udp_socket_sendto(unsafe { self.sockets.get_unchecked(0) }, to_address, data, packet_ttl); } #[inline(always)] - pub fn raw_socket(&self) -> FastUDPRawOsSocket { - *self.sockets.get(0).unwrap() + pub fn raw_socket(&self) -> &FastUDPRawOsSocket { + debug_assert!(!self.sockets.is_empty()); + unsafe { self.sockets.get_unchecked(0) } } } @@ -258,17 +268,17 @@ impl Drop for FastUDPSocket { self.thread_run.store(false, Ordering::Relaxed); for s in self.sockets.iter() { unsafe { - libc::sendto(*s, tmp.as_ptr().cast(), 0, 0, (&self.bind_address as *const InetAddress).cast(), std::mem::size_of::() as osdep::socklen_t); + libc::sendto(s.get().as_(), tmp.as_ptr().cast(), 0, 0, (&self.bind_address as *const InetAddress).cast(), std::mem::size_of::() as osdep::socklen_t); } } for s in self.sockets.iter() { unsafe { - libc::shutdown(*s, libc::SHUT_RDWR.as_()); + libc::shutdown(s.get().as_(), libc::SHUT_RDWR.as_()); } } for s in self.sockets.iter() { unsafe { - libc::close(*s); + libc::close(s.get().as_()); } } while !self.threads.is_empty() { diff --git a/zerotier-system-service/src/localconfig.rs b/zerotier-system-service/src/localconfig.rs index eae8a5d48..6af31f286 100644 --- a/zerotier-system-service/src/localconfig.rs +++ b/zerotier-system-service/src/localconfig.rs @@ -151,10 +151,6 @@ impl Default for LocalConfigLogSettings { pub struct LocalConfigSettings { #[serde(rename = "primaryPort")] pub primary_port: u16, - #[serde(rename = "secondaryPort")] - pub secondary_port: Option, - #[serde(rename = "autoPortSearch")] - pub auto_port_search: bool, #[serde(rename = "portMapping")] pub port_mapping: bool, #[serde(rename = "log")] @@ -175,8 +171,6 @@ impl Default for LocalConfigSettings { LocalConfigSettings { primary_port: zerotier_core::DEFAULT_PORT, - secondary_port: Some(zerotier_core::DEFAULT_SECONDARY_PORT), - auto_port_search: true, port_mapping: true, log: LocalConfigLogSettings::default(), interface_prefix_blacklist: bl, @@ -187,7 +181,7 @@ impl Default for LocalConfigSettings { impl LocalConfigSettings { #[cfg(target_os = "macos")] - const DEFAULT_PREFIX_BLACKLIST: [&'static str; 8] = ["lo", "utun", "gif", "stf", "iptap", "pktap", "feth", "zt"]; + const DEFAULT_PREFIX_BLACKLIST: [&'static str; 9] = ["lo", "utun", "gif", "stf", "iptap", "pktap", "feth", "zt", "llw"]; #[cfg(target_os = "linux")] const DEFAULT_PREFIX_BLACKLIST: [&'static str; 5] = ["lo", "tun", "tap", "ipsec", "zt"]; diff --git a/zerotier-system-service/src/log.rs b/zerotier-system-service/src/log.rs index bf291e244..75cd2a9d5 100644 --- a/zerotier-system-service/src/log.rs +++ b/zerotier-system-service/src/log.rs @@ -132,16 +132,24 @@ impl Log { } #[macro_export] -macro_rules! l( +macro_rules! log( ($logger:expr, $($arg:tt)*) => { - $logger.lock().log(format!($($arg)*)) + $logger.lock().log(format!($($arg)*)); } ); #[macro_export] -macro_rules! d( +macro_rules! debug( ($logger:expr, $($arg:tt)*) => { - $logger.lock().debug(format!($($arg)*)) + $logger.lock().debug(format!($($arg)*)); + } +); + +#[macro_export] +macro_rules! fatal( + ($logger:expr, $($arg:tt)*) => { + $logger.lock().fatal(format!($($arg)*)); + std::process::exit(-1); } ); diff --git a/zerotier-system-service/src/main.rs b/zerotier-system-service/src/main.rs index 4e7a785e8..b8ebee8c6 100644 --- a/zerotier-system-service/src/main.rs +++ b/zerotier-system-service/src/main.rs @@ -22,6 +22,7 @@ use std::str::FromStr; use clap::{App, Arg, ArgMatches, ErrorKind}; use zerotier_network_hypervisor::{VERSION_MAJOR, VERSION_MINOR, VERSION_REVISION}; +use crate::store::platform_default_home_path; pub const HTTP_API_OBJECT_SIZE_LIMIT: usize = 131072; @@ -113,6 +114,9 @@ pub fn print_help(long_help: bool) { pub struct GlobalCommandLineFlags { pub json_output: bool, + pub base_path: String, + pub auth_token_path_override: Option, + pub auth_token_override: Option } fn main() { @@ -207,7 +211,10 @@ fn main() { }); let global_cli_flags = GlobalCommandLineFlags { - json_output: cli_args.is_present("json") + json_output: cli_args.is_present("json"), + base_path: cli_args.value_of("path").map_or_else(|| platform_default_home_path(), |p| p.into_string()), + auth_token_path_override: cli_args.value_of("token_path").map(|p| p.into_string()), + auth_token_override: cli_args.value_of("token").map(|t| t.into_string()) }; std::process::exit({ @@ -233,7 +240,7 @@ fn main() { ("leave", Some(sub_cli_args)) => todo!(), ("service", None) => { drop(cli_args); // free no longer needed memory before entering service - service::run() + service::run(&global_cli_flags) } ("controller", Some(sub_cli_args)) => todo!(), ("identity", Some(sub_cli_args)) => todo!(), diff --git a/zerotier-system-service/src/service.rs b/zerotier-system-service/src/service.rs index a8a890954..76668f089 100644 --- a/zerotier-system-service/src/service.rs +++ b/zerotier-system-service/src/service.rs @@ -8,20 +8,27 @@ use std::num::NonZeroI64; use std::sync::Arc; +use std::sync::atomic::{AtomicBool, AtomicUsize, Ordering}; use parking_lot::Mutex; use zerotier_network_hypervisor::{Interface, NetworkHypervisor}; -use zerotier_network_hypervisor::vl1::{Endpoint, Identity, NodeInterface}; +use zerotier_network_hypervisor::vl1::{Endpoint, Identity, IdentityType, Node, NodeInterface}; use zerotier_network_hypervisor::vl2::SwitchInterface; +use crate::fastudpsocket::{fast_udp_socket_sendto, FastUDPRawOsSocket}; +use crate::GlobalCommandLineFlags; use crate::log::Log; use crate::utils::{ms_monotonic, ms_since_epoch}; use crate::localconfig::LocalConfig; +use crate::store::{platform_default_home_path, StateObjectType, Store}; struct ServiceInterface { - pub log: Arc>, - pub config: Mutex + pub store: Store, + pub config: Arc>, + pub online: AtomicBool, + pub all_sockets: Mutex>, + pub all_sockets_spin_ptr: AtomicUsize, } impl NodeInterface for ServiceInterface { @@ -31,27 +38,55 @@ impl NodeInterface for ServiceInterface { fn event_identity_collision(&self) {} - fn event_online_status_change(&self, online: bool) {} + #[inline(always)] + fn event_online_status_change(&self, online: bool) { + self.online.store(online, Ordering::Relaxed); + } fn event_user_message(&self, source: &Identity, message_type: u64, message: &[u8]) {} - fn load_node_identity(&self) -> Option<&[u8]> { - todo!() + #[inline(always)] + fn load_node_identity(&self) -> Option> { + self.store.load_object(StateObjectType::IdentitySecret, &[]).map_or(None, |b| Some(b)) } - fn save_node_identity(&self, id: &Identity, public: &[u8], secret: &[u8]) {} + fn save_node_identity(&self, _: &Identity, public: &[u8], secret: &[u8]) { + let _ = self.store.store_object(StateObjectType::IdentityPublic, &[], public); + let _ = self.store.store_object(StateObjectType::IdentitySecret, &[], secret); + } #[inline(always)] fn wire_send(&self, endpoint: &Endpoint, local_socket: Option, local_interface: Option, data: &[&[u8]], packet_ttl: u8) -> bool { - todo!() + match endpoint { + Endpoint::IpUdp(ip) => { + local_socket.map_or_else(|| { + let ptr = self.all_sockets_spin_ptr.fetch_add(1, Ordering::Relaxed); + let all_sockets = self.all_sockets.lock(); + if !all_sockets.is_empty() { + let s = unsafe { all_sockets.get_unchecked(ptr % all_sockets.len()) }.clone(); + drop(all_sockets); // release mutex + fast_udp_socket_sendto(&s, ip, data, packet_ttl); + true + } else { + false + } + }, |local_socket| { + fast_udp_socket_sendto(&local_socket, ip, data, packet_ttl); + true + }) + } + _ => false + } } fn check_path(&self, id: &Identity, endpoint: &Endpoint, local_socket: Option, local_interface: Option) -> bool { + // TODO true } fn get_path_hints(&self, id: &Identity) -> Option<&[(&Endpoint, Option, Option)]> { - todo!() + // TODO + None } #[inline(always)] @@ -65,7 +100,21 @@ impl SwitchInterface for ServiceInterface {} impl Interface for ServiceInterface {} -pub fn run() -> i32 { +pub fn run(global_cli_flags: &GlobalCommandLineFlags) -> i32 { + let store = Store::new(global_cli_flags.base_path.as_str(), &global_cli_flags.auth_token_path_override, &global_cli_flags.auth_token_override); + if store.is_err() { + } + + let si = ServiceInterface { + store: store.unwrap(), + config: Arc::new(Mutex::new(LocalConfig::default())), + online: AtomicBool::new(false), + all_sockets: Mutex::new(Vec::new()), + all_sockets_spin_ptr: AtomicUsize::new(0), + }; + + let node = Node::new(&si, Some(IdentityType::C25519)); + 0 } diff --git a/zerotier-system-service/src/store.rs b/zerotier-system-service/src/store.rs index 579919ec3..4a2f140dc 100644 --- a/zerotier-system-service/src/store.rs +++ b/zerotier-system-service/src/store.rs @@ -22,11 +22,24 @@ const LOCAL_CONF: &'static str = "local.conf"; const AUTHTOKEN_SECRET: &'static str = "authtoken.secret"; const SERVICE_LOG: &'static str = "service.log"; +#[derive(Clone, Copy)] +pub enum StateObjectType { + IdentityPublic, + IdentitySecret, + NetworkConfig, + Peer +} + +#[cfg(any(target_os = "macos", target_os = "ios"))] +pub fn platform_default_home_path() -> String { + "/Library/Application Support/ZeroTier".into_string() +} + /// In-filesystem data store for configuration and objects. pub(crate) struct Store { pub base_path: Box, pub default_log_path: Box, - prev_local_config: Mutex, + previous_local_config_on_disk: Mutex, peers_path: Box, controller_path: Box, networks_path: Box, @@ -36,19 +49,13 @@ pub(crate) struct Store { /// Restrict file permissions using OS-specific code in osdep/OSUtils.cpp. pub fn lock_down_file(path: &str) { - let p = CString::new(path.as_bytes()); - if p.is_ok() { - let p = p.unwrap(); - unsafe { - crate::osdep::lockDownFile(p.as_ptr(), 0); - } - } + // TODO: need both Windows and Unix implementations } impl Store { const MAX_OBJECT_SIZE: usize = 262144; // sanity limit - pub fn new(base_path: &str, auth_token_path_override: Option, auth_token_override: Option) -> std::io::Result { + pub fn new(base_path: &str, auth_token_path_override: &Option, auth_token_override: &Option) -> std::io::Result { let bp = Path::new(base_path); let _ = std::fs::create_dir_all(bp); let md = bp.metadata()?; @@ -59,7 +66,7 @@ impl Store { let s = Store { base_path: bp.to_path_buf().into_boxed_path(), default_log_path: bp.join(SERVICE_LOG).into_boxed_path(), - prev_local_config: Mutex::new(String::new()), + previous_local_config_on_disk: Mutex::new(String::new()), peers_path: bp.join("peers.d").into_boxed_path(), controller_path: bp.join("controller.d").into_boxed_path(), networks_path: bp.join("networks.d").into_boxed_path(), @@ -82,12 +89,10 @@ impl Store { Ok(s) } - fn make_obj_path_internal(&self, obj_type: &StateObjectType, obj_id: &[u64]) -> Option { + fn make_obj_path_internal(&self, obj_type: StateObjectType, obj_id: &[u64]) -> Option { match obj_type { StateObjectType::IdentityPublic => Some(self.base_path.join("identity.public")), StateObjectType::IdentitySecret => Some(self.base_path.join("identity.secret")), - StateObjectType::TrustStore => Some(self.base_path.join("truststore")), - StateObjectType::Locator => Some(self.base_path.join("locator")), StateObjectType::NetworkConfig => { if obj_id.len() < 1 { None @@ -204,13 +209,13 @@ impl Store { } let data = data.unwrap(); if skip_if_unchanged { - let mut prev = self.prev_local_config.lock().unwrap(); + let mut prev = self.previous_local_config_on_disk.lock().unwrap(); if prev.eq(&data) { return None; } *prev = data.clone(); } else { - *(self.prev_local_config.lock().unwrap()) = data.clone(); + *(self.previous_local_config_on_disk.lock().unwrap()) = data.clone(); } let lc = serde_json::from_str::(data.as_str()); if lc.is_err() { @@ -244,40 +249,22 @@ impl Store { let _ = std::fs::remove_file(self.base_path.join(ZEROTIER_PID)); } - pub fn write_uri(&self, uri: &str) -> std::io::Result<()> { - self.write_file(ZEROTIER_URI, uri.as_bytes()) - } - - pub fn load_uri(&self) -> std::io::Result { - let uri = String::from_utf8(self.read_file(ZEROTIER_URI)?); - uri.map_or_else(|e| { - Err(std::io::Error::new(std::io::ErrorKind::InvalidData, e.to_string())) - }, |uri| { - let uri = hyper::Uri::from_str(uri.trim()); - uri.map_or_else(|e| { - Err(std::io::Error::new(std::io::ErrorKind::InvalidData, e.to_string())) - }, |uri| { - Ok(uri) - }) - }) - } - - pub fn load_object(&self, obj_type: &StateObjectType, obj_id: &[u64]) -> std::io::Result> { - let obj_path = self.make_obj_path_internal(&obj_type, obj_id); + pub fn load_object(&self, obj_type: StateObjectType, obj_id: &[u64]) -> std::io::Result> { + let obj_path = self.make_obj_path_internal(obj_type, obj_id); if obj_path.is_some() { return self.read_internal(obj_path.unwrap()); } Err(std::io::Error::new(std::io::ErrorKind::NotFound, "does not exist or is not readable")) } - pub fn erase_object(&self, obj_type: &StateObjectType, obj_id: &[u64]) { + pub fn erase_object(&self, obj_type: StateObjectType, obj_id: &[u64]) { let obj_path = self.make_obj_path_internal(obj_type, obj_id); if obj_path.is_some() { let _ = std::fs::remove_file(obj_path.unwrap()); } } - pub fn store_object(&self, obj_type: &StateObjectType, obj_id: &[u64], obj_data: &[u8]) -> std::io::Result<()> { + pub fn store_object(&self, obj_type: StateObjectType, obj_id: &[u64], obj_data: &[u8]) -> std::io::Result<()> { let obj_path = self.make_obj_path_internal(obj_type, obj_id); if obj_path.is_some() { let obj_path = obj_path.unwrap(); diff --git a/zerotier-system-service/src/utils.rs b/zerotier-system-service/src/utils.rs index 4b37a753a..0c8f976db 100644 --- a/zerotier-system-service/src/utils.rs +++ b/zerotier-system-service/src/utils.rs @@ -16,6 +16,7 @@ use serde::de::DeserializeOwned; use serde::Serialize; use zerotier_network_hypervisor::vl1::Identity; +use zerotier_core_crypto::hex; use crate::osdep; @@ -29,13 +30,18 @@ pub fn ms_monotonic() -> i64 { let mut tb: mach::mach_time::mach_timebase_info_data_t = std::mem::zeroed(); if mach::mach_time::mach_timebase_info(&mut tb) == 0 { let mt = mach::mach_time::mach_continuous_approximate_time(); - (((mt as u128) * tb.numer as u128 * 1000000_u128) / (tb.denom as u128)) as i64 + (((mt as u128) * tb.numer as u128 * 1000000_u128) / (tb.denom as u128)) as i64 // milliseconds since X } else { panic!("FATAL: mach_timebase_info() failed"); } } } +#[cfg(not(any(target_os = "macos", target_os = "ios")))] +pub fn ms_monotonic() -> i64 { + std::time::Instant::now().elapsed().as_millis() as i64 +} + pub fn parse_bool(v: &str) -> Result { if !v.is_empty() { match v.chars().next().unwrap() { @@ -180,3 +186,18 @@ pub fn json_patch_object(obj: O, patch: &s }) }) } + +#[cfg(test)] +mod tests { + use std::time::Duration; + use crate::utils::ms_monotonic; + + #[test] + fn monotonic_clock_sanity_check() { + let start = ms_monotonic(); + std::thread::sleep(Duration::from_millis(500)); + let end = ms_monotonic(); + assert!((end - start).abs() > 450); + assert!((end - start).abs() < 550); + } +} diff --git a/zerotier-system-service/src/vnic/common.rs b/zerotier-system-service/src/vnic/common.rs index 62cd6ad43..c4494b645 100644 --- a/zerotier-system-service/src/vnic/common.rs +++ b/zerotier-system-service/src/vnic/common.rs @@ -7,13 +7,11 @@ */ use std::collections::HashSet; - -#[allow(unused_imports)] -use num_traits::AsPrimitive; - #[allow(unused_imports)] use std::os::raw::c_int; +#[allow(unused_imports)] +use num_traits::AsPrimitive; #[allow(unused_imports)] use zerotier_network_hypervisor::vl1::MAC; @@ -35,7 +33,7 @@ extern "C" { #[cfg(any(target_os = "macos", target_os = "ios", target_os = "netbsd", target_os = "openbsd", target_os = "dragonfly", target_os = "freebsd", target_os = "darwin"))] pub fn get_l2_multicast_subscriptions(dev: &str) -> HashSet { - let mut groups: HashSet = HashSet::new(); + let mut groups: HashSet = HashSet::new(); let dev = dev.as_bytes(); unsafe { let mut maddrs: *mut ifmaddrs = std::ptr::null_mut(); @@ -47,7 +45,7 @@ pub fn get_l2_multicast_subscriptions(dev: &str) -> HashSet { let la: &libc::sockaddr_dl = &*((*i).ifma_addr.cast()); if la.sdl_alen == 6 && in_.sdl_nlen <= dev.len().as_() && crate::libc::memcmp(dev.as_ptr().cast(), in_.sdl_data.as_ptr().cast(), in_.sdl_nlen.as_()) == 0 { let mi = la.sdl_nlen as usize; - groups.insert(MAC::from_u64((la.sdl_data[mi] as u64) << 40 | (la.sdl_data[mi+1] as u64) << 32 | (la.sdl_data[mi+2] as u64) << 24 | (la.sdl_data[mi+3] as u64) << 16 | (la.sdl_data[mi+4] as u64) << 8 | la.sdl_data[mi+5] as u64)); + MAC::from_u64((la.sdl_data[mi] as u64) << 40 | (la.sdl_data[mi+1] as u64) << 32 | (la.sdl_data[mi+2] as u64) << 24 | (la.sdl_data[mi+3] as u64) << 16 | (la.sdl_data[mi+4] as u64) << 8 | la.sdl_data[mi+5] as u64).map(|mac| groups.insert(mac)); } } i = (*i).ifma_next;