An absolutely gigantic amount of refactoring to make the core and its service (or other users) interact in a way that is rustier and also better for performance. Also vastly simplifies some code.

This commit is contained in:
Adam Ierymenko 2022-05-18 13:40:44 -04:00
parent f14efdcd3d
commit 9d5c2a925d
No known key found for this signature in database
GPG key ID: C8877CF2D7A5D7F3
30 changed files with 1182 additions and 1117 deletions

View file

@ -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]))
}

View file

@ -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"

View file

@ -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::util::buffer::Buffer<{ crate::vl1::protocol::PACKET_SIZE_MAX }>, 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::util::buffer::Buffer<{ crate::vl1::protocol::PACKET_SIZE_MAX }>, 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};

View file

@ -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<I: Interface> {
vl1: Node<I>,
vl2: Switch,
}
impl NetworkHypervisor {
pub fn new<I: Interface>(ii: &I, auto_generate_identity: bool) -> Result<NetworkHypervisor, InvalidParameterError> {
impl<I: Interface> NetworkHypervisor<I> {
pub fn new(ii: &I, auto_generate_identity: bool) -> Result<Self, InvalidParameterError> {
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<I: Interface>(&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<I: Interface>(&self, ii: &I, source_endpoint: &Endpoint, source_local_socket: Option<NonZeroI64>, source_local_interface: Option<NonZeroI64>, 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)
}

View file

@ -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<const L: usize>(usize, [u8; L]);
unsafe impl<const L: usize> FlatBlob for Buffer<L> {}
impl<const L: usize> Default for Buffer<L> {
#[inline(always)]
fn default() -> Self {
@ -76,20 +63,21 @@ impl<const L: usize> Buffer<L> {
/// 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<Self> {
unsafe { Box::from_raw(std::alloc::alloc_zeroed(std::alloc::Layout::new::<Self>()).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<Self> {
let l = b.len();
if l <= L {
@ -138,7 +126,6 @@ impl<const L: usize> Buffer<L> {
}
}
#[inline(always)]
pub fn clear(&mut self) {
self.1[0..self.0].fill(0);
self.0 = 0;
@ -189,7 +176,7 @@ impl<const L: usize> Buffer<L> {
/// Append a structure and return a mutable reference to its memory.
#[inline(always)]
pub fn append_struct_get_mut<T: FlatBlob>(&mut self) -> std::io::Result<&mut T> {
pub fn append_struct_get_mut<T: Copy>(&mut self) -> std::io::Result<&mut T> {
let ptr = self.0;
let end = ptr + size_of::<T>();
if end <= L {
@ -226,6 +213,7 @@ impl<const L: usize> Buffer<L> {
}
}
#[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<const L: usize> Buffer<L> {
}
}
#[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<const L: usize> Buffer<L> {
}
}
#[inline(always)]
pub fn append_bytes_fixed<const S: usize>(&mut self, buf: &[u8; S]) -> std::io::Result<()> {
let ptr = self.0;
let end = ptr + S;
@ -318,19 +308,35 @@ impl<const L: usize> Buffer<L> {
}
}
/// Get a structure at a given position in the buffer.
#[inline(always)]
pub fn struct_at<T: FlatBlob>(&self, ptr: usize) -> std::io::Result<&T> {
if (ptr + size_of::<T>()) <= self.0 {
unsafe { Ok(&*self.1.as_ptr().cast::<u8>().offset(ptr as isize).cast::<T>()) }
pub fn bytes_fixed_at<const S: usize>(&self, ptr: usize) -> std::io::Result<&[u8; S]> {
if (ptr + S) <= self.0 {
unsafe { Ok(&*self.1.as_ptr().cast::<u8>().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<T: FlatBlob>(&mut self, ptr: usize) -> std::io::Result<&mut T> {
pub fn bytes_fixed_mut_at<const S: usize>(&mut self, ptr: usize) -> std::io::Result<&mut [u8; S]> {
if (ptr + S) <= self.0 {
unsafe { Ok(&mut *self.1.as_mut_ptr().cast::<u8>().add(ptr).cast::<[u8; S]>()) }
} else {
Err(overflow_err())
}
}
#[inline(always)]
pub fn struct_at<T: Copy>(&self, ptr: usize) -> std::io::Result<&T> {
if (ptr + size_of::<T>()) <= self.0 {
unsafe { Ok(&*self.1.as_ptr().cast::<u8>().add(ptr).cast::<T>()) }
} else {
Err(overflow_err())
}
}
#[inline(always)]
pub fn struct_mut_at<T: Copy>(&mut self, ptr: usize) -> std::io::Result<&mut T> {
if (ptr + size_of::<T>()) <= self.0 {
unsafe { Ok(&mut *self.1.as_mut_ptr().cast::<u8>().offset(ptr as isize).cast::<T>()) }
} else {
@ -347,9 +353,8 @@ impl<const L: usize> Buffer<L> {
}
}
/// Get a structure at a given position in the buffer and advance the cursor.
#[inline(always)]
pub fn read_struct<T: FlatBlob>(&self, cursor: &mut usize) -> std::io::Result<&T> {
pub fn read_struct<T: Copy>(&self, cursor: &mut usize) -> std::io::Result<&T> {
let ptr = *cursor;
let end = ptr + size_of::<T>();
debug_assert!(end <= L);
@ -454,15 +459,6 @@ impl<const L: usize> Buffer<L> {
}
}
impl<const L: usize> PartialEq for Buffer<L> {
#[inline(always)]
fn eq(&self, other: &Self) -> bool {
self.1[0..self.0].eq(&other.1[0..other.0])
}
}
impl<const L: usize> Eq for Buffer<L> {}
impl<const L: usize> Write for Buffer<L> {
#[inline(always)]
fn write(&mut self, buf: &[u8]) -> std::io::Result<usize> {

View file

@ -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<u8> {
assert!(Self::MAX_MARSHAL_SIZE <= TEMP_BUF_SIZE);
let mut tmp = Buffer::<TEMP_BUF_SIZE>::new_boxed();
let mut tmp = Buffer::<TEMP_BUF_SIZE>::new();
assert!(self.marshal(&mut tmp).is_ok());
tmp.as_bytes().to_vec()
}

View file

@ -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<O> {
fn create(&self) -> O;
fn reset(&self, obj: &mut O);
}
#[repr(C)]
struct PoolEntry<O, F: PoolFactory<O>> {
obj: O,
return_pool: Weak<PoolInner<O, F>>,
@ -102,10 +103,7 @@ impl<O, F: PoolFactory<O>> Drop for Pooled<O, F> {
#[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<O, F: PoolFactory<O>> Pool<O, F> {
}
/// Get a pooled object, or allocate one if the pool is empty.
#[inline(always)]
pub fn get(&self) -> Pooled<O, F> {
Pooled::<O, F>(self.0.pool.lock().pop().unwrap_or_else(|| unsafe {
Pooled::<O, F>(self.0.pool.lock().pop().unwrap_or_else(
#[inline(always)]
|| unsafe {
NonNull::new_unchecked(Box::into_raw(Box::new(PoolEntry::<O, F> {
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<O, F: PoolFactory<O>> Pool<O, F> {
/// 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()) })
}
}
}

View file

@ -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<Address> {
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<Address> {
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<Address> {
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<const BL: usize>(&self, buf: &mut Buffer<BL>) -> std::io::Result<()> {
buf.append_bytes(&self.0.get().to_be_bytes()[8 - ADDRESS_SIZE..])
}
#[inline(always)]
#[inline]
fn unmarshal<const BL: usize>(buf: &Buffer<BL>, cursor: &mut usize) -> std::io::Result<Self> {
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))
}

View file

@ -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<PacketBuffer>; PACKET_FRAGMENT_COUNT_MAX],
pub frags: [Option<PooledPacketBuffer>; 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

View file

@ -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<const A: usize, const B: usize, const C: usize, const D: usiz
}
impl Identity {
/// Curve25519 and Ed25519
pub const ALGORITHM_X25519: u8 = 0x01;
/// NIST P-384 ECDH and ECDSA
pub const ALGORITHM_EC_NIST_P384: u8 = 0x02;
/// Bit mask to include all algorithms.
pub const ALGORITHM_ALL: u8 = 0xff;
/// Generate a new identity.
pub fn generate() -> 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<u8> = 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<MAX_MARSHAL_SIZE> {
let mut b: Buffer<MAX_MARSHAL_SIZE> = 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<const BL: usize>(&self, buf: &mut Buffer<BL>, 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() {
let p384 = self.p384.as_ref().unwrap();
/*
* 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.
* 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 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
} else {
buf.append_u16(Self::P384_PUBLIC_ONLY_BUNDLE_SIZE + 1 + 2)?;
false
};
let remaining_data_size_field_at = buf.len();
buf.append_padding(0, 2)?;
buf.append_u8(IDENTITY_ALGORITHM_EC_NIST_P384)?;
if p384_has_private {
buf.append_u16(Self::P384_PUBLIC_AND_PRIVATE_BUNDLE_SIZE)?;
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 };
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)?;
}
((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<const BL: usize>(&self, buf: &mut Buffer<BL>) -> std::io::Result<()> {
self.marshal_with_options(buf, IDENTITY_ALGORITHM_ALL, false)
self.marshal_with_options(buf, Self::ALGORITHM_ALL, false)
}
fn unmarshal<const BL: usize>(buf: &Buffer<BL>, cursor: &mut usize) -> std::io::Result<Identity> {
@ -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::<C25519_PUBLIC_KEY_SIZE>(cursor)?;
let b = buf.read_bytes_fixed::<ED25519_PUBLIC_KEY_SIZE>(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::<C25519_SECRET_KEY_SIZE>(cursor)?;
let b = buf.read_bytes_fixed::<ED25519_SECRET_KEY_SIZE>(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;
*cursor += 2;
}
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 {
*cursor += size as usize;
}
}
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::<P384_PUBLIC_KEY_SIZE>(cursor)?;
let b = buf.read_bytes_fixed::<P384_PUBLIC_KEY_SIZE>(cursor)?;
};
let a = P384PublicKey::from_bytes(buf.read_bytes_fixed::<P384_PUBLIC_KEY_SIZE>(cursor)?);
let b = P384PublicKey::from_bytes(buf.read_bytes_fixed::<P384_PUBLIC_KEY_SIZE>(cursor)?);
let c = buf.read_bytes_fixed::<P384_ECDSA_SIGNATURE_SIZE>(cursor)?;
let d = buf.read_bytes_fixed::<ED25519_SIGNATURE_SIZE>(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::<P384_SECRET_KEY_SIZE>(cursor)?;
let b = buf.read_bytes_fixed::<P384_SECRET_KEY_SIZE>(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<MAX_MARSHAL_SIZE> = 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<MAX_MARSHAL_SIZE> = 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);

View file

@ -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<SocketAddr> {
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 {

View file

@ -29,12 +29,12 @@ impl Debug for MAC {
}
impl MAC {
#[inline(always)]
#[inline]
pub fn from_u64(i: u64) -> Option<MAC> {
NonZeroU64::new(i & 0xffffffffffff).map(|i| MAC(i))
}
#[inline(always)]
#[inline]
pub fn from_bytes(b: &[u8]) -> Option<MAC> {
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<MAC> {
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<const BL: usize>(&self, buf: &mut Buffer<BL>) -> std::io::Result<()> {
buf.append_bytes(&self.0.get().to_be_bytes()[2..])
}
#[inline(always)]
#[inline]
fn unmarshal<const BL: usize>(buf: &Buffer<BL>, cursor: &mut usize) -> std::io::Result<Self> {
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))
}

View file

@ -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};

View file

@ -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<Vec<u8>>;
fn load_node_identity(&self) -> Option<Identity>;
/// 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<NonZeroI64>, local_interface: Option<NonZeroI64>, 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<NonZeroI64>, local_interface: Option<NonZeroI64>) -> 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<Vec<(Endpoint, Option<NonZeroI64>, Option<NonZeroI64>)>>;
@ -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<Path>, forward_secrecy: bool, extended_authentication: bool, verb: u8, payload: &Buffer<{ PACKET_SIZE_MAX }>) -> bool;
fn handle_packet<SI: SystemInterface>(&self, peer: &Peer<SI>, source_path: &Path<SI>, 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<Path>, 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<SI: SystemInterface>(&self, peer: &Peer<SI>, source_path: &Path<SI>, 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<Path>, 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<SI: SystemInterface>(&self, peer: &Peer<SI>, source_path: &Path<SI>, 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<SI: SystemInterface>(&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_SYNC_INTERVAL_MS>,
root_hello: IntervalGate<ROOT_HELLO_INTERVAL>,
}
struct RootInfo {
roots: HashMap<Arc<Peer>, Vec<Endpoint>>,
struct RootInfo<SI: SystemInterface> {
roots: HashMap<Arc<Peer<SI>>, Vec<Endpoint>>,
sets: HashMap<String, RootSet>,
sets_modified: bool,
}
/// A VL1 global P2P network node.
pub struct Node {
pub struct Node<SI: SystemInterface> {
/// A random ID generated to identify this particular running instance.
pub instance_id: u64,
@ -150,16 +147,16 @@ pub struct Node {
intervals: Mutex<BackgroundTaskIntervals>,
/// Canonicalized network paths, held as Weak<> to be automatically cleaned when no longer in use.
paths: DashMap<(u64, u64), Weak<Path>>,
paths: DashMap<Endpoint, parking_lot::RwLock<HashMap<SI::LocalSocket, Arc<Path<SI>>>>>,
/// Peers with which we are currently communicating.
peers: DashMap<Address, Arc<Peer>>,
peers: DashMap<Address, Arc<Peer<SI>>>,
/// This node's trusted roots, sorted in ascending order of quality/preference, and cluster definitions.
roots: Mutex<RootInfo>,
roots: Mutex<RootInfo<SI>>,
/// Current best root.
best_root: RwLock<Option<Arc<Peer>>>,
best_root: RwLock<Option<Arc<Peer<SI>>>>,
/// 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<SI: SystemInterface> Node<SI> {
/// Create a new Node.
pub fn new<SI: SystemInterface>(si: &SI, auto_generate_identity: bool) -> Result<Self, InvalidParameterError> {
pub fn new(si: &SI, auto_generate_identity: bool) -> Result<Self, InvalidParameterError> {
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 {
@ -181,15 +178,9 @@ impl Node {
si.save_node_identity(&id);
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()
}
}
};
// Automatically upgrade old type identities to add P-384 keys.
@ -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<Arc<Peer>> {
pub fn peer(&self, a: Address) -> Option<Arc<Peer<SI>>> {
self.peers.get(&a).map(|peer| peer.value().clone())
}
/// Get all peers currently in the peer cache.
pub fn peers(&self) -> Vec<Arc<Peer>> {
let mut v: Vec<Arc<Peer>> = 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<SI: SystemInterface>(&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::<SI>::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<Peer>> = None;
let mut best: Option<&Arc<Peer<SI>>> = 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<SI: SystemInterface, PH: InnerProtocolInterface>(&self, si: &SI, ph: &PH, source_endpoint: &Endpoint, source_local_socket: Option<NonZeroI64>, source_local_interface: Option<NonZeroI64>, mut data: PacketBuffer) {
pub fn wire_receive<PH: InnerProtocolInterface>(&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::<FragmentHeader>(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<Arc<Peer>> {
pub fn root(&self) -> Option<Arc<Peer<SI>>> {
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<SI>) -> 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<NonZeroI64>, local_interface: Option<NonZeroI64>) -> Arc<Path> {
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<Path<SI>> {
// 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();
}
}

View file

@ -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<SI: SystemInterface> {
pub endpoint: Endpoint,
pub local_socket: Option<NonZeroI64>,
pub local_interface: Option<NonZeroI64>,
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<HashMap<u64, FragmentedPacket, U64NoOpHasher>>,
}
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<NonZeroI64>, local_interface: Option<NonZeroI64>) -> (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<NonZeroI64>, local_interface: Option<NonZeroI64>) -> Self {
impl<SI: SystemInterface> Path<SI> {
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<FragmentedPacket> {
pub(crate) fn receive_fragment(&self, packet_id: u64, fragment_no: u8, fragment_expecting_count: u8, packet: PooledPacketBuffer, time_ticks: i64) -> Option<FragmentedPacket> {
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<SI: SystemInterface>(&self, si: &SI, _: &Node, time_ticks: i64) -> bool {
self.fragmented_packets.lock().retain(|_, frag| (time_ticks - frag.ts_ticks) < PACKET_FRAGMENT_EXPIRATION);
pub(crate) fn service(&self, si: &SI, _: &Node<SI>, 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, self.local_socket, self.local_interface, &[&ZEROES[..1]], 0);
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
}
}
}

View file

@ -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<SI: SystemInterface> {
path: Weak<Path<SI>>,
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<SI: SystemInterface> {
// 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<Option<Arc<EphemeralSymmetricSecret>>>,
ephemeral_symmetric_key: RwLock<Option<EphemeralSymmetricSecret>>,
// Paths sorted in descending order of quality / preference.
paths: Mutex<Vec<Arc<Path>>>,
paths: Mutex<Vec<PeerPath<SI>>>,
// 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<PacketBuffer>], payload: &mut Buffer<PACKET_SIZE_MAX>) -> Option<u64> {
fn try_aead_decrypt(secret: &SymmetricSecret, packet_frag0_payload_bytes: &[u8], header: &PacketHeader, fragments: &[Option<PooledPacketBuffer>], payload: &mut PacketBuffer) -> Option<u64> {
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<SI: SystemInterface> Peer<SI> {
/// 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<Peer> {
this_node_identity.agree(&id).map(|static_secret| -> Peer {
Peer {
pub(crate) fn new(this_node_identity: &Identity, id: Identity, time_clock: i64) -> Option<Peer<SI>> {
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<SI: SystemInterface, VI: InnerProtocolInterface>(&self, node: &Node, si: &SI, vi: &VI, time_ticks: i64, source_path: &Arc<Path>, header: &PacketHeader, frag0: &Buffer<{ PACKET_SIZE_MAX }>, fragments: &[Option<PacketBuffer>]) {
let _ = frag0.as_bytes_starting_at(PACKET_VERB_INDEX).map(|packet_frag0_payload_bytes| {
let mut payload: Buffer<PACKET_SIZE_MAX> = unsafe { Buffer::new_without_memzero() };
pub(crate) fn receive<VI: InnerProtocolInterface>(&self, node: &Node<SI>, si: &SI, vi: &VI, time_ticks: i64, source_path: &Arc<Path<SI>>, header: &PacketHeader, frag0: &PacketBuffer, fragments: &[Option<PooledPacketBuffer>]) {
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.total_bytes_received.fetch_add((payload.len() + PACKET_HEADER_SIZE) as u64, Ordering::Relaxed);
let mut verb = payload.as_bytes()[0];
// If this flag is set, the end of the payload is a full HMAC-SHA384 authentication
// tag for much stronger authentication than is offered by the packet MAC.
let extended_authentication = (verb & VERB_FLAG_EXTENDED_AUTHENTICATION) != 0;
let extended_authentication = (verb & packet_constants::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;
//}
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;
}
}
if (verb & VERB_FLAG_COMPRESSED) != 0 {
let mut decompressed_payload: [u8; PACKET_SIZE_MAX] = unsafe { MaybeUninit::uninit().assume_init() };
// ---------------------------------------------------------------
// If we made it here it decrypted and passed authentication.
// ---------------------------------------------------------------
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;
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)]);
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 &= VERB_MASK; // mask off flags
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 => {}
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),
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),
_ => {}
}
}
});
}
}
}
fn send_to_endpoint<SI: SystemInterface>(&self, si: &SI, endpoint: &Endpoint, local_socket: Option<NonZeroI64>, local_interface: Option<NonZeroI64>, 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<SI: SystemInterface>(&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<SI>, 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<SI: SystemInterface>(&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<SI: SystemInterface>(&self, si: &SI, node: &Node, explicit_endpoint: Option<&Endpoint>) -> bool {
pub(crate) fn send_hello(&self, si: &SI, node: &Node<SI>, 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<PACKET_SIZE_MAX> = 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::<PacketHeader>(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<SI: SystemInterface>(&self, si: &SI, node: &Node, time_ticks: i64, source_path: &Arc<Path>, payload: &Buffer<PACKET_SIZE_MAX>) {}
fn receive_hello(&self, si: &SI, node: &Node<SI>, time_ticks: i64, source_path: &Arc<Path<SI>>, payload: &PacketBuffer) {}
#[inline(always)]
fn receive_error<SI: SystemInterface, PH: InnerProtocolInterface>(&self, si: &SI, ph: &PH, node: &Node, time_ticks: i64, source_path: &Arc<Path>, forward_secrecy: bool, extended_authentication: bool, payload: &Buffer<PACKET_SIZE_MAX>) {
let mut cursor: usize = 0;
let _ = payload.read_struct::<message_component_structs::ErrorHeader>(&mut cursor).map(|error_header| {
fn receive_error<PH: InnerProtocolInterface>(&self, si: &SI, ph: &PH, node: &Node<SI>, time_ticks: i64, source_path: &Arc<Path<SI>>, forward_secrecy: bool, extended_authentication: bool, payload: &PacketBuffer) {
let mut cursor: usize = 1;
if let Ok(error_header) = payload.read_struct::<message_component_structs::ErrorHeader>(&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<SI: SystemInterface, PH: InnerProtocolInterface>(&self, si: &SI, ph: &PH, node: &Node, time_ticks: i64, source_path: &Arc<Path>, forward_secrecy: bool, extended_authentication: bool, payload: &Buffer<PACKET_SIZE_MAX>) {
let mut cursor: usize = 0;
let _ = payload.read_struct::<message_component_structs::OkHeader>(&mut cursor).map(|ok_header| {
fn receive_ok<PH: InnerProtocolInterface>(&self, si: &SI, ph: &PH, node: &Node<SI>, time_ticks: i64, source_path: &Arc<Path<SI>>, forward_secrecy: bool, extended_authentication: bool, payload: &PacketBuffer) {
let mut cursor: usize = 1;
if let Ok(ok_header) = payload.read_struct::<message_component_structs::OkHeader>(&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<SI: SystemInterface>(&self, si: &SI, node: &Node, time_ticks: i64, source_path: &Arc<Path>, payload: &Buffer<{ PACKET_SIZE_MAX }>) {}
fn receive_whois(&self, si: &SI, node: &Node<SI>, time_ticks: i64, source_path: &Arc<Path<SI>>, payload: &PacketBuffer) {}
#[inline(always)]
fn receive_rendezvous<SI: SystemInterface>(&self, si: &SI, node: &Node, time_ticks: i64, source_path: &Arc<Path>, payload: &Buffer<{ PACKET_SIZE_MAX }>) {}
fn receive_rendezvous(&self, si: &SI, node: &Node<SI>, time_ticks: i64, source_path: &Arc<Path<SI>>, payload: &PacketBuffer) {}
#[inline(always)]
fn receive_echo<SI: SystemInterface>(&self, si: &SI, node: &Node, time_ticks: i64, source_path: &Arc<Path>, payload: &Buffer<{ PACKET_SIZE_MAX }>) {}
fn receive_echo(&self, si: &SI, node: &Node<SI>, time_ticks: i64, source_path: &Arc<Path<SI>>, payload: &PacketBuffer) {}
#[inline(always)]
fn receive_push_direct_paths<SI: SystemInterface>(&self, si: &SI, node: &Node, time_ticks: i64, source_path: &Arc<Path>, payload: &Buffer<{ PACKET_SIZE_MAX }>) {}
fn receive_push_direct_paths(&self, si: &SI, node: &Node<SI>, time_ticks: i64, source_path: &Arc<Path<SI>>, payload: &PacketBuffer) {}
#[inline(always)]
fn receive_user_message<SI: SystemInterface>(&self, si: &SI, node: &Node, time_ticks: i64, source_path: &Arc<Path>, payload: &Buffer<{ PACKET_SIZE_MAX }>) {}
fn receive_user_message(&self, si: &SI, node: &Node<SI>, time_ticks: i64, source_path: &Arc<Path<SI>>, 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<Arc<Path>> {
self.paths.lock().first().map(|p| p.clone())
pub fn direct_path(&self) -> Option<Arc<Path<SI>>> {
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<Arc<Path>> {
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<SI>) -> Option<Arc<Path<SI>>> {
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<SI>, 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<SI: SystemInterface> PartialEq for Peer<SI> {
#[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<SI: SystemInterface> Eq for Peer<SI> {}
impl Hash for Peer {
impl<SI: SystemInterface> Hash for Peer<SI> {
#[inline(always)]
fn hash<H: Hasher>(&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<SI: SystemInterface>(&self, si: &SI, node: &Node, time_ticks: i64) -> bool {
true
self.identity.hash(state);
}
}

View file

@ -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<PacketBuffer, PacketBufferFactory>;
/// Source for instances of PacketBuffer
pub type PacketBufferPool = crate::util::pool::Pool<PacketBuffer, PacketBufferFactory>;
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,6 +83,109 @@ pub const VERB_VL1_USER_MESSAGE: u8 = 0x14;
/// two fragments.
pub const UDP_DEFAULT_MTU: usize = 1432;
/// Length of an address in bytes.
pub const ADDRESS_SIZE: usize = 5;
/// Length of an address in string format.
pub const ADDRESS_SIZE_STRING: usize = 10;
/// Prefix indicating reserved addresses (that can't actually be addresses).
pub const ADDRESS_RESERVED_PREFIX: u8 = 0xff;
pub mod packet_constants {
/// Size of packet header that lies outside the encryption envelope.
pub const HEADER_SIZE: usize = 27;
/// 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;
/// Minimum packet, which is the header plus a verb.
pub const SIZE_MIN: usize = HEADER_SIZE + 1;
/// Maximum size of an entire packet.
pub const SIZE_MAX: usize = HEADER_SIZE + PAYLOAD_SIZE_MAX;
/// Index of packet verb after header.
pub const VERB_INDEX: usize = 27;
/// 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 MAC_FIELD_INDEX: usize = 19;
/// 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 FLAGS_FIELD_MASK_HOPS: u8 = 0x07;
/// 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 FLAGS_FIELD_INDEX: usize = 18;
/// Minimum size of a fragment.
pub const FRAGMENT_SIZE_MIN: usize = 16;
/// Size of fragment header after which data begins.
pub const FRAGMENT_HEADER_SIZE: usize = 16;
/// Maximum allowed number of fragments.
pub const FRAGMENT_COUNT_MAX: usize = 8;
/// Time after which an incomplete fragmented packet expires.
pub const FRAGMENT_EXPIRATION: i64 = 1500;
/// Maximum number of inbound fragmented packets to handle at once per path.
/// This is a sanity limit to prevent memory exhaustion due to DOS attacks or broken peers.
pub const FRAGMENT_MAX_INBOUND_PACKETS_PER_PATH: usize = 256;
/// Index of packet fragment indicator byte to detect fragments.
pub const FRAGMENT_INDICATOR_INDEX: usize = 13;
/// Byte found at FRAGMENT_INDICATOR_INDEX to indicate a fragment.
pub const FRAGMENT_INDICATOR: u8 = 0xff;
/// Verb (inner) flag indicating that the packet's payload (after the verb) is LZ4 compressed.
pub const VERB_FLAG_COMPRESSED: u8 = 0x80;
/// Verb (inner) flag indicating that payload is authenticated with HMAC-SHA384.
pub const VERB_FLAG_EXTENDED_AUTHENTICATION: u8 = 0x40;
/// Mask to get only the verb from the verb + verb flags byte.
pub const VERB_MASK: u8 = 0x1f;
/// Maximum number of verbs that the protocol can support.
pub const VERB_MAX_COUNT: usize = 32;
/// Header (outer) flag indicating that this packet has additional fragments.
pub const HEADER_FLAG_FRAGMENTED: u8 = 0x40;
}
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;
/// 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;
/// Formerly 'NONE' which is deprecated; reserved for future use.
pub const CIPHER_RESERVED: u8 = 0x20;
/// Packet is encrypted and authenticated with AES-GMAC-SIV (AES-256).
pub const CIPHER_AES_GMAC_SIV: u8 = 0x30;
/// KBKDF usage label indicating a key used to HMAC packets for extended authentication.
pub const KBKDF_KEY_USAGE_LABEL_PACKET_HMAC: u8 = b'M';
@ -55,112 +212,13 @@ pub const EPHEMERAL_SECRET_REJECT_AFTER_TIME: i64 = EPHEMERAL_SECRET_REKEY_AFTER
/// 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;
/// Length of an address in string format.
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;
/// Minimum packet, which is the header plus a verb.
pub const PACKET_SIZE_MIN: usize = PACKET_HEADER_SIZE + 1;
/// Maximum size of an entire packet.
pub const PACKET_SIZE_MAX: usize = PACKET_HEADER_SIZE + PACKET_PAYLOAD_SIZE_MAX;
/// 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;
/// Index of packet verb after header.
pub const PACKET_VERB_INDEX: usize = 27;
/// Index of destination in both fragment and full packet headers.
pub const PACKET_DESTINATION_INDEX: usize = 8;
/// Index of 8-byte MAC field in packet header.
pub const HEADER_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 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 HEADER_FLAGS_FIELD_MASK_HIDE_HOPS: u8 = 0xf8;
/// Index of hops/flags field
pub const HEADER_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;
/// 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;
/// Formerly 'NONE' which is deprecated; reserved for future use.
pub const CIPHER_RESERVED: u8 = 0x20;
/// Packet is encrypted and authenticated with AES-GMAC-SIV (AES-256).
pub const CIPHER_AES_GMAC_SIV: u8 = 0x30;
/// Header (outer) flag indicating that this packet has additional fragments.
pub const HEADER_FLAG_FRAGMENTED: u8 = 0x40;
/// Minimum size of a fragment.
pub const PACKET_FRAGMENT_SIZE_MIN: usize = 16;
/// Size of fragment header after which data begins.
pub const FRAGMENT_HEADER_SIZE: usize = 16;
/// Maximum allowed number of fragments.
pub const PACKET_FRAGMENT_COUNT_MAX: usize = 8;
/// Time after which an incomplete fragmented packet expires.
pub const PACKET_FRAGMENT_EXPIRATION: i64 = 1500;
/// Maximum number of inbound fragmented packets to handle at once per path.
/// This is a sanity limit to prevent memory exhaustion due to DOS attacks or broken peers.
pub const PACKET_FRAGMENT_MAX_INBOUND_PACKETS_PER_PATH: usize = 256;
/// Index of packet fragment indicator byte to detect fragments.
pub const PACKET_FRAGMENT_INDICATOR_INDEX: usize = 13;
/// Byte found at FRAGMENT_INDICATOR_INDEX to indicate a fragment.
pub const PACKET_FRAGMENT_INDICATOR: u8 = 0xff;
/// Verb (inner) flag indicating that the packet's payload (after the verb) is LZ4 compressed.
pub const VERB_FLAG_COMPRESSED: u8 = 0x80;
/// Verb (inner) flag indicating that payload is authenticated with HMAC-SHA384.
pub const VERB_FLAG_EXTENDED_AUTHENTICATION: u8 = 0x40;
/// Mask to get only the verb from the verb + verb flags byte.
pub const VERB_MASK: u8 = 0x1f;
/// Maximum number of verbs that the protocol can support.
pub const VERB_MAX_COUNT: usize = 32;
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<PACKET_SIZE_MAX>) -> bool {
if src.len() > (PACKET_VERB_INDEX + 16) {
pub fn compress_packet<const S: usize>(src: &[u8], dest: &mut Buffer<S>) -> 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<PACKET_SIZE_MAX>) -> 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::<message_component_structs::OkHeader>(), 9);
assert_eq!(size_of::<message_component_structs::ErrorHeader>(), 10);
assert_eq!(size_of::<PacketHeader>(), PACKET_HEADER_SIZE);
assert_eq!(size_of::<FragmentHeader>(), FRAGMENT_HEADER_SIZE);
assert_eq!(size_of::<PacketHeader>(), packet_constants::HEADER_SIZE);
assert_eq!(size_of::<FragmentHeader>(), packet_constants::FRAGMENT_HEADER_SIZE);
let mut foo = [0_u8; 32];
unsafe {

View file

@ -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<std::cmp::Ordering> {
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<MAX_MARSHAL_SIZE> {
let mut tmp = Buffer::<MAX_MARSHAL_SIZE>::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<const BL: usize>(&self, buf: &mut Buffer<BL>) -> std::io::Result<()> {

View file

@ -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<AesGmacSiv> 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<AesGmacSiv, AesGmacSivPoolFactory>,
}
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<AesGmacSiv> 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();
}
}

View file

@ -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<SI: SystemInterface>(&self, node: &Node, si: &SI, target: Address, packet: Option<QueuedPacket>) {
pub fn query<SI: SystemInterface>(&self, node: &Node<SI>, si: &SI, target: Address, packet: Option<QueuedPacket>) {
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<SI: SystemInterface>(&self, node: &Node, si: &SI, targets: &[Address]) {
fn send_whois<SI: SystemInterface>(&self, node: &Node<SI>, si: &SI, targets: &[Address]) {
todo!()
}
}
impl BackgroundServicable for WhoisQueue {
const SERVICE_INTERVAL_MS: i64 = WHOIS_RETRY_INTERVAL;
fn service<SI: SystemInterface>(&self, si: &SI, node: &Node, time_ticks: i64) -> bool {
pub(crate) fn service<SI: SystemInterface>(&self, si: &SI, node: &Node<SI>, time_ticks: i64) -> bool {
let mut targets: Vec<Address> = Vec::new();
self.0.lock().retain(|target, qi| {
if qi.retry_count < WHOIS_RETRY_MAX {

View file

@ -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<Path>, forward_secrecy: bool, extended_authentication: bool, verb: u8, payload: &Buffer<{ PACKET_SIZE_MAX }>) -> bool {
fn handle_packet<SI: SystemInterface>(&self, peer: &Peer<SI>, source_path: &Path<SI>, forward_secrecy: bool, extended_authentication: bool, verb: u8, payload: &PacketBuffer) -> bool {
false
}
fn handle_error(&self, peer: &Peer, source_path: &Arc<Path>, 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<SI: SystemInterface>(&self, peer: &Peer<SI>, source_path: &Path<SI>, 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<Path>, 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<SI: SystemInterface>(&self, peer: &Peer<SI>, source_path: &Path<SI>, forward_secrecy: bool, extended_authentication: bool, in_re_verb: u8, in_re_message_id: u64, payload: &PacketBuffer, cursor: &mut usize) -> bool {
false
}

View file

@ -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",

View file

@ -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!(),

View file

@ -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));

View file

@ -19,7 +19,7 @@ fn s6_addr_as_ptr<A>(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<F: FnMut(&InetAddress, &str)>(mut f: F) {
pub fn for_each_address<F: FnMut(&InetAddress, &str)>(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.")
}
}

View file

@ -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<String>,
}
async fn async_main(flags: Flags, global_args: ArgMatches) -> i32 {
async fn async_main(flags: Flags, global_args: Box<ArgMatches>) -> 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"),

View file

@ -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<dyn Error>;
pub struct Service {
pub rt: tokio::runtime::Handle,
pub data: DataDir,
pub local_socket_unique_id_counter: AtomicUsize,
pub udp_sockets: parking_lot::RwLock<HashMap<u16, Vec<Arc<BoundUdpSocket>>>>,
pub core: Option<NetworkHypervisor<Self>>,
}
impl Service {
pub async fn new(base_path: &str) -> Result<Self, DynamicError> {
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<BoundUdpSocket>, 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<H: std::hash::Hasher>(&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<Identity> {
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<Vec<(Endpoint, Option<Self::LocalSocket>, Option<Self::LocalInterface>)>> {
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 {}

View file

@ -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<Arc<BoundUdpSocket>>, port: u16, device_prefix_blacklist: &Vec<String>, cidr_blacklist: &Vec<InetAddress>) -> Vec<std::io::Error> {
let mut errors = Vec::new();
let mut existing_bind_points: HashMap<String, Vec<InetAddress>> = 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<RawFd, &'static str> {
let (af, sa_len) = match address.family() {
InetAddressFamily::IPv4 => (libc::AF_INET, std::mem::size_of::<libc::sockaddr_in>().as_()),
InetAddressFamily::IPv6 => (libc::AF_INET6, std::mem::size_of::<libc::sockaddr_in6>().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::<c_int>().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::<c_int>().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::<c_int>().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::<c_int>().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::<c_int>().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::<c_int>().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::<c_int>().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::<c_int>().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)
}

View file

@ -96,85 +96,6 @@ lazy_static! {
static ref MAC_FETH_BPF_DEVICES_USED: Mutex<BTreeSet<u32>> = 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 },

View file

@ -6,8 +6,9 @@
* https://www.zerotier.com/
*/
pub mod vnic;
//mod common;
mod vnic;
//#[cfg(target_os = "macos")]
//mod mac_feth_tap;