diff --git a/network-hypervisor/Cargo.lock b/network-hypervisor/Cargo.lock index cf7c0d82e..fd7164217 100644 --- a/network-hypervisor/Cargo.lock +++ b/network-hypervisor/Cargo.lock @@ -86,6 +86,16 @@ dependencies = [ "zeroize", ] +[[package]] +name = "dashmap" +version = "4.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e77a43b28d0668df09411cb0bc9a8c2adc40f9a048afe863e05fd43251e8e39c" +dependencies = [ + "cfg-if 1.0.0", + "num_cpus", +] + [[package]] name = "digest" version = "0.9.0" @@ -162,6 +172,24 @@ dependencies = [ "libgpg-error-sys", ] +[[package]] +name = "hermit-abi" +version = "0.1.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "62b467343b94ba476dcb2500d242dadbb39557df889310ac77c5d99100aaac33" +dependencies = [ + "libc", +] + +[[package]] +name = "instant" +version = "0.1.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bee0328b1209d157ef001c94dd85b4f8f64139adb0eac2659f4b08382b2f474d" +dependencies = [ + "cfg-if 1.0.0", +] + [[package]] name = "libc" version = "0.2.98" @@ -190,12 +218,40 @@ dependencies = [ "winreg", ] +[[package]] +name = "lock_api" +version = "0.4.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0382880606dff6d15c9476c416d18690b72742aa7b605bb6dd6ec9030fbf07eb" +dependencies = [ + "scopeguard", +] + +[[package]] +name = "lz4_flex" +version = "0.8.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5827b976d911b5d2e42b2ccfc7c0d2461a1414e8280436885218762fc529b3f8" +dependencies = [ + "twox-hash", +] + [[package]] name = "memchr" version = "2.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b16bd47d9e329435e309c58469fe0791c2d0d1ba96ec0954152a5ae2b04387dc" +[[package]] +name = "num_cpus" +version = "1.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "05499f3756671c15885fee9034446956fff3f243d6077b91e5767df161f766b3" +dependencies = [ + "hermit-abi", + "libc", +] + [[package]] name = "once_cell" version = "1.8.0" @@ -208,6 +264,31 @@ version = "0.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "624a8340c38c1b80fd549087862da4ba43e08858af025b236e509b6649fc13d5" +[[package]] +name = "parking_lot" +version = "0.11.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6d7744ac029df22dca6284efe4e898991d28e3085c706c972bcd7da4a27a15eb" +dependencies = [ + "instant", + "lock_api", + "parking_lot_core", +] + +[[package]] +name = "parking_lot_core" +version = "0.8.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fa7a782938e745763fe6907fc6ba86946d72f49fe7e21de074e08128a99fb018" +dependencies = [ + "cfg-if 1.0.0", + "instant", + "libc", + "redox_syscall", + "smallvec", + "winapi", +] + [[package]] name = "ppv-lite86" version = "0.2.10" @@ -273,6 +354,21 @@ dependencies = [ "rand_core", ] +[[package]] +name = "redox_syscall" +version = "0.2.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5ab49abadf3f9e1c4bc499e8845e152ad87d2ad2d30371841171169e9d75feee" +dependencies = [ + "bitflags", +] + +[[package]] +name = "scopeguard" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d29ab0c6d3fc0ee92fe66e2d99f700eab17a8d57d1c1d3b748380fb20baa78cd" + [[package]] name = "serde" version = "1.0.126" @@ -298,6 +394,18 @@ version = "1.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c19772be3c4dd2ceaacf03cb41d5885f2a02c4d8804884918e3a258480803335" +[[package]] +name = "smallvec" +version = "1.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fe0f37c9e8f3c5a4a66ad655a93c74daac4ad00c441533bf5c6e7990bb42604e" + +[[package]] +name = "static_assertions" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a2eb9349b6444b326872e140eb1cf5e7c522154d69e7a0ffb0fb81c06b37543f" + [[package]] name = "subtle" version = "2.4.1" @@ -327,6 +435,16 @@ dependencies = [ "unicode-xid", ] +[[package]] +name = "twox-hash" +version = "1.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "04f8ab788026715fa63b31960869617cba39117e520eb415b0139543e325ab59" +dependencies = [ + "cfg-if 0.1.10", + "static_assertions", +] + [[package]] name = "typenum" version = "1.13.0" @@ -426,9 +544,12 @@ version = "2.0.0" dependencies = [ "aes-gmac-siv", "base64", + "dashmap", "ed25519-dalek", "gcrypt", "libc", + "lz4_flex", + "parking_lot", "rand_core", "urlencoding", "winapi", diff --git a/network-hypervisor/Cargo.toml b/network-hypervisor/Cargo.toml index eaef07b91..bf8852908 100644 --- a/network-hypervisor/Cargo.toml +++ b/network-hypervisor/Cargo.toml @@ -3,11 +3,6 @@ name = "zerotier-network-hypervisor" version = "2.0.0" edition = "2018" -# [profile.test] -# opt-level = 3 -# lto = true -# codegen-units = 1 - [profile.release] lto = true opt-level = 'z' @@ -22,9 +17,12 @@ ed25519-dalek = "^1" gcrypt = "^0" base64 = "^0" urlencoding = "^2" +lz4_flex = { version = "^0", features = ["safe-encode", "safe-decode", "checked-decode"] } +dashmap = "^4" +parking_lot = "^0" [target."cfg(not(windows))".dependencies] libc = "^0" [target."cfg(windows)".dependencies] -winapi = { version = "0.3.9", features = ["handleapi", "ws2ipdef", "ws2tcpip"] } +winapi = { version = "0.3.9", features = ["ws2tcpip"] } diff --git a/network-hypervisor/src/crypto/balloon.rs b/network-hypervisor/src/crypto/balloon.rs index 3c3ee5c32..396c44244 100644 --- a/network-hypervisor/src/crypto/balloon.rs +++ b/network-hypervisor/src/crypto/balloon.rs @@ -4,7 +4,7 @@ use std::mem::MaybeUninit; #[inline(always)] fn hash_int_le(sha: &mut crate::crypto::hash::SHA512, i: u64) { #[cfg(target_endian = "big")] { - sha.update(i.to_le_bytes()); + sha.update(&i.to_le_bytes()); } #[cfg(target_endian = "little")] { sha.update(unsafe { &*(&i as *const u64).cast::<[u8; 8]>() }); diff --git a/network-hypervisor/src/util/mod.rs b/network-hypervisor/src/util/mod.rs index 55537c5d6..651cf1e7c 100644 --- a/network-hypervisor/src/util/mod.rs +++ b/network-hypervisor/src/util/mod.rs @@ -1,4 +1,5 @@ pub mod hex; +pub(crate) mod pool; pub(crate) const ZEROES: [u8; 64] = [0_u8; 64]; diff --git a/network-hypervisor/src/util/pool.rs b/network-hypervisor/src/util/pool.rs new file mode 100644 index 000000000..bea63912c --- /dev/null +++ b/network-hypervisor/src/util/pool.rs @@ -0,0 +1,199 @@ +use std::ops::{Deref, DerefMut}; +use std::sync::{Arc, Weak}; + +use parking_lot::Mutex; +use std::mem::size_of; + +/// Trait for objects that can be used with Pool. +pub trait Reusable: Default + Sized { + fn reset(&mut self); +} + +struct PoolEntry { + obj: O, + return_pool: Weak>, +} + +type PoolInner = Mutex>>; + +/// Container for pooled objects that have been checked out of the pool. +/// When this is dropped the object is returned to the pool or if the pool or is +/// dropped if the pool has been dropped. There is also an into_raw() and from_raw() +/// functionality that allows conversion to/from naked pointers to O for +/// interoperation with C/C++ APIs. +#[repr(transparent)] +#[derive(Clone)] +pub struct Pooled(*mut PoolEntry); + +impl Pooled { + /// Get a raw pointer to the object wrapped by this pooled object container. + /// The returned raw pointer MUST be restored into a Pooled instance with + /// from_raw() or memory will leak. + #[inline(always)] + pub unsafe fn into_raw(self) -> *mut O { + debug_assert!(!self.0.is_null()); + debug_assert_eq!(self.0.cast::(), (&mut (*self.0).obj as *mut O).cast::()); + self.0.cast() + } + + /// Restore a raw pointer from into_raw() into a Pooled object. + /// The supplied pointer MUST have been obtained from a Pooled object or + /// undefined behavior will occur. Pointers from other sources can't be used + /// here. None is returned if the pointer is null. + #[inline(always)] + pub unsafe fn from_raw(raw: *mut O) -> Option { + if !raw.is_null() { + Some(Self(raw.cast())) + } else { + None + } + } +} + +impl Deref for Pooled { + type Target = O; + + #[inline(always)] + fn deref(&self) -> &Self::Target { + debug_assert!(!self.0.is_null()); + unsafe { &(*self.0).obj } + } +} + +impl AsRef for Pooled { + #[inline(always)] + fn as_ref(&self) -> &O { + debug_assert!(!self.0.is_null()); + unsafe { &(*self.0).obj } + } +} + +impl DerefMut for Pooled { + #[inline(always)] + fn deref_mut(&mut self) -> &mut Self::Target { + debug_assert!(!self.0.is_null()); + unsafe { &mut (*self.0).obj } + } +} + +impl AsMut for Pooled { + #[inline(always)] + fn as_mut(&mut self) -> &mut O { + debug_assert!(!self.0.is_null()); + unsafe { &mut (*self.0).obj } + } +} + +impl Drop for Pooled { + fn drop(&mut self) { + unsafe { + Weak::upgrade(&(*self.0).return_pool).map_or_else(|| { + drop(Box::from_raw(self.0)) + }, |p| { + (*self.0).obj.reset(); + p.lock().push(self.0) + }) + } + } +} + +/// An object pool for Reusable objects. +/// The pool is safe in that checked out objects return automatically when their Pooled +/// transparent container is dropped, or deallocate if the pool has been dropped. +pub struct Pool(Arc>); + +impl Pool { + pub fn new(initial_stack_capacity: usize) -> Self { + Self(Arc::new(Mutex::new(Vec::with_capacity(initial_stack_capacity)))) + } + + /// Get a pooled object, or allocate one if the pool is empty. + pub fn get(&self) -> Pooled { + Pooled::(self.0.lock().pop().map_or_else(|| { + Box::into_raw(Box::new(PoolEntry:: { + obj: O::default(), + return_pool: Arc::downgrade(&self.0), + })) + }, |obj| { + debug_assert!(!obj.is_null()); + obj + })) + } + + /// Get approximate memory use in bytes (does not include checked out objects). + #[inline(always)] + pub fn pool_memory_bytes(&self) -> usize { + self.0.lock().len() * (size_of::>() + size_of::()) + } + + /// Dispose of all pooled objects, freeing any memory they use. + /// If get() is called after this new objects will be allocated, and any outstanding + /// objects will still be returned on drop unless the pool itself is dropped. This can + /// be done to free some memory if there has been a spike in memory use. + pub fn purge(&self) { + let mut p = self.0.lock(); + for obj in p.iter() { + drop(unsafe { Box::from_raw(*obj) }); + } + p.clear(); + } +} + +impl Drop for Pool { + fn drop(&mut self) { + self.purge(); + } +} + +unsafe impl Sync for Pool {} + +unsafe impl Send for Pool {} + +#[cfg(test)] +mod tests { + use std::sync::atomic::{AtomicUsize, Ordering}; + use std::ops::DerefMut; + use std::time::Duration; + + use crate::util::pool::{Reusable, Pool}; + use std::sync::Arc; + + struct ReusableTestObject(usize); + + impl Default for ReusableTestObject { + fn default() -> Self { + Self(0) + } + } + + impl Reusable for ReusableTestObject { + fn reset(&mut self) {} + } + + #[test] + fn threaded_pool_use() { + let p: Arc> = Arc::new(Pool::new(2)); + let ctr = Arc::new(AtomicUsize::new(0)); + for _ in 0..64 { + let p2 = p.clone(); + let ctr2 = ctr.clone(); + let _ = std::thread::spawn(move || { + for _ in 0..16384 { + let mut o1 = p2.get(); + o1.deref_mut().0 += 1; + let mut o2 = p2.get(); + drop(o1); + o2.deref_mut().0 += 1; + ctr2.fetch_add(1, Ordering::Relaxed); + } + }); + } + loop { + std::thread::sleep(Duration::from_millis(100)); + if ctr.load(Ordering::Relaxed) >= 16384 * 8 { + break; + } + } + //println!("pool memory size: {}", p.pool_memory_bytes()); + } +} diff --git a/network-hypervisor/src/vl1/buffer.rs b/network-hypervisor/src/vl1/buffer.rs index 2e331e633..b54dce595 100644 --- a/network-hypervisor/src/vl1/buffer.rs +++ b/network-hypervisor/src/vl1/buffer.rs @@ -1,5 +1,4 @@ -use std::mem::{size_of, MaybeUninit, zeroed}; -use std::ptr::write_bytes; +use std::mem::size_of; use std::io::Write; const OVERFLOW_ERR_MSG: &'static str = "overflow"; @@ -9,7 +8,7 @@ const OVERFLOW_ERR_MSG: &'static str = "overflow"; /// This is ONLY used for packed protocol header or segment objects. pub unsafe trait RawObject: Sized {} -/// A byte array that supports safe appending of data or raw objects. +/// A byte array that supports safe and efficient appending of data or raw objects. #[derive(Clone, PartialEq, Eq)] pub struct Buffer(usize, [u8; L]); @@ -18,25 +17,28 @@ unsafe impl RawObject for Buffer {} impl Default for Buffer { #[inline(always)] fn default() -> Self { - unsafe { zeroed() } + Self(0, [0_u8; L]) } } impl Buffer { #[inline(always)] pub fn new() -> Self { - unsafe { zeroed() } + Self(0, [0_u8; L]) } - /// Create a buffer that contains a copy of a slice, truncating if the slice is too long. + /// Get a Buffer that is a copy of a byte slice, or return None if the slice doesn't fit. #[inline(always)] - pub fn from_bytes_lossy(b: &[u8]) -> Self { - let l = b.len().min(L); - let mut tmp = unsafe { MaybeUninit::::uninit().assume_init() }; - tmp.0 = l; - tmp.1[0..l].copy_from_slice(b); - tmp.1[l..L].fill(0); - tmp + pub fn from_bytes(b: &[u8]) -> Option { + let l = b.len(); + if l <= L { + let mut tmp = Self::new(); + tmp.0 = l; + tmp.1[0..l].copy_from_slice(b); + Some(tmp) + } else { + None + } } /// Get a slice containing the entire buffer in raw form including the header. @@ -51,10 +53,11 @@ impl Buffer { &mut self.1[0..self.0] } - /// Erase contents and reset size to the size of the header. + /// Erase contents and zero size. #[inline(always)] - pub fn clear(&mut self) { - unsafe { write_bytes((self as *mut Self).cast::(), 0, size_of::()) } + pub fn reset(&mut self) { + self.0 = 0; + self.1.fill(0); } /// Get the length of this buffer (including header, if any). @@ -195,11 +198,28 @@ impl Buffer { } } + /// Get a structure at position 0. + #[inline(always)] + pub fn header(&self) -> &H { + debug_assert!(size_of::() <= L); + debug_assert!(size_of::() <= self.0); + unsafe { &*self.1.as_ptr().cast::() } + } + + /// Get a structure at position 0 (mutable). + #[inline(always)] + pub fn header_mut(&mut self) -> &mut H { + debug_assert!(size_of::() <= L); + debug_assert!(size_of::() <= self.0); + unsafe { &mut *self.1.as_mut_ptr().cast::() } + } + /// Get a structure at a given position in the buffer and advance the cursor. #[inline(always)] - pub fn get_struct(&self, cursor: &mut usize) -> std::io::Result<&T> { + pub fn read_struct(&self, cursor: &mut usize) -> std::io::Result<&T> { let ptr = *cursor; let end = ptr + size_of::(); + debug_assert!(end <= L); if end <= self.0 { *cursor = end; unsafe { @@ -213,9 +233,10 @@ impl Buffer { /// Get a fixed length byte array and advance the cursor. /// This is slightly more efficient than reading a runtime sized byte slice. #[inline(always)] - pub fn get_bytes_fixed(&self, cursor: &mut usize) -> std::io::Result<&[u8; S]> { + pub fn read_bytes_fixed(&self, cursor: &mut usize) -> std::io::Result<&[u8; S]> { let ptr = *cursor; let end = ptr + S; + debug_assert!(end <= L); if end <= self.0 { *cursor = end; unsafe { @@ -228,9 +249,10 @@ impl Buffer { /// Get a runtime specified length byte slice and advance the cursor. #[inline(always)] - pub fn get_bytes(&self, l: usize, cursor: &mut usize) -> std::io::Result<&[u8]> { + pub fn read_bytes(&self, l: usize, cursor: &mut usize) -> std::io::Result<&[u8]> { let ptr = *cursor; let end = ptr + l; + debug_assert!(end <= L); if end <= self.0 { *cursor = end; Ok(&self.1[ptr..end]) @@ -241,8 +263,9 @@ impl Buffer { /// Get the next u8 and advance the cursor. #[inline(always)] - pub fn get_u8(&self, cursor: &mut usize) -> std::io::Result { + pub fn read_u8(&self, cursor: &mut usize) -> std::io::Result { let ptr = *cursor; + debug_assert!(ptr < L); if ptr < self.0 { *cursor = ptr + 1; Ok(self.1[ptr]) @@ -253,9 +276,10 @@ impl Buffer { /// Get the next u16 and advance the cursor. #[inline(always)] - pub fn get_u16(&self, cursor: &mut usize) -> std::io::Result { + pub fn read_u16(&self, cursor: &mut usize) -> std::io::Result { let ptr = *cursor; let end = ptr + 2; + debug_assert!(end <= L); if end <= self.0 { *cursor = end; Ok(crate::util::integer_load_be_u16(&self.1[ptr..end])) @@ -266,9 +290,10 @@ impl Buffer { /// Get the next u32 and advance the cursor. #[inline(always)] - pub fn get_u32(&self, cursor: &mut usize) -> std::io::Result { + pub fn read_u32(&self, cursor: &mut usize) -> std::io::Result { let ptr = *cursor; let end = ptr + 4; + debug_assert!(end <= L); if end <= self.0 { *cursor = end; Ok(crate::util::integer_load_be_u32(&self.1[ptr..end])) @@ -279,9 +304,10 @@ impl Buffer { /// Get the next u64 and advance the cursor. #[inline(always)] - pub fn get_u64(&self, cursor: &mut usize) -> std::io::Result { + pub fn read_u64(&self, cursor: &mut usize) -> std::io::Result { let ptr = *cursor; let end = ptr + 8; + debug_assert!(end <= L); if end <= self.0 { *cursor = end; Ok(crate::util::integer_load_be_u64(&self.1[ptr..end])) diff --git a/network-hypervisor/src/vl1/concurrentmap.rs b/network-hypervisor/src/vl1/concurrentmap.rs new file mode 100644 index 000000000..89dfb8b92 --- /dev/null +++ b/network-hypervisor/src/vl1/concurrentmap.rs @@ -0,0 +1,44 @@ +// This just defines a ConcurrentMap type, selecting standard locked HashMap for smaller systems +// or DashMap on larger ones where it would be faster. It also defines some wrappers for read +// and write locking that do nothing for DashMap and return a lock guard for RwLock>. + +#[allow(unused_imports)] +use std::sync::{Arc, RwLock, RwLockReadGuard, RwLockWriteGuard}; +#[allow(unused_imports)] +use std::collections::HashMap; +#[cfg(any(target_arch = "aarch64", target_arch = "x86_64", target_arch = "powerpc64"))] +use dashmap::DashMap; + +#[cfg(not(any(target_arch = "aarch64", target_arch = "x86_64", target_arch = "powerpc64")))] +pub type ConcurrentMap = RwLock>; + +#[cfg(any(target_arch = "aarch64", target_arch = "x86_64", target_arch = "powerpc64"))] +pub type ConcurrentMap = DashMap; + +/// Wrapper to get a read lock guard on a concurrent map. +#[cfg(not(any(target_arch = "aarch64", target_arch = "x86_64", target_arch = "powerpc64")))] +#[inline(always)] +pub fn read(m: &Arc>) -> RwLockReadGuard> { + m.read().unwrap() +} + +/// Wrapper to get a read lock guard on a concurrent map. +#[cfg(any(target_arch = "aarch64", target_arch = "x86_64", target_arch = "powerpc64"))] +#[inline(always)] +pub fn read(m: &Arc>) -> &ConcurrentMap { + m.as_ref() +} + +/// Wrapper to get a write lock guard on a concurrent map. +#[cfg(not(any(target_arch = "aarch64", target_arch = "x86_64", target_arch = "powerpc64")))] +#[inline(always)] +pub fn write(m: &Arc>) -> RwLockWriteGuard> { + m.write().unwrap() +} + +/// Wrapper to get a write lock guard on a concurrent map. +#[cfg(any(target_arch = "aarch64", target_arch = "x86_64", target_arch = "powerpc64"))] +#[inline(always)] +pub fn write(m: &Arc>) -> &ConcurrentMap { + m.as_ref() +} diff --git a/network-hypervisor/src/vl1/constants.rs b/network-hypervisor/src/vl1/constants.rs index 4482d44f2..d8af89490 100644 --- a/network-hypervisor/src/vl1/constants.rs +++ b/network-hypervisor/src/vl1/constants.rs @@ -27,13 +27,16 @@ pub const HEADER_FLAGS_FIELD_MASK_HOPS: u8 = 0x07; /// Packet is not encrypted but contains a Poly1305 MAC of the plaintext. /// Poly1305 is initialized with Salsa20/12 in the same manner as SALSA2012_POLY1305. -pub const CIPHER_NOCRYPT_POLY1305: u8 = 0; +pub const CIPHER_NOCRYPT_POLY1305: u8 = 0x00; /// Packet is encrypted and authenticated with Salsa20/12 and Poly1305. /// Construction is the same as that which is used in the NaCl secret box functions. pub const CIPHER_SALSA2012_POLY1305: u8 = 0x10; -/// Packet is encrypted and authenticated with AES-GMAC-SIV. +/// Formerly 'NONE' which is deprecated; reserved for future use. +pub const CIPHER_RESERVED: u8 = 0x20; + +/// Packet is encrypted and authenticated with AES-GMAC-SIV (AES-256). pub const CIPHER_AES_GMAC_SIV: u8 = 0x30; /// Header (outer) flag indicating that this packet has additional fragments. diff --git a/network-hypervisor/src/vl1/dictionary.rs b/network-hypervisor/src/vl1/dictionary.rs index c6fc10916..f32830cd5 100644 --- a/network-hypervisor/src/vl1/dictionary.rs +++ b/network-hypervisor/src/vl1/dictionary.rs @@ -1,5 +1,6 @@ use std::collections::BTreeMap; use std::io::Write; + use crate::util::hex::HEX_CHARS; /// Dictionary is an extremely simple key=value serialization format. @@ -22,7 +23,7 @@ fn write_escaped(b: &[u8], w: &mut W) -> std::io::Result<()> { let l = b.len(); while i < l { let ii = i + 1; - match unsafe { b.get_unchecked(i) } { + match b[i] { 0 => { w.write_all(&[b'\\', b'0'])?; } b'\n' => { w.write_all(&[b'\\', b'n'])?; } b'\r' => { w.write_all(&[b'\\', b'r'])?; } @@ -86,7 +87,7 @@ impl Dictionary { if v.is_empty() { Some(false) } else { - Some(match unsafe { v.get_unchecked(0) } { + Some(match v[0] { b'1' | b't' | b'T' | b'y' | b'Y' => true, _ => false }) diff --git a/network-hypervisor/src/vl1/endpoint.rs b/network-hypervisor/src/vl1/endpoint.rs index d62a73c6f..87aa6b26c 100644 --- a/network-hypervisor/src/vl1/endpoint.rs +++ b/network-hypervisor/src/vl1/endpoint.rs @@ -126,7 +126,7 @@ impl Endpoint { } pub fn unmarshal(buf: &Buffer, cursor: &mut usize) -> std::io::Result { - let type_byte = buf.get_u8(cursor)?; + let type_byte = buf.read_u8(cursor)?; if type_byte < 16 { let ip = InetAddress::unmarshal(buf, cursor)?; if ip.is_nil() { @@ -137,20 +137,20 @@ impl Endpoint { } else { match type_byte - 16 { TYPE_NIL => Ok(Endpoint::Nil), - TYPE_ZEROTIER => Ok(Endpoint::ZeroTier(Address::from(buf.get_bytes_fixed(cursor)?))), - TYPE_ETHERNET => Ok(Endpoint::Ethernet(MAC::from(buf.get_bytes_fixed(cursor)?))), - TYPE_WIFIDIRECT => Ok(Endpoint::WifiDirect(MAC::from(buf.get_bytes_fixed(cursor)?))), - TYPE_BLUETOOTH => Ok(Endpoint::Bluetooth(MAC::from(buf.get_bytes_fixed(cursor)?))), + TYPE_ZEROTIER => Ok(Endpoint::ZeroTier(Address::from(buf.read_bytes_fixed(cursor)?))), + TYPE_ETHERNET => Ok(Endpoint::Ethernet(MAC::from(buf.read_bytes_fixed(cursor)?))), + TYPE_WIFIDIRECT => Ok(Endpoint::WifiDirect(MAC::from(buf.read_bytes_fixed(cursor)?))), + TYPE_BLUETOOTH => Ok(Endpoint::Bluetooth(MAC::from(buf.read_bytes_fixed(cursor)?))), TYPE_IP => Ok(Endpoint::Ip(InetAddress::unmarshal(buf, cursor)?)), TYPE_IPUDP => Ok(Endpoint::IpUdp(InetAddress::unmarshal(buf, cursor)?)), TYPE_IPTCP => Ok(Endpoint::IpTcp(InetAddress::unmarshal(buf, cursor)?)), TYPE_HTTP => { - let l = buf.get_u16(cursor)?; - Ok(Endpoint::Http(String::from_utf8_lossy(buf.get_bytes(l as usize, cursor)?).to_string())) + let l = buf.read_u16(cursor)?; + Ok(Endpoint::Http(String::from_utf8_lossy(buf.read_bytes(l as usize, cursor)?).to_string())) } TYPE_WEBRTC => { - let l = buf.get_u16(cursor)?; - Ok(Endpoint::WebRTC(String::from_utf8_lossy(buf.get_bytes(l as usize, cursor)?).to_string())) + let l = buf.read_u16(cursor)?; + Ok(Endpoint::WebRTC(String::from_utf8_lossy(buf.read_bytes(l as usize, cursor)?).to_string())) } _ => std::io::Result::Err(std::io::Error::new(std::io::ErrorKind::InvalidData, "unrecognized endpoint type in stream")) } diff --git a/network-hypervisor/src/vl1/headers.rs b/network-hypervisor/src/vl1/headers.rs new file mode 100644 index 000000000..be02d5b9e --- /dev/null +++ b/network-hypervisor/src/vl1/headers.rs @@ -0,0 +1,124 @@ +use std::ops::Not; + +use crate::vl1::buffer::{RawObject, Buffer}; +use crate::vl1::constants::{HEADER_FLAGS_FIELD_MASK_CIPHER, HEADER_FLAGS_FIELD_MASK_HOPS, HEADER_FLAG_FRAGMENTED, FRAGMENT_INDICATOR}; +use crate::vl1::Address; + +/// 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. +type PacketID = u64; + +/// ZeroTier unencrypted outer 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. +#[derive(Clone)] +#[repr(packed)] +pub struct PacketHeader { + pub id: PacketID, + pub dest: [u8; 5], + pub src: [u8; 5], + pub flags_cipher_hops: u8, + pub message_auth: [u8; 8], +} + +unsafe impl RawObject for PacketHeader {} + +impl PacketHeader { + #[inline(always)] + pub fn cipher(&self) -> u8 { + self.flags_cipher_hops & HEADER_FLAGS_FIELD_MASK_CIPHER + } + + /// Get this packet's hops field. + /// This is the only field in the unencrypted header that is not authenticated, allowing intermediate + /// nodes to increment it as they forward packets between indirectly connected peers. + #[inline(always)] + pub fn hops(&self) -> u8 { + self.flags_cipher_hops & HEADER_FLAGS_FIELD_MASK_HOPS + } + + #[inline(always)] + pub fn increment_hops(&mut self) { + let f = self.flags_cipher_hops; + self.flags_cipher_hops = (f & HEADER_FLAGS_FIELD_MASK_HOPS.not()) | ((f + 1) & HEADER_FLAGS_FIELD_MASK_HOPS); + } + + /// If true, this packet requires one or more fragments to fully assemble. + /// The one with the full header is always fragment 0. Note that is_fragment() is checked first + /// to see if this IS a fragment. + #[inline(always)] + pub fn is_fragmented(&self) -> bool { + (self.flags_cipher_hops & HEADER_FLAG_FRAGMENTED) != 0 + } + + /// If true, this packet is actually a fragment and its header should be interpreted as a FragmentHeader instead. + #[inline(always)] + pub fn is_fragment(&self) -> bool { + self.src[0] == FRAGMENT_INDICATOR + } + + #[inline(always)] + pub fn destination(&self) -> Address { + Address::from(&self.dest) + } + + #[inline(always)] + pub fn source(&self) -> Address { + Address::from(&self.src) + } +} + +/// ZeroTier fragment header +/// Fragments are indicated by byte 0xff at the start of the source address, which +/// is normally illegal since addresses can't begin with that. Fragmented packets +/// will arrive with the first fragment carrying a normal header with the fragment +/// bit set and remaining fragments being these. +#[derive(Clone)] +#[repr(packed)] +pub struct FragmentHeader { + pub id: PacketID, // 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) + pub reserved_hops: u8, // rrrrrHHH (3 hops bits, rest reserved) +} + +unsafe impl crate::vl1::buffer::RawObject for FragmentHeader {} + +impl FragmentHeader { + #[inline(always)] + pub fn total_fragments(&self) -> u8 { + self.total_and_fragment_no >> 4 + } + + #[inline(always)] + pub fn fragment_no(&self) -> u8 { + self.total_and_fragment_no & 0x0f + } + + #[inline(always)] + pub fn hops(&self) -> u8 { + self.reserved_hops & HEADER_FLAGS_FIELD_MASK_HOPS + } + + #[inline(always)] + pub fn destination(&self) -> Address { + Address::from(&self.dest) + } +} + +#[cfg(test)] +mod tests { + use std::mem::size_of; + use crate::vl1::headers::{PacketHeader, FragmentHeader}; + use crate::vl1::constants::{PACKET_HEADER_SIZE, FRAGMENT_SIZE_MIN}; + + #[test] + fn object_sizing() { + assert_eq!(size_of::(), PACKET_HEADER_SIZE); + assert_eq!(size_of::(), FRAGMENT_SIZE_MIN); + } +} diff --git a/network-hypervisor/src/vl1/identity.rs b/network-hypervisor/src/vl1/identity.rs index 0b9790a70..9daf17db6 100644 --- a/network-hypervisor/src/vl1/identity.rs +++ b/network-hypervisor/src/vl1/identity.rs @@ -51,8 +51,7 @@ pub struct Identity { /// Compute result from the bespoke "frankenhash" from the old V0 work function. /// The supplied genmem_ptr must be of size V0_IDENTITY_GEN_MEMORY and aligned to an 8-byte boundary. fn v0_frankenhash(digest: &mut [u8; 64], genmem_ptr: *mut u8) { - let genmem = unsafe { &mut *slice_from_raw_parts_mut(genmem_ptr, V0_IDENTITY_GEN_MEMORY) }; - let genmem_alias_hack = unsafe { &*slice_from_raw_parts(genmem_ptr, V0_IDENTITY_GEN_MEMORY) }; + let (genmem, genmem_alias_hack) = unsafe { (&mut *slice_from_raw_parts_mut(genmem_ptr, V0_IDENTITY_GEN_MEMORY), &*slice_from_raw_parts(genmem_ptr, V0_IDENTITY_GEN_MEMORY)) }; let genmem_u64_ptr = genmem_ptr.cast::(); let mut s20 = Salsa::new(&digest[0..32], &digest[32..40], false).unwrap(); @@ -67,13 +66,15 @@ fn v0_frankenhash(digest: &mut [u8; 64], genmem_ptr: *mut u8) { i = 0; while i < (V0_IDENTITY_GEN_MEMORY / 8) { - let idx1 = ((unsafe { *genmem_u64_ptr.offset(i as isize) }.to_be() % 8) * 8) as usize; - let idx2 = (unsafe { *genmem_u64_ptr.offset((i + 1) as isize) }.to_be() % (V0_IDENTITY_GEN_MEMORY as u64 / 8)) as usize; - let genmem_u64_at_idx2_ptr = unsafe { genmem_u64_ptr.offset(idx2 as isize) }; - let tmp = unsafe { *genmem_u64_at_idx2_ptr }; - let digest_u64_ptr = unsafe { digest.as_mut_ptr().offset(idx1 as isize).cast::() }; - unsafe { *genmem_u64_at_idx2_ptr = *digest_u64_ptr }; - unsafe { *digest_u64_ptr = tmp }; + unsafe { + let idx1 = (((*genmem_u64_ptr.offset(i as isize)).to_be() % 8) * 8) as usize; + let idx2 = ((*genmem_u64_ptr.offset((i + 1) as isize)).to_be() % (V0_IDENTITY_GEN_MEMORY as u64 / 8)) as usize; + let genmem_u64_at_idx2_ptr = genmem_u64_ptr.offset(idx2 as isize); + let tmp = *genmem_u64_at_idx2_ptr; + let digest_u64_ptr = digest.as_mut_ptr().offset(idx1 as isize).cast::(); + *genmem_u64_at_idx2_ptr = *digest_u64_ptr; + *digest_u64_ptr = tmp; + } s20.crypt_in_place(digest); i += 2; } @@ -255,7 +256,7 @@ impl Identity { // // For NIST P-521 key agreement, we use a single step key derivation function to derive // the final shared secret using the C25519 shared secret as a "salt." This should be - // FIPS140-compliant as per section 8.2 of: + // FIPS-compliant as per section 8.2 of: // // https://nvlpubs.nist.gov/nistpubs/SpecialPublications/NIST.SP.800-56Cr2.pdf // @@ -382,15 +383,15 @@ impl Identity { /// Deserialize an Identity from a buffer. /// The supplied cursor is advanced. pub fn unmarshal(buf: &Buffer, cursor: &mut usize) -> std::io::Result { - let addr = Address::from_bytes(buf.get_bytes_fixed::<5>(cursor)?).unwrap(); - let id_type = buf.get_u8(cursor)?; + let addr = Address::from_bytes(buf.read_bytes_fixed::<5>(cursor)?).unwrap(); + let id_type = buf.read_u8(cursor)?; if id_type == Type::C25519 as u8 { - let c25519_public_bytes = buf.get_bytes_fixed::<{ C25519_PUBLIC_KEY_SIZE }>(cursor)?; - let ed25519_public_bytes = buf.get_bytes_fixed::<{ ED25519_PUBLIC_KEY_SIZE }>(cursor)?; - let secrets_len = buf.get_u8(cursor)?; + 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)?; + let secrets_len = buf.read_u8(cursor)?; if secrets_len == (C25519_SECRET_KEY_SIZE + ED25519_SECRET_KEY_SIZE) as u8 { - let c25519_secret_bytes = buf.get_bytes_fixed::<{ C25519_SECRET_KEY_SIZE }>(cursor)?; - let ed25519_secret_bytes = buf.get_bytes_fixed::<{ ED25519_SECRET_KEY_SIZE }>(cursor)?; + let c25519_secret_bytes = buf.read_bytes_fixed::<{ C25519_SECRET_KEY_SIZE }>(cursor)?; + let ed25519_secret_bytes = buf.read_bytes_fixed::<{ ED25519_SECRET_KEY_SIZE }>(cursor)?; Ok(Identity { address: addr, c25519: c25519_public_bytes.clone(), @@ -414,18 +415,18 @@ impl Identity { std::io::Result::Err(std::io::Error::new(std::io::ErrorKind::InvalidData, "unrecognized scret key length (type 0)")) } } else if id_type == Type::P521 as u8 { - let c25519_public_bytes = buf.get_bytes_fixed::<{ C25519_PUBLIC_KEY_SIZE }>(cursor)?; - let ed25519_public_bytes = buf.get_bytes_fixed::<{ ED25519_PUBLIC_KEY_SIZE }>(cursor)?; - let p521_ecdh_public_bytes = buf.get_bytes_fixed::<{ P521_PUBLIC_KEY_SIZE }>(cursor)?; - let p521_ecdsa_public_bytes = buf.get_bytes_fixed::<{ P521_PUBLIC_KEY_SIZE }>(cursor)?; - let p521_signature = buf.get_bytes_fixed::<{ P521_ECDSA_SIGNATURE_SIZE }>(cursor)?; - let bh_digest = buf.get_bytes_fixed::<{ SHA512_HASH_SIZE }>(cursor)?; - let secrets_len = buf.get_u8(cursor)?; + 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)?; + let p521_ecdh_public_bytes = buf.read_bytes_fixed::<{ P521_PUBLIC_KEY_SIZE }>(cursor)?; + let p521_ecdsa_public_bytes = buf.read_bytes_fixed::<{ P521_PUBLIC_KEY_SIZE }>(cursor)?; + let p521_signature = buf.read_bytes_fixed::<{ P521_ECDSA_SIGNATURE_SIZE }>(cursor)?; + let bh_digest = buf.read_bytes_fixed::<{ SHA512_HASH_SIZE }>(cursor)?; + let secrets_len = buf.read_u8(cursor)?; if secrets_len == (C25519_SECRET_KEY_SIZE + ED25519_SECRET_KEY_SIZE + P521_SECRET_KEY_SIZE + P521_SECRET_KEY_SIZE) as u8 { - let c25519_secret_bytes = buf.get_bytes_fixed::<{ C25519_SECRET_KEY_SIZE }>(cursor)?; - let ed25519_secret_bytes = buf.get_bytes_fixed::<{ ED25519_SECRET_KEY_SIZE }>(cursor)?; - let p521_ecdh_secret_bytes = buf.get_bytes_fixed::<{ P521_SECRET_KEY_SIZE }>(cursor)?; - let p521_ecdsa_secret_bytes = buf.get_bytes_fixed::<{ P521_SECRET_KEY_SIZE }>(cursor)?; + let c25519_secret_bytes = buf.read_bytes_fixed::<{ C25519_SECRET_KEY_SIZE }>(cursor)?; + let ed25519_secret_bytes = buf.read_bytes_fixed::<{ ED25519_SECRET_KEY_SIZE }>(cursor)?; + let p521_ecdh_secret_bytes = buf.read_bytes_fixed::<{ P521_SECRET_KEY_SIZE }>(cursor)?; + let p521_ecdsa_secret_bytes = buf.read_bytes_fixed::<{ P521_SECRET_KEY_SIZE }>(cursor)?; Ok(Identity { address: addr, c25519: c25519_public_bytes.clone(), @@ -464,10 +465,14 @@ impl Identity { /// On success the identity and the number of bytes actually read from the slice are /// returned. pub fn unmarshal_from_bytes(bytes: &[u8]) -> std::io::Result<(Identity, usize)> { - let buf = Buffer::<2048>::from_bytes_lossy(bytes); - let mut cursor: usize = 0; - let id = Self::unmarshal(&buf, &mut cursor)?; - Ok((id, cursor)) + let buf = Buffer::<2048>::from_bytes(bytes); + if buf.is_none() { + std::io::Result::Err(std::io::Error::new(std::io::ErrorKind::InvalidData, "data object too large")) + } else { + let mut cursor: usize = 0; + let id = Self::unmarshal(buf.as_ref().unwrap(), &mut cursor)?; + Ok((id, cursor)) + } } /// Get this identity in string format, including its secret keys. diff --git a/network-hypervisor/src/vl1/inetaddress.rs b/network-hypervisor/src/vl1/inetaddress.rs index e9c839b91..1823e387f 100644 --- a/network-hypervisor/src/vl1/inetaddress.rs +++ b/network-hypervisor/src/vl1/inetaddress.rs @@ -225,6 +225,24 @@ impl InetAddress { } } + /// Fills in the InetAddress specific parts of a path lookup key. + /// This assumes that the key's default contents are zero bits and does not clear unused regions. + pub(crate) fn fill_path_lookup_key(&self, k: &mut [u64; 4]) { + unsafe { + match self.sa.sa_family as u8 { + AF_INET => { + k[1] |= self.sin.sin_port as u64 | 0x40000; // OR because most significant 32 bits contain endpoint info + k[2] = self.sin.sin_addr.s_addr as u64; + } + AF_INET6 => { + k[1] |= self.sin6.sin6_port as u64 | 0x60000; // OR because most significant 32 bits contain endpoint info + copy_nonoverlapping((&(self.sin6.sin6_addr) as *const in6_addr).cast::(), k.as_mut_ptr().cast::().offset(16), 16); + } + _ => {} + } + } + } + /// Set the IP port. #[inline(always)] pub fn set_port(&mut self, port: u16) { @@ -400,13 +418,13 @@ impl InetAddress { } pub fn unmarshal(buf: &Buffer, cursor: &mut usize) -> std::io::Result { - match buf.get_u8(cursor)? { + match buf.read_u8(cursor)? { 4 => { - let b: &[u8; 6] = buf.get_bytes_fixed(cursor)?; + let b: &[u8; 6] = buf.read_bytes_fixed(cursor)?; Ok(InetAddress::from_ip_port(&b[0..4], crate::util::integer_load_be_u16(&b[4..6]))) } 6 => { - let b: &[u8; 18] = buf.get_bytes_fixed(cursor)?; + let b: &[u8; 18] = buf.read_bytes_fixed(cursor)?; Ok(InetAddress::from_ip_port(&b[0..16], crate::util::integer_load_be_u16(&b[16..18]))) } _ => Ok(InetAddress::new()) diff --git a/network-hypervisor/src/vl1/mod.rs b/network-hypervisor/src/vl1/mod.rs index ae0262728..cdd965977 100644 --- a/network-hypervisor/src/vl1/mod.rs +++ b/network-hypervisor/src/vl1/mod.rs @@ -1,15 +1,17 @@ +pub(crate) mod concurrentmap; pub(crate) mod constants; -pub(crate) mod protocol; +pub(crate) mod headers; pub(crate) mod buffer; pub(crate) mod node; pub(crate) mod path; pub(crate) mod peer; +pub(crate) mod state; -pub mod dictionary; pub mod identity; pub mod inetaddress; pub mod endpoint; +mod dictionary; mod address; mod mac; @@ -17,3 +19,4 @@ pub use address::Address; pub use mac::MAC; pub use identity::Identity; pub use endpoint::Endpoint; +pub use dictionary::Dictionary; diff --git a/network-hypervisor/src/vl1/node.rs b/network-hypervisor/src/vl1/node.rs index e69de29bb..2add36943 100644 --- a/network-hypervisor/src/vl1/node.rs +++ b/network-hypervisor/src/vl1/node.rs @@ -0,0 +1,12 @@ +use std::sync::Arc; + +use crate::vl1::{Address, Identity}; +use crate::vl1::concurrentmap::ConcurrentMap; +use crate::vl1::path::Path; +use crate::vl1::peer::Peer; + +pub struct Node { + identity: Identity, + paths: ConcurrentMap<[u64; 4], Arc>, + peers: ConcurrentMap>, +} diff --git a/network-hypervisor/src/vl1/path.rs b/network-hypervisor/src/vl1/path.rs index e69de29bb..d596f9735 100644 --- a/network-hypervisor/src/vl1/path.rs +++ b/network-hypervisor/src/vl1/path.rs @@ -0,0 +1 @@ +pub struct Path; diff --git a/network-hypervisor/src/vl1/peer.rs b/network-hypervisor/src/vl1/peer.rs index e69de29bb..177ec4940 100644 --- a/network-hypervisor/src/vl1/peer.rs +++ b/network-hypervisor/src/vl1/peer.rs @@ -0,0 +1 @@ +pub struct Peer; diff --git a/network-hypervisor/src/vl1/protocol.rs b/network-hypervisor/src/vl1/protocol.rs deleted file mode 100644 index fe554ed01..000000000 --- a/network-hypervisor/src/vl1/protocol.rs +++ /dev/null @@ -1,82 +0,0 @@ -use crate::vl1::constants::{HEADER_FLAGS_FIELD_MASK_CIPHER, HEADER_FLAGS_FIELD_MASK_HOPS, HEADER_FLAG_FRAGMENTED}; -use std::ops::Not; -use crate::vl1::buffer::RawObject; - -type PacketID = u64; - -#[derive(Clone)] -#[repr(packed)] -pub struct PacketHeader { - pub id: PacketID, - pub dest: [u8; 5], - pub src: [u8; 5], - pub flags_cipher_hops: u8, - pub message_auth: [u8; 8], -} - -unsafe impl RawObject for PacketHeader {} - -impl PacketHeader { - #[inline(always)] - pub fn cipher(&self) -> u8 { - self.flags_cipher_hops & HEADER_FLAGS_FIELD_MASK_CIPHER - } - - #[inline(always)] - pub fn hops(&self) -> u8 { - self.flags_cipher_hops & HEADER_FLAGS_FIELD_MASK_HOPS - } - - #[inline(always)] - pub fn increment_hops(&mut self) { - let f = self.flags_cipher_hops; - self.flags_cipher_hops = (f & HEADER_FLAGS_FIELD_MASK_HOPS.not()) | ((f + 1) & HEADER_FLAGS_FIELD_MASK_HOPS); - } - - #[inline(always)] - pub fn is_fragmented(&self) -> bool { - (self.flags_cipher_hops & HEADER_FLAG_FRAGMENTED) != 0 - } -} - -#[derive(Clone)] -#[repr(packed)] -pub struct FragmentHeader { - pub id: PacketID, - pub dest: [u8; 5], - pub fragment_indicator: u8, - pub total_and_fragment_no: u8, - pub hops: u8, -} - -unsafe impl crate::vl1::buffer::RawObject for FragmentHeader {} - -impl FragmentHeader { - #[inline(always)] - pub fn total_fragments(&self) -> u8 { - self.total_and_fragment_no >> 4 - } - - #[inline(always)] - pub fn fragment_no(&self) -> u8 { - self.total_and_fragment_no & 0x0f - } - - #[inline(always)] - pub fn hops(&self) -> u8 { - self.hops & HEADER_FLAGS_FIELD_MASK_HOPS - } -} - -#[cfg(test)] -mod tests { - use std::mem::size_of; - use crate::vl1::protocol::{PacketHeader, FragmentHeader}; - use crate::vl1::constants::{PACKET_HEADER_SIZE, FRAGMENT_SIZE_MIN}; - - #[test] - fn object_sizing() { - assert_eq!(size_of::(), PACKET_HEADER_SIZE); - assert_eq!(size_of::(), FRAGMENT_SIZE_MIN); - } -} diff --git a/network-hypervisor/src/vl1/state.rs b/network-hypervisor/src/vl1/state.rs new file mode 100644 index 000000000..e69de29bb