diff --git a/zerotier-core-crypto/src/kbkdf.rs b/zerotier-core-crypto/src/kbkdf.rs index a51eee4cc..c27590390 100644 --- a/zerotier-core-crypto/src/kbkdf.rs +++ b/zerotier-core-crypto/src/kbkdf.rs @@ -8,47 +8,21 @@ use crate::secret::Secret; -// HMAC'd message is: preface | iteration[4], preface[2], label, 0x00, context, hash size[4] -// See: https://nvlpubs.nist.gov/nistpubs/Legacy/SP/nistspecialpublication800-108.pdf page 12 +/* + * HMAC'd message is: preface | iteration[4], preface[2], label, 0x00, context, hash size[4] + * + * Iteration and context are always zero here. Preface is 'ZT'. Hash size is in bits. Integers + * larger than one byte are big-endian. + * + * See: https://csrc.nist.gov/publications/detail/sp/800-108/final (page 12) + */ -pub fn zt_kbkdf_hmac_sha384(key: &[u8], label: u8, context: u8, iter: u32) -> Secret<48> { - Secret(crate::hash::hmac_sha384( - key, - &[ - (iter >> 24) as u8, - (iter >> 16) as u8, - (iter >> 8) as u8, - iter as u8, - b'Z', - b'T', // can also be considered part of "label" - label, - 0, - context, - 0, - 0, - 0x01, - 0x80, // 384 bits - ], - )) +/// Derive a key using HMAC-SHA384 and a single byte label, ZeroTier variant with "ZT" preface. +pub fn zt_kbkdf_hmac_sha384(key: &[u8], label: u8) -> Secret<48> { + Secret(crate::hash::hmac_sha384(key, &[0, 0, 0, 0, b'Z', b'T', label, 0, 0, 0, 0, 0x01, 0x80])) } -pub fn zt_kbkdf_hmac_sha512(key: &[u8], label: u8, context: u8, iter: u32) -> Secret<64> { - Secret(crate::hash::hmac_sha512( - key, - &[ - (iter >> 24) as u8, - (iter >> 16) as u8, - (iter >> 8) as u8, - iter as u8, - b'Z', - b'T', // can also be considered part of "label" - label, - 0, - context, - 0, - 0, - 0x01, - 0x80, // 384 bits - ], - )) +/// Derive a key using HMAC-SHA512 and a single byte label, ZeroTier variant with "ZT" preface. +pub fn zt_kbkdf_hmac_sha512(key: &[u8], label: u8) -> Secret<64> { + Secret(crate::hash::hmac_sha512(key, &[0, 0, 0, 0, b'Z', b'T', label, 0, 0, 0, 0, 0x02, 0x00])) } diff --git a/zerotier-network-hypervisor/Cargo.toml b/zerotier-network-hypervisor/Cargo.toml index 752e269a5..4b7680a2d 100644 --- a/zerotier-network-hypervisor/Cargo.toml +++ b/zerotier-network-hypervisor/Cargo.toml @@ -15,7 +15,6 @@ panic = 'abort' zerotier-core-crypto = { path = "../zerotier-core-crypto" } base64 = "^0" lz4_flex = { version = "^0", features = ["safe-encode", "safe-decode", "checked-decode"] } -metrohash = "^1" dashmap = "^5" parking_lot = "^0" lazy_static = "^1" diff --git a/zerotier-network-hypervisor/src/lib.rs b/zerotier-network-hypervisor/src/lib.rs index 043d48e01..7b7008648 100644 --- a/zerotier-network-hypervisor/src/lib.rs +++ b/zerotier-network-hypervisor/src/lib.rs @@ -16,53 +16,6 @@ pub mod vl1; pub mod vl2; mod networkhypervisor; + pub use networkhypervisor::{Interface, NetworkHypervisor}; - -/// Standard packet buffer type including pool container. -pub type PacketBuffer = crate::util::pool::Pooled, crate::PacketBufferFactory>; - -/// Factory type to supply to a new PacketBufferPool. -pub type PacketBufferFactory = crate::util::buffer::PooledBufferFactory<{ crate::vl1::protocol::PACKET_SIZE_MAX }>; - -/// Source for instances of PacketBuffer -pub type PacketBufferPool = crate::util::pool::Pool, crate::PacketBufferFactory>; - -/* - * Protocol versions - * - * 1 - 0.2.0 ... 0.2.5 - * 2 - 0.3.0 ... 0.4.5 - * + Added signature and originating peer to multicast frame - * + Double size of multicast frame bloom filter - * 3 - 0.5.0 ... 0.6.0 - * + Yet another multicast redesign - * + New crypto completely changes key agreement cipher - * 4 - 0.6.0 ... 1.0.6 - * + BREAKING CHANGE: New identity format based on hashcash design - * 5 - 1.1.0 ... 1.1.5 - * + Supports echo - * + Supports in-band world (root server definition) updates - * + Clustering! (Though this will work with protocol v4 clients.) - * + Otherwise backward compatible with protocol v4 - * 6 - 1.1.5 ... 1.1.10 - * + Network configuration format revisions including binary values - * 7 - 1.1.10 ... 1.1.17 - * + Introduce trusted paths for local SDN use - * 8 - 1.1.17 ... 1.2.0 - * + Multipart network configurations for large network configs - * + Tags and Capabilities - * + inline push of CertificateOfMembership deprecated - * 9 - 1.2.0 ... 1.2.14 - * 10 - 1.4.0 ... 1.4.6 - * + Contained early pre-alpha versions of multipath, which are deprecated - * 11 - 1.6.0 ... 2.0.0 - * + Supports and prefers AES-GMAC-SIV symmetric crypto, backported. - * - * 20 - 2.0.0 ... CURRENT - * + Forward secrecy with cryptographic ratchet! Finally!!! - * + New identity format including both x25519 and NIST P-521 keys. - * + AES-GMAC-SIV, a FIPS-compliant SIV construction using AES. - * + HELLO and OK(HELLO) include an extra HMAC to harden authentication - * + HELLO and OK(HELLO) use a dictionary for better extensibilit. - */ -pub const VERSION_PROTO: u8 = 20; +pub use vl1::protocol::{PacketBuffer, PooledPacketBuffer}; diff --git a/zerotier-network-hypervisor/src/networkhypervisor.rs b/zerotier-network-hypervisor/src/networkhypervisor.rs index f8ceb2733..ebe1a6aa3 100644 --- a/zerotier-network-hypervisor/src/networkhypervisor.rs +++ b/zerotier-network-hypervisor/src/networkhypervisor.rs @@ -6,23 +6,22 @@ * https://www.zerotier.com/ */ -use std::num::NonZeroI64; use std::time::Duration; use crate::error::InvalidParameterError; +use crate::vl1::protocol::PooledPacketBuffer; use crate::vl1::{Address, Endpoint, Identity, Node, RootSet, SystemInterface}; use crate::vl2::{Switch, SwitchInterface}; -use crate::PacketBuffer; pub trait Interface: SystemInterface + SwitchInterface {} -pub struct NetworkHypervisor { - vl1: Node, +pub struct NetworkHypervisor { + vl1: Node, vl2: Switch, } -impl NetworkHypervisor { - pub fn new(ii: &I, auto_generate_identity: bool) -> Result { +impl NetworkHypervisor { + pub fn new(ii: &I, auto_generate_identity: bool) -> Result { Ok(NetworkHypervisor { vl1: Node::new(ii, auto_generate_identity)?, vl2: Switch::new(), @@ -30,7 +29,7 @@ impl NetworkHypervisor { } #[inline(always)] - pub fn get_packet_buffer(&self) -> PacketBuffer { + pub fn get_packet_buffer(&self) -> PooledPacketBuffer { self.vl1.get_packet_buffer() } @@ -45,12 +44,12 @@ impl NetworkHypervisor { } #[inline(always)] - pub fn do_background_tasks(&self, ii: &I) -> Duration { + pub fn do_background_tasks(&self, ii: &I) -> Duration { self.vl1.do_background_tasks(ii) } #[inline(always)] - pub fn wire_receive(&self, ii: &I, source_endpoint: &Endpoint, source_local_socket: Option, source_local_interface: Option, data: PacketBuffer) { + pub fn wire_receive(&self, ii: &I, source_endpoint: &Endpoint, source_local_socket: &I::LocalSocket, source_local_interface: &I::LocalInterface, data: PooledPacketBuffer) { self.vl1.wire_receive(ii, &self.vl2, source_endpoint, source_local_socket, source_local_interface, data) } diff --git a/zerotier-network-hypervisor/src/util/buffer.rs b/zerotier-network-hypervisor/src/util/buffer.rs index 30ba0ee83..4c9f84981 100644 --- a/zerotier-network-hypervisor/src/util/buffer.rs +++ b/zerotier-network-hypervisor/src/util/buffer.rs @@ -11,23 +11,10 @@ use std::mem::{size_of, MaybeUninit}; use crate::util::pool::PoolFactory; -/// Annotates a structure as containing only primitive types. -/// -/// This means the structure is safe to copy in raw form, does not need to be dropped, and otherwise -/// contains nothing complex that requires any special handling. It also implies that it is safe to -/// access without concern for alignment on platforms on which this is an issue, or at least that -/// the implementer must take care to guard any unaligned access in appropriate ways. FlatBlob -/// structures are generally repr(C, packed) as well to make them deterministic across systems. -/// -/// The Buffer has special methods allowing these structs to be read and written in place, which -/// would be unsafe without these concerns being flagged as not applicable. -pub unsafe trait FlatBlob: Sized {} - /// A safe bounds checked I/O buffer with extensions for convenient appending of RawObject types. +#[derive(Clone, Copy, PartialEq, Eq)] pub struct Buffer(usize, [u8; L]); -unsafe impl FlatBlob for Buffer {} - impl Default for Buffer { #[inline(always)] fn default() -> Self { @@ -76,20 +63,21 @@ impl Buffer { /// Create an empty zeroed buffer on the heap without intermediate stack allocation. /// This can be used to allocate buffers too large for the stack. + #[inline(always)] pub fn new_boxed() -> Box { unsafe { Box::from_raw(std::alloc::alloc_zeroed(std::alloc::Layout::new::()).cast()) } } /// Create an empty buffer without internally zeroing its memory. /// - /// This is technically unsafe because unwritten memory in the buffer will have undefined contents. - /// Otherwise it behaves exactly like new(). + /// This is unsafe because unwritten memory in the buffer will have undefined contents. + /// This means that some of the append_X_get_mut() functions may return mutable references to + /// undefined memory contents rather than zeroed memory. #[inline(always)] pub unsafe fn new_without_memzero() -> Self { Self(0, MaybeUninit::uninit().assume_init()) } - #[inline(always)] pub fn from_bytes(b: &[u8]) -> std::io::Result { let l = b.len(); if l <= L { @@ -138,7 +126,6 @@ impl Buffer { } } - #[inline(always)] pub fn clear(&mut self) { self.1[0..self.0].fill(0); self.0 = 0; @@ -189,7 +176,7 @@ impl Buffer { /// Append a structure and return a mutable reference to its memory. #[inline(always)] - pub fn append_struct_get_mut(&mut self) -> std::io::Result<&mut T> { + 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 { @@ -226,6 +213,7 @@ impl Buffer { } } + #[inline(always)] pub fn append_padding(&mut self, b: u8, count: usize) -> std::io::Result<()> { let ptr = self.0; let end = ptr + count; @@ -238,6 +226,7 @@ impl Buffer { } } + #[inline(always)] pub fn append_bytes(&mut self, buf: &[u8]) -> std::io::Result<()> { let ptr = self.0; let end = ptr + buf.len(); @@ -250,6 +239,7 @@ impl Buffer { } } + #[inline(always)] pub fn append_bytes_fixed(&mut self, buf: &[u8; S]) -> std::io::Result<()> { let ptr = self.0; let end = ptr + S; @@ -318,19 +308,35 @@ impl Buffer { } } - /// Get a structure at a given position in the buffer. #[inline(always)] - pub fn struct_at(&self, ptr: usize) -> std::io::Result<&T> { - if (ptr + size_of::()) <= self.0 { - unsafe { Ok(&*self.1.as_ptr().cast::().offset(ptr as isize).cast::()) } + pub fn bytes_fixed_at(&self, ptr: usize) -> std::io::Result<&[u8; S]> { + if (ptr + S) <= self.0 { + unsafe { Ok(&*self.1.as_ptr().cast::().add(ptr).cast::<[u8; S]>()) } } else { Err(overflow_err()) } } - /// Get a structure at a given position in the buffer. #[inline(always)] - pub fn struct_mut_at(&mut self, ptr: usize) -> std::io::Result<&mut T> { + pub fn bytes_fixed_mut_at(&mut self, ptr: usize) -> std::io::Result<&mut [u8; S]> { + if (ptr + S) <= self.0 { + unsafe { Ok(&mut *self.1.as_mut_ptr().cast::().add(ptr).cast::<[u8; S]>()) } + } else { + Err(overflow_err()) + } + } + + #[inline(always)] + pub fn struct_at(&self, ptr: usize) -> std::io::Result<&T> { + if (ptr + size_of::()) <= self.0 { + unsafe { Ok(&*self.1.as_ptr().cast::().add(ptr).cast::()) } + } else { + Err(overflow_err()) + } + } + + #[inline(always)] + pub fn struct_mut_at(&mut self, ptr: usize) -> std::io::Result<&mut T> { if (ptr + size_of::()) <= self.0 { unsafe { Ok(&mut *self.1.as_mut_ptr().cast::().offset(ptr as isize).cast::()) } } else { @@ -347,9 +353,8 @@ impl Buffer { } } - /// Get a structure at a given position in the buffer and advance the cursor. #[inline(always)] - pub fn read_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); @@ -454,15 +459,6 @@ impl Buffer { } } -impl PartialEq for Buffer { - #[inline(always)] - fn eq(&self, other: &Self) -> bool { - self.1[0..self.0].eq(&other.1[0..other.0]) - } -} - -impl Eq for Buffer {} - impl Write for Buffer { #[inline(always)] fn write(&mut self, buf: &[u8]) -> std::io::Result { diff --git a/zerotier-network-hypervisor/src/util/marshalable.rs b/zerotier-network-hypervisor/src/util/marshalable.rs index aa5f59141..68ec4a05f 100644 --- a/zerotier-network-hypervisor/src/util/marshalable.rs +++ b/zerotier-network-hypervisor/src/util/marshalable.rs @@ -10,7 +10,7 @@ use crate::util::buffer::Buffer; /// Must be larger than any object we want to use with to_bytes() or from_bytes(). /// This hack can go away once Rust allows us to reference trait consts as generics. -const TEMP_BUF_SIZE: usize = 131072; +const TEMP_BUF_SIZE: usize = 16384; /// A super-lightweight zero-allocation serialization interface. pub trait Marshalable: Sized { @@ -48,7 +48,7 @@ pub trait Marshalable: Sized { /// Marshal and convert to a Rust vector. fn to_bytes(&self) -> Vec { assert!(Self::MAX_MARSHAL_SIZE <= TEMP_BUF_SIZE); - let mut tmp = Buffer::::new_boxed(); + let mut tmp = Buffer::::new(); assert!(self.marshal(&mut tmp).is_ok()); tmp.as_bytes().to_vec() } diff --git a/zerotier-network-hypervisor/src/util/pool.rs b/zerotier-network-hypervisor/src/util/pool.rs index 1a4196d5d..1df92b58c 100644 --- a/zerotier-network-hypervisor/src/util/pool.rs +++ b/zerotier-network-hypervisor/src/util/pool.rs @@ -11,12 +11,13 @@ use std::sync::{Arc, Weak}; use parking_lot::Mutex; -/// Trait for objects that create and reset poolable objects. +/// Each pool requires a factory that creates and resets (for re-use) pooled objects. pub trait PoolFactory { fn create(&self) -> O; fn reset(&self, obj: &mut O); } +#[repr(C)] struct PoolEntry> { obj: O, return_pool: Weak>, @@ -102,10 +103,7 @@ impl> Drop for Pooled { #[inline(always)] fn drop(&mut self) { unsafe { - // Return to pool if the pool still exists. Deallocate otherwise. - let p = Weak::upgrade(&self.0.as_ref().return_pool); - if p.is_some() { - let p = p.unwrap_unchecked(); + if let Some(p) = self.0.as_ref().return_pool.upgrade() { p.factory.reset(&mut self.0.as_mut().obj); p.pool.lock().push(self.0); } else { @@ -129,14 +127,16 @@ impl> Pool { } /// Get a pooled object, or allocate one if the pool is empty. - #[inline(always)] pub fn get(&self) -> Pooled { - Pooled::(self.0.pool.lock().pop().unwrap_or_else(|| unsafe { - NonNull::new_unchecked(Box::into_raw(Box::new(PoolEntry:: { - obj: self.0.factory.create(), - return_pool: Arc::downgrade(&self.0), - }))) - })) + Pooled::(self.0.pool.lock().pop().unwrap_or_else( + #[inline(always)] + || unsafe { + NonNull::new_unchecked(Box::into_raw(Box::new(PoolEntry:: { + obj: self.0.factory.create(), + return_pool: Arc::downgrade(&self.0), + }))) + }, + )) } /// Dispose of all pooled objects, freeing any memory they use. @@ -145,14 +145,8 @@ impl> Pool { /// 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.pool.lock(); - loop { - let o = p.pop(); - if o.is_some() { - drop(unsafe { Box::from_raw(o.unwrap().as_ptr()) }) - } else { - break; - } + for o in self.0.pool.lock().drain(..) { + drop(unsafe { Box::from_raw(o.as_ptr()) }) } } } diff --git a/zerotier-network-hypervisor/src/vl1/address.rs b/zerotier-network-hypervisor/src/vl1/address.rs index ea9799e05..b076720bb 100644 --- a/zerotier-network-hypervisor/src/vl1/address.rs +++ b/zerotier-network-hypervisor/src/vl1/address.rs @@ -25,13 +25,13 @@ pub struct Address(NonZeroU64); impl Address { /// Get an address from a 64-bit integer or return None if it is zero or reserved. - #[inline(always)] + #[inline] pub fn from_u64(mut i: u64) -> Option
{ i &= 0xffffffffff; NonZeroU64::new(i).and_then(|ii| if (i >> 32) != ADDRESS_RESERVED_PREFIX as u64 { Some(Address(ii)) } else { None }) } - #[inline(always)] + #[inline] pub fn from_bytes(b: &[u8]) -> Option
{ if b.len() >= ADDRESS_SIZE { 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) @@ -40,12 +40,12 @@ impl Address { } } - #[inline(always)] + #[inline] pub fn from_bytes_fixed(b: &[u8; ADDRESS_SIZE]) -> Option
{ 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)] + #[inline] pub fn to_bytes(&self) -> [u8; ADDRESS_SIZE] { let i = self.0.get(); [(i >> 32) as u8, (i >> 24) as u8, (i >> 16) as u8, (i >> 8) as u8, i as u8] @@ -60,12 +60,12 @@ impl Address { impl Marshalable for Address { const MAX_MARSHAL_SIZE: usize = ADDRESS_SIZE; - #[inline(always)] + #[inline] fn marshal(&self, buf: &mut Buffer) -> std::io::Result<()> { buf.append_bytes(&self.0.get().to_be_bytes()[8 - ADDRESS_SIZE..]) } - #[inline(always)] + #[inline] fn unmarshal(buf: &Buffer, cursor: &mut usize) -> std::io::Result { Self::from_bytes_fixed(buf.read_bytes_fixed(cursor)?).map_or_else(|| Err(std::io::Error::new(std::io::ErrorKind::InvalidData, "cannot be zero")), |a| Ok(a)) } diff --git a/zerotier-network-hypervisor/src/vl1/fragmentedpacket.rs b/zerotier-network-hypervisor/src/vl1/fragmentedpacket.rs index 5c179f20d..e765b469d 100644 --- a/zerotier-network-hypervisor/src/vl1/fragmentedpacket.rs +++ b/zerotier-network-hypervisor/src/vl1/fragmentedpacket.rs @@ -7,7 +7,6 @@ */ use crate::vl1::protocol::*; -use crate::PacketBuffer; /// Packet fragment re-assembler and container. /// @@ -19,15 +18,14 @@ use crate::PacketBuffer; /// the size of frags[] and the number of bits in 'have' and 'expecting'. pub(crate) struct FragmentedPacket { pub ts_ticks: i64, - pub frags: [Option; PACKET_FRAGMENT_COUNT_MAX], + pub frags: [Option; packet_constants::FRAGMENT_COUNT_MAX], pub have: u8, pub expecting: u8, } impl FragmentedPacket { - #[inline(always)] + #[inline] pub fn new(ts: i64) -> Self { - debug_assert_eq!(PACKET_FRAGMENT_COUNT_MAX, 8); Self { ts_ticks: ts, frags: [None, None, None, None, None, None, None, None], @@ -37,8 +35,8 @@ impl FragmentedPacket { } /// Add a fragment to this fragment set and return true if all fragments are present. - #[inline(always)] - pub fn add_fragment(&mut self, frag: PacketBuffer, no: u8, expecting: u8) -> bool { + #[inline] + pub fn add_fragment(&mut self, frag: PooledPacketBuffer, no: u8, expecting: u8) -> bool { self.frags.get_mut(no as usize).map_or(false, |entry| { /* * This works by setting bit N in the 'have' bit mask and then setting X bits diff --git a/zerotier-network-hypervisor/src/vl1/identity.rs b/zerotier-network-hypervisor/src/vl1/identity.rs index defc1a191..aa0d48ac1 100644 --- a/zerotier-network-hypervisor/src/vl1/identity.rs +++ b/zerotier-network-hypervisor/src/vl1/identity.rs @@ -33,30 +33,6 @@ use crate::util::pool::{Pool, PoolFactory, Pooled}; use crate::vl1::protocol::{ADDRESS_SIZE, ADDRESS_SIZE_STRING, IDENTITY_POW_THRESHOLD}; use crate::vl1::Address; -/// Curve25519 and Ed25519 -pub const IDENTITY_ALGORITHM_X25519: u8 = 0x01; - -/// NIST P-384 ECDH and ECDSA -pub const IDENTITY_ALGORITHM_EC_NIST_P384: u8 = 0x02; - -/// Bit mask to include all algorithms. -pub const IDENTITY_ALGORITHM_ALL: u8 = 0xff; - -/// Current sanity limit for the size of a marshaled Identity -/// This is padded just a little up to 512 and can be increased if new key types are ever added. -pub(crate) const MAX_MARSHAL_SIZE: usize = 25 - + ADDRESS_SIZE - + C25519_PUBLIC_KEY_SIZE - + ED25519_PUBLIC_KEY_SIZE - + C25519_SECRET_KEY_SIZE - + ED25519_SECRET_KEY_SIZE - + P384_PUBLIC_KEY_SIZE - + P384_PUBLIC_KEY_SIZE - + P384_SECRET_KEY_SIZE - + P384_SECRET_KEY_SIZE - + P384_ECDSA_SIGNATURE_SIZE - + ED25519_SIGNATURE_SIZE; - /// Secret keys associated with NIST P-384 public keys. #[derive(Clone)] pub struct IdentityP384Secret { @@ -122,6 +98,15 @@ fn concat_arrays_4 Self { // First generate an identity with just x25519 keys and derive its address. @@ -184,7 +169,7 @@ impl Identity { let _ = self_sign_buf.write_all(&self.address.to_bytes()); let _ = self_sign_buf.write_all(&self.c25519); let _ = self_sign_buf.write_all(&self.ed25519); - self_sign_buf.push(IDENTITY_ALGORITHM_EC_NIST_P384); + self_sign_buf.push(Self::ALGORITHM_EC_NIST_P384); let _ = self_sign_buf.write_all(p384_ecdh.public_key_bytes()); let _ = self_sign_buf.write_all(p384_ecdsa.public_key_bytes()); @@ -226,12 +211,11 @@ impl Identity { } /// Get a bit mask of algorithms present in this identity. - #[inline(always)] pub fn algorithms(&self) -> u8 { if self.p384.is_some() { - IDENTITY_ALGORITHM_X25519 | IDENTITY_ALGORITHM_EC_NIST_P384 + Self::ALGORITHM_X25519 | Self::ALGORITHM_EC_NIST_P384 } else { - IDENTITY_ALGORITHM_X25519 + Self::ALGORITHM_X25519 } } @@ -246,7 +230,7 @@ impl Identity { let _ = self_sign_buf.write_all(&self.address.to_bytes()); let _ = self_sign_buf.write_all(&self.c25519); let _ = self_sign_buf.write_all(&self.ed25519); - self_sign_buf.push(IDENTITY_ALGORITHM_EC_NIST_P384); + self_sign_buf.push(Self::ALGORITHM_EC_NIST_P384); let _ = self_sign_buf.write_all(p384.ecdh.as_bytes()); let _ = self_sign_buf.write_all(p384.ecdsa.as_bytes()); @@ -313,12 +297,12 @@ impl Identity { } else { let mut tmp: Vec = Vec::with_capacity(1 + P384_ECDSA_SIGNATURE_SIZE + ED25519_SIGNATURE_SIZE); tmp.push(0); - if secret.p384.is_some() && (include_algorithms & IDENTITY_ALGORITHM_EC_NIST_P384) != 0 { - *tmp.first_mut().unwrap() |= IDENTITY_ALGORITHM_EC_NIST_P384; + if secret.p384.is_some() && (include_algorithms & Self::ALGORITHM_EC_NIST_P384) != 0 { + *tmp.first_mut().unwrap() |= Self::ALGORITHM_EC_NIST_P384; let _ = tmp.write_all(&secret.p384.as_ref().unwrap().ecdsa.sign(msg)); } - if (include_algorithms & IDENTITY_ALGORITHM_X25519) != 0 { - *tmp.first_mut().unwrap() |= IDENTITY_ALGORITHM_X25519; + if (include_algorithms & Self::ALGORITHM_X25519) != 0 { + *tmp.first_mut().unwrap() |= Self::ALGORITHM_X25519; let _ = tmp.write_all(&secret.ed25519.sign(msg)); } if tmp.len() > 1 { @@ -347,14 +331,14 @@ impl Identity { let algorithms = signature[0]; signature = &signature[1..]; let mut passed = false; // makes sure we can't pass with an empty signature! - if (algorithms & IDENTITY_ALGORITHM_EC_NIST_P384) != 0 && signature.len() >= P384_ECDSA_SIGNATURE_SIZE && self.p384.is_some() { + if (algorithms & Self::ALGORITHM_EC_NIST_P384) != 0 && signature.len() >= P384_ECDSA_SIGNATURE_SIZE && self.p384.is_some() { if !self.p384.as_ref().unwrap().ecdsa.verify(msg, &signature[..P384_ECDSA_SIGNATURE_SIZE]) { return false; } signature = &signature[P384_ECDSA_SIGNATURE_SIZE..]; passed = true; } - if (algorithms & IDENTITY_ALGORITHM_X25519) != 0 && signature.len() >= ED25519_SIGNATURE_SIZE { + if (algorithms & Self::ALGORITHM_X25519) != 0 && signature.len() >= ED25519_SIGNATURE_SIZE { if !ed25519_verify(&self.ed25519, &signature[..ED25519_SIGNATURE_SIZE], msg) { return false; } @@ -367,22 +351,19 @@ impl Identity { } } - pub fn to_buffer_with_options(&self, include_algorithms: u8, include_private: bool) -> Buffer { - let mut b: Buffer = Buffer::new(); + pub fn to_buffer_with_options(&self, include_algorithms: u8, include_private: bool) -> Buffer<{ Self::MAX_MARSHAL_SIZE }> { + let mut b: Buffer<{ Self::MAX_MARSHAL_SIZE }> = Buffer::new(); assert!(self.marshal_with_options(&mut b, include_algorithms, include_private).is_ok()); b } - const P384_PUBLIC_AND_PRIVATE_BUNDLE_SIZE: u16 = (P384_PUBLIC_KEY_SIZE + P384_PUBLIC_KEY_SIZE + P384_ECDSA_SIGNATURE_SIZE + ED25519_SIGNATURE_SIZE + P384_SECRET_KEY_SIZE + P384_SECRET_KEY_SIZE) as u16; - const P384_PUBLIC_ONLY_BUNDLE_SIZE: u16 = (P384_PUBLIC_KEY_SIZE + P384_PUBLIC_KEY_SIZE + P384_ECDSA_SIGNATURE_SIZE + ED25519_SIGNATURE_SIZE) as u16; - pub fn marshal_with_options(&self, buf: &mut Buffer, include_algorithms: u8, include_private: bool) -> std::io::Result<()> { let algorithms = self.algorithms() & include_algorithms; let secret = self.secret.as_ref(); buf.append_bytes_fixed(&self.address.to_bytes())?; - if (algorithms & IDENTITY_ALGORITHM_X25519) != 0 { + if (algorithms & Self::ALGORITHM_X25519) != 0 { buf.append_u8(0x00)?; // 0x00 is used for X25519 for backward compatibility with v0 identities buf.append_bytes_fixed(&self.c25519)?; buf.append_bytes_fixed(&self.ed25519)?; @@ -396,53 +377,40 @@ impl Identity { } } - if (algorithms & IDENTITY_ALGORITHM_EC_NIST_P384) != 0 && self.p384.is_some() { + /* + * For legacy backward compatibility, any key pairs and other material after the x25519 + * keys are prefixed by 0x03 followed by the number of remaining bytes. This allows old nodes + * to parse HELLO normally and ignore the rest of the extended identity. It's ignored by + * newer nodes. + */ + buf.append_u8(0x03)?; + let remaining_data_size_field_at = buf.len(); + buf.append_padding(0, 2)?; + + if (algorithms & Self::ALGORITHM_EC_NIST_P384) != 0 && self.p384.is_some() { let p384 = self.p384.as_ref().unwrap(); + let p384s = if include_private { secret.clone().map_or(None, |s| s.p384.as_ref()) } else { None }; - /* - * For legacy backward compatibility, any key pairs and other material after the x25519 - * keys are prefixed by 0x03 followed by the total size of this section. This lets us parsimoniously - * maintain backward compatibility with old versions' parsing of HELLO. - * - * In old HELLO the identity was followed by an InetAddress. The InetAddress encoding does support - * a variable length encoding for unknown "future use" address types. This consists of 0x03 followed - * by a 16-bit size. - * - * By mimicking this we can create a HELLO containing a new format identity and cleverly skip the - * InetAddress after it and old nodes will parse this as an old x25519 only identity followed by - * an unrecognized type InetAddress that will be ignored. - * - * Key agreement can then proceed using only x25519 keys. - */ - buf.append_u8(0x03)?; - let p384_has_private = if include_private && secret.map_or(false, |s| s.p384.is_some()) { - buf.append_u16(Self::P384_PUBLIC_AND_PRIVATE_BUNDLE_SIZE + 1 + 2)?; - true + buf.append_u8(Self::ALGORITHM_EC_NIST_P384)?; + buf.append_varint(if p384s.is_some() { + ((P384_PUBLIC_KEY_SIZE * 2) + P384_ECDSA_SIGNATURE_SIZE + ED25519_SIGNATURE_SIZE + (P384_SECRET_KEY_SIZE * 2)) as u64 } else { - buf.append_u16(Self::P384_PUBLIC_ONLY_BUNDLE_SIZE + 1 + 2)?; - false - }; - - buf.append_u8(IDENTITY_ALGORITHM_EC_NIST_P384)?; - if p384_has_private { - buf.append_u16(Self::P384_PUBLIC_AND_PRIVATE_BUNDLE_SIZE)?; - } else { - buf.append_u16(Self::P384_PUBLIC_ONLY_BUNDLE_SIZE)?; - } + ((P384_PUBLIC_KEY_SIZE * 2) + P384_ECDSA_SIGNATURE_SIZE + ED25519_SIGNATURE_SIZE) as u64 + })?; buf.append_bytes_fixed(p384.ecdh.as_bytes())?; buf.append_bytes_fixed(p384.ecdsa.as_bytes())?; buf.append_bytes_fixed(&p384.ecdsa_self_signature)?; buf.append_bytes_fixed(&p384.ed25519_self_signature)?; - if p384_has_private { - let p384s = secret.unwrap().p384.as_ref().unwrap(); + if let Some(p384s) = p384s { buf.append_bytes_fixed(&p384s.ecdh.secret_key_bytes().0)?; buf.append_bytes_fixed(&p384s.ecdsa.secret_key_bytes().0)?; } } - // A size of zero tells unmarshal() to stop. - buf.append_u8(0x03)?; - buf.append_u16(0)?; + buf.append_u8(0xff)?; + + // Fill in the remaining data field earmarked above. + *buf.bytes_fixed_mut_at(remaining_data_size_field_at).unwrap() = ((buf.len() - remaining_data_size_field_at) as u16).to_be_bytes(); Ok(()) } @@ -452,12 +420,12 @@ impl Identity { /// The include_algorithms bitmap controls which algorithms will be included, provided we have them. /// If include_private is true private keys will be included, again if we have them. pub fn to_string_with_options(&self, include_algorithms: u8, include_private: bool) -> String { - let include_p384 = self.p384.is_some() && ((include_algorithms & IDENTITY_ALGORITHM_EC_NIST_P384) != 0); + let include_p384 = self.p384.is_some() && ((include_algorithms & Self::ALGORITHM_EC_NIST_P384) != 0); - let mut s = String::with_capacity(MAX_MARSHAL_SIZE * 2); + let mut s = String::with_capacity(Self::MAX_MARSHAL_SIZE * 2); s.push_str(self.address.to_string().as_str()); - if (include_algorithms & IDENTITY_ALGORITHM_X25519) != 0 { + if (include_algorithms & Self::ALGORITHM_X25519) != 0 { s.push_str(":0:"); // 0 used for x25519 for legacy reasons just like in marshal() s.push_str(hex::to_string(&self.c25519).as_str()); s.push_str(hex::to_string(&self.ed25519).as_str()); @@ -493,7 +461,7 @@ impl Identity { /// Get this identity in string form with all ciphers and with secrets (if present) pub fn to_secret_string(&self) -> String { - self.to_string_with_options(IDENTITY_ALGORITHM_ALL, true) + self.to_string_with_options(Self::ALGORITHM_ALL, true) } } @@ -501,7 +469,7 @@ impl ToString for Identity { /// Get only the public portion of this identity as a string, including all cipher suites. #[inline(always)] fn to_string(&self) -> String { - self.to_string_with_options(IDENTITY_ALGORITHM_ALL, false) + self.to_string_with_options(Self::ALGORITHM_ALL, false) } } @@ -567,7 +535,7 @@ impl FromStr for Identity { sha.update(&address.to_bytes()); sha.update(&keys[0].as_slice()[0..64]); if !keys[2].is_empty() { - sha.update(&[IDENTITY_ALGORITHM_EC_NIST_P384]); + sha.update(&[Self::ALGORITHM_EC_NIST_P384]); sha.update(&keys[2].as_slice()[0..(P384_PUBLIC_KEY_SIZE * 2)]); } @@ -639,11 +607,24 @@ impl FromStr for Identity { } impl Marshalable for Identity { - const MAX_MARSHAL_SIZE: usize = MAX_MARSHAL_SIZE; + /// Current sanity limit for the size of a marshaled Identity + /// This is padded just a little up to 512 and can be increased if new key types are ever added. + const MAX_MARSHAL_SIZE: usize = 25 + + ADDRESS_SIZE + + C25519_PUBLIC_KEY_SIZE + + ED25519_PUBLIC_KEY_SIZE + + C25519_SECRET_KEY_SIZE + + ED25519_SECRET_KEY_SIZE + + P384_PUBLIC_KEY_SIZE + + P384_PUBLIC_KEY_SIZE + + P384_SECRET_KEY_SIZE + + P384_SECRET_KEY_SIZE + + P384_ECDSA_SIGNATURE_SIZE + + ED25519_SIGNATURE_SIZE; #[inline(always)] fn marshal(&self, buf: &mut Buffer) -> std::io::Result<()> { - self.marshal_with_options(buf, IDENTITY_ALGORITHM_ALL, false) + self.marshal_with_options(buf, Self::ALGORITHM_ALL, false) } fn unmarshal(buf: &Buffer, cursor: &mut usize) -> std::io::Result { @@ -653,27 +634,26 @@ impl Marshalable for Identity { } let address = address.unwrap(); - let mut x25519_public: Option<([u8; C25519_PUBLIC_KEY_SIZE], [u8; ED25519_PUBLIC_KEY_SIZE])> = None; - let mut x25519_secret: Option<([u8; C25519_SECRET_KEY_SIZE], [u8; ED25519_SECRET_KEY_SIZE])> = None; - let mut p384_ecdh_ecdsa_public: Option<(P384PublicKey, P384PublicKey, [u8; P384_ECDSA_SIGNATURE_SIZE], [u8; ED25519_SIGNATURE_SIZE])> = None; - let mut p384_ecdh_ecdsa_secret: Option<([u8; P384_SECRET_KEY_SIZE], [u8; P384_SECRET_KEY_SIZE])> = None; + let mut x25519_public: Option<(&[u8; C25519_PUBLIC_KEY_SIZE], &[u8; ED25519_PUBLIC_KEY_SIZE])> = None; + let mut x25519_secret: Option<(&[u8; C25519_SECRET_KEY_SIZE], &[u8; ED25519_SECRET_KEY_SIZE])> = None; + let mut p384_ecdh_ecdsa_public: Option<(P384PublicKey, P384PublicKey, &[u8; P384_ECDSA_SIGNATURE_SIZE], &[u8; ED25519_SIGNATURE_SIZE])> = None; + let mut p384_ecdh_ecdsa_secret: Option<(&[u8; P384_SECRET_KEY_SIZE], &[u8; P384_SECRET_KEY_SIZE])> = None; loop { - let algorithm = buf.read_u8(cursor); - if algorithm.is_err() { - break; + let mut algorithm = buf.read_u8(cursor)?; + if algorithm == 0 { + algorithm = Self::ALGORITHM_X25519; } - let algorithm = algorithm.unwrap(); match algorithm { - 0x00 | IDENTITY_ALGORITHM_X25519 => { + Self::ALGORITHM_X25519 => { let a = buf.read_bytes_fixed::(cursor)?; let b = buf.read_bytes_fixed::(cursor)?; - x25519_public = Some((a.clone(), b.clone())); + x25519_public = Some((a, b)); let sec_size = buf.read_u8(cursor)?; if sec_size == (C25519_SECRET_KEY_SIZE + ED25519_SECRET_KEY_SIZE) as u8 { let a = buf.read_bytes_fixed::(cursor)?; let b = buf.read_bytes_fixed::(cursor)?; - x25519_secret = Some((a.clone(), b.clone())); + x25519_secret = Some((a, b)); } else if sec_size != 0 { return std::io::Result::Err(std::io::Error::new(std::io::ErrorKind::InvalidData, "invalid x25519 secret")); } @@ -682,43 +662,38 @@ impl Marshalable for Identity { // This isn't an algorithm; each algorithm is identified by just one bit. This // indicates the total size of the section after the x25519 keys for backward // compatibility. See comments in marshal(). New versions can ignore this field. - let size = buf.read_u16(cursor)?; - if size == 0 { - break; - } else { - *cursor += size as usize; - } + *cursor += 2; } - IDENTITY_ALGORITHM_EC_NIST_P384 => { - let size = buf.read_u16(cursor)?; - if size < Self::P384_PUBLIC_ONLY_BUNDLE_SIZE { - return std::io::Result::Err(std::io::Error::new(std::io::ErrorKind::InvalidData, "invalid p384 public key")); - } - let a = buf.read_bytes_fixed::(cursor)?; - let b = buf.read_bytes_fixed::(cursor)?; + Self::ALGORITHM_EC_NIST_P384 => { + let field_length = buf.read_varint(cursor)?; + let has_secret = if field_length == ((P384_PUBLIC_KEY_SIZE * 2) + P384_ECDSA_SIGNATURE_SIZE + ED25519_SIGNATURE_SIZE) as u64 { + false + } else { + if field_length == ((P384_PUBLIC_KEY_SIZE * 2) + P384_ECDSA_SIGNATURE_SIZE + ED25519_SIGNATURE_SIZE + (P384_SECRET_KEY_SIZE * 2)) as u64 { + true + } else { + return std::io::Result::Err(std::io::Error::new(std::io::ErrorKind::InvalidData, "invalid p384 public key")); + } + }; + + let a = P384PublicKey::from_bytes(buf.read_bytes_fixed::(cursor)?); + let b = P384PublicKey::from_bytes(buf.read_bytes_fixed::(cursor)?); let c = buf.read_bytes_fixed::(cursor)?; let d = buf.read_bytes_fixed::(cursor)?; - let a = P384PublicKey::from_bytes(a); - let b = P384PublicKey::from_bytes(b); if a.is_none() || b.is_none() { return std::io::Result::Err(std::io::Error::new(std::io::ErrorKind::InvalidData, "invalid p384 public key")); } - p384_ecdh_ecdsa_public = Some((a.unwrap(), b.unwrap(), c.clone(), d.clone())); - if size > Self::P384_PUBLIC_ONLY_BUNDLE_SIZE { - if size != Self::P384_PUBLIC_AND_PRIVATE_BUNDLE_SIZE { - return std::io::Result::Err(std::io::Error::new(std::io::ErrorKind::InvalidData, "invalid p384 secret key")); - } + p384_ecdh_ecdsa_public = Some((a.unwrap(), b.unwrap(), c, d)); + + if has_secret { let a = buf.read_bytes_fixed::(cursor)?; let b = buf.read_bytes_fixed::(cursor)?; - p384_ecdh_ecdsa_secret = Some((a.clone(), b.clone())); + p384_ecdh_ecdsa_secret = Some((a, b)); } } + 0xff => break, _ => { - // Skip any unrecognized cipher suites, all of which will be prefixed by a size. - *cursor += buf.read_u16(cursor)? as usize; - if *cursor > buf.len() { - return std::io::Result::Err(std::io::Error::new(std::io::ErrorKind::InvalidData, "invalid field length")); - } + *cursor += buf.read_varint(cursor)? as usize; } } } @@ -730,11 +705,11 @@ impl Marshalable for Identity { let mut sha = SHA512::new(); sha.update(&address.to_bytes()); - sha.update(&x25519_public.0); - sha.update(&x25519_public.1); + sha.update(x25519_public.0); + sha.update(x25519_public.1); if p384_ecdh_ecdsa_public.is_some() { let p384 = p384_ecdh_ecdsa_public.as_ref().unwrap(); - sha.update(&[IDENTITY_ALGORITHM_EC_NIST_P384]); + sha.update(&[Self::ALGORITHM_EC_NIST_P384]); sha.update(p384.0.as_bytes()); sha.update(p384.1.as_bytes()); } @@ -756,8 +731,8 @@ impl Marshalable for Identity { }, secret: if x25519_secret.is_some() { let x25519_secret = x25519_secret.unwrap(); - let c25519_secret = C25519KeyPair::from_bytes(&x25519_public.0, &x25519_secret.0); - let ed25519_secret = Ed25519KeyPair::from_bytes(&x25519_public.1, &x25519_secret.1); + let c25519_secret = C25519KeyPair::from_bytes(x25519_public.0, x25519_secret.0); + let ed25519_secret = Ed25519KeyPair::from_bytes(x25519_public.1, x25519_secret.1); if c25519_secret.is_none() || ed25519_secret.is_none() { return std::io::Result::Err(std::io::Error::new(std::io::ErrorKind::InvalidData, "x25519 public key invalid")); } @@ -767,8 +742,8 @@ impl Marshalable for Identity { p384: if p384_ecdh_ecdsa_secret.is_some() && p384_ecdh_ecdsa_public.is_some() { let p384_ecdh_ecdsa_public = p384_ecdh_ecdsa_public.as_ref().unwrap(); let p384_ecdh_ecdsa_secret = p384_ecdh_ecdsa_secret.as_ref().unwrap(); - let p384_ecdh_secret = P384KeyPair::from_bytes(p384_ecdh_ecdsa_public.0.as_bytes(), &p384_ecdh_ecdsa_secret.0); - let p384_ecdsa_secret = P384KeyPair::from_bytes(p384_ecdh_ecdsa_public.1.as_bytes(), &p384_ecdh_ecdsa_secret.1); + let p384_ecdh_secret = P384KeyPair::from_bytes(p384_ecdh_ecdsa_public.0.as_bytes(), p384_ecdh_ecdsa_secret.0); + let p384_ecdsa_secret = P384KeyPair::from_bytes(p384_ecdh_ecdsa_public.1.as_bytes(), p384_ecdh_ecdsa_secret.1); if p384_ecdh_secret.is_none() || p384_ecdsa_secret.is_none() { return std::io::Result::Err(std::io::Error::new(std::io::ErrorKind::InvalidData, "p384 secret key invalid")); } @@ -823,10 +798,10 @@ impl Serialize for Identity { S: Serializer, { if serializer.is_human_readable() { - serializer.serialize_str(self.to_string_with_options(IDENTITY_ALGORITHM_ALL, false).as_str()) + serializer.serialize_str(self.to_string_with_options(Self::ALGORITHM_ALL, false).as_str()) } else { - let mut tmp: Buffer = Buffer::new(); - assert!(self.marshal_with_options(&mut tmp, IDENTITY_ALGORITHM_ALL, false).is_ok()); + let mut tmp: Buffer<{ Self::MAX_MARSHAL_SIZE }> = Buffer::new(); + assert!(self.marshal_with_options(&mut tmp, Self::ALGORITHM_ALL, false).is_ok()); serializer.serialize_bytes(tmp.as_bytes()) } } @@ -845,8 +820,8 @@ impl<'de> serde::de::Visitor<'de> for IdentityVisitor { where E: serde::de::Error, { - if v.len() <= MAX_MARSHAL_SIZE { - let mut tmp: Buffer = Buffer::new(); + if v.len() <= Identity::MAX_MARSHAL_SIZE { + let mut tmp: Buffer<{ Identity::MAX_MARSHAL_SIZE }> = Buffer::new(); let _ = tmp.append_bytes(v); let mut cursor = 0; Identity::unmarshal(&tmp, &mut cursor).map_err(|e| E::custom(e.to_string())) @@ -960,7 +935,7 @@ pub(crate) fn purge_verification_memory_pool() { #[cfg(test)] mod tests { use crate::util::marshalable::Marshalable; - use crate::vl1::identity::{Identity, IDENTITY_ALGORITHM_ALL}; + use crate::vl1::identity::*; use std::str::FromStr; use std::time::{Duration, SystemTime}; #[allow(unused_imports)] @@ -996,14 +971,17 @@ mod tests { let gen = Identity::generate(); assert!(gen.agree(&gen).is_some()); assert!(gen.validate_identity()); - let bytes = gen.to_buffer_with_options(IDENTITY_ALGORITHM_ALL, true); - let string = gen.to_string_with_options(IDENTITY_ALGORITHM_ALL, true); - //println!("{}", string); + let bytes = gen.to_buffer_with_options(Identity::ALGORITHM_ALL, true); + let string = gen.to_string_with_options(Identity::ALGORITHM_ALL, true); assert!(Identity::from_str(string.as_str()).unwrap().eq(&gen)); + let mut cursor = 0_usize; - assert!(Identity::unmarshal(&bytes, &mut cursor).unwrap().eq(&gen)); - cursor = 0; - assert!(Identity::unmarshal(&bytes, &mut cursor).unwrap().secret.is_some()); + let gen_unmarshaled = Identity::unmarshal(&bytes, &mut cursor).unwrap(); + assert!(gen_unmarshaled.secret.is_some()); + if !gen_unmarshaled.eq(&gen) { + println!("{} != {}", hex::to_string(&gen_unmarshaled.fingerprint), hex::to_string(&gen.fingerprint)); + } + assert!(Identity::from_str(string.as_str()).unwrap().secret.is_some()); let gen2 = Identity::generate(); @@ -1012,18 +990,18 @@ mod tests { for id_str in GOOD_V0_IDENTITIES { let mut id = Identity::from_str(id_str).unwrap(); - assert_eq!(id.to_string_with_options(IDENTITY_ALGORITHM_ALL, true).as_str(), id_str); + assert_eq!(id.to_string_with_options(Identity::ALGORITHM_ALL, true).as_str(), id_str); assert!(id.validate_identity()); assert!(id.p384.is_none()); - let idb = id.to_buffer_with_options(IDENTITY_ALGORITHM_ALL, true); + let idb = id.to_buffer_with_options(Identity::ALGORITHM_ALL, true); let mut cursor = 0; let id_unmarshal = Identity::unmarshal(&idb, &mut cursor).unwrap(); assert!(id == id_unmarshal); assert!(id_unmarshal.secret.is_some()); - let idb2 = id_unmarshal.to_buffer_with_options(IDENTITY_ALGORITHM_ALL, false); + let idb2 = id_unmarshal.to_buffer_with_options(Identity::ALGORITHM_ALL, false); cursor = 0; let id_unmarshal2 = Identity::unmarshal(&idb2, &mut cursor).unwrap(); assert!(id_unmarshal2 == id_unmarshal); @@ -1043,18 +1021,18 @@ mod tests { } for id_str in GOOD_V1_IDENTITIES { let id = Identity::from_str(id_str).unwrap(); - assert_eq!(id.to_string_with_options(IDENTITY_ALGORITHM_ALL, true).as_str(), id_str); + assert_eq!(id.to_string_with_options(Identity::ALGORITHM_ALL, true).as_str(), id_str); assert!(id.validate_identity()); assert!(id.p384.is_some()); - let idb = id.to_buffer_with_options(IDENTITY_ALGORITHM_ALL, true); + let idb = id.to_buffer_with_options(Identity::ALGORITHM_ALL, true); let mut cursor = 0; let id_unmarshal = Identity::unmarshal(&idb, &mut cursor).unwrap(); assert!(id == id_unmarshal); cursor = 0; - let idb2 = id_unmarshal.to_buffer_with_options(IDENTITY_ALGORITHM_ALL, false); + let idb2 = id_unmarshal.to_buffer_with_options(Identity::ALGORITHM_ALL, false); let id_unmarshal2 = Identity::unmarshal(&idb2, &mut cursor).unwrap(); assert!(id_unmarshal2 == id_unmarshal); assert!(id_unmarshal2 == id); diff --git a/zerotier-network-hypervisor/src/vl1/inetaddress.rs b/zerotier-network-hypervisor/src/vl1/inetaddress.rs index a9bed2f50..039a4f407 100644 --- a/zerotier-network-hypervisor/src/vl1/inetaddress.rs +++ b/zerotier-network-hypervisor/src/vl1/inetaddress.rs @@ -267,7 +267,7 @@ impl Default for InetAddress { impl std::fmt::Debug for InetAddress { #[inline(always)] fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - f.write_str(&self.to_string()) + f.write_str(self.to_string().as_str()) } } @@ -471,6 +471,17 @@ impl InetAddress { } } + /// Get a Rust stdlib SocketAddr structure from this InetAddress. + pub fn to_socketaddr(&self) -> Option { + unsafe { + match self.sa.sa_family { + AF_INET => Some(SocketAddr::V4(SocketAddrV4::new(Ipv4Addr::from(self.sin.sin_addr.s_addr.to_ne_bytes()), u16::from_be(self.sin.sin_port as u16)))), + AF_INET6 => Some(SocketAddr::V6(SocketAddrV6::new(Ipv6Addr::from(self.sin6.sin6_addr.s6_addr), u16::from_be(self.sin6.sin6_port as u16), 0, 0))), + _ => None, + } + } + } + /// Get the IP port for this InetAddress. pub fn port(&self) -> u16 { unsafe { @@ -497,6 +508,47 @@ impl InetAddress { } } + /// Check whether this IP address is within a CIDR range + /// + /// The argument is a CIDR range in which the port is interpreted as the number of bits, e.g. 10.0.0.0/24. + pub fn is_within(&self, cidr: &InetAddress) -> bool { + unsafe { + if self.sa.sa_family == cidr.sa.sa_family { + let mut cidr_bits = cidr.port() as u32; + match self.sa.sa_family as u8 { + AF_INET => { + if cidr_bits <= 32 { + let discard_bits = 32 - cidr_bits; + if u32::from_be(self.sin.sin_addr.s_addr as u32).wrapping_shr(discard_bits) == u32::from_be(cidr.sin.sin_addr.s_addr as u32).wrapping_shr(discard_bits) { + return true; + } + } + } + AF_INET6 => { + if cidr_bits <= 128 { + let a = &self.sin6.sin6_addr.s6_addr; + let b = &cidr.sin6.sin6_addr.s6_addr; + let mut p = 0; + while cidr_bits >= 8 { + cidr_bits -= 8; + if a[p] != b[p] { + return false; + } + p += 1; + } + let discard_bits = 8 - cidr_bits; + if a[p].wrapping_shr(discard_bits) == b[p].wrapping_shr(discard_bits) { + return true; + } + } + } + _ => {} + } + } + } + return false; + } + /// Get this IP address's scope as per RFC documents and what is advertised via BGP. pub fn scope(&self) -> IpScope { unsafe { diff --git a/zerotier-network-hypervisor/src/vl1/mac.rs b/zerotier-network-hypervisor/src/vl1/mac.rs index d9777432b..3abe95574 100644 --- a/zerotier-network-hypervisor/src/vl1/mac.rs +++ b/zerotier-network-hypervisor/src/vl1/mac.rs @@ -29,12 +29,12 @@ impl Debug for MAC { } impl MAC { - #[inline(always)] + #[inline] pub fn from_u64(i: u64) -> Option { NonZeroU64::new(i & 0xffffffffffff).map(|i| MAC(i)) } - #[inline(always)] + #[inline] pub fn from_bytes(b: &[u8]) -> Option { if b.len() >= 6 { NonZeroU64::new((b[0] as u64) << 40 | (b[1] as u64) << 32 | (b[2] as u64) << 24 | (b[3] as u64) << 16 as u64 | (b[4] as u64) << 8 | b[5] as u64).map(|i| MAC(i)) @@ -43,12 +43,12 @@ impl MAC { } } - #[inline(always)] + #[inline] pub fn from_bytes_fixed(b: &[u8; 6]) -> Option { NonZeroU64::new((b[0] as u64) << 40 | (b[1] as u64) << 32 | (b[2] as u64) << 24 | (b[3] as u64) << 16 as u64 | (b[4] as u64) << 8 | b[5] as u64).map(|i| MAC(i)) } - #[inline(always)] + #[inline] pub fn to_bytes(&self) -> [u8; 6] { let i = self.0.get(); [(i >> 40) as u8, (i >> 32) as u8, (i >> 24) as u8, (i >> 16) as u8, (i >> 8) as u8, i as u8] @@ -63,12 +63,12 @@ impl MAC { impl Marshalable for MAC { const MAX_MARSHAL_SIZE: usize = 6; - #[inline(always)] + #[inline] fn marshal(&self, buf: &mut Buffer) -> std::io::Result<()> { buf.append_bytes(&self.0.get().to_be_bytes()[2..]) } - #[inline(always)] + #[inline] fn unmarshal(buf: &Buffer, cursor: &mut usize) -> std::io::Result { Self::from_bytes_fixed(buf.read_bytes_fixed(cursor)?).map_or_else(|| Err(std::io::Error::new(std::io::ErrorKind::InvalidData, "cannot be zero")), |a| Ok(a)) } diff --git a/zerotier-network-hypervisor/src/vl1/mod.rs b/zerotier-network-hypervisor/src/vl1/mod.rs index 03a1f0abc..95e20dbc3 100644 --- a/zerotier-network-hypervisor/src/vl1/mod.rs +++ b/zerotier-network-hypervisor/src/vl1/mod.rs @@ -6,31 +6,30 @@ * https://www.zerotier.com/ */ -pub mod endpoint; -pub mod identity; -pub mod inetaddress; - mod address; mod dictionary; +mod endpoint; +mod fragmentedpacket; +mod identity; +mod inetaddress; mod mac; mod path; mod peer; mod rootset; +mod symmetricsecret; +mod whoisqueue; -pub(crate) mod fragmentedpacket; pub(crate) mod node; #[allow(unused)] pub(crate) mod protocol; -pub(crate) mod symmetricsecret; -pub(crate) mod whoisqueue; pub use address::Address; pub use dictionary::Dictionary; pub use endpoint::Endpoint; -pub use identity::Identity; -pub use inetaddress::InetAddress; +pub use identity::*; +pub use inetaddress::{InetAddress, IpScope}; pub use mac::MAC; -pub use node::{Node, SystemInterface}; +pub use node::{InnerProtocolInterface, Node, SystemInterface}; pub use path::Path; pub use peer::Peer; pub use rootset::{Root, RootSet}; diff --git a/zerotier-network-hypervisor/src/vl1/node.rs b/zerotier-network-hypervisor/src/vl1/node.rs index d37699bb5..e0b24ebdd 100644 --- a/zerotier-network-hypervisor/src/vl1/node.rs +++ b/zerotier-network-hypervisor/src/vl1/node.rs @@ -7,31 +7,34 @@ */ use std::collections::HashMap; +use std::hash::Hash; use std::num::NonZeroI64; -use std::str::FromStr; use std::sync::atomic::Ordering; -use std::sync::{Arc, Weak}; +use std::sync::Arc; use std::time::Duration; use dashmap::DashMap; -use lazy_static::lazy_static; use parking_lot::{Mutex, RwLock}; use crate::error::InvalidParameterError; -use crate::util::buffer::Buffer; use crate::util::gate::IntervalGate; use crate::vl1::path::Path; use crate::vl1::peer::Peer; use crate::vl1::protocol::*; use crate::vl1::whoisqueue::{QueuedPacket, WhoisQueue}; use crate::vl1::{Address, Endpoint, Identity, RootSet}; -use crate::{PacketBuffer, PacketBufferFactory, PacketBufferPool}; /// Trait implemented by external code to handle events and provide an interface to the system or application. /// /// These methods are basically callbacks that the core calls to request or transmit things. They are called /// during calls to things like wire_recieve() and do_background_tasks(). -pub trait SystemInterface: Sync + Send { +pub trait SystemInterface: Sync + Send + 'static { + /// Type for local system sockets. + type LocalSocket: Sync + Send + Sized + Hash + PartialEq + Eq + Clone; + + /// Type for local system interfaces. + type LocalInterface: Sync + Send + Sized + Hash + PartialEq + Eq + Clone; + /// Node is up and ready for operation. fn event_node_is_up(&self); @@ -47,28 +50,35 @@ pub trait SystemInterface: Sync + Send { /// VL1 core generated a security warning. fn event_security_warning(&self, warning: &str); + /// Check a local socket for validity. + /// + /// This could return false if the socket's interface no longer exists, its port has been + /// unbound, etc. + fn local_socket_is_valid(&self, socket: &Self::LocalSocket) -> bool; + /// Load this node's identity from the data store. - fn load_node_identity(&self) -> Option>; + fn load_node_identity(&self) -> Option; /// Save this node's identity. fn save_node_identity(&self, id: &Identity); /// Called to send a packet over the physical network (virtual -> physical). /// - /// This may return false if the send definitely failed, and may return true if the send - /// succeeded or may have succeeded (in the case of UDP and similar). + /// This may return false if the send definitely failed. Otherwise it should return true + /// which indicates possible success but with no guarantee (UDP semantics). /// - /// If local socket and/or local interface are None, the sending code should make its - /// own decision about what local socket or interface to use. It may send on a random - /// one, the best fit, or all at once. + /// If a local socket is specified the implementation should send from that socket or not + /// at all (returning false). If a local interface is specified the implementation should + /// send from all sockets on that interface. If neither is specified the packet may be + /// sent on all sockets or a random subset. /// - /// If packet TTL is non-zero it should be used to set the packet TTL for outgoing packets - /// for supported protocols such as UDP, but otherwise it can be ignored. It can also be - /// ignored if the platform does not support setting the TTL. - fn wire_send(&self, endpoint: &Endpoint, local_socket: Option, local_interface: Option, data: &[&[u8]], packet_ttl: u8) -> bool; + /// For endpoint types that support a packet TTL, the implementation may set the TTL + /// if the 'ttl' parameter is not zero. If the parameter is zero or TTL setting is not + /// supported, the default TTL should be used. + fn wire_send(&self, endpoint: &Endpoint, local_socket: Option<&Self::LocalSocket>, local_interface: Option<&Self::LocalInterface>, data: &[&[u8]], packet_ttl: u8) -> bool; /// Called to check and see if a physical address should be used for ZeroTier traffic to a node. - fn check_path(&self, id: &Identity, endpoint: &Endpoint, local_socket: Option, local_interface: Option) -> bool; + fn check_path(&self, id: &Identity, endpoint: &Endpoint, local_socket: Option<&Self::LocalSocket>, local_interface: Option<&Self::LocalInterface>) -> bool; /// Called to look up any statically defined or memorized paths to known nodes. fn get_path_hints(&self, id: &Identity) -> Option, Option)>>; @@ -86,19 +96,19 @@ pub trait SystemInterface: Sync + Send { /// /// This is implemented by Switch in VL2. It's usually not used outside of VL2 in the core but /// it could also be implemented for testing or "off label" use of VL1 to carry different protocols. -pub trait InnerProtocolInterface: Sync + Send { +pub trait InnerProtocolInterface: Sync + Send + 'static { /// Handle a packet, returning true if it was handled by the next layer. /// /// Do not attempt to handle OK or ERROR. Instead implement handle_ok() and handle_error(). /// The return values of these must follow the same semantic of returning true if the message /// was handled. - fn handle_packet(&self, peer: &Peer, source_path: &Arc, forward_secrecy: bool, extended_authentication: bool, verb: u8, payload: &Buffer<{ PACKET_SIZE_MAX }>) -> bool; + fn handle_packet(&self, peer: &Peer, source_path: &Path, forward_secrecy: bool, extended_authentication: bool, verb: u8, payload: &PacketBuffer) -> bool; /// Handle errors, returning true if the error was recognized. - fn handle_error(&self, peer: &Peer, source_path: &Arc, forward_secrecy: bool, extended_authentication: bool, in_re_verb: u8, in_re_message_id: u64, error_code: u8, payload: &Buffer<{ PACKET_SIZE_MAX }>, cursor: &mut usize) -> bool; + fn handle_error(&self, peer: &Peer, source_path: &Path, forward_secrecy: bool, extended_authentication: bool, in_re_verb: u8, in_re_message_id: u64, error_code: u8, payload: &PacketBuffer, cursor: &mut usize) -> bool; /// Handle an OK, returing true if the OK was recognized. - fn handle_ok(&self, peer: &Peer, source_path: &Arc, forward_secrecy: bool, extended_authentication: bool, in_re_verb: u8, in_re_message_id: u64, payload: &Buffer<{ PACKET_SIZE_MAX }>, cursor: &mut usize) -> bool; + fn handle_ok(&self, peer: &Peer, source_path: &Path, forward_secrecy: bool, extended_authentication: bool, in_re_verb: u8, in_re_message_id: u64, payload: &PacketBuffer, cursor: &mut usize) -> bool; /// Check if this remote peer has a trust relationship with this node. /// @@ -107,39 +117,26 @@ pub trait InnerProtocolInterface: Sync + Send { fn has_trust_relationship(&self, id: &Identity) -> bool; } -/// Trait for objects that are serviced in the background loop (the actual loop is external). -pub(crate) trait BackgroundServicable { - /// How often in milliseconds to call service(). - const SERVICE_INTERVAL_MS: i64; - - /// Service object and return true if the object should be retained (if applicable). - fn service(&self, si: &SI, node: &Node, time_ticks: i64) -> bool; -} - /// How often to check the root cluster definitions against the root list and update. const ROOT_SYNC_INTERVAL_MS: i64 = 1000; -lazy_static! { - static ref BACKGROUND_TASK_INTERVAL: Duration = Duration::from_millis((ROOT_SYNC_INTERVAL_MS.min(WhoisQueue::SERVICE_INTERVAL_MS).min(Path::SERVICE_INTERVAL_MS).min(Peer::SERVICE_INTERVAL_MS) as u64) / 2); -} - #[derive(Default)] struct BackgroundTaskIntervals { - whois: IntervalGate<{ WhoisQueue::SERVICE_INTERVAL_MS }>, - paths: IntervalGate<{ Path::SERVICE_INTERVAL_MS }>, - peers: IntervalGate<{ Peer::SERVICE_INTERVAL_MS }>, + whois: IntervalGate<{ crate::vl1::whoisqueue::SERVICE_INTERVAL_MS }>, + paths: IntervalGate<{ crate::vl1::path::SERVICE_INTERVAL_MS }>, + peers: IntervalGate<{ crate::vl1::peer::SERVICE_INTERVAL_MS }>, root_sync: IntervalGate, root_hello: IntervalGate, } -struct RootInfo { - roots: HashMap, Vec>, +struct RootInfo { + roots: HashMap>, Vec>, sets: HashMap, sets_modified: bool, } /// A VL1 global P2P network node. -pub struct Node { +pub struct Node { /// A random ID generated to identify this particular running instance. pub instance_id: u64, @@ -150,16 +147,16 @@ pub struct Node { intervals: Mutex, /// Canonicalized network paths, held as Weak<> to be automatically cleaned when no longer in use. - paths: DashMap<(u64, u64), Weak>, + paths: DashMap>>>>, /// Peers with which we are currently communicating. - peers: DashMap>, + peers: DashMap>>, /// This node's trusted roots, sorted in ascending order of quality/preference, and cluster definitions. - roots: Mutex, + roots: Mutex>, /// Current best root. - best_root: RwLock>>, + best_root: RwLock>>>, /// Identity lookup queue, also holds packets waiting on a lookup. whois: WhoisQueue, @@ -168,12 +165,12 @@ pub struct Node { buffer_pool: PacketBufferPool, } -impl Node { +impl Node { /// Create a new Node. - pub fn new(si: &SI, auto_generate_identity: bool) -> Result { + pub fn new(si: &SI, auto_generate_identity: bool) -> Result { let mut id = { - let id_str = si.load_node_identity(); - if id_str.is_none() { + let id = si.load_node_identity(); + if id.is_none() { if !auto_generate_identity { return Err(InvalidParameterError("no identity found and auto-generate not enabled")); } else { @@ -182,13 +179,7 @@ impl Node { id } } else { - let id_str = String::from_utf8_lossy(id_str.as_ref().unwrap().as_slice()); - let id = Identity::from_str(id_str.as_ref().trim()); - if id.is_err() { - return Err(InvalidParameterError("invalid identity")); - } else { - id.unwrap() - } + id.unwrap() } }; @@ -216,31 +207,21 @@ impl Node { /// Get a packet buffer that will automatically check itself back into the pool on drop. #[inline(always)] - pub fn get_packet_buffer(&self) -> PacketBuffer { + pub fn get_packet_buffer(&self) -> PooledPacketBuffer { self.buffer_pool.get() } /// Get a peer by address. #[inline(always)] - pub fn peer(&self, a: Address) -> Option> { + pub fn peer(&self, a: Address) -> Option>> { self.peers.get(&a).map(|peer| peer.value().clone()) } - /// Get all peers currently in the peer cache. - pub fn peers(&self) -> Vec> { - let mut v: Vec> = Vec::new(); - v.reserve(self.peers.len()); - for p in self.peers.iter() { - v.push(p.value().clone()); - } - v - } - /// Run background tasks and return desired delay until next call in milliseconds. /// /// This should only be called periodically from a single thread, but that thread can be /// different each time. Calling it concurrently won't crash but won't accomplish anything. - pub fn do_background_tasks(&self, si: &SI) -> Duration { + pub fn do_background_tasks(&self, si: &SI) -> Duration { let mut intervals = self.intervals.lock(); let tt = si.time_ticks(); @@ -270,7 +251,7 @@ impl Node { let _ = self .peers .entry(m.identity.address) - .or_try_insert_with(|| Peer::new(&self.identity, m.identity.clone(), tt).map_or(Err(crate::error::UnexpectedError), |new_root| Ok(Arc::new(new_root)))) + .or_try_insert_with(|| Peer::::new(&self.identity, m.identity.clone(), si.time_clock()).map_or(Err(crate::error::UnexpectedError), |new_root| Ok(Arc::new(new_root)))) .and_then(|root_peer_entry| { let rp = root_peer_entry.value(); if rp.identity.eq(&m.identity) { @@ -305,7 +286,7 @@ impl Node { // The best root is the one that has replied to a HELLO most recently. Since we send HELLOs in unison // this is a proxy for latency and also causes roots that fail to reply to drop out quickly. let mut latest_hello_reply = 0; - let mut best: Option<&Arc> = None; + let mut best: Option<&Arc>> = None; for (r, _) in roots.iter() { let t = r.last_hello_reply_time_ticks.load(Ordering::Relaxed); if t >= latest_hello_reply { @@ -325,25 +306,40 @@ impl Node { } if intervals.paths.gate(tt) { - self.paths.retain(|_, path| path.upgrade().map_or(false, |p| p.service(si, self, tt))); + // Service all paths, removing expired or invalid ones. + self.paths.retain(|_, pbs| { + let mut expired_paths = Vec::new(); + for (ls, path) in pbs.read().iter() { + if !si.local_socket_is_valid(ls) || !path.service(si, self, tt) { + expired_paths.push(Arc::as_ptr(path)); + } + } + if expired_paths.is_empty() { + true + } else { + let mut pbs_w = pbs.write(); + pbs_w.retain(|_, path| !expired_paths.contains(&Arc::as_ptr(path))); + !pbs_w.is_empty() + } + }) } if intervals.whois.gate(tt) { let _ = self.whois.service(si, self, tt); } - *BACKGROUND_TASK_INTERVAL + Duration::from_millis((ROOT_SYNC_INTERVAL_MS.min(crate::vl1::whoisqueue::SERVICE_INTERVAL_MS).min(crate::vl1::path::SERVICE_INTERVAL_MS).min(crate::vl1::peer::SERVICE_INTERVAL_MS) as u64) / 2) } /// Called when a packet is received on the physical wire. - pub fn wire_receive(&self, si: &SI, ph: &PH, source_endpoint: &Endpoint, source_local_socket: Option, source_local_interface: Option, mut data: PacketBuffer) { + pub fn wire_receive(&self, si: &SI, ph: &PH, source_endpoint: &Endpoint, source_local_socket: &SI::LocalSocket, source_local_interface: &SI::LocalInterface, mut data: PooledPacketBuffer) { if let Ok(fragment_header) = data.struct_mut_at::(0) { - if let Some(dest) = Address::from_bytes(&fragment_header.dest) { + if let Some(dest) = Address::from_bytes_fixed(&fragment_header.dest) { let time_ticks = si.time_ticks(); if dest == self.identity.address { - // Handle packets (seemingly) addressed to this node. + // Handle packets addressed to this node. - let path = self.canonical_path(source_endpoint, source_local_socket, source_local_interface); + let path = self.canonical_path(source_endpoint, source_local_socket, source_local_interface, time_ticks); path.log_receive_anything(time_ticks); if fragment_header.is_fragment() { @@ -405,12 +401,12 @@ impl Node { /// Get the current best root peer that we should use for WHOIS, relaying, etc. #[inline(always)] - pub fn root(&self) -> Option> { + pub fn root(&self) -> Option>> { self.best_root.read().clone() } /// Return true if a peer is a root. - pub fn is_peer_root(&self, peer: &Peer) -> bool { + pub fn is_peer_root(&self, peer: &Peer) -> bool { self.roots.lock().roots.contains_key(peer) } @@ -426,37 +422,41 @@ impl Node { /// This returns true if the new root set was accepted and false otherwise. pub fn add_update_root_set(&self, rs: RootSet) -> bool { let mut roots = self.roots.lock(); - let entry = roots.sets.get_mut(&rs.name); - if entry.is_some() { - let old_rs = entry.unwrap(); - if rs.should_replace(old_rs) { - *old_rs = rs; + if let Some(entry) = roots.sets.get_mut(&rs.name) { + if rs.should_replace(entry) { + *entry = rs; roots.sets_modified = true; - true - } else { - false + return true; } } else if rs.verify() { roots.sets.insert(rs.name.clone(), rs); roots.sets_modified = true; - true - } else { - false + return true; } + return false; } /// Get the canonical Path object for a given endpoint and local socket information. /// /// This is a canonicalizing function that returns a unique path object for every tuple /// of endpoint, local socket, and local interface. - pub fn canonical_path(&self, ep: &Endpoint, local_socket: Option, local_interface: Option) -> Arc { - let mut path_entry = self.paths.entry(Path::local_lookup_key(ep, local_socket, local_interface)).or_default(); - if let Some(path) = path_entry.value().upgrade() { - path - } else { - let p = Arc::new(Path::new(ep.clone(), local_socket, local_interface)); - *path_entry.value_mut() = Arc::downgrade(&p); - p + pub fn canonical_path(&self, ep: &Endpoint, local_socket: &SI::LocalSocket, local_interface: &SI::LocalInterface, time_ticks: i64) -> Arc> { + // It's faster to do a read only lookup first since most of the time this will succeed. The second + // version below this only gets invoked if it's a new path. + if let Some(path) = self.paths.get(ep) { + if let Some(path) = path.value().read().get(local_socket) { + return path.clone(); + } } + + return self + .paths + .entry(ep.clone()) + .or_insert_with(|| parking_lot::RwLock::new(HashMap::with_capacity(2))) + .value_mut() + .write() + .entry(local_socket.clone()) + .or_insert_with(|| Arc::new(Path::new(ep.clone(), local_socket.clone(), local_interface.clone(), time_ticks))) + .clone(); } } diff --git a/zerotier-network-hypervisor/src/vl1/path.rs b/zerotier-network-hypervisor/src/vl1/path.rs index 31ad7af36..2a3e82cee 100644 --- a/zerotier-network-hypervisor/src/vl1/path.rs +++ b/zerotier-network-hypervisor/src/vl1/path.rs @@ -7,111 +7,54 @@ */ use std::collections::HashMap; -use std::hash::{Hash, Hasher}; -use std::num::NonZeroI64; use std::sync::atomic::{AtomicI64, Ordering}; -use lazy_static::lazy_static; -use metrohash::MetroHash128; use parking_lot::Mutex; -use zerotier_core_crypto::random; use crate::util::*; +use crate::vl1::endpoint::Endpoint; use crate::vl1::fragmentedpacket::FragmentedPacket; use crate::vl1::node::*; use crate::vl1::protocol::*; -use crate::vl1::{endpoint, Endpoint}; -use crate::PacketBuffer; -lazy_static! { - static ref METROHASH_SEED: u64 = random::next_u64_secure(); -} +pub(crate) const SERVICE_INTERVAL_MS: i64 = PATH_KEEPALIVE_INTERVAL; /// A remote endpoint paired with a local socket and a local interface. /// These are maintained in Node and canonicalized so that all unique paths have /// one and only one unique path object. That enables statistics to be tracked /// for them and uniform application of things like keepalives. -pub struct Path { +pub struct Path { pub endpoint: Endpoint, - pub local_socket: Option, - pub local_interface: Option, - last_send_time_ticks: AtomicI64, - last_receive_time_ticks: AtomicI64, + pub local_socket: SI::LocalSocket, + pub local_interface: SI::LocalInterface, + pub(crate) last_send_time_ticks: AtomicI64, + pub(crate) last_receive_time_ticks: AtomicI64, + pub(crate) create_time_ticks: i64, fragmented_packets: Mutex>, } -impl Path { - /// Get a 128-bit key to look up this endpoint in the local node path map. - pub(crate) fn local_lookup_key(endpoint: &Endpoint, local_socket: Option, local_interface: Option) -> (u64, u64) { - let mut h = MetroHash128::with_seed(*METROHASH_SEED); - h.write_u64(local_socket.map_or(0, |s| s.get() as u64)); - h.write_u64(local_interface.map_or(0, |s| s.get() as u64)); - match endpoint { - Endpoint::Nil => h.write_u8(endpoint::TYPE_NIL), - Endpoint::ZeroTier(_, fingerprint) => { - h.write_u8(endpoint::TYPE_ZEROTIER); - h.write(fingerprint); - } - Endpoint::Ethernet(m) => { - h.write_u8(endpoint::TYPE_ETHERNET); - h.write_u64(m.to_u64()); - } - Endpoint::WifiDirect(m) => { - h.write_u8(endpoint::TYPE_WIFIDIRECT); - h.write_u64(m.to_u64()); - } - Endpoint::Bluetooth(m) => { - h.write_u8(endpoint::TYPE_BLUETOOTH); - h.write_u64(m.to_u64()); - } - Endpoint::Ip(ip) => { - h.write_u8(endpoint::TYPE_IP); - h.write(ip.ip_bytes()); - } - Endpoint::IpUdp(ip) => { - h.write_u8(endpoint::TYPE_IPUDP); - ip.hash(&mut h); - } - Endpoint::IpTcp(ip) => { - h.write_u8(endpoint::TYPE_IPTCP); - ip.hash(&mut h); - } - Endpoint::Http(s) => { - h.write_u8(endpoint::TYPE_HTTP); - h.write(s.as_bytes()); - } - Endpoint::WebRTC(b) => { - h.write_u8(endpoint::TYPE_WEBRTC); - h.write(b.as_slice()); - } - Endpoint::ZeroTierEncap(_, fingerprint) => { - h.write_u8(endpoint::TYPE_ZEROTIER_ENCAP); - h.write(fingerprint); - } - } - h.finish128() - } - - pub fn new(endpoint: Endpoint, local_socket: Option, local_interface: Option) -> Self { +impl Path { + pub fn new(endpoint: Endpoint, local_socket: SI::LocalSocket, local_interface: SI::LocalInterface, time_ticks: i64) -> Self { Self { endpoint, local_socket, local_interface, last_send_time_ticks: AtomicI64::new(0), last_receive_time_ticks: AtomicI64::new(0), + create_time_ticks: time_ticks, fragmented_packets: Mutex::new(HashMap::with_capacity_and_hasher(4, U64NoOpHasher::new())), } } /// Receive a fragment and return a FragmentedPacket if the entire packet was assembled. /// This returns None if more fragments are needed to assemble the packet. - pub(crate) fn receive_fragment(&self, packet_id: u64, fragment_no: u8, fragment_expecting_count: u8, packet: PacketBuffer, time_ticks: i64) -> Option { + pub(crate) fn receive_fragment(&self, packet_id: u64, fragment_no: u8, fragment_expecting_count: u8, packet: PooledPacketBuffer, time_ticks: i64) -> Option { let mut fp = self.fragmented_packets.lock(); // 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 > PACKET_FRAGMENT_MAX_INBOUND_PACKETS_PER_PATH { + if fps > packet_constants::FRAGMENT_MAX_INBOUND_PACKETS_PER_PATH { let mut entries: Vec<(i64, u64)> = Vec::new(); entries.reserve(fps); for f in fp.iter() { @@ -130,7 +73,6 @@ impl Path { } } - /// Called when any packet is received. #[inline(always)] pub(crate) fn log_receive_anything(&self, time_ticks: i64) { self.last_receive_time_ticks.store(time_ticks, Ordering::Relaxed); @@ -140,17 +82,19 @@ impl Path { pub(crate) fn log_send_anything(&self, time_ticks: i64) { self.last_send_time_ticks.store(time_ticks, Ordering::Relaxed); } -} -impl BackgroundServicable for Path { - const SERVICE_INTERVAL_MS: i64 = PATH_KEEPALIVE_INTERVAL; - - fn service(&self, si: &SI, _: &Node, time_ticks: i64) -> bool { - self.fragmented_packets.lock().retain(|_, frag| (time_ticks - frag.ts_ticks) < PACKET_FRAGMENT_EXPIRATION); - if (time_ticks - self.last_send_time_ticks.load(Ordering::Relaxed)) >= PATH_KEEPALIVE_INTERVAL { - self.last_send_time_ticks.store(time_ticks, Ordering::Relaxed); - si.wire_send(&self.endpoint, self.local_socket, self.local_interface, &[&ZEROES[..1]], 0); + pub(crate) fn service(&self, si: &SI, _: &Node, time_ticks: i64) -> bool { + self.fragmented_packets.lock().retain(|_, frag| (time_ticks - frag.ts_ticks) < packet_constants::FRAGMENT_EXPIRATION); + if (time_ticks - self.last_receive_time_ticks.load(Ordering::Relaxed)) < PATH_EXPIRATION_TIME { + if (time_ticks - self.last_send_time_ticks.load(Ordering::Relaxed)) >= PATH_KEEPALIVE_INTERVAL { + self.last_send_time_ticks.store(time_ticks, Ordering::Relaxed); + si.wire_send(&self.endpoint, Some(&self.local_socket), Some(&self.local_interface), &[&ZEROES[..1]], 0); + } + true + } else if (time_ticks - self.create_time_ticks) < PATH_EXPIRATION_TIME { + true + } else { + false } - true } } diff --git a/zerotier-network-hypervisor/src/vl1/peer.rs b/zerotier-network-hypervisor/src/vl1/peer.rs index 0b4c057e4..0f9717b1d 100644 --- a/zerotier-network-hypervisor/src/vl1/peer.rs +++ b/zerotier-network-hypervisor/src/vl1/peer.rs @@ -6,14 +6,12 @@ * https://www.zerotier.com/ */ -use std::convert::TryInto; use std::hash::{Hash, Hasher}; use std::mem::MaybeUninit; -use std::num::NonZeroI64; use std::sync::atomic::{AtomicI64, AtomicU64, AtomicU8, Ordering}; -use std::sync::Arc; +use std::sync::{Arc, Weak}; -use parking_lot::Mutex; +use parking_lot::{Mutex, RwLock}; use zerotier_core_crypto::aes_gmac_siv::AesCtr; use zerotier_core_crypto::hash::*; @@ -22,23 +20,25 @@ use zerotier_core_crypto::random::{get_bytes_secure, next_u64_secure}; use zerotier_core_crypto::salsa::Salsa; use zerotier_core_crypto::secret::Secret; -use crate::util::buffer::Buffer; use crate::util::byte_array_range; use crate::util::marshalable::Marshalable; -use crate::vl1::identity::{IDENTITY_ALGORITHM_ALL, IDENTITY_ALGORITHM_X25519}; use crate::vl1::node::*; use crate::vl1::protocol::*; use crate::vl1::symmetricsecret::{EphemeralSymmetricSecret, SymmetricSecret}; use crate::vl1::{Dictionary, Endpoint, Identity, Path}; -use crate::{PacketBuffer, VERSION_MAJOR, VERSION_MINOR, VERSION_PROTO, VERSION_REVISION}; +use crate::{VERSION_MAJOR, VERSION_MINOR, VERSION_REVISION}; + +pub(crate) const SERVICE_INTERVAL_MS: i64 = security_constants::EPHEMERAL_SECRET_REKEY_AFTER_TIME / 10; + +struct PeerPath { + path: Weak>, + last_receive_time_ticks: i64, +} /// A remote peer known to this node. /// -/// NOTE: this implements PartialEq/Eq and Hash in terms of the pointer identity of -/// the structure. This means two peers are equal only if they are the same instance in -/// memory. This is done because they are only stored in an Arc<> internally and we want -/// to use these as efficient hash map keys. -pub struct Peer { +/// Equality and hashing is implemented in terms of the identity. +pub struct Peer { // This peer's identity. pub(crate) identity: Identity, @@ -46,22 +46,16 @@ pub struct Peer { identity_symmetric_key: SymmetricSecret, // Latest ephemeral secret or None if not yet negotiated. - ephemeral_symmetric_key: Mutex>>, + ephemeral_symmetric_key: RwLock>, // Paths sorted in descending order of quality / preference. - paths: Mutex>>, + paths: Mutex>>, // Statistics and times of events. - create_time_ticks: i64, pub(crate) last_send_time_ticks: AtomicI64, pub(crate) last_receive_time_ticks: AtomicI64, pub(crate) last_hello_reply_time_ticks: AtomicI64, - last_forward_time_ticks: AtomicI64, - total_bytes_sent: AtomicU64, - total_bytes_sent_indirect: AtomicU64, - total_bytes_received: AtomicU64, - total_bytes_received_indirect: AtomicU64, - total_bytes_forwarded: AtomicU64, + pub(crate) last_forward_time_ticks: AtomicI64, // Counter for assigning sequential message IDs. message_id_counter: AtomicU64, @@ -71,48 +65,38 @@ pub struct Peer { remote_protocol_version: AtomicU8, } -/// Derive per-packet key for Sals20/12 encryption (and Poly1305 authentication). -/// -/// This effectively adds a few additional bits of entropy to the IV from packet -/// characteristics such as its size and direction of communication. It also -/// effectively incorporates header information as AAD, since if the header info -/// is different the key will be wrong and MAC will fail. -/// -/// This is only used for Salsa/Poly modes. -fn salsa_derive_per_packet_key(key: &Secret<64>, header: &PacketHeader, packet_size: usize) -> Secret<64> { - let hb = header.as_bytes(); - let mut k = key.clone(); - for i in 0..18 { - k.0[i] ^= hb[i]; - } - k.0[18] ^= hb[HEADER_FLAGS_FIELD_INDEX] & HEADER_FLAGS_FIELD_MASK_HIDE_HOPS; - k.0[19] ^= (packet_size >> 8) as u8; - k.0[20] ^= packet_size as u8; - k -} - /// Create initialized instances of Salsa20/12 and Poly1305 for a packet. +/// This is deprecated and is not used with AES-GMAC-SIV. fn salsa_poly_create(secret: &SymmetricSecret, header: &PacketHeader, packet_size: usize) -> (Salsa<12>, Poly1305) { - let key = salsa_derive_per_packet_key(&secret.key, header, packet_size); - let mut salsa = Salsa::<12>::new(&key.0[0..32], &header.id); + // Create a per-packet key from the IV, source, destination, and packet size. + let mut key: Secret<32> = secret.key.first_n(); + let hb = header.as_bytes(); + for i in 0..18 { + key.0[i] ^= hb[i]; + } + key.0[18] ^= hb[packet_constants::FLAGS_FIELD_INDEX] & packet_constants::FLAGS_FIELD_MASK_HIDE_HOPS; + key.0[19] ^= (packet_size >> 8) as u8; + key.0[20] ^= packet_size as u8; + + let mut salsa = Salsa::<12>::new(&key.0, &header.id); 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. Returns message ID on success. -fn try_aead_decrypt(secret: &SymmetricSecret, packet_frag0_payload_bytes: &[u8], header: &PacketHeader, fragments: &[Option], payload: &mut Buffer) -> Option { +fn try_aead_decrypt(secret: &SymmetricSecret, packet_frag0_payload_bytes: &[u8], header: &PacketHeader, fragments: &[Option], payload: &mut PacketBuffer) -> Option { packet_frag0_payload_bytes.get(0).map_or(None, |verb| { match header.cipher() { - CIPHER_NOCRYPT_POLY1305 => { - if (verb & VERB_MASK) == VERB_VL1_HELLO { - let mut total_packet_len = packet_frag0_payload_bytes.len() + PACKET_HEADER_SIZE; + security_constants::CIPHER_NOCRYPT_POLY1305 => { + if (verb & packet_constants::VERB_MASK) == verbs::VL1_HELLO { + let mut total_packet_len = packet_frag0_payload_bytes.len() + packet_constants::HEADER_SIZE; for f in fragments.iter() { total_packet_len += f.as_ref().map_or(0, |f| f.len()); } 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 _ = f.as_ref().map(|f| f.as_bytes_starting_at(packet_constants::HEADER_SIZE).map(|f| payload.append_bytes(f))); } let (_, mut poly) = salsa_poly_create(secret, header, total_packet_len); poly.update(payload.as_bytes()); @@ -127,8 +111,8 @@ fn try_aead_decrypt(secret: &SymmetricSecret, packet_frag0_payload_bytes: &[u8], } } - CIPHER_SALSA2012_POLY1305 => { - let mut total_packet_len = packet_frag0_payload_bytes.len() + PACKET_HEADER_SIZE; + security_constants::CIPHER_SALSA2012_POLY1305 => { + let mut total_packet_len = packet_frag0_payload_bytes.len() + packet_constants::HEADER_SIZE; for f in fragments.iter() { total_packet_len += f.as_ref().map_or(0, |f| f.len()); } @@ -137,7 +121,7 @@ fn try_aead_decrypt(secret: &SymmetricSecret, packet_frag0_payload_bytes: &[u8], 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| { + f.as_bytes_starting_at(packet_constants::FRAGMENT_HEADER_SIZE).map(|f| { poly.update(f); let _ = payload.append_bytes_get_mut(f.len()).map(|b| salsa.crypt(f, b)); }) @@ -150,7 +134,7 @@ fn try_aead_decrypt(secret: &SymmetricSecret, packet_frag0_payload_bytes: &[u8], } } - CIPHER_AES_GMAC_SIV => { + security_constants::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()); @@ -159,7 +143,7 @@ fn try_aead_decrypt(secret: &SymmetricSecret, packet_frag0_payload_bytes: &[u8], 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| { + f.as_bytes_starting_at(packet_constants::FRAGMENT_HEADER_SIZE).map(|f| { let _ = payload.append_bytes_get_mut(f.len()).map(|b| aes.decrypt(f, b)); }) }); @@ -177,29 +161,35 @@ fn try_aead_decrypt(secret: &SymmetricSecret, packet_frag0_payload_bytes: &[u8], }) } -impl Peer { +impl Peer { /// Create a new peer. /// /// This only returns None if this_node_identity does not have its secrets or if some /// fatal error occurs performing key agreement between the two identities. - pub(crate) fn new(this_node_identity: &Identity, id: Identity, time_ticks: i64) -> Option { - this_node_identity.agree(&id).map(|static_secret| -> Peer { - Peer { + pub(crate) fn new(this_node_identity: &Identity, id: Identity, time_clock: i64) -> Option> { + this_node_identity.agree(&id).map(|static_secret| -> Self { + /* + * SECURITY NOTE: + * + * The message ID counter is initialized from the number of minutes since the Unix epoch (according to + * the current clock) in the most significant 26 bits followed by two zero bits followed by 36 random + * bits. + * + * The nature of AES-GMAC-SIV means that message ID duplication is not particularly dangerous, but we + * still want to avoid it. If the clock is at least marginally correct this will mean that message IDs + * will remain unique for over a hundred years. Message IDs are kept secret as well because they are + * encrypted along with a GMAC code to form an opaque 128-bit packet tag. + */ + Self { identity: id, identity_symmetric_key: SymmetricSecret::new(static_secret), - ephemeral_symmetric_key: Mutex::new(None), - paths: Mutex::new(Vec::new()), - create_time_ticks: time_ticks, + ephemeral_symmetric_key: RwLock::new(None), + paths: Mutex::new(Vec::with_capacity(4)), last_send_time_ticks: AtomicI64::new(0), last_receive_time_ticks: AtomicI64::new(0), - last_hello_reply_time_ticks: AtomicI64::new(0), last_forward_time_ticks: AtomicI64::new(0), - total_bytes_sent: AtomicU64::new(0), - total_bytes_sent_indirect: AtomicU64::new(0), - total_bytes_received: AtomicU64::new(0), - total_bytes_received_indirect: AtomicU64::new(0), - total_bytes_forwarded: AtomicU64::new(0), - message_id_counter: AtomicU64::new(next_u64_secure()), + last_hello_reply_time_ticks: AtomicI64::new(0), + message_id_counter: AtomicU64::new(((time_clock as u64) / 60000).wrapping_shl(38) ^ next_u64_secure().wrapping_shr(28)), remote_version: AtomicU64::new(0), remote_protocol_version: AtomicU8::new(0), } @@ -209,18 +199,20 @@ impl Peer { /// Get the next message ID for sending a message to this peer. #[inline(always)] pub(crate) fn next_message_id(&self) -> u64 { - self.message_id_counter.fetch_add(1, Ordering::Relaxed) + // SECURITY NOTE: uses the strictest memory ordering to avoid duplicate IDs on loose architectures like ARM64. + self.message_id_counter.fetch_add(1, Ordering::SeqCst) } /// 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, si: &SI, vi: &VI, time_ticks: i64, source_path: &Arc, header: &PacketHeader, frag0: &Buffer<{ PACKET_SIZE_MAX }>, fragments: &[Option]) { - let _ = frag0.as_bytes_starting_at(PACKET_VERB_INDEX).map(|packet_frag0_payload_bytes| { - let mut payload: Buffer = unsafe { Buffer::new_without_memzero() }; + pub(crate) fn receive(&self, node: &Node, si: &SI, vi: &VI, time_ticks: i64, source_path: &Arc>, header: &PacketHeader, frag0: &PacketBuffer, fragments: &[Option]) { + if let Ok(packet_frag0_payload_bytes) = frag0.as_bytes_starting_at(packet_constants::VERB_INDEX) { + let mut payload = unsafe { PacketBuffer::new_without_memzero() }; - let (forward_secrecy, mut message_id) = if let Some(ephemeral_secret) = self.ephemeral_symmetric_key.lock().clone() { + // First try decrypting and authenticating with an ephemeral secret if one is negotiated. + let (forward_secrecy, mut message_id) = if let Some(ephemeral_secret) = self.ephemeral_symmetric_key.read().as_ref() { if let Some(message_id) = try_aead_decrypt(&ephemeral_secret.secret, packet_frag0_payload_bytes, header, fragments, &mut payload) { // Decryption successful with ephemeral secret ephemeral_secret.decrypt_uses.fetch_add(1, Ordering::Relaxed); @@ -233,6 +225,8 @@ impl Peer { // There is no ephemeral secret negotiated (yet?). (false, 0) }; + + // Then try the permanent secret. if !forward_secrecy { if let Some(message_id2) = try_aead_decrypt(&self.identity_symmetric_key, packet_frag0_payload_bytes, header, fragments, &mut payload) { // Decryption successful with static secret. @@ -242,69 +236,72 @@ impl Peer { return; } } - debug_assert!(!payload.is_empty()); - // --------------------------------------------------------------- - // If we made it here it decrypted and passed authentication. - // --------------------------------------------------------------- + if let Ok(mut verb) = payload.u8_at(0) { + self.last_receive_time_ticks.store(time_ticks, Ordering::Relaxed); - 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 extended_authentication = (verb & packet_constants::VERB_FLAG_EXTENDED_AUTHENTICATION) != 0; + if extended_authentication { + if payload.len() >= SHA512_HASH_SIZE { + let actual_end_of_payload = payload.len() - SHA512_HASH_SIZE; + let mut hmac = HMACSHA512::new(self.identity_symmetric_key.packet_hmac_key.as_bytes()); + hmac.update(&message_id.to_ne_bytes()); + hmac.update(&payload.as_bytes()[..actual_end_of_payload]); + if !hmac.finish().eq(&payload.as_bytes()[actual_end_of_payload..]) { + return; + } + payload.set_size(actual_end_of_payload); + } else { + return; + } + } - let mut verb = payload.as_bytes()[0]; + // --------------------------------------------------------------- + // If we made it here it decrypted and passed authentication. + // --------------------------------------------------------------- - // If this flag is set, the end of the payload is a full HMAC-SHA384 authentication - // tag for much stronger authentication than is offered by the packet MAC. - 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 = hmac_sha384(self.static_secret.packet_hmac_key.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 & packet_constants::VERB_FLAG_COMPRESSED) != 0 { + let mut decompressed_payload: [u8; packet_constants::SIZE_MAX] = unsafe { MaybeUninit::uninit().assume_init() }; + decompressed_payload[0] = verb; + if let Ok(dlen) = lz4_flex::block::decompress_into(&payload.as_bytes()[1..], &mut decompressed_payload[1..]) { + payload.set_to(&decompressed_payload[..(dlen + 1)]); + } else { + return; + } + } + + let source_path_ptr = Arc::as_ptr(source_path); + for p in self.paths.lock().iter_mut() { + if Weak::as_ptr(&p.path) == source_path_ptr { + p.last_receive_time_ticks = time_ticks; + break; + } + } + + // 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 &= packet_constants::VERB_MASK; // mask off flags + if !vi.handle_packet(self, source_path, forward_secrecy, extended_authentication, verb, &payload) { + match verb { + //VERB_VL1_NOP => {} + verbs::VL1_HELLO => self.receive_hello(si, node, time_ticks, source_path, &payload), + verbs::VL1_ERROR => self.receive_error(si, vi, node, time_ticks, source_path, forward_secrecy, extended_authentication, &payload), + verbs::VL1_OK => self.receive_ok(si, vi, node, time_ticks, source_path, forward_secrecy, extended_authentication, &payload), + verbs::VL1_WHOIS => self.receive_whois(si, node, time_ticks, source_path, &payload), + verbs::VL1_RENDEZVOUS => self.receive_rendezvous(si, node, time_ticks, source_path, &payload), + verbs::VL1_ECHO => self.receive_echo(si, node, time_ticks, source_path, &payload), + verbs::VL1_PUSH_DIRECT_PATHS => self.receive_push_direct_paths(si, node, time_ticks, source_path, &payload), + verbs::VL1_USER_MESSAGE => self.receive_user_message(si, node, time_ticks, source_path, &payload), + _ => {} + } } } - - 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; // mask off flags - if !vi.handle_packet(self, source_path, forward_secrecy, extended_authentication, verb, &payload) { - match verb { - //VERB_VL1_NOP => {} - VERB_VL1_HELLO => self.receive_hello(si, node, time_ticks, source_path, &payload), - VERB_VL1_ERROR => self.receive_error(si, vi, node, time_ticks, source_path, forward_secrecy, extended_authentication, &payload), - VERB_VL1_OK => self.receive_ok(si, vi, node, time_ticks, source_path, forward_secrecy, extended_authentication, &payload), - VERB_VL1_WHOIS => self.receive_whois(si, node, time_ticks, source_path, &payload), - VERB_VL1_RENDEZVOUS => self.receive_rendezvous(si, node, time_ticks, source_path, &payload), - VERB_VL1_ECHO => self.receive_echo(si, node, time_ticks, source_path, &payload), - VERB_VL1_PUSH_DIRECT_PATHS => self.receive_push_direct_paths(si, node, time_ticks, source_path, &payload), - VERB_VL1_USER_MESSAGE => self.receive_user_message(si, node, time_ticks, source_path, &payload), - _ => {} - } - } - }); + } } - fn send_to_endpoint(&self, si: &SI, endpoint: &Endpoint, local_socket: Option, local_interface: Option, packet: &Buffer<{ PACKET_SIZE_MAX }>) -> bool { - debug_assert!(packet.len() <= PACKET_SIZE_MAX); - debug_assert!(packet.len() >= PACKET_SIZE_MIN); + fn send_to_endpoint(&self, si: &SI, endpoint: &Endpoint, local_socket: Option<&SI::LocalSocket>, local_interface: Option<&SI::LocalInterface>, packet: &PacketBuffer) -> bool { match endpoint { Endpoint::Ip(_) | Endpoint::IpUdp(_) | Endpoint::Ethernet(_) | Endpoint::Bluetooth(_) | Endpoint::WifiDirect(_) => { let packet_size = packet.len(); @@ -317,18 +314,18 @@ impl Peer { 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 fragment_count = (overrun_size / (UDP_DEFAULT_MTU - packet_constants::FRAGMENT_HEADER_SIZE) as u32) + (((overrun_size % (UDP_DEFAULT_MTU - packet_constants::FRAGMENT_HEADER_SIZE) as u32) != 0) as u32); + debug_assert!(fragment_count <= packet_constants::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, + id: *packet.bytes_fixed_at(0).unwrap(), + dest: *packet.bytes_fixed_at(packet_constants::DESTINATION_INDEX).unwrap(), + fragment_indicator: packet_constants::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); + let mut chunk_size = (packet_size - pos).min(UDP_DEFAULT_MTU - packet_constants::HEADER_SIZE); loop { header.total_and_fragment_no += 1; let next_pos = pos + chunk_size; @@ -337,7 +334,7 @@ impl Peer { } pos = next_pos; if pos < packet_size { - chunk_size = (packet_size - pos).min(UDP_DEFAULT_MTU - FRAGMENT_HEADER_SIZE); + chunk_size = (packet_size - pos).min(UDP_DEFAULT_MTU - packet_constants::HEADER_SIZE); } else { return true; } @@ -356,16 +353,14 @@ impl 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, si: &SI, node: &Node, time_ticks: i64, packet: &Buffer<{ PACKET_SIZE_MAX }>) -> bool { - self.path(node).map_or(false, |path| { - if self.send_to_endpoint(si, &path.endpoint, path.local_socket, path.local_interface, packet) { + pub(crate) fn send(&self, si: &SI, node: &Node, time_ticks: i64, packet: &PacketBuffer) -> bool { + if let Some(path) = self.path(node) { + if self.send_to_endpoint(si, &path.endpoint, Some(&path.local_socket), Some(&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 - } else { - false + return true; } - }) + } + return false; } /// Forward a packet to this peer. @@ -375,16 +370,14 @@ impl Peer { /// /// This doesn't fragment large packets since fragments are forwarded individually. /// Intermediates don't need to adjust fragmentation. - pub(crate) fn forward(&self, si: &SI, time_ticks: i64, packet: &Buffer<{ PACKET_SIZE_MAX }>) -> bool { - self.direct_path().map_or(false, |path| { - if si.wire_send(&path.endpoint, path.local_socket, path.local_interface, &[packet.as_bytes()], 0) { + pub(crate) fn forward(&self, si: &SI, time_ticks: i64, packet: &PacketBuffer) -> bool { + if let Some(path) = self.direct_path() { + if si.wire_send(&path.endpoint, Some(&path.local_socket), Some(&path.local_interface), &[packet.as_bytes()], 0) { self.last_forward_time_ticks.store(time_ticks, Ordering::Relaxed); - self.total_bytes_forwarded.fetch_add(packet.len() as u64, Ordering::Relaxed); - true - } else { - false + return true; } - }) + } + return false; } /// Send a HELLO to this peer. @@ -394,7 +387,7 @@ impl Peer { /// /// Unlike other messages HELLO is sent partially in the clear and always with the long-lived /// static identity key. - pub(crate) fn send_hello(&self, si: &SI, node: &Node, explicit_endpoint: Option<&Endpoint>) -> bool { + pub(crate) fn send_hello(&self, si: &SI, node: &Node, explicit_endpoint: Option<&Endpoint>) -> bool { let mut path = None; let destination = explicit_endpoint.map_or_else( || { @@ -410,7 +403,7 @@ impl Peer { } let destination = destination.unwrap(); - let mut packet: Buffer = unsafe { Buffer::new_without_memzero() }; + let mut packet = PacketBuffer::new(); let time_ticks = si.time_ticks(); let message_id = self.next_message_id(); @@ -419,35 +412,31 @@ impl Peer { 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.identity.address.to_bytes(); - packet_header.flags_cipher_hops = CIPHER_NOCRYPT_POLY1305; + packet_header.flags_cipher_hops = security_constants::CIPHER_NOCRYPT_POLY1305; } { let hello_fixed_headers: &mut message_component_structs::HelloFixedHeaderFields = packet.append_struct_get_mut().unwrap(); - hello_fixed_headers.verb = VERB_VL1_HELLO | VERB_FLAG_EXTENDED_AUTHENTICATION; - hello_fixed_headers.version_proto = VERSION_PROTO; + hello_fixed_headers.verb = verbs::VL1_HELLO | packet_constants::VERB_FLAG_EXTENDED_AUTHENTICATION; + hello_fixed_headers.version_proto = PROTOCOL_VERSION; 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_bytes(); hello_fixed_headers.timestamp = (time_ticks as u64).to_be_bytes(); } - assert!(self.identity.marshal_with_options(&mut packet, IDENTITY_ALGORITHM_ALL, false).is_ok()); - if self.identity.algorithms() == IDENTITY_ALGORITHM_X25519 { - // LEGACY: append an extra zero when marshaling identities containing only x25519 keys. - // See comments in Identity::marshal(). This can go away eventually. - assert!(packet.append_u8(0).is_ok()); - } + // Full identity of the node establishing the session. + assert!(self.identity.marshal_with_options(&mut packet, Identity::ALGORITHM_ALL, false).is_ok()); - // 8 reserved bytes, must be zero for legacy compatibility. + // 8 reserved bytes, must be zero for compatibility with old nodes. assert!(packet.append_padding(0, 8).is_ok()); // Generate a 12-byte nonce for the private section of HELLO. let mut nonce = get_bytes_secure::<12>(); - // 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. Newer nodes will treat this - // as part of a 12-byte nonce and otherwise ignore it. These bytes will be random. + // LEGACY: create a 16-bit encrypted field that specifies zero "moons." Current nodes ignore this + // and treat it as part of the random AES-CTR nonce, but old versions need it to parse the packet + // correctly. let mut salsa_iv = message_id.to_ne_bytes(); salsa_iv[7] &= 0xf8; Salsa::<12>::new(&self.identity_symmetric_key.key.0[0..32], &salsa_iv).crypt(&[0_u8, 0_u8], &mut nonce[8..10]); @@ -455,12 +444,13 @@ impl Peer { // Append 12-byte AES-CTR nonce. assert!(packet.append_bytes_fixed(&nonce).is_ok()); - // Add encrypted private field map. Plain AES-CTR is used with no MAC or SIV because - // the whole packet is authenticated with HMAC-SHA512. + // Add session meta-data, which is encrypted using plain AES-CTR. No authentication (AEAD) is needed + // because the whole packet is authenticated. Data in the session is not technically secret in a + // cryptographic sense but we encrypt it for privacy and as a defense in depth. let mut fields = Dictionary::new(); - fields.set_u64(SESSION_METADATA_INSTANCE_ID, node.instance_id); - fields.set_u64(SESSION_METADATA_CLOCK, si.time_clock() as u64); - fields.set_bytes(SESSION_METADATA_SENT_TO, destination.to_buffer::<{ Endpoint::MAX_MARSHAL_SIZE }>().unwrap().as_bytes().to_vec()); + fields.set_u64(session_metadata::INSTANCE_ID, node.instance_id); + fields.set_u64(session_metadata::CLOCK, si.time_clock() as u64); + fields.set_bytes(session_metadata::SENT_TO, destination.to_buffer::<{ Endpoint::MAX_MARSHAL_SIZE }>().unwrap().as_bytes().to_vec()); let fields = fields.to_bytes(); assert!(fields.len() <= 0xffff); // sanity check, should be impossible assert!(packet.append_u16(fields.len() as u16).is_ok()); // prefix with unencrypted size @@ -472,24 +462,23 @@ impl Peer { drop(aes); drop(fields); - // Add extended authentication at end of packet. + // Seal packet with HMAC-SHA512 extended authentication. let mut hmac = HMACSHA512::new(self.identity_symmetric_key.packet_hmac_key.as_bytes()); hmac.update(&message_id.to_ne_bytes()); - hmac.update(&packet.as_bytes()[PACKET_HEADER_SIZE..]); + hmac.update(&packet.as_bytes()[packet_constants::HEADER_SIZE..]); assert!(packet.append_bytes_fixed(&hmac.finish()).is_ok()); - // Set legacy poly1305 MAC in packet header. Newer nodes also check HMAC-SHA512 but older ones only use this. + // Set poly1305 in header, which is the only authentication for old nodes. let (_, mut poly) = salsa_poly_create(&self.identity_symmetric_key, packet.struct_at::(0).unwrap(), packet.len()); - poly.update(packet.as_bytes_starting_at(PACKET_HEADER_SIZE).unwrap()); - packet.as_mut()[HEADER_MAC_FIELD_INDEX..HEADER_MAC_FIELD_INDEX + 8].copy_from_slice(&poly.finish()[0..8]); + poly.update(packet.as_bytes_starting_at(packet_constants::HEADER_SIZE).unwrap()); + packet.as_mut()[packet_constants::MAC_FIELD_INDEX..packet_constants::MAC_FIELD_INDEX + 8].copy_from_slice(&poly.finish()[0..8]); self.last_send_time_ticks.store(time_ticks, Ordering::Relaxed); - self.total_bytes_sent.fetch_add(packet.len() as u64, Ordering::Relaxed); path.map_or_else( || self.send_to_endpoint(si, &destination, None, None, &packet), |p| { - if self.send_to_endpoint(si, &destination, p.local_socket, p.local_interface, &packet) { + if self.send_to_endpoint(si, &destination, Some(&p.local_socket), Some(&p.local_interface), &packet) { p.log_send_anything(time_ticks); true } else { @@ -499,13 +488,11 @@ impl Peer { ) } - #[inline(always)] - fn receive_hello(&self, si: &SI, node: &Node, time_ticks: i64, source_path: &Arc, payload: &Buffer) {} + fn receive_hello(&self, si: &SI, node: &Node, time_ticks: i64, source_path: &Arc>, payload: &PacketBuffer) {} - #[inline(always)] - fn receive_error(&self, si: &SI, ph: &PH, node: &Node, time_ticks: i64, source_path: &Arc, forward_secrecy: bool, extended_authentication: bool, payload: &Buffer) { - let mut cursor: usize = 0; - let _ = payload.read_struct::(&mut cursor).map(|error_header| { + fn receive_error(&self, si: &SI, ph: &PH, node: &Node, time_ticks: i64, source_path: &Arc>, forward_secrecy: bool, extended_authentication: bool, payload: &PacketBuffer) { + let mut cursor: usize = 1; + if let Ok(error_header) = payload.read_struct::(&mut cursor) { let in_re_message_id = u64::from_ne_bytes(error_header.in_re_message_id); let current_packet_id_counter = self.message_id_counter.load(Ordering::Relaxed); if current_packet_id_counter.wrapping_sub(in_re_message_id) <= PACKET_RESPONSE_COUNTER_DELTA_MAX { @@ -515,54 +502,59 @@ impl Peer { } } } - }); + } } - #[inline(always)] - fn receive_ok(&self, si: &SI, ph: &PH, node: &Node, time_ticks: i64, source_path: &Arc, forward_secrecy: bool, extended_authentication: bool, payload: &Buffer) { - let mut cursor: usize = 0; - let _ = payload.read_struct::(&mut cursor).map(|ok_header| { + fn receive_ok(&self, si: &SI, ph: &PH, node: &Node, time_ticks: i64, source_path: &Arc>, forward_secrecy: bool, extended_authentication: bool, payload: &PacketBuffer) { + let mut cursor: usize = 1; + if let Ok(ok_header) = payload.read_struct::(&mut cursor) { let in_re_message_id = u64::from_ne_bytes(ok_header.in_re_message_id); let current_packet_id_counter = self.message_id_counter.load(Ordering::Relaxed); if current_packet_id_counter.wrapping_sub(in_re_message_id) <= PACKET_RESPONSE_COUNTER_DELTA_MAX { match ok_header.in_re_verb { - VERB_VL1_HELLO => { + verbs::VL1_HELLO => { // TODO - self.last_hello_reply_time_ticks.store(time_ticks, Ordering::Relaxed); } - VERB_VL1_WHOIS => {} + verbs::VL1_WHOIS => {} _ => { ph.handle_ok(self, source_path, forward_secrecy, extended_authentication, ok_header.in_re_verb, in_re_message_id, payload, &mut cursor); } } } - }); + } } - #[inline(always)] - fn receive_whois(&self, si: &SI, node: &Node, time_ticks: i64, source_path: &Arc, payload: &Buffer<{ PACKET_SIZE_MAX }>) {} + fn receive_whois(&self, si: &SI, node: &Node, time_ticks: i64, source_path: &Arc>, payload: &PacketBuffer) {} - #[inline(always)] - fn receive_rendezvous(&self, si: &SI, node: &Node, time_ticks: i64, source_path: &Arc, payload: &Buffer<{ PACKET_SIZE_MAX }>) {} + fn receive_rendezvous(&self, si: &SI, node: &Node, time_ticks: i64, source_path: &Arc>, payload: &PacketBuffer) {} - #[inline(always)] - fn receive_echo(&self, si: &SI, node: &Node, time_ticks: i64, source_path: &Arc, payload: &Buffer<{ PACKET_SIZE_MAX }>) {} + fn receive_echo(&self, si: &SI, node: &Node, time_ticks: i64, source_path: &Arc>, payload: &PacketBuffer) {} - #[inline(always)] - fn receive_push_direct_paths(&self, si: &SI, node: &Node, time_ticks: i64, source_path: &Arc, payload: &Buffer<{ PACKET_SIZE_MAX }>) {} + fn receive_push_direct_paths(&self, si: &SI, node: &Node, time_ticks: i64, source_path: &Arc>, payload: &PacketBuffer) {} - #[inline(always)] - fn receive_user_message(&self, si: &SI, node: &Node, time_ticks: i64, source_path: &Arc, payload: &Buffer<{ PACKET_SIZE_MAX }>) {} + fn receive_user_message(&self, si: &SI, node: &Node, time_ticks: i64, source_path: &Arc>, payload: &PacketBuffer) {} /// Get current best path or None if there are no direct paths to this peer. - #[inline(always)] - pub fn direct_path(&self) -> Option> { - self.paths.lock().first().map(|p| p.clone()) + pub fn direct_path(&self) -> Option>> { + for p in self.paths.lock().iter() { + let pp = p.path.upgrade(); + if pp.is_some() { + return pp; + } + } + return None; } /// Get either the current best direct path or an indirect path. - pub fn path(&self, node: &Node) -> Option> { - self.direct_path().map_or_else(|| node.root().map_or(None, |root| root.direct_path().map_or(None, |bp| Some(bp))), |bp| Some(bp)) + pub fn path(&self, node: &Node) -> Option>> { + let direct_path = self.direct_path(); + if direct_path.is_some() { + return direct_path; + } + if let Some(root) = node.root() { + return root.direct_path(); + } + return None; } /// Get the remote version of this peer: major, minor, revision, and build. @@ -585,29 +577,32 @@ impl Peer { None } } + + pub(crate) fn service(&self, _: &SI, _: &Node, time_ticks: i64) -> bool { + let mut paths = self.paths.lock(); + if (time_ticks - self.last_receive_time_ticks.load(Ordering::Relaxed)) < PEER_EXPIRATION_TIME { + paths.retain(|p| ((time_ticks - p.last_receive_time_ticks) < PEER_EXPIRATION_TIME) && (p.path.strong_count() > 0)); + paths.sort_unstable_by(|a, b| a.last_receive_time_ticks.cmp(&b.last_receive_time_ticks).reverse()); + true + } else { + paths.clear(); + false + } + } } -impl PartialEq for Peer { +impl PartialEq for Peer { #[inline(always)] fn eq(&self, other: &Self) -> bool { - std::ptr::eq(self as *const Peer, other as *const Peer) + self.identity.eq(&other.identity) } } -impl Eq for Peer {} +impl Eq for Peer {} -impl Hash for Peer { +impl Hash for Peer { #[inline(always)] fn hash(&self, state: &mut H) { - state.write_usize((self as *const Peer) as usize) - } -} - -impl BackgroundServicable for Peer { - const SERVICE_INTERVAL_MS: i64 = EPHEMERAL_SECRET_REKEY_AFTER_TIME / 10; - - #[inline(always)] - fn service(&self, si: &SI, node: &Node, time_ticks: i64) -> bool { - true + self.identity.hash(state); } } diff --git a/zerotier-network-hypervisor/src/vl1/protocol.rs b/zerotier-network-hypervisor/src/vl1/protocol.rs index 1e6e79a5d..eb5430d14 100644 --- a/zerotier-network-hypervisor/src/vl1/protocol.rs +++ b/zerotier-network-hypervisor/src/vl1/protocol.rs @@ -9,18 +9,72 @@ use std::convert::TryFrom; use std::mem::MaybeUninit; -use crate::util::buffer::{Buffer, FlatBlob}; +use crate::util::buffer::Buffer; use crate::vl1::Address; -pub const VERB_VL1_NOP: u8 = 0x00; -pub const VERB_VL1_HELLO: u8 = 0x01; -pub const VERB_VL1_ERROR: u8 = 0x02; -pub const VERB_VL1_OK: u8 = 0x03; -pub const VERB_VL1_WHOIS: u8 = 0x04; -pub const VERB_VL1_RENDEZVOUS: u8 = 0x05; -pub const VERB_VL1_ECHO: u8 = 0x08; -pub const VERB_VL1_PUSH_DIRECT_PATHS: u8 = 0x10; -pub const VERB_VL1_USER_MESSAGE: u8 = 0x14; +/* + * Protocol versions + * + * 1 - 0.2.0 ... 0.2.5 + * 2 - 0.3.0 ... 0.4.5 + * + Added signature and originating peer to multicast frame + * + Double size of multicast frame bloom filter + * 3 - 0.5.0 ... 0.6.0 + * + Yet another multicast redesign + * + New crypto completely changes key agreement cipher + * 4 - 0.6.0 ... 1.0.6 + * + BREAKING CHANGE: New identity format based on hashcash design + * 5 - 1.1.0 ... 1.1.5 + * + Supports echo + * + Supports in-band world (root server definition) updates + * + Clustering! (Though this will work with protocol v4 clients.) + * + Otherwise backward compatible with protocol v4 + * 6 - 1.1.5 ... 1.1.10 + * + Network configuration format revisions including binary values + * 7 - 1.1.10 ... 1.1.17 + * + Introduce trusted paths for local SDN use + * 8 - 1.1.17 ... 1.2.0 + * + Multipart network configurations for large network configs + * + Tags and Capabilities + * + inline push of CertificateOfMembership deprecated + * 9 - 1.2.0 ... 1.2.14 + * 10 - 1.4.0 ... 1.4.6 + * + Contained early pre-alpha versions of multipath, which are deprecated + * 11 - 1.6.0 ... 2.0.0 + * + Supports and prefers AES-GMAC-SIV symmetric crypto, backported. + * + * 20 - 2.0.0 ... CURRENT + * + Forward secrecy with cryptographic ratchet! Finally!!! + * + New identity format including both x25519 and NIST P-521 keys. + * + AES-GMAC-SIV, a FIPS-compliant SIV construction using AES. + * + HELLO and OK(HELLO) include an extra HMAC to harden authentication + * + HELLO and OK(HELLO) use a dictionary for better extensibilit. + */ +pub const PROTOCOL_VERSION: u8 = 20; + +/// Buffer sized for ZeroTier packets. +pub type PacketBuffer = Buffer<{ packet_constants::SIZE_MAX }>; + +/// Factory type to supply to a new PacketBufferPool, used in PooledPacketBuffer and PacketBufferPool types. +pub type PacketBufferFactory = crate::util::buffer::PooledBufferFactory<{ crate::vl1::protocol::packet_constants::SIZE_MAX }>; + +/// Packet buffer checked out of pool, automatically returns on drop. +pub type PooledPacketBuffer = crate::util::pool::Pooled; + +/// Source for instances of PacketBuffer +pub type PacketBufferPool = crate::util::pool::Pool; + +pub mod verbs { + pub const VL1_NOP: u8 = 0x00; + pub const VL1_HELLO: u8 = 0x01; + pub const VL1_ERROR: u8 = 0x02; + pub const VL1_OK: u8 = 0x03; + pub const VL1_WHOIS: u8 = 0x04; + pub const VL1_RENDEZVOUS: u8 = 0x05; + pub const VL1_ECHO: u8 = 0x08; + pub const VL1_PUSH_DIRECT_PATHS: u8 = 0x10; + pub const VL1_USER_MESSAGE: u8 = 0x14; +} /// Default maximum payload size for UDP transport. /// @@ -29,39 +83,6 @@ pub const VERB_VL1_USER_MESSAGE: u8 = 0x14; /// two fragments. pub const UDP_DEFAULT_MTU: usize = 1432; -/// KBKDF usage label indicating a key used to HMAC packets for extended authentication. -pub const KBKDF_KEY_USAGE_LABEL_PACKET_HMAC: u8 = b'M'; - -/// KBKDF usage label for the first AES-GMAC-SIV key. -pub const KBKDF_KEY_USAGE_LABEL_AES_GMAC_SIV_K0: u8 = b'0'; - -/// KBKDF usage label for the second AES-GMAC-SIV key. -pub const KBKDF_KEY_USAGE_LABEL_AES_GMAC_SIV_K1: u8 = b'1'; - -/// KBKDF usage label for the private section of HELLOs. -pub const KBKDF_KEY_USAGE_LABEL_HELLO_PRIVATE_SECTION: u8 = b'h'; - -/// KBKDF usage label for the key used to advance the ratchet. -pub const KBKDF_KEY_USAGE_LABEL_EPHEMERAL_RATCHET_KEY: u8 = b'e'; - -/// Try to re-key ephemeral keys after this time. -pub const EPHEMERAL_SECRET_REKEY_AFTER_TIME: i64 = 300000; // 5 minutes - -/// Maximum number of times to use an ephemeral secret before trying to replace it. -pub const EPHEMERAL_SECRET_REKEY_AFTER_USES: usize = 536870912; // 1/4 the NIST/FIPS security bound of 2^31 - -/// Ephemeral secret reject after time. -pub const EPHEMERAL_SECRET_REJECT_AFTER_TIME: i64 = EPHEMERAL_SECRET_REKEY_AFTER_TIME * 2; - -/// Ephemeral secret reject after uses. -pub const EPHEMERAL_SECRET_REJECT_AFTER_USES: usize = 2147483648; // NIST/FIPS security bound - -pub const SESSION_METADATA_INSTANCE_ID: &'static str = "i"; -pub const SESSION_METADATA_CLOCK: &'static str = "t"; -pub const SESSION_METADATA_SENT_TO: &'static str = "d"; -pub const SESSION_METADATA_EPHEMERAL_CURRENT_SYMMETRIC_KEY_ID: &'static str = "e"; -pub const SESSION_METADATA_EPHEMERAL_PUBLIC_OFFER: &'static str = "E"; - /// Length of an address in bytes. pub const ADDRESS_SIZE: usize = 5; @@ -71,96 +92,133 @@ pub const ADDRESS_SIZE_STRING: usize = 10; /// Prefix indicating reserved addresses (that can't actually be addresses). pub const ADDRESS_RESERVED_PREFIX: u8 = 0xff; -/// Size of packet header that lies outside the encryption envelope. -pub const PACKET_HEADER_SIZE: usize = 27; +pub mod packet_constants { + /// Size of packet header that lies outside the encryption envelope. + pub const HEADER_SIZE: usize = 27; -/// Minimum packet, which is the header plus a verb. -pub const PACKET_SIZE_MIN: usize = PACKET_HEADER_SIZE + 1; + /// Maximum packet payload size including the verb/flags field. + /// + /// This is large enough to carry "jumbo MTU" packets. The exact + /// value is because 10005+27 == 10032 which is divisible by 16. This + /// improves memory layout and alignment when buffers are allocated. + /// This value could technically be increased but it would require a + /// protocol version bump and only new nodes would be able to accept + /// the new size. + pub const PAYLOAD_SIZE_MAX: usize = 10005; -/// Maximum size of an entire packet. -pub const PACKET_SIZE_MAX: usize = PACKET_HEADER_SIZE + PACKET_PAYLOAD_SIZE_MAX; + /// Minimum packet, which is the header plus a verb. + pub const SIZE_MIN: usize = HEADER_SIZE + 1; -/// Maximum packet payload size including the verb/flags field. -/// -/// This is large enough to carry "jumbo MTU" packets. The exact -/// value is because 10005+27 == 10032 which is divisible by 16. This -/// improves memory layout and alignment when buffers are allocated. -/// This value could technically be increased but it would require a -/// protocol version bump and only new nodes would be able to accept -/// the new size. -pub const PACKET_PAYLOAD_SIZE_MAX: usize = 10005; + /// Maximum size of an entire packet. + pub const SIZE_MAX: usize = HEADER_SIZE + PAYLOAD_SIZE_MAX; -/// Index of packet verb after header. -pub const PACKET_VERB_INDEX: usize = 27; + /// Index of packet verb after header. + pub const VERB_INDEX: usize = 27; -/// Index of destination in both fragment and full packet headers. -pub const PACKET_DESTINATION_INDEX: usize = 8; + /// Index of destination in both fragment and full packet headers. + pub const DESTINATION_INDEX: usize = 8; -/// Index of 8-byte MAC field in packet header. -pub const HEADER_MAC_FIELD_INDEX: usize = 19; + /// Index of 8-byte MAC field in packet header. + pub const MAC_FIELD_INDEX: usize = 19; -/// Mask to select cipher from header flags field. -pub const HEADER_FLAGS_FIELD_MASK_CIPHER: u8 = 0x30; + /// Mask to select cipher from header flags field. + pub const FLAGS_FIELD_MASK_CIPHER: u8 = 0x30; -/// Mask to select packet hops from header flags field. -pub const HEADER_FLAGS_FIELD_MASK_HOPS: u8 = 0x07; + /// Mask to select packet hops from header flags field. + pub const FLAGS_FIELD_MASK_HOPS: u8 = 0x07; -/// Mask to select packet hops from header flags field. -pub const HEADER_FLAGS_FIELD_MASK_HIDE_HOPS: u8 = 0xf8; + /// Mask to select packet hops from header flags field. + pub const FLAGS_FIELD_MASK_HIDE_HOPS: u8 = 0xf8; -/// Index of hops/flags field -pub const HEADER_FLAGS_FIELD_INDEX: usize = 18; + /// Index of hops/flags field + pub const FLAGS_FIELD_INDEX: usize = 18; -/// 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 = 0x00; + /// Minimum size of a fragment. + pub const FRAGMENT_SIZE_MIN: usize = 16; -/// 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; + /// Size of fragment header after which data begins. + pub const FRAGMENT_HEADER_SIZE: usize = 16; -/// Formerly 'NONE' which is deprecated; reserved for future use. -pub const CIPHER_RESERVED: u8 = 0x20; + /// Maximum allowed number of fragments. + pub const FRAGMENT_COUNT_MAX: usize = 8; -/// Packet is encrypted and authenticated with AES-GMAC-SIV (AES-256). -pub const CIPHER_AES_GMAC_SIV: u8 = 0x30; + /// Time after which an incomplete fragmented packet expires. + pub const FRAGMENT_EXPIRATION: i64 = 1500; -/// Header (outer) flag indicating that this packet has additional fragments. -pub const HEADER_FLAG_FRAGMENTED: u8 = 0x40; + /// 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; -/// Minimum size of a fragment. -pub const PACKET_FRAGMENT_SIZE_MIN: usize = 16; + /// Index of packet fragment indicator byte to detect fragments. + pub const FRAGMENT_INDICATOR_INDEX: usize = 13; -/// Size of fragment header after which data begins. -pub const FRAGMENT_HEADER_SIZE: usize = 16; + /// Byte found at FRAGMENT_INDICATOR_INDEX to indicate a fragment. + pub const FRAGMENT_INDICATOR: u8 = 0xff; -/// Maximum allowed number of fragments. -pub const PACKET_FRAGMENT_COUNT_MAX: usize = 8; + /// Verb (inner) flag indicating that the packet's payload (after the verb) is LZ4 compressed. + pub const VERB_FLAG_COMPRESSED: u8 = 0x80; -/// Time after which an incomplete fragmented packet expires. -pub const PACKET_FRAGMENT_EXPIRATION: i64 = 1500; + /// Verb (inner) flag indicating that payload is authenticated with HMAC-SHA384. + pub const VERB_FLAG_EXTENDED_AUTHENTICATION: u8 = 0x40; -/// 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 PACKET_FRAGMENT_MAX_INBOUND_PACKETS_PER_PATH: usize = 256; + /// Mask to get only the verb from the verb + verb flags byte. + pub const VERB_MASK: u8 = 0x1f; -/// Index of packet fragment indicator byte to detect fragments. -pub const PACKET_FRAGMENT_INDICATOR_INDEX: usize = 13; + /// Maximum number of verbs that the protocol can support. + pub const VERB_MAX_COUNT: usize = 32; -/// Byte found at FRAGMENT_INDICATOR_INDEX to indicate a fragment. -pub const PACKET_FRAGMENT_INDICATOR: u8 = 0xff; + /// Header (outer) flag indicating that this packet has additional fragments. + pub const HEADER_FLAG_FRAGMENTED: u8 = 0x40; +} -/// Verb (inner) flag indicating that the packet's payload (after the verb) is LZ4 compressed. -pub const VERB_FLAG_COMPRESSED: u8 = 0x80; +pub mod security_constants { + /// 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 = 0x00; -/// Verb (inner) flag indicating that payload is authenticated with HMAC-SHA384. -pub const VERB_FLAG_EXTENDED_AUTHENTICATION: u8 = 0x40; + /// 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; -/// Mask to get only the verb from the verb + verb flags byte. -pub const VERB_MASK: u8 = 0x1f; + /// Formerly 'NONE' which is deprecated; reserved for future use. + pub const CIPHER_RESERVED: u8 = 0x20; -/// Maximum number of verbs that the protocol can support. -pub const VERB_MAX_COUNT: usize = 32; + /// Packet is encrypted and authenticated with AES-GMAC-SIV (AES-256). + pub const CIPHER_AES_GMAC_SIV: u8 = 0x30; + + /// KBKDF usage label indicating a key used to HMAC packets for extended authentication. + pub const KBKDF_KEY_USAGE_LABEL_PACKET_HMAC: u8 = b'M'; + + /// KBKDF usage label for the first AES-GMAC-SIV key. + pub const KBKDF_KEY_USAGE_LABEL_AES_GMAC_SIV_K0: u8 = b'0'; + + /// KBKDF usage label for the second AES-GMAC-SIV key. + pub const KBKDF_KEY_USAGE_LABEL_AES_GMAC_SIV_K1: u8 = b'1'; + + /// KBKDF usage label for the private section of HELLOs. + pub const KBKDF_KEY_USAGE_LABEL_HELLO_PRIVATE_SECTION: u8 = b'h'; + + /// KBKDF usage label for the key used to advance the ratchet. + pub const KBKDF_KEY_USAGE_LABEL_EPHEMERAL_RATCHET_KEY: u8 = b'e'; + + /// Try to re-key ephemeral keys after this time. + pub const EPHEMERAL_SECRET_REKEY_AFTER_TIME: i64 = 300000; // 5 minutes + + /// Maximum number of times to use an ephemeral secret before trying to replace it. + pub const EPHEMERAL_SECRET_REKEY_AFTER_USES: usize = 536870912; // 1/4 the NIST/FIPS security bound of 2^31 + + /// Ephemeral secret reject after time. + pub const EPHEMERAL_SECRET_REJECT_AFTER_TIME: i64 = EPHEMERAL_SECRET_REKEY_AFTER_TIME * 2; + + /// Ephemeral secret reject after uses. + pub const EPHEMERAL_SECRET_REJECT_AFTER_USES: usize = 2147483648; // NIST/FIPS security bound +} + +pub mod session_metadata { + pub const INSTANCE_ID: &'static str = "i"; + pub const CLOCK: &'static str = "t"; + pub const SENT_TO: &'static str = "d"; +} /// Maximum number of packet hops allowed by the protocol. pub const PROTOCOL_MAX_HOPS: u8 = 7; @@ -168,7 +226,7 @@ pub const PROTOCOL_MAX_HOPS: u8 = 7; /// Maximum number of hops to allow. pub const FORWARD_MAX_HOPS: u8 = 3; -/// Maximum difference between current packet ID counter and OK/ERROR in-re packet ID. +/// Maximum difference between current message ID and OK/ERROR in-re message ID. pub const PACKET_RESPONSE_COUNTER_DELTA_MAX: u64 = 1024; /// Frequency for WHOIS retries @@ -183,9 +241,18 @@ pub const WHOIS_MAX_WAITING_PACKETS: usize = 64; /// Keepalive interval for paths in milliseconds. pub const PATH_KEEPALIVE_INTERVAL: i64 = 20000; +/// Path object expiration time in milliseconds since last receive. +pub const PATH_EXPIRATION_TIME: i64 = (PATH_KEEPALIVE_INTERVAL * 2) + 10000; + /// How often to send HELLOs to roots, which is more often than normal peers. pub const ROOT_HELLO_INTERVAL: i64 = PATH_KEEPALIVE_INTERVAL * 2; +/// How often to send HELLOs to regular peers. +pub const PEER_HELLO_INTERVAL_MAX: i64 = 300000; + +/// Timeout for path association with peers and for peers themselves. +pub const PEER_EXPIRATION_TIME: i64 = (PEER_HELLO_INTERVAL_MAX * 2) + 10000; + /// Proof of work difficulty (threshold) for identity generation. pub const IDENTITY_POW_THRESHOLD: u8 = 17; @@ -194,18 +261,18 @@ pub const IDENTITY_POW_THRESHOLD: u8 = 17; /// If this returns true the destination buffer will contain a compressed packet. If false is /// returned the contents of 'dest' are entirely undefined. This indicates that the data was not /// compressable or some other error occurred. -pub fn compress_packet(src: &[u8], dest: &mut Buffer) -> bool { - if src.len() > (PACKET_VERB_INDEX + 16) { +pub fn compress_packet(src: &[u8], dest: &mut Buffer) -> bool { + if src.len() > (packet_constants::VERB_INDEX + 16) { let compressed_data_size = { let d = unsafe { dest.entire_buffer_mut() }; - d[0..PACKET_VERB_INDEX].copy_from_slice(&src[0..PACKET_VERB_INDEX]); - d[PACKET_VERB_INDEX] = src[PACKET_VERB_INDEX] | VERB_FLAG_COMPRESSED; - lz4_flex::block::compress_into(&src[PACKET_VERB_INDEX + 1..], &mut d[PACKET_VERB_INDEX + 1..]) + d[..packet_constants::VERB_INDEX].copy_from_slice(&src[0..packet_constants::VERB_INDEX]); + d[packet_constants::VERB_INDEX] = src[packet_constants::VERB_INDEX] | packet_constants::VERB_FLAG_COMPRESSED; + lz4_flex::block::compress_into(&src[packet_constants::VERB_INDEX + 1..], &mut d[packet_constants::VERB_INDEX + 1..]) }; if compressed_data_size.is_ok() { let compressed_data_size = compressed_data_size.unwrap(); - if compressed_data_size > 0 && compressed_data_size < (src.len() - PACKET_VERB_INDEX) { - unsafe { dest.set_size_unchecked(PACKET_VERB_INDEX + 1 + compressed_data_size) }; + if compressed_data_size > 0 && compressed_data_size < (src.len() - packet_constants::VERB_INDEX) { + unsafe { dest.set_size_unchecked(packet_constants::VERB_INDEX + 1 + compressed_data_size) }; return true; } } @@ -217,6 +284,7 @@ pub fn compress_packet(src: &[u8], dest: &mut Buffer) -> bool { /// /// 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, Copy)] #[repr(C, packed)] pub struct PacketHeader { pub id: [u8; 8], @@ -226,35 +294,33 @@ pub struct PacketHeader { pub mac: [u8; 8], } -unsafe impl FlatBlob for PacketHeader {} - impl PacketHeader { #[inline(always)] pub fn cipher(&self) -> u8 { - self.flags_cipher_hops & HEADER_FLAGS_FIELD_MASK_CIPHER + self.flags_cipher_hops & packet_constants::FLAGS_FIELD_MASK_CIPHER } #[inline(always)] pub fn hops(&self) -> u8 { - self.flags_cipher_hops & HEADER_FLAGS_FIELD_MASK_HOPS + self.flags_cipher_hops & packet_constants::FLAGS_FIELD_MASK_HOPS } #[inline(always)] pub fn increment_hops(&mut self) -> u8 { let f = self.flags_cipher_hops; - let h = (f + 1) & HEADER_FLAGS_FIELD_MASK_HOPS; - self.flags_cipher_hops = (f & HEADER_FLAGS_FIELD_MASK_HIDE_HOPS) | h; + let h = (f + 1) & packet_constants::FLAGS_FIELD_MASK_HOPS; + self.flags_cipher_hops = (f & packet_constants::FLAGS_FIELD_MASK_HIDE_HOPS) | h; h } #[inline(always)] pub fn is_fragmented(&self) -> bool { - (self.flags_cipher_hops & HEADER_FLAG_FRAGMENTED) != 0 + (self.flags_cipher_hops & packet_constants::HEADER_FLAG_FRAGMENTED) != 0 } #[inline(always)] - pub fn as_bytes(&self) -> &[u8; PACKET_HEADER_SIZE] { - unsafe { &*(self as *const Self).cast::<[u8; PACKET_HEADER_SIZE]>() } + pub fn as_bytes(&self) -> &[u8; packet_constants::HEADER_SIZE] { + unsafe { &*(self as *const Self).cast::<[u8; packet_constants::HEADER_SIZE]>() } } #[inline(always)] @@ -262,7 +328,7 @@ impl PacketHeader { let mut id = unsafe { MaybeUninit::<[u8; 11]>::uninit().assume_init() }; id[0..5].copy_from_slice(&self.dest); id[5..10].copy_from_slice(&self.src); - id[10] = self.flags_cipher_hops & HEADER_FLAGS_FIELD_MASK_HIDE_HOPS; + id[10] = self.flags_cipher_hops & packet_constants::FLAGS_FIELD_MASK_HIDE_HOPS; id } @@ -281,6 +347,7 @@ impl PacketHeader { /// 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, Copy)] #[repr(C, packed)] pub struct FragmentHeader { pub id: [u8; 8], // (outer) packet ID @@ -290,12 +357,10 @@ pub struct FragmentHeader { pub reserved_hops: u8, // rrrrrHHH (3 hops bits, rest reserved) } -unsafe impl FlatBlob for FragmentHeader {} - impl FragmentHeader { #[inline(always)] pub fn is_fragment(&self) -> bool { - self.fragment_indicator == PACKET_FRAGMENT_INDICATOR + self.fragment_indicator == packet_constants::FRAGMENT_INDICATOR } #[inline(always)] @@ -310,34 +375,32 @@ impl FragmentHeader { #[inline(always)] pub fn hops(&self) -> u8 { - self.reserved_hops & HEADER_FLAGS_FIELD_MASK_HOPS + self.reserved_hops & packet_constants::FLAGS_FIELD_MASK_HOPS } #[inline(always)] pub fn increment_hops(&mut self) -> u8 { let f = self.reserved_hops; - let h = (f + 1) & HEADER_FLAGS_FIELD_MASK_HOPS; - self.reserved_hops = (f & HEADER_FLAGS_FIELD_MASK_HIDE_HOPS) | h; + let h = (f + 1) & packet_constants::FLAGS_FIELD_MASK_HOPS; + self.reserved_hops = (f & packet_constants::FLAGS_FIELD_MASK_HIDE_HOPS) | h; h } #[inline(always)] - pub fn as_bytes(&self) -> &[u8; FRAGMENT_HEADER_SIZE] { - unsafe { &*(self as *const Self).cast::<[u8; FRAGMENT_HEADER_SIZE]>() } + pub fn as_bytes(&self) -> &[u8; packet_constants::FRAGMENT_HEADER_SIZE] { + unsafe { &*(self as *const Self).cast::<[u8; packet_constants::FRAGMENT_HEADER_SIZE]>() } } } pub(crate) mod message_component_structs { - use crate::util::buffer::FlatBlob; - + #[derive(Clone, Copy)] #[repr(C, packed)] pub struct OkHeader { pub in_re_verb: u8, pub in_re_message_id: [u8; 8], } - unsafe impl FlatBlob for OkHeader {} - + #[derive(Clone, Copy)] #[repr(C, packed)] pub struct ErrorHeader { pub in_re_verb: u8, @@ -345,8 +408,7 @@ pub(crate) mod message_component_structs { pub error_code: u8, } - unsafe impl FlatBlob for ErrorHeader {} - + #[derive(Clone, Copy)] #[repr(C, packed)] pub struct HelloFixedHeaderFields { pub verb: u8, @@ -357,8 +419,7 @@ pub(crate) mod message_component_structs { pub timestamp: [u8; 8], // u64 } - unsafe impl FlatBlob for HelloFixedHeaderFields {} - + #[derive(Clone, Copy)] #[repr(C, packed)] pub struct OkHelloFixedHeaderFields { pub timestamp_echo: [u8; 8], // u64 @@ -367,8 +428,6 @@ pub(crate) mod message_component_structs { pub version_minor: u8, pub version_revision: [u8; 2], // u16 } - - unsafe impl FlatBlob for OkHelloFixedHeaderFields {} } #[cfg(test)] @@ -381,8 +440,8 @@ mod tests { fn representation() { assert_eq!(size_of::(), 9); assert_eq!(size_of::(), 10); - assert_eq!(size_of::(), PACKET_HEADER_SIZE); - assert_eq!(size_of::(), FRAGMENT_HEADER_SIZE); + assert_eq!(size_of::(), packet_constants::HEADER_SIZE); + assert_eq!(size_of::(), packet_constants::FRAGMENT_HEADER_SIZE); let mut foo = [0_u8; 32]; unsafe { diff --git a/zerotier-network-hypervisor/src/vl1/rootset.rs b/zerotier-network-hypervisor/src/vl1/rootset.rs index d35c4a59f..daf37b9da 100644 --- a/zerotier-network-hypervisor/src/vl1/rootset.rs +++ b/zerotier-network-hypervisor/src/vl1/rootset.rs @@ -12,7 +12,6 @@ use std::io::Write; use crate::util::buffer::Buffer; use crate::util::marshalable::Marshalable; use crate::vl1::identity::*; -use crate::vl1::protocol::PACKET_SIZE_MAX; use crate::vl1::Endpoint; use serde::{Deserialize, Serialize}; @@ -48,14 +47,14 @@ pub struct Root { impl PartialOrd for Root { #[inline(always)] fn partial_cmp(&self, other: &Self) -> Option { - self.identity.address.partial_cmp(&other.identity.address) + self.identity.partial_cmp(&other.identity) } } impl Ord for Root { #[inline(always)] fn cmp(&self, other: &Self) -> std::cmp::Ordering { - self.identity.address.cmp(&other.identity.address) + self.identity.cmp(&other.identity) } } @@ -112,7 +111,7 @@ impl RootSet { buf.append_varint(self.revision)?; buf.append_varint(self.members.len() as u64)?; for m in self.members.iter() { - m.identity.marshal_with_options(buf, IDENTITY_ALGORITHM_ALL, false)?; + m.identity.marshal_with_options(buf, Identity::ALGORITHM_ALL, false)?; if m.endpoints.is_some() { let endpoints = m.endpoints.as_ref().unwrap(); buf.append_varint(endpoints.len() as u64)?; @@ -135,8 +134,8 @@ impl RootSet { } /// Internal method to marshal without signatures for use during sign and verify. - fn marshal_for_signing(&self) -> Buffer { - let mut tmp = Buffer::::new(); + fn marshal_for_signing(&self) -> Buffer<{ Self::MAX_MARSHAL_SIZE }> { + let mut tmp = Buffer::<{ Self::MAX_MARSHAL_SIZE }>::new(); assert!(self.marshal_internal(&mut tmp, false).is_ok()); tmp } @@ -183,7 +182,7 @@ impl RootSet { /// All current members must sign whether they are disabled (witnessing) or active. The verify() /// method will return true when signing is complete. pub fn sign(&mut self, member_identity: &Identity) -> bool { - let signature = member_identity.sign(self.marshal_for_signing().as_bytes(), IDENTITY_ALGORITHM_ALL, false); + let signature = member_identity.sign(self.marshal_for_signing().as_bytes(), Identity::ALGORITHM_ALL, false); let unsigned_entry = self.members.iter().find_map(|m| if m.identity.eq(member_identity) { Some(m.clone()) } else { None }); if unsigned_entry.is_some() && signature.is_some() { let unsigned_entry = unsigned_entry.unwrap(); @@ -243,7 +242,7 @@ impl RootSet { } impl Marshalable for RootSet { - const MAX_MARSHAL_SIZE: usize = PACKET_SIZE_MAX; + const MAX_MARSHAL_SIZE: usize = crate::vl1::protocol::packet_constants::SIZE_MAX; #[inline(always)] fn marshal(&self, buf: &mut Buffer) -> std::io::Result<()> { diff --git a/zerotier-network-hypervisor/src/vl1/symmetricsecret.rs b/zerotier-network-hypervisor/src/vl1/symmetricsecret.rs index 57ef06dfc..0f99a3987 100644 --- a/zerotier-network-hypervisor/src/vl1/symmetricsecret.rs +++ b/zerotier-network-hypervisor/src/vl1/symmetricsecret.rs @@ -15,21 +15,6 @@ use zerotier_core_crypto::secret::Secret; use crate::util::pool::{Pool, PoolFactory}; use crate::vl1::protocol::*; -/// Pool of reusable AES-GMAC-SIV instances. -pub(crate) struct AesGmacSivPoolFactory(Secret<32>, Secret<32>); - -impl PoolFactory for AesGmacSivPoolFactory { - #[inline(always)] - fn create(&self) -> AesGmacSiv { - AesGmacSiv::new(self.0.as_bytes(), self.1.as_bytes()) - } - - #[inline(always)] - fn reset(&self, obj: &mut AesGmacSiv) { - obj.reset(); - } -} - /// A symmetric secret key negotiated between peers. /// /// This contains the key and several sub-keys and ciphers keyed with sub-keys. @@ -50,22 +35,14 @@ pub(crate) struct SymmetricSecret { pub aes_gmac_siv: Pool, } -impl PartialEq for SymmetricSecret { - #[inline(always)] - fn eq(&self, other: &Self) -> bool { - self.key == other.key - } -} - -impl Eq for SymmetricSecret {} - impl SymmetricSecret { /// Create a new symmetric secret, deriving all sub-keys and such. pub fn new(key: Secret<64>) -> SymmetricSecret { - let hello_private_section_key = zt_kbkdf_hmac_sha384(&key.0, KBKDF_KEY_USAGE_LABEL_HELLO_PRIVATE_SECTION, 0, 0); - let packet_hmac_key = zt_kbkdf_hmac_sha512(&key.0, KBKDF_KEY_USAGE_LABEL_PACKET_HMAC, 0, 0); - let ephemeral_ratchet_key = zt_kbkdf_hmac_sha512(&key.0, KBKDF_KEY_USAGE_LABEL_EPHEMERAL_RATCHET_KEY, 0, 0); - let aes_factory = AesGmacSivPoolFactory(zt_kbkdf_hmac_sha384(&key.0[0..48], KBKDF_KEY_USAGE_LABEL_AES_GMAC_SIV_K0, 0, 0).first_n(), zt_kbkdf_hmac_sha384(&key.0[0..48], KBKDF_KEY_USAGE_LABEL_AES_GMAC_SIV_K1, 0, 0).first_n()); + let hello_private_section_key = zt_kbkdf_hmac_sha384(&key.0, security_constants::KBKDF_KEY_USAGE_LABEL_HELLO_PRIVATE_SECTION); + let packet_hmac_key = zt_kbkdf_hmac_sha512(&key.0, security_constants::KBKDF_KEY_USAGE_LABEL_PACKET_HMAC); + let ephemeral_ratchet_key = zt_kbkdf_hmac_sha512(&key.0, security_constants::KBKDF_KEY_USAGE_LABEL_EPHEMERAL_RATCHET_KEY); + let aes_factory = + AesGmacSivPoolFactory(zt_kbkdf_hmac_sha384(&key.0[..48], security_constants::KBKDF_KEY_USAGE_LABEL_AES_GMAC_SIV_K0).first_n(), zt_kbkdf_hmac_sha384(&key.0[..48], security_constants::KBKDF_KEY_USAGE_LABEL_AES_GMAC_SIV_K1).first_n()); SymmetricSecret { key, hello_private_section_key, @@ -78,10 +55,9 @@ impl SymmetricSecret { /// An ephemeral symmetric secret with usage timers and counters. pub(crate) struct EphemeralSymmetricSecret { - pub id: [u8; 16], // first 16 bytes of SHA384 of symmetric secret pub secret: SymmetricSecret, - pub rekey_time: i64, - pub expire_time: i64, + pub rekey_time_ticks: i64, + pub expire_time_ticks: i64, pub ratchet_count: u64, pub encrypt_uses: AtomicUsize, pub decrypt_uses: AtomicUsize, @@ -91,11 +67,25 @@ pub(crate) struct EphemeralSymmetricSecret { impl EphemeralSymmetricSecret { #[inline(always)] pub fn should_rekey(&self, time_ticks: i64) -> bool { - time_ticks >= self.rekey_time || self.encrypt_uses.load(Ordering::Relaxed).max(self.decrypt_uses.load(Ordering::Relaxed)) >= EPHEMERAL_SECRET_REKEY_AFTER_USES + time_ticks >= self.rekey_time_ticks || self.encrypt_uses.load(Ordering::Relaxed).max(self.decrypt_uses.load(Ordering::Relaxed)) >= security_constants::EPHEMERAL_SECRET_REKEY_AFTER_USES } #[inline(always)] - pub fn expired(&self, time_ticks: i64) -> bool { - time_ticks >= self.expire_time || self.encrypt_uses.load(Ordering::Relaxed).max(self.decrypt_uses.load(Ordering::Relaxed)) >= EPHEMERAL_SECRET_REJECT_AFTER_USES + pub fn is_expired(&self, time_ticks: i64) -> bool { + time_ticks >= self.expire_time_ticks || self.encrypt_uses.load(Ordering::Relaxed).max(self.decrypt_uses.load(Ordering::Relaxed)) >= security_constants::EPHEMERAL_SECRET_REJECT_AFTER_USES + } +} + +pub(crate) struct AesGmacSivPoolFactory(Secret<32>, Secret<32>); + +impl PoolFactory for AesGmacSivPoolFactory { + #[inline(always)] + fn create(&self) -> AesGmacSiv { + AesGmacSiv::new(self.0.as_bytes(), self.1.as_bytes()) + } + + #[inline(always)] + fn reset(&self, obj: &mut AesGmacSiv) { + obj.reset(); } } diff --git a/zerotier-network-hypervisor/src/vl1/whoisqueue.rs b/zerotier-network-hypervisor/src/vl1/whoisqueue.rs index f4768231c..126002c4c 100644 --- a/zerotier-network-hypervisor/src/vl1/whoisqueue.rs +++ b/zerotier-network-hypervisor/src/vl1/whoisqueue.rs @@ -12,13 +12,14 @@ use parking_lot::Mutex; use crate::util::gate::IntervalGate; use crate::vl1::fragmentedpacket::FragmentedPacket; -use crate::vl1::node::{BackgroundServicable, Node, SystemInterface}; -use crate::vl1::protocol::{WHOIS_MAX_WAITING_PACKETS, WHOIS_RETRY_INTERVAL, WHOIS_RETRY_MAX}; +use crate::vl1::node::{Node, SystemInterface}; +use crate::vl1::protocol::{PooledPacketBuffer, WHOIS_MAX_WAITING_PACKETS, WHOIS_RETRY_INTERVAL, WHOIS_RETRY_MAX}; use crate::vl1::Address; -use crate::PacketBuffer; + +pub(crate) const SERVICE_INTERVAL_MS: i64 = WHOIS_RETRY_INTERVAL; pub(crate) enum QueuedPacket { - Unfragmented(PacketBuffer), + Unfragmented(PooledPacketBuffer), Fragmented(FragmentedPacket), } @@ -36,7 +37,7 @@ impl WhoisQueue { } /// Launch or renew a WHOIS query and enqueue a packet to be processed when (if) it is received. - pub fn query(&self, node: &Node, si: &SI, target: Address, packet: Option) { + pub fn query(&self, node: &Node, si: &SI, target: Address, packet: Option) { let mut q = self.0.lock(); let qi = q.entry(target).or_insert_with(|| WhoisQueueItem { @@ -63,15 +64,11 @@ impl WhoisQueue { let _ = qi.map(|mut qi| qi.packet_queue.iter_mut().for_each(packet_handler)); } - fn send_whois(&self, node: &Node, si: &SI, targets: &[Address]) { + fn send_whois(&self, node: &Node, si: &SI, targets: &[Address]) { todo!() } -} -impl BackgroundServicable for WhoisQueue { - const SERVICE_INTERVAL_MS: i64 = WHOIS_RETRY_INTERVAL; - - fn service(&self, si: &SI, node: &Node, time_ticks: i64) -> bool { + pub(crate) fn service(&self, si: &SI, node: &Node, time_ticks: i64) -> bool { let mut targets: Vec
= Vec::new(); self.0.lock().retain(|target, qi| { if qi.retry_count < WHOIS_RETRY_MAX { diff --git a/zerotier-network-hypervisor/src/vl2/switch.rs b/zerotier-network-hypervisor/src/vl2/switch.rs index c7f136f89..0f7bd50aa 100644 --- a/zerotier-network-hypervisor/src/vl2/switch.rs +++ b/zerotier-network-hypervisor/src/vl2/switch.rs @@ -6,10 +6,7 @@ * https://www.zerotier.com/ */ -use std::sync::Arc; - -use crate::util::buffer::Buffer; -use crate::vl1::node::InnerProtocolInterface; +use crate::vl1::node::{InnerProtocolInterface, SystemInterface}; use crate::vl1::protocol::*; use crate::vl1::{Identity, Path, Peer}; @@ -18,15 +15,15 @@ pub trait SwitchInterface: Sync + Send {} pub struct Switch {} impl InnerProtocolInterface for Switch { - fn handle_packet(&self, peer: &Peer, source_path: &Arc, forward_secrecy: bool, extended_authentication: bool, verb: u8, payload: &Buffer<{ PACKET_SIZE_MAX }>) -> bool { + fn handle_packet(&self, peer: &Peer, source_path: &Path, forward_secrecy: bool, extended_authentication: bool, verb: u8, payload: &PacketBuffer) -> bool { false } - fn handle_error(&self, peer: &Peer, source_path: &Arc, forward_secrecy: bool, extended_authentication: bool, in_re_verb: u8, in_re_message_id: u64, error_code: u8, payload: &Buffer<{ PACKET_SIZE_MAX }>, cursor: &mut usize) -> bool { + fn handle_error(&self, peer: &Peer, source_path: &Path, forward_secrecy: bool, extended_authentication: bool, in_re_verb: u8, in_re_message_id: u64, error_code: u8, payload: &PacketBuffer, cursor: &mut usize) -> bool { false } - fn handle_ok(&self, peer: &Peer, source_path: &Arc, forward_secrecy: bool, extended_authentication: bool, in_re_verb: u8, in_re_message_id: u64, payload: &Buffer<{ PACKET_SIZE_MAX }>, cursor: &mut usize) -> bool { + fn handle_ok(&self, peer: &Peer, source_path: &Path, forward_secrecy: bool, extended_authentication: bool, in_re_verb: u8, in_re_message_id: u64, payload: &PacketBuffer, cursor: &mut usize) -> bool { false } diff --git a/zerotier-system-service/Cargo.lock b/zerotier-system-service/Cargo.lock index 59597d2c6..287cbebb4 100644 --- a/zerotier-system-service/Cargo.lock +++ b/zerotier-system-service/Cargo.lock @@ -366,12 +366,6 @@ version = "2.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "308cc39be01b73d0d18f82a0e7b2a3df85245f84af96fdddc5d202d27e47b86a" -[[package]] -name = "metrohash" -version = "1.0.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3ba553cb19e2acbc54baa16faef215126243fe45e53357a3b2e9f4ebc7b0506c" - [[package]] name = "mio" version = "0.8.2" @@ -1044,7 +1038,6 @@ dependencies = [ "lazy_static", "libc", "lz4_flex", - "metrohash", "parking_lot", "serde", "winapi", diff --git a/zerotier-system-service/src/cli/rootset.rs b/zerotier-system-service/src/cli/rootset.rs index bc341424d..01333e535 100644 --- a/zerotier-system-service/src/cli/rootset.rs +++ b/zerotier-system-service/src/cli/rootset.rs @@ -15,7 +15,7 @@ use crate::{exitcode, Flags}; use zerotier_network_hypervisor::util::marshalable::Marshalable; use zerotier_network_hypervisor::vl1::RootSet; -pub async fn cmd(flags: Flags, cmd_args: &ArgMatches) -> i32 { +pub async fn cmd(_: Flags, cmd_args: &ArgMatches) -> i32 { match cmd_args.subcommand() { Some(("trust", sc_args)) => todo!(), diff --git a/zerotier-system-service/src/datadir.rs b/zerotier-system-service/src/datadir.rs index 85502f4ac..b26ece3b6 100644 --- a/zerotier-system-service/src/datadir.rs +++ b/zerotier-system-service/src/datadir.rs @@ -15,9 +15,9 @@ use crate::utils::{read_limit, DEFAULT_FILE_IO_READ_LIMIT}; use tokio::sync::{Mutex, RwLock, RwLockReadGuard}; use zerotier_core_crypto::random::next_u32_secure; -use zerotier_network_hypervisor::vl1::identity::{Identity, IDENTITY_ALGORITHM_ALL}; +use zerotier_network_hypervisor::vl1::Identity; -const AUTH_TOKEN_DEFAULT_LENGTH: usize = 64; +const AUTH_TOKEN_DEFAULT_LENGTH: usize = 48; const AUTH_TOKEN_POSSIBLE_CHARS: &'static str = "0123456789abcdefghijklmnopqrstuvwxyz"; const AUTH_TOKEN_FILENAME: &'static str = "authtoken.secret"; const IDENTITY_PUBLIC_FILENAME: &'static str = "identity.public"; @@ -72,8 +72,8 @@ impl DataDir { /// Save identity.secret and identity.public to data directory. pub async fn save_identity(&self, id: &Identity) -> std::io::Result<()> { assert!(id.secret.is_some()); - let id_secret_str = id.to_string_with_options(IDENTITY_ALGORITHM_ALL, true); - let id_public_str = id.to_string_with_options(IDENTITY_ALGORITHM_ALL, false); + let id_secret_str = id.to_string_with_options(Identity::ALGORITHM_ALL, true); + let id_public_str = id.to_string_with_options(Identity::ALGORITHM_ALL, false); let secret_path = self.base_path.join(IDENTITY_SECRET_FILENAME); tokio::fs::write(&secret_path, id_secret_str.as_bytes()).await?; assert!(crate::utils::fs_restrict_permissions(&secret_path)); diff --git a/zerotier-system-service/src/getifaddrs.rs b/zerotier-system-service/src/getifaddrs.rs index ea21b5472..a696e7649 100644 --- a/zerotier-system-service/src/getifaddrs.rs +++ b/zerotier-system-service/src/getifaddrs.rs @@ -19,7 +19,7 @@ fn s6_addr_as_ptr(a: &A) -> *const A { /// Call supplied function or closure for each physical IP address in the system. #[cfg(unix)] -pub(crate) fn for_each_address(mut f: F) { +pub fn for_each_address(mut f: F) { unsafe { let mut ifa_name = [0_u8; libc::IFNAMSIZ as usize]; let mut ifap: *mut libc::ifaddrs = null_mut(); @@ -90,7 +90,7 @@ mod tests { #[test] fn test_getifaddrs() { println!("starting getifaddrs..."); - crate::getifaddrs::for_each_address(|a: &InetAddress, dev: &str| println!(" {} {}", dev, a.to_string())); + crate::vnic::getifaddrs::for_each_address(|a: &InetAddress, dev: &str| println!(" {} {}", dev, a.to_string())); println!("done.") } } diff --git a/zerotier-system-service/src/main.rs b/zerotier-system-service/src/main.rs index 333523467..c17e0e3f8 100644 --- a/zerotier-system-service/src/main.rs +++ b/zerotier-system-service/src/main.rs @@ -6,22 +6,24 @@ * https://www.zerotier.com/ */ -use std::io::Write; - -use clap::error::{ContextKind, ContextValue}; -use clap::{Arg, ArgMatches, Command}; - -use zerotier_network_hypervisor::{VERSION_MAJOR, VERSION_MINOR, VERSION_REVISION}; - pub mod cli; pub mod datadir; pub mod exitcode; pub mod getifaddrs; pub mod jsonformatter; pub mod localconfig; +pub mod service; +pub mod udp; pub mod utils; pub mod vnic; +use std::io::Write; + +use clap::error::{ContextKind, ContextValue}; +use clap::{Arg, ArgMatches, Command}; + +use zerotier_network_hypervisor::{VERSION_MAJOR, VERSION_MINOR, VERSION_REVISION}; + fn make_help() -> String { format!( r###"ZeroTier Network Hypervisor Service Version {}.{}.{} @@ -124,7 +126,7 @@ pub struct Flags { pub auth_token_override: Option, } -async fn async_main(flags: Flags, global_args: ArgMatches) -> i32 { +async fn async_main(flags: Flags, global_args: Box) -> i32 { #[allow(unused)] return match global_args.subcommand() { Some(("help", _)) => { @@ -141,7 +143,10 @@ async fn async_main(flags: Flags, global_args: ArgMatches) -> i32 { Some(("network", cmd_args)) => todo!(), Some(("join", cmd_args)) => todo!(), Some(("leave", cmd_args)) => todo!(), - Some(("service", _)) => todo!(), + Some(("service", _)) => { + drop(global_args); // free unnecessary heap + assert!(service::Service::new(flags.base_path.as_str()).await.is_ok()); + } Some(("identity", cmd_args)) => todo!(), Some(("rootset", cmd_args)) => cli::rootset::cmd(flags, cmd_args).await, _ => { @@ -152,7 +157,7 @@ async fn async_main(flags: Flags, global_args: ArgMatches) -> i32 { } fn main() { - let global_args = { + let global_args = Box::new({ let help = make_help(); Command::new("zerotier") .arg(Arg::new("json").short('j')) @@ -239,7 +244,7 @@ fn main() { std::process::exit(exitcode::ERR_USAGE); } }) - }; + }); let flags = Flags { json_output: global_args.is_present("json"), diff --git a/zerotier-system-service/src/service.rs b/zerotier-system-service/src/service.rs new file mode 100644 index 000000000..5496b5db3 --- /dev/null +++ b/zerotier-system-service/src/service.rs @@ -0,0 +1,124 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at https://mozilla.org/MPL/2.0/. + * + * (c)2021 ZeroTier, Inc. + * https://www.zerotier.com/ + */ + +use std::collections::HashMap; +use std::hash::Hash; +use std::num::NonZeroI64; +use std::sync::atomic::AtomicUsize; +use std::sync::{Arc, Weak}; + +use tokio::net::UdpSocket; + +use zerotier_network_hypervisor::vl1::*; +use zerotier_network_hypervisor::vl2::*; +use zerotier_network_hypervisor::*; + +use crate::datadir::DataDir; +use crate::udp::BoundUdpSocket; +use crate::utils::{ms_monotonic, ms_since_epoch}; + +pub type DynamicError = Box; + +pub struct Service { + pub rt: tokio::runtime::Handle, + pub data: DataDir, + pub local_socket_unique_id_counter: AtomicUsize, + pub udp_sockets: parking_lot::RwLock>>>, + pub core: Option>, +} + +impl Service { + pub async fn new(base_path: &str) -> Result { + let mut svc = Self { + rt: tokio::runtime::Handle::current(), + data: DataDir::open(base_path).await.map_err(|e| Box::new(e))?, + local_socket_unique_id_counter: AtomicUsize::new(1), + udp_sockets: parking_lot::RwLock::new(HashMap::with_capacity(4)), + core: None, + }; + let _ = svc.core.insert(NetworkHypervisor::new(&svc, true).map_err(|e| Box::new(e))?); + + let config = svc.data.config().await; + + Ok(svc) + } +} + +/// Local socket wrapper implementing equality and hash in terms of an arbitrary unique ID. +#[derive(Clone)] +struct LocalSocket(Weak, usize); + +impl PartialEq for LocalSocket { + #[inline(always)] + fn eq(&self, other: &Self) -> bool { + self.1 == other.1 + } +} + +impl Eq for LocalSocket {} + +impl Hash for LocalSocket { + #[inline(always)] + fn hash(&self, state: &mut H) { + self.1.hash(state) + } +} + +impl SystemInterface for Service { + type LocalSocket = crate::service::LocalSocket; + + type LocalInterface = String; + + fn event_node_is_up(&self) {} + + fn event_node_is_down(&self) {} + + fn event_online_status_change(&self, online: bool) {} + + fn event_user_message(&self, source: &Identity, message_type: u64, message: &[u8]) {} + + fn event_security_warning(&self, warning: &str) {} + + fn local_socket_is_valid(&self, socket: &Self::LocalSocket) -> bool { + socket.0.strong_count() > 0 + } + + fn load_node_identity(&self) -> Option { + self.rt.block_on(async { self.data.load_identity().await.map_or(None, |i| Some(i)) }) + } + + fn save_node_identity(&self, id: &Identity) { + self.rt.block_on(async { assert!(self.data.save_identity(id).await.is_ok()) }); + } + + fn wire_send(&self, endpoint: &Endpoint, local_socket: Option<&Self::LocalSocket>, local_interface: Option<&Self::LocalInterface>, data: &[&[u8]], packet_ttl: u8) -> bool { + todo!() + } + + fn check_path(&self, id: &Identity, endpoint: &Endpoint, local_socket: Option<&Self::LocalSocket>, local_interface: Option<&Self::LocalInterface>) -> bool { + true + } + + fn get_path_hints(&self, id: &Identity) -> Option, Option)>> { + None + } + + #[inline(always)] + fn time_ticks(&self) -> i64 { + ms_monotonic() + } + + #[inline(always)] + fn time_clock(&self) -> i64 { + ms_since_epoch() + } +} + +impl SwitchInterface for Service {} + +impl Interface for Service {} diff --git a/zerotier-system-service/src/udp.rs b/zerotier-system-service/src/udp.rs new file mode 100644 index 000000000..2c5b1b675 --- /dev/null +++ b/zerotier-system-service/src/udp.rs @@ -0,0 +1,172 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at https://mozilla.org/MPL/2.0/. + * + * (c)2021 ZeroTier, Inc. + * https://www.zerotier.com/ + */ + +use std::collections::{HashMap, HashSet}; +use std::hash::Hash; +use std::num::NonZeroI64; +use std::sync::Arc; + +#[cfg(unix)] +use std::os::unix::io::{FromRawFd, RawFd}; + +use lazy_static::lazy_static; + +#[allow(unused_imports)] +use num_traits::AsPrimitive; + +use crate::getifaddrs; + +use zerotier_network_hypervisor::vl1::inetaddress::{InetAddress, IpScope}; + +/// A locally bound UDP socket. +pub struct BoundUdpSocket { + /// Locally bound address. + pub address: InetAddress, + /// Locally bound (to device) socket. + pub socket: tokio::net::UdpSocket, + /// Local interface device name or other unique identifier (OS-specific). + pub interface: String, + /// Raw socket FD, which only remains valid as long as 'socket' exists. + pub fd: RawFd, + /// Monotonic time of last activity. + pub last_activity_time_ticks: i64, +} + +impl BoundUdpSocket { + /// Update 'sockets' by adding any missing local bindings and removing any that are no longer valid. + /// + /// Any device or local IP within any of the supplied blacklists is ignored. Multicast or loopback addresses are + /// also ignored. All errors encountered are returned. + /// + /// This should always be called on the same port for the same socket collection. Calling on the same 'sockets' + /// with different ports will lead to redundant or missed bindings. + /// + /// We must bind directly to each device/address pair for each port so default route override can work. + fn update_bindings_for_port(sockets: &mut Vec>, port: u16, device_prefix_blacklist: &Vec, cidr_blacklist: &Vec) -> Vec { + let mut errors = Vec::new(); + let mut existing_bind_points: HashMap> = HashMap::with_capacity(id_assignment_state.devices.len() + 1); + let now = crate::utils::ms_monotonic(); + getifaddrs::for_each_address(|address, device| { + if address.is_ip() + && matches!(address.scope(), IpScope::Global | IpScope::PseudoPrivate | IpScope::Private | IpScope::Shared) + && !device_prefix_blacklist.iter().any(|pfx| device.starts_with(pfx.as_str())) + && !cidr_blacklist.iter().any(|r| address.is_within(r)) + { + existing_bind_points.entry(device.to_string()).or_default().push(address.clone()); + if !sockets.iter().any(|_, s| s.address == address || s.local_device_id == did) { + let s = unsafe { bind_udp_to_device(device, address) }; + if s.is_ok() { + let fd = s.unwrap(); + let s = tokio::net::UdpSocket::from_std(unsafe { std::net::UdpSocket::from_raw_fd(fd) }); + if s.is_ok() { + id_assignment_state.socket_id_counter += 1; + let lsid = NonZeroI64::new(id_assignment_state.socket_id_counter).unwrap(); + sockets.push(Arc::new(BoundUdpSocket { + address: address.clone(), + socket: s.unwrap(), + interface: device.to_string(), + fd, + last_activity_time_ticks: now, + })); + } else { + errors.push(s.err().unwrap()); + } + } else { + errors.push(std::io::Error::new(std::io::ErrorKind::AddrInUse, s.err().unwrap())); + } + } + } + }); + sockets.retain(|s| existing_bind_points.get(&s.local_interface).map_or(false, |addr_list| addr_list.contains(&s.address))); + errors + } +} + +#[allow(unused_variables)] +#[cfg(unix)] +unsafe fn bind_udp_to_device(device_name: &str, address: &InetAddress) -> Result { + let (af, sa_len) = match address.family() { + InetAddressFamily::IPv4 => (libc::AF_INET, std::mem::size_of::().as_()), + InetAddressFamily::IPv6 => (libc::AF_INET6, std::mem::size_of::().as_()), + _ => { + return Err("unrecognized address family"); + } + }; + + let s = libc::socket(af.as_(), libc::SOCK_DGRAM, 0); + if s <= 0 { + return Err("unable to create socket"); + } + + let mut setsockopt_results: c_int = 0; + + let mut fl: c_int = 0; + setsockopt_results |= libc::setsockopt(s, libc::SOL_SOCKET.as_(), libc::SO_LINGER.as_(), (&mut fl as *mut c_int).cast(), std::mem::size_of::().as_()); + + fl = 1; + setsockopt_results |= libc::setsockopt(s, libc::SOL_SOCKET.as_(), libc::SO_BROADCAST.as_(), (&mut fl as *mut c_int).cast(), std::mem::size_of::().as_()); + if af == libc::AF_INET6 { + fl = 1; + setsockopt_results |= libc::setsockopt(s, libc::IPPROTO_IPV6.as_(), libc::IPV6_V6ONLY.as_(), (&mut fl as *mut c_int).cast(), std::mem::size_of::().as_()); + } + + #[cfg(target_os = "linux")] + { + if !device_name.is_empty() { + let _ = std::ffi::CString::new(device_name).map(|dn| { + let dnb = dn.as_bytes_with_nul(); + let _ = libc::setsockopt(s.as_(), libc::SOL_SOCKET.as_(), libc::SO_BINDTODEVICE.as_(), dnb.as_ptr().cast(), (dnb.len() - 1).as_()); + }); + } + } + + if setsockopt_results != 0 { + libc::close(s); + return Err("setsockopt() failed"); + } + + if af == libc::AF_INET { + #[cfg(not(target_os = "linux"))] + { + fl = 0; + libc::setsockopt(s, libc::IPPROTO_IP.as_(), libc::IP_DF.as_(), (&mut fl as *mut c_int).cast(), std::mem::size_of::().as_()); + } + #[cfg(target_os = "linux")] + { + fl = libc::IP_PMTUDISC_DONT as c_int; + libc::setsockopt(s, libc::IPPROTO_IP.as_(), libc::IP_MTU_DISCOVER.as_(), (&mut fl as *mut c_int).cast(), std::mem::size_of::().as_()); + } + } + + if af == libc::AF_INET6 { + fl = 0; + libc::setsockopt(s, libc::IPPROTO_IPV6.as_(), libc::IPV6_DONTFRAG.as_(), (&mut fl as *mut c_int).cast(), std::mem::size_of::().as_()); + } + + fl = 1048576; + while fl >= 131072 { + if libc::setsockopt(s, libc::SOL_SOCKET.as_(), libc::SO_RCVBUF.as_(), (&mut fl as *mut c_int).cast(), std::mem::size_of::().as_()) == 0 { + break; + } + fl -= 65536; + } + fl = 1048576; + while fl >= 131072 { + if libc::setsockopt(s, libc::SOL_SOCKET.as_(), libc::SO_SNDBUF.as_(), (&mut fl as *mut c_int).cast(), std::mem::size_of::().as_()) == 0 { + break; + } + fl -= 65536; + } + + if libc::bind(s, (address as *const InetAddress).cast(), sa_len) != 0 { + libc::close(s); + return Err("bind to address failed"); + } + + Ok(s as RawFd) +} diff --git a/zerotier-system-service/src/vnic/mac_feth_tap.rs b/zerotier-system-service/src/vnic/mac_feth_tap.rs index 84aebd9ff..b6ec29e44 100644 --- a/zerotier-system-service/src/vnic/mac_feth_tap.rs +++ b/zerotier-system-service/src/vnic/mac_feth_tap.rs @@ -96,85 +96,6 @@ lazy_static! { static ref MAC_FETH_BPF_DEVICES_USED: Mutex> = Mutex::new(BTreeSet::new()); } -/* -struct nd_ifinfo { - u_int32_t linkmtu; /* LinkMTU */ - u_int32_t maxmtu; /* Upper bound of LinkMTU */ - u_int32_t basereachable; /* BaseReachableTime */ - u_int32_t reachable; /* Reachable Time */ - u_int32_t retrans; /* Retrans Timer */ - u_int32_t flags; /* Flags */ - int recalctm; /* BaseReacable re-calculation timer */ - u_int8_t chlim; /* CurHopLimit */ - u_int8_t receivedra; -}; -struct in6_ndireq { - char ifname[IFNAMSIZ]; - struct nd_ifinfo ndi; -}; -struct in6_addrlifetime { - time_t ia6t_expire; /* valid lifetime expiration time */ - time_t ia6t_preferred; /* preferred lifetime expiration time */ - u_int32_t ia6t_vltime; /* valid lifetime */ - u_int32_t ia6t_pltime; /* prefix lifetime */ -}; -struct in6_ifstat { - ifs6_in_receive; /* # of total input datagram */ - ifs6_in_hdrerr; /* # of datagrams with invalid hdr */ - ifs6_in_toobig; /* # of datagrams exceeded MTU */ - ifs6_in_noroute; /* # of datagrams with no route */ - ifs6_in_addrerr; /* # of datagrams with invalid dst */ - ifs6_in_protounknown; /* # of datagrams with unknown proto */ - /* NOTE: increment on final dst if */ - ifs6_in_truncated; /* # of truncated datagrams */ - ifs6_in_discard; /* # of discarded datagrams */ - /* NOTE: fragment timeout is not here */ - ifs6_in_deliver; /* # of datagrams delivered to ULP */ - /* NOTE: increment on final dst if */ - ifs6_out_forward; /* # of datagrams forwarded */ - /* NOTE: increment on outgoing if */ - ifs6_out_request; /* # of outgoing datagrams from ULP */ - /* NOTE: does not include forwrads */ - ifs6_out_discard; /* # of discarded datagrams */ - ifs6_out_fragok; /* # of datagrams fragmented */ - ifs6_out_fragfail; /* # of datagrams failed on fragment */ - ifs6_out_fragcreat; /* # of fragment datagrams */ - /* NOTE: this is # after fragment */ - ifs6_reass_reqd; /* # of incoming fragmented packets */ - /* NOTE: increment on final dst if */ - ifs6_reass_ok; /* # of reassembled packets */ - /* NOTE: this is # after reass */ - /* NOTE: increment on final dst if */ - ifs6_atmfrag_rcvd; /* # of atomic fragments received */ - ifs6_reass_fail; /* # of reass failures */ - /* NOTE: may not be packet count */ - /* NOTE: increment on final dst if */ - ifs6_in_mcast; /* # of inbound multicast datagrams */ - ifs6_out_mcast; /* # of outbound multicast datagrams */ - - ifs6_cantfoward_icmp6; /* # of ICMPv6 packets received for unreachable dest */ - ifs6_addr_expiry_cnt; /* # of address expiry events (excluding privacy addresses) */ - ifs6_pfx_expiry_cnt; /* # of prefix expiry events */ - ifs6_defrtr_expiry_cnt; /* # of default router expiry events */ -}; -struct in6_ifreq { - char ifr_name[IFNAMSIZ]; - union { - struct sockaddr_in6 ifru_addr; - struct sockaddr_in6 ifru_dstaddr; - int ifru_flags; - int ifru_flags6; - int ifru_metric; - int ifru_intval; - caddr_t ifru_data; - struct in6_addrlifetime ifru_lifetime; - struct in6_ifstat ifru_stat; - struct icmp6_ifstat ifru_icmp6stat; - u_int32_t ifru_scope_id[SCOPE6_ID_MAX]; - } ifr_ifru; -}; -*/ - #[allow(non_camel_case_types)] #[derive(Clone, Copy)] #[repr(C)] @@ -347,81 +268,6 @@ fn device_ipv6_set_params(device: &String, perform_nud: bool, accept_ra: bool) - ok } -/* -struct ifkpi { - unsigned int ifk_module_id; - unsigned int ifk_type; - union { - void *ifk_ptr; - int ifk_value; - } ifk_data; -}; -struct ifdevmtu { - int ifdm_current; - int ifdm_min; - int ifdm_max; -}; -struct ifreq { -#ifndef IFNAMSIZ -#define IFNAMSIZ IF_NAMESIZE -#endif - char ifr_name[IFNAMSIZ]; /* if name, e.g. "en0" */ - union { - struct sockaddr ifru_addr; - struct sockaddr ifru_dstaddr; - struct sockaddr ifru_broadaddr; - short ifru_flags; - int ifru_metric; - int ifru_mtu; - int ifru_phys; - int ifru_media; - int ifru_intval; - caddr_t ifru_data; - struct ifdevmtu ifru_devmtu; - struct ifkpi ifru_kpi; - u_int32_t ifru_wake_flags; - u_int32_t ifru_route_refcnt; - int ifru_cap[2]; - u_int32_t ifru_functional_type; -#define IFRTYPE_FUNCTIONAL_UNKNOWN 0 -#define IFRTYPE_FUNCTIONAL_LOOPBACK 1 -#define IFRTYPE_FUNCTIONAL_WIRED 2 -#define IFRTYPE_FUNCTIONAL_WIFI_INFRA 3 -#define IFRTYPE_FUNCTIONAL_WIFI_AWDL 4 -#define IFRTYPE_FUNCTIONAL_CELLULAR 5 -#define IFRTYPE_FUNCTIONAL_INTCOPROC 6 -#define IFRTYPE_FUNCTIONAL_COMPANIONLINK 7 -#define IFRTYPE_FUNCTIONAL_LAST 7 - } ifr_ifru; -#define ifr_addr ifr_ifru.ifru_addr /* address */ -#define ifr_dstaddr ifr_ifru.ifru_dstaddr /* other end of p-to-p link */ -#define ifr_broadaddr ifr_ifru.ifru_broadaddr /* broadcast address */ -#ifdef __APPLE__ -#define ifr_flags ifr_ifru.ifru_flags /* flags */ -#else -#define ifr_flags ifr_ifru.ifru_flags[0] /* flags */ -#define ifr_prevflags ifr_ifru.ifru_flags[1] /* flags */ -#endif /* __APPLE__ */ -#define ifr_metric ifr_ifru.ifru_metric /* metric */ -#define ifr_mtu ifr_ifru.ifru_mtu /* mtu */ -#define ifr_phys ifr_ifru.ifru_phys /* physical wire */ -#define ifr_media ifr_ifru.ifru_media /* physical media */ -#define ifr_data ifr_ifru.ifru_data /* for use by interface */ -#define ifr_devmtu ifr_ifru.ifru_devmtu -#define ifr_intval ifr_ifru.ifru_intval /* integer value */ -#define ifr_kpi ifr_ifru.ifru_kpi -#define ifr_wake_flags ifr_ifru.ifru_wake_flags /* wake capabilities */ -#define ifr_route_refcnt ifr_ifru.ifru_route_refcnt /* route references count */ -#define ifr_reqcap ifr_ifru.ifru_cap[0] /* requested capabilities */ -#define ifr_curcap ifr_ifru.ifru_cap[1] /* current capabilities */ -}; -struct sockaddr_ndrv { - unsigned char snd_len; - unsigned char snd_family; - unsigned char snd_name[IFNAMSIZ]; /* from if.h */ -}; -*/ - #[allow(non_camel_case_types)] #[derive(Clone, Copy)] #[repr(C)] @@ -814,7 +660,8 @@ impl VNIC for MacFethTap { fn put(&self, source_mac: &zerotier_network_hypervisor::vl1::MAC, dest_mac: &zerotier_network_hypervisor::vl1::MAC, ethertype: u16, _vlan_id: u16, data: *const u8, len: usize) -> bool { let dm = dest_mac.0; let sm = source_mac.0; - let mut hdr: [u8; 14] = [(dm >> 40) as u8, (dm >> 32) as u8, (dm >> 24) as u8, (dm >> 16) as u8, (dm >> 8) as u8, dm as u8, (sm >> 40) as u8, (sm >> 32) as u8, (sm >> 24) as u8, (sm >> 16) as u8, (sm >> 8) as u8, sm as u8, (ethertype >> 8) as u8, ethertype as u8]; + let mut hdr: [u8; 14] = + [(dm >> 40) as u8, (dm >> 32) as u8, (dm >> 24) as u8, (dm >> 16) as u8, (dm >> 8) as u8, dm as u8, (sm >> 40) as u8, (sm >> 32) as u8, (sm >> 24) as u8, (sm >> 16) as u8, (sm >> 8) as u8, sm as u8, (ethertype >> 8) as u8, ethertype as u8]; unsafe { let iov: [libc::iovec; 2] = [ libc::iovec { iov_base: hdr.as_mut_ptr().cast(), iov_len: 14 }, diff --git a/zerotier-system-service/src/vnic/mod.rs b/zerotier-system-service/src/vnic/mod.rs index 2bcc39190..000bac2b2 100644 --- a/zerotier-system-service/src/vnic/mod.rs +++ b/zerotier-system-service/src/vnic/mod.rs @@ -6,8 +6,9 @@ * https://www.zerotier.com/ */ +pub mod vnic; + //mod common; -mod vnic; //#[cfg(target_os = "macos")] //mod mac_feth_tap;