mirror of
https://github.com/zerotier/ZeroTierOne.git
synced 2025-04-26 17:03:43 +02:00
Lots of Rust stuff.
This commit is contained in:
parent
617b7c86b6
commit
a8da84c055
23 changed files with 613 additions and 191 deletions
|
@ -7,6 +7,13 @@ edition = "2018"
|
|||
opt-level = 3
|
||||
lto = true
|
||||
codegen-units = 1
|
||||
panic = 'abort'
|
||||
|
||||
[profile.release]
|
||||
opt-level = 3
|
||||
lto = true
|
||||
codegen-units = 1
|
||||
panic = 'abort'
|
||||
|
||||
[target."cfg(not(any(target_os = \"macos\", target_os = \"ios\")))".dependencies]
|
||||
gcrypt = "^0"
|
||||
|
|
39
network-hypervisor/Cargo.lock
generated
39
network-hypervisor/Cargo.lock
generated
|
@ -190,12 +190,6 @@ dependencies = [
|
|||
"cfg-if 1.0.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "itoa"
|
||||
version = "0.4.7"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "dd25036021b0de88a0aff6b850051563c6516d0bf53f8638938edbb9de732736"
|
||||
|
||||
[[package]]
|
||||
name = "libc"
|
||||
version = "0.2.98"
|
||||
|
@ -369,12 +363,6 @@ dependencies = [
|
|||
"bitflags",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "ryu"
|
||||
version = "1.0.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "71d301d4193d031abdd79ff7e3dd721168a9572ef3fe51a1517aba235bd8f86e"
|
||||
|
||||
[[package]]
|
||||
name = "scopeguard"
|
||||
version = "1.1.0"
|
||||
|
@ -386,31 +374,6 @@ name = "serde"
|
|||
version = "1.0.126"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ec7505abeacaec74ae4778d9d9328fe5a5d04253220a85c4ee022239fc996d03"
|
||||
dependencies = [
|
||||
"serde_derive",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "serde_derive"
|
||||
version = "1.0.126"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "963a7dbc9895aeac7ac90e74f34a5d5261828f79df35cbed41e10189d3804d43"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "serde_json"
|
||||
version = "1.0.66"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "336b10da19a12ad094b59d870ebde26a45402e5b470add4b5fd03c5048a32127"
|
||||
dependencies = [
|
||||
"itoa",
|
||||
"ryu",
|
||||
"serde",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "sha2"
|
||||
|
@ -588,8 +551,6 @@ dependencies = [
|
|||
"lz4_flex",
|
||||
"parking_lot",
|
||||
"rand_core",
|
||||
"serde",
|
||||
"serde_json",
|
||||
"urlencoding",
|
||||
"winapi",
|
||||
"x25519-dalek",
|
||||
|
|
|
@ -5,7 +5,7 @@ edition = "2018"
|
|||
|
||||
[profile.release]
|
||||
lto = true
|
||||
opt-level = 'z'
|
||||
opt-level = 3
|
||||
codegen-units = 1
|
||||
panic = 'abort'
|
||||
|
||||
|
@ -20,8 +20,6 @@ urlencoding = "^2"
|
|||
lz4_flex = { version = "^0", features = ["safe-encode", "safe-decode", "checked-decode"] }
|
||||
dashmap = "^4"
|
||||
parking_lot = "^0"
|
||||
serde = { version = "1", features = ["derive"] }
|
||||
serde_json = "1"
|
||||
|
||||
[target."cfg(not(windows))".dependencies]
|
||||
libc = "^0"
|
||||
|
|
|
@ -15,7 +15,6 @@ impl SHA512 {
|
|||
h
|
||||
}
|
||||
|
||||
/// Compute HMAC-SHA512(key, msg)
|
||||
#[inline(always)]
|
||||
pub fn hmac(key: &[u8], msg: &[u8]) -> [u8; SHA512_HASH_SIZE] {
|
||||
let mut m = gcrypt::mac::Mac::new(gcrypt::mac::Algorithm::HmacSha512).unwrap();
|
||||
|
@ -47,8 +46,6 @@ impl SHA512 {
|
|||
self.0.get_only_digest().unwrap().try_into().unwrap()
|
||||
}
|
||||
|
||||
/// Return a reference to an internally stored result.
|
||||
/// This saves a copy, but the returned result is only valid so long as no other methods are called.
|
||||
#[inline(always)]
|
||||
pub fn finish_get_ref(&mut self) -> &[u8] {
|
||||
self.0.finish();
|
||||
|
@ -79,7 +76,6 @@ impl SHA384 {
|
|||
h
|
||||
}
|
||||
|
||||
/// Compute HMAC-SHA384(key, msg)
|
||||
#[inline(always)]
|
||||
pub fn hmac(key: &[u8], msg: &[u8]) -> [u8; SHA384_HASH_SIZE] {
|
||||
let mut m = gcrypt::mac::Mac::new(gcrypt::mac::Algorithm::HmacSha384).unwrap();
|
||||
|
@ -111,8 +107,6 @@ impl SHA384 {
|
|||
self.0.get_only_digest().unwrap().try_into().unwrap()
|
||||
}
|
||||
|
||||
/// Return a reference to an internally stored result.
|
||||
/// This saves a copy, but the returned result is only valid so long as no other methods are called.
|
||||
#[inline(always)]
|
||||
pub fn finish_get_ref(&mut self) -> &[u8] {
|
||||
self.0.finish();
|
||||
|
|
|
@ -5,8 +5,10 @@ use crate::crypto::secret::Secret;
|
|||
/// This is a fixed cost key derivation function used to derive sub-keys from a single original
|
||||
/// shared secret for different uses, such as the K0/K1 in AES-GMAC-SIV.
|
||||
/// Key must be 384 bits in length.
|
||||
#[inline(always)]
|
||||
pub fn zt_kbkdf_hmac_sha384(key: &[u8], label: u8, context: u8, iter: u32) -> Secret<{ SHA384_HASH_SIZE }> {
|
||||
debug_assert!(key.len() == SHA384_HASH_SIZE);
|
||||
debug_assert_eq!(key.len(), SHA384_HASH_SIZE);
|
||||
|
||||
// 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
|
||||
Secret(SHA384::hmac(key, &[(iter >> 24) as u8, (iter >> 16) as u8, (iter >> 8) as u8, iter as u8, b'Z', b'T', label, 0, context, 0, 0, 0x01, 0x80]))
|
||||
|
|
|
@ -9,8 +9,3 @@ pub mod random;
|
|||
pub mod secret;
|
||||
|
||||
pub use aes_gmac_siv;
|
||||
|
||||
pub fn init() {
|
||||
// We always run gcrypt in "FIPS mode," but it doesn't count as fully compliant unless it's a FIPS-certified library.
|
||||
let _ = gcrypt::init_fips_mode(|_| -> Result<(), std::convert::Infallible> { Ok(()) });
|
||||
}
|
||||
|
|
|
@ -39,3 +39,22 @@ impl RngCore for SecureRandom {
|
|||
}
|
||||
|
||||
impl CryptoRng for SecureRandom {}
|
||||
|
||||
#[inline(always)]
|
||||
pub(crate) fn next_u32_secure() -> u32 {
|
||||
let mut tmp = 0_u32;
|
||||
randomize(Level::Strong, unsafe { &mut *(&mut tmp as *mut u32).cast::<[u8; 4]>() });
|
||||
tmp
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
pub(crate) fn next_u64_secure() -> u64 {
|
||||
let mut tmp = 0_u64;
|
||||
randomize(Level::Strong, unsafe { &mut *(&mut tmp as *mut u64).cast::<[u8; 8]>() });
|
||||
tmp
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
pub(crate) fn fill_bytes_secure(dest: &mut [u8]) {
|
||||
randomize(Level::Strong, dest);
|
||||
}
|
||||
|
|
|
@ -3,7 +3,7 @@ use std::ptr::write_volatile;
|
|||
|
||||
/// Container for secrets that clears them on drop.
|
||||
#[derive(Clone, PartialEq, Eq)]
|
||||
pub struct Secret<const L: usize>(pub [u8; L]);
|
||||
pub struct Secret<const L: usize>(pub(crate) [u8; L]);
|
||||
|
||||
impl<const L: usize> Secret<L> {
|
||||
#[inline(always)]
|
||||
|
|
|
@ -10,8 +10,9 @@ impl Display for InvalidFormatError {
|
|||
}
|
||||
|
||||
impl Debug for InvalidFormatError {
|
||||
#[inline(always)]
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
f.write_str("InvalidFormatError")
|
||||
<Self as Display>::fmt(self, f)
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -26,8 +27,9 @@ impl Display for InvalidParameterError {
|
|||
}
|
||||
|
||||
impl Debug for InvalidParameterError {
|
||||
#[inline(always)]
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
write!(f, "InvalidParameterError: {}", self.0)
|
||||
<Self as Display>::fmt(self, f)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -5,6 +5,6 @@ pub mod vl1;
|
|||
pub mod vl2;
|
||||
|
||||
pub const VERSION_MAJOR: u8 = 1;
|
||||
pub const VERSION_MINOR: u8 = 9;
|
||||
pub const VERSION_MINOR: u8 = 99;
|
||||
pub const VERSION_REVISION: u8 = 1;
|
||||
pub const VERSION_STR: &'static str = "1.9.1";
|
||||
pub const VERSION_STR: &'static str = "1.99.1";
|
||||
|
|
|
@ -36,6 +36,7 @@ impl<O: Reusable> Pooled<O> {
|
|||
pub unsafe fn into_raw(self) -> *mut O {
|
||||
debug_assert!(!self.0.is_null());
|
||||
debug_assert_eq!(self.0.cast::<u8>(), (&mut (*self.0).obj as *mut O).cast::<u8>());
|
||||
std::mem::forget(self);
|
||||
self.0.cast()
|
||||
}
|
||||
|
||||
|
|
|
@ -1,9 +1,9 @@
|
|||
use std::str::FromStr;
|
||||
use std::hash::{Hash, Hasher};
|
||||
use std::str::FromStr;
|
||||
|
||||
use crate::vl1::constants::{ADDRESS_RESERVED_PREFIX, ADDRESS_SIZE};
|
||||
use crate::error::InvalidFormatError;
|
||||
use crate::util::hex::HEX_CHARS;
|
||||
use crate::vl1::constants::{ADDRESS_RESERVED_PREFIX, ADDRESS_SIZE};
|
||||
|
||||
#[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
|
||||
pub struct Address(u64);
|
||||
|
|
|
@ -50,31 +50,42 @@ impl<const L: usize> Buffer<L> {
|
|||
}
|
||||
}
|
||||
|
||||
/// Get a slice containing the entire buffer in raw form including the header.
|
||||
#[inline(always)]
|
||||
pub fn as_bytes(&self) -> &[u8] {
|
||||
&self.1[0..self.0]
|
||||
}
|
||||
|
||||
/// Get a slice containing the entire buffer in raw form including the header.
|
||||
#[inline(always)]
|
||||
pub fn as_bytes_mut(&mut self) -> &mut [u8] {
|
||||
&mut self.1[0..self.0]
|
||||
}
|
||||
|
||||
/// Erase contents and zero size.
|
||||
/// Get all bytes after a given position.
|
||||
#[inline(always)]
|
||||
pub fn as_bytes_after(&self, start: usize) -> std::io::Result<&[u8]> {
|
||||
if start <= self.0 {
|
||||
Ok(&self.1[start..])
|
||||
} else {
|
||||
Err(std::io::Error::new(std::io::ErrorKind::UnexpectedEof, OVERFLOW_ERR_MSG))
|
||||
}
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
pub fn clear(&mut self) {
|
||||
self.0 = 0;
|
||||
self.1.fill(0);
|
||||
}
|
||||
|
||||
/// Get the length of this buffer (including header, if any).
|
||||
#[inline(always)]
|
||||
pub fn len(&self) -> usize {
|
||||
self.0
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
pub fn is_empty(&self) -> bool {
|
||||
self.0 == 0
|
||||
}
|
||||
|
||||
/// Append a packed structure and call a function to initialize it in place.
|
||||
/// Anything not initialized will be zero.
|
||||
#[inline(always)]
|
||||
|
@ -87,7 +98,7 @@ impl<const L: usize> Buffer<L> {
|
|||
Ok(initializer(&mut *self.1.as_mut_ptr().cast::<u8>().offset(ptr as isize).cast::<T>()))
|
||||
}
|
||||
} else {
|
||||
std::io::Result::Err(std::io::Error::new(std::io::ErrorKind::UnexpectedEof, OVERFLOW_ERR_MSG))
|
||||
Err(std::io::Error::new(std::io::ErrorKind::UnexpectedEof, OVERFLOW_ERR_MSG))
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -104,7 +115,7 @@ impl<const L: usize> Buffer<L> {
|
|||
Ok(initializer(&mut *self.1.as_mut_ptr().cast::<u8>().offset(ptr as isize).cast::<[u8; N]>()))
|
||||
}
|
||||
} else {
|
||||
std::io::Result::Err(std::io::Error::new(std::io::ErrorKind::UnexpectedEof, OVERFLOW_ERR_MSG))
|
||||
Err(std::io::Error::new(std::io::ErrorKind::UnexpectedEof, OVERFLOW_ERR_MSG))
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -118,7 +129,7 @@ impl<const L: usize> Buffer<L> {
|
|||
self.0 = end;
|
||||
Ok(initializer(&mut self.1[ptr..end]))
|
||||
} else {
|
||||
std::io::Result::Err(std::io::Error::new(std::io::ErrorKind::UnexpectedEof, OVERFLOW_ERR_MSG))
|
||||
Err(std::io::Error::new(std::io::ErrorKind::UnexpectedEof, OVERFLOW_ERR_MSG))
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -133,7 +144,7 @@ impl<const L: usize> Buffer<L> {
|
|||
self.1[ptr..end].copy_from_slice(buf);
|
||||
Ok(())
|
||||
} else {
|
||||
std::io::Result::Err(std::io::Error::new(std::io::ErrorKind::UnexpectedEof, OVERFLOW_ERR_MSG))
|
||||
Err(std::io::Error::new(std::io::ErrorKind::UnexpectedEof, OVERFLOW_ERR_MSG))
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -148,7 +159,7 @@ impl<const L: usize> Buffer<L> {
|
|||
self.1[ptr..end].copy_from_slice(buf);
|
||||
Ok(())
|
||||
} else {
|
||||
std::io::Result::Err(std::io::Error::new(std::io::ErrorKind::UnexpectedEof, OVERFLOW_ERR_MSG))
|
||||
Err(std::io::Error::new(std::io::ErrorKind::UnexpectedEof, OVERFLOW_ERR_MSG))
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -161,7 +172,7 @@ impl<const L: usize> Buffer<L> {
|
|||
self.1[ptr] = i;
|
||||
Ok(())
|
||||
} else {
|
||||
std::io::Result::Err(std::io::Error::new(std::io::ErrorKind::UnexpectedEof, OVERFLOW_ERR_MSG))
|
||||
Err(std::io::Error::new(std::io::ErrorKind::UnexpectedEof, OVERFLOW_ERR_MSG))
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -175,7 +186,7 @@ impl<const L: usize> Buffer<L> {
|
|||
crate::util::integer_store_be_u16(i, &mut self.1[ptr..end]);
|
||||
Ok(())
|
||||
} else {
|
||||
std::io::Result::Err(std::io::Error::new(std::io::ErrorKind::UnexpectedEof, OVERFLOW_ERR_MSG))
|
||||
Err(std::io::Error::new(std::io::ErrorKind::UnexpectedEof, OVERFLOW_ERR_MSG))
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -189,7 +200,7 @@ impl<const L: usize> Buffer<L> {
|
|||
crate::util::integer_store_be_u32(i, &mut self.1[ptr..end]);
|
||||
Ok(())
|
||||
} else {
|
||||
std::io::Result::Err(std::io::Error::new(std::io::ErrorKind::UnexpectedEof, OVERFLOW_ERR_MSG))
|
||||
Err(std::io::Error::new(std::io::ErrorKind::UnexpectedEof, OVERFLOW_ERR_MSG))
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -203,7 +214,7 @@ impl<const L: usize> Buffer<L> {
|
|||
crate::util::integer_store_be_u64(i, &mut self.1[ptr..end]);
|
||||
Ok(())
|
||||
} else {
|
||||
std::io::Result::Err(std::io::Error::new(std::io::ErrorKind::UnexpectedEof, OVERFLOW_ERR_MSG))
|
||||
Err(std::io::Error::new(std::io::ErrorKind::UnexpectedEof, OVERFLOW_ERR_MSG))
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -215,7 +226,7 @@ impl<const L: usize> Buffer<L> {
|
|||
Ok(&*self.1.as_ptr().cast::<u8>().offset(ptr as isize).cast::<T>())
|
||||
}
|
||||
} else {
|
||||
std::io::Result::Err(std::io::Error::new(std::io::ErrorKind::UnexpectedEof, OVERFLOW_ERR_MSG))
|
||||
Err(std::io::Error::new(std::io::ErrorKind::UnexpectedEof, OVERFLOW_ERR_MSG))
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -227,7 +238,17 @@ impl<const L: usize> Buffer<L> {
|
|||
Ok(&mut *self.1.as_mut_ptr().cast::<u8>().offset(ptr as isize).cast::<T>())
|
||||
}
|
||||
} else {
|
||||
std::io::Result::Err(std::io::Error::new(std::io::ErrorKind::UnexpectedEof, OVERFLOW_ERR_MSG))
|
||||
Err(std::io::Error::new(std::io::ErrorKind::UnexpectedEof, OVERFLOW_ERR_MSG))
|
||||
}
|
||||
}
|
||||
|
||||
/// Get a byte at a fixed position.
|
||||
#[inline(always)]
|
||||
pub fn u8_at(&self, ptr: usize) -> std::io::Result<u8> {
|
||||
if ptr < self.0 {
|
||||
Ok(self.1[ptr])
|
||||
} else {
|
||||
Err(std::io::Error::new(std::io::ErrorKind::UnexpectedEof, OVERFLOW_ERR_MSG))
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -243,7 +264,7 @@ impl<const L: usize> Buffer<L> {
|
|||
Ok(&*self.1.as_ptr().cast::<u8>().offset(ptr as isize).cast::<T>())
|
||||
}
|
||||
} else {
|
||||
std::io::Result::Err(std::io::Error::new(std::io::ErrorKind::UnexpectedEof, OVERFLOW_ERR_MSG))
|
||||
Err(std::io::Error::new(std::io::ErrorKind::UnexpectedEof, OVERFLOW_ERR_MSG))
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -260,7 +281,7 @@ impl<const L: usize> Buffer<L> {
|
|||
Ok(&*self.1.as_ptr().cast::<u8>().offset(ptr as isize).cast::<[u8; S]>())
|
||||
}
|
||||
} else {
|
||||
std::io::Result::Err(std::io::Error::new(std::io::ErrorKind::UnexpectedEof, OVERFLOW_ERR_MSG))
|
||||
Err(std::io::Error::new(std::io::ErrorKind::UnexpectedEof, OVERFLOW_ERR_MSG))
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -274,7 +295,7 @@ impl<const L: usize> Buffer<L> {
|
|||
*cursor = end;
|
||||
Ok(&self.1[ptr..end])
|
||||
} else {
|
||||
std::io::Result::Err(std::io::Error::new(std::io::ErrorKind::UnexpectedEof, OVERFLOW_ERR_MSG))
|
||||
Err(std::io::Error::new(std::io::ErrorKind::UnexpectedEof, OVERFLOW_ERR_MSG))
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -287,7 +308,7 @@ impl<const L: usize> Buffer<L> {
|
|||
*cursor = ptr + 1;
|
||||
Ok(self.1[ptr])
|
||||
} else {
|
||||
std::io::Result::Err(std::io::Error::new(std::io::ErrorKind::UnexpectedEof, OVERFLOW_ERR_MSG))
|
||||
Err(std::io::Error::new(std::io::ErrorKind::UnexpectedEof, OVERFLOW_ERR_MSG))
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -301,7 +322,7 @@ impl<const L: usize> Buffer<L> {
|
|||
*cursor = end;
|
||||
Ok(crate::util::integer_load_be_u16(&self.1[ptr..end]))
|
||||
} else {
|
||||
std::io::Result::Err(std::io::Error::new(std::io::ErrorKind::UnexpectedEof, OVERFLOW_ERR_MSG))
|
||||
Err(std::io::Error::new(std::io::ErrorKind::UnexpectedEof, OVERFLOW_ERR_MSG))
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -315,7 +336,7 @@ impl<const L: usize> Buffer<L> {
|
|||
*cursor = end;
|
||||
Ok(crate::util::integer_load_be_u32(&self.1[ptr..end]))
|
||||
} else {
|
||||
std::io::Result::Err(std::io::Error::new(std::io::ErrorKind::UnexpectedEof, OVERFLOW_ERR_MSG))
|
||||
Err(std::io::Error::new(std::io::ErrorKind::UnexpectedEof, OVERFLOW_ERR_MSG))
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -329,7 +350,7 @@ impl<const L: usize> Buffer<L> {
|
|||
*cursor = end;
|
||||
Ok(crate::util::integer_load_be_u64(&self.1[ptr..end]))
|
||||
} else {
|
||||
std::io::Result::Err(std::io::Error::new(std::io::ErrorKind::UnexpectedEof, OVERFLOW_ERR_MSG))
|
||||
Err(std::io::Error::new(std::io::ErrorKind::UnexpectedEof, OVERFLOW_ERR_MSG))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -344,7 +365,7 @@ impl<const L: usize> Write for Buffer<L> {
|
|||
self.1[ptr..end].copy_from_slice(buf);
|
||||
Ok(buf.len())
|
||||
} else {
|
||||
std::io::Result::Err(std::io::Error::new(std::io::ErrorKind::UnexpectedEof, OVERFLOW_ERR_MSG))
|
||||
Err(std::io::Error::new(std::io::ErrorKind::UnexpectedEof, OVERFLOW_ERR_MSG))
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -4,6 +4,18 @@ pub const ADDRESS_SIZE: usize = 5;
|
|||
/// Prefix indicating reserved addresses (that can't actually be addresses).
|
||||
pub const ADDRESS_RESERVED_PREFIX: u8 = 0xff;
|
||||
|
||||
/// KBKDF usage label indicating a key used to encrypt the dictionary inside HELLO.
|
||||
pub const KBKDF_KEY_USAGE_LABEL_HELLO_DICTIONARY_ENCRYPT: u8 = b'H';
|
||||
|
||||
/// KBKDF usage label indicating a key used to HMAC packets, which is currently only used for HELLO.
|
||||
pub const KBKDF_KEY_USAGE_LABEL_PACKET_HMAC: u8 = b'M';
|
||||
|
||||
/// KBKDF usage label for the first AES-GMAC-SIV key.
|
||||
pub const KBKDF_KEY_USAGE_LABEL_AES_GMAC_SIV_K0: u8 = b'0';
|
||||
|
||||
/// KBKDF usage label for the second AES-GMAC-SIV key.
|
||||
pub const KBKDF_KEY_USAGE_LABEL_AES_GMAC_SIV_K1: u8 = b'1';
|
||||
|
||||
/// Size of packet header that lies outside the encryption envelope.
|
||||
pub const PACKET_HEADER_SIZE: usize = 27;
|
||||
|
||||
|
@ -22,15 +34,27 @@ 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;
|
||||
|
||||
/// 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;
|
||||
|
||||
/// Maximum number of paths to a remote peer.
|
||||
pub const PEER_MAX_PATHS: usize = 16;
|
||||
|
||||
/// 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;
|
||||
|
@ -51,6 +75,9 @@ pub const HEADER_FLAG_FRAGMENTED: u8 = 0x40;
|
|||
/// 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 = 16;
|
||||
|
||||
|
@ -63,9 +90,15 @@ pub const FRAGMENT_INDICATOR: u8 = 0xff;
|
|||
/// Maximum number of inbound fragments to handle at once per path.
|
||||
pub const FRAGMENT_MAX_PER_PATH: usize = 64;
|
||||
|
||||
/// Time after which an incomplete fragmented packet expires.
|
||||
pub const FRAGMENT_EXPIRATION: i64 = 1500;
|
||||
|
||||
/// Verb (inner) flag indicating that the packet's payload (after the verb) is LZ4 compressed.
|
||||
pub const VERB_FLAG_COMPRESSED: u8 = 0x80;
|
||||
|
||||
/// Mask to get only the verb from the verb + verb flags byte.
|
||||
pub const VERB_MASK: u8 = 0x1f;
|
||||
|
||||
/// Maximum number of packet hops allowed by the protocol.
|
||||
pub const PROTOCOL_MAX_HOPS: usize = 7;
|
||||
|
||||
|
|
|
@ -66,6 +66,11 @@ impl Dictionary {
|
|||
self.0.len()
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
pub fn is_empty(&self) -> bool {
|
||||
self.0.is_empty()
|
||||
}
|
||||
|
||||
pub fn get_str(&self, k: &str) -> Option<&str> {
|
||||
self.0.get(k).map_or(None, |v| std::str::from_utf8(v.as_slice()).map_or(None, |s| Some(s)))
|
||||
}
|
||||
|
|
|
@ -17,16 +17,16 @@ const TYPE_WEBRTC: u8 = 9;
|
|||
|
||||
#[repr(u8)]
|
||||
pub enum Type {
|
||||
Nil = 0,
|
||||
ZeroTier = 1,
|
||||
Ethernet = 2,
|
||||
WifiDirect = 3,
|
||||
Bluetooth = 4,
|
||||
Ip = 5,
|
||||
IpUdp = 6,
|
||||
IpTcp = 7,
|
||||
Http = 8,
|
||||
WebRTC = 9,
|
||||
Nil = TYPE_NIL,
|
||||
ZeroTier = TYPE_ZEROTIER,
|
||||
Ethernet = TYPE_ETHERNET,
|
||||
WifiDirect = TYPE_WIFIDIRECT,
|
||||
Bluetooth = TYPE_BLUETOOTH,
|
||||
Ip = TYPE_IP,
|
||||
IpUdp = TYPE_IPUDP,
|
||||
IpTcp = TYPE_IPTCP,
|
||||
Http = TYPE_HTTP,
|
||||
WebRTC = TYPE_WEBRTC,
|
||||
}
|
||||
|
||||
#[derive(Clone, PartialEq, Eq, PartialOrd, Ord)]
|
||||
|
@ -165,39 +165,39 @@ impl Hash for Endpoint {
|
|||
match self {
|
||||
Endpoint::Nil => {
|
||||
state.write_u8(Type::Nil as u8);
|
||||
},
|
||||
}
|
||||
Endpoint::ZeroTier(a) => {
|
||||
state.write_u8(Type::ZeroTier as u8);
|
||||
state.write_u64(a.to_u64())
|
||||
},
|
||||
}
|
||||
Endpoint::Ethernet(m) => {
|
||||
state.write_u8(Type::Ethernet as u8);
|
||||
state.write_u64(m.to_u64())
|
||||
},
|
||||
}
|
||||
Endpoint::WifiDirect(m) => {
|
||||
state.write_u8(Type::WifiDirect as u8);
|
||||
state.write_u64(m.to_u64())
|
||||
},
|
||||
}
|
||||
Endpoint::Bluetooth(m) => {
|
||||
state.write_u8(Type::Bluetooth as u8);
|
||||
state.write_u64(m.to_u64())
|
||||
},
|
||||
}
|
||||
Endpoint::Ip(ip) => {
|
||||
state.write_u8(Type::Ip as u8);
|
||||
ip.hash(state);
|
||||
},
|
||||
}
|
||||
Endpoint::IpUdp(ip) => {
|
||||
state.write_u8(Type::IpUdp as u8);
|
||||
ip.hash(state);
|
||||
},
|
||||
}
|
||||
Endpoint::IpTcp(ip) => {
|
||||
state.write_u8(Type::IpTcp as u8);
|
||||
ip.hash(state);
|
||||
},
|
||||
}
|
||||
Endpoint::Http(url) => {
|
||||
state.write_u8(Type::Http as u8);
|
||||
url.hash(state);
|
||||
},
|
||||
}
|
||||
Endpoint::WebRTC(offer) => {
|
||||
state.write_u8(Type::WebRTC as u8);
|
||||
offer.hash(state);
|
||||
|
|
|
@ -5,12 +5,14 @@ use crate::vl1::constants::FRAGMENT_COUNT_MAX;
|
|||
use crate::vl1::Path;
|
||||
use crate::vl1::protocol::PacketID;
|
||||
|
||||
pub struct FragmentedPacket {
|
||||
/// Packet fragment re-assembler and container.
|
||||
/// This is only used in the receive path.
|
||||
pub(crate) struct FragmentedPacket {
|
||||
pub id: PacketID,
|
||||
pub ts_ticks: i64,
|
||||
frags: [Option<PacketBuffer>; FRAGMENT_COUNT_MAX],
|
||||
have: u8,
|
||||
expecting: u8,
|
||||
pub frags: [Option<PacketBuffer>; FRAGMENT_COUNT_MAX],
|
||||
pub have: u8,
|
||||
pub expecting: u8,
|
||||
}
|
||||
|
||||
impl Default for FragmentedPacket {
|
||||
|
@ -26,29 +28,32 @@ impl Default for FragmentedPacket {
|
|||
}
|
||||
|
||||
impl FragmentedPacket {
|
||||
/// Reset this fragmented packet for re-use.
|
||||
/// Return fragments to pool and reset id and ts_ticks to 0 and -1 respectively.
|
||||
#[inline(always)]
|
||||
pub fn reset(&mut self) {
|
||||
pub fn clear(&mut self) {
|
||||
self.id = 0;
|
||||
self.ts_ticks = -1;
|
||||
self.frags.fill(None);
|
||||
self.have = 0;
|
||||
self.expecting = 0;
|
||||
}
|
||||
|
||||
/// Initialize for a new packet.
|
||||
/// Initialize for a new packet and log the first fragment.
|
||||
/// This will panic if 'no' is out of bounds.
|
||||
#[inline(always)]
|
||||
pub fn init(&mut self, id: PacketID, ts_ticks: i64) {
|
||||
pub fn first_fragment(&mut self, id: PacketID, ts_ticks: i64, frag: PacketBuffer, no: u8, expecting: u8) {
|
||||
self.id = id;
|
||||
self.ts_ticks = ts_ticks;
|
||||
let _ = self.frags[no as usize].replace(frag);
|
||||
self.have = 1;
|
||||
self.expecting = expecting;
|
||||
}
|
||||
|
||||
/// Add a fragment to this fragment set and return true if the packet appears complete.
|
||||
/// This will panic if 'no' is out of bounds.
|
||||
#[inline(always)]
|
||||
pub fn add(&mut self, frag: PacketBuffer, no: u8, expecting: u8) -> bool {
|
||||
if self.frags[no].replace(frag).is_none() {
|
||||
pub fn add_fragment(&mut self, frag: PacketBuffer, no: u8, expecting: u8) -> bool {
|
||||
if self.frags[no as usize].replace(frag).is_none() {
|
||||
self.have = self.have.wrapping_add(1);
|
||||
self.expecting |= expecting;
|
||||
self.expecting |= expecting; // in valid streams expecting is either 0 or the (same) total
|
||||
self.have == self.expecting
|
||||
} else {
|
||||
false
|
||||
|
|
|
@ -7,7 +7,7 @@ pub(crate) mod dictionary;
|
|||
pub(crate) mod address;
|
||||
pub(crate) mod mac;
|
||||
pub(crate) mod fragmentedpacket;
|
||||
mod(crate) mod whois;
|
||||
pub(crate) mod whois;
|
||||
|
||||
pub mod constants;
|
||||
pub mod identity;
|
||||
|
|
|
@ -17,7 +17,7 @@ use crate::vl1::constants::PACKET_SIZE_MAX;
|
|||
use crate::vl1::path::Path;
|
||||
use crate::vl1::peer::Peer;
|
||||
use crate::vl1::protocol::{FragmentHeader, is_fragment, PacketHeader, PacketID};
|
||||
use crate::vl1::whois::Whois;
|
||||
use crate::vl1::whois::WhoisQueue;
|
||||
|
||||
/// Standard packet buffer type including pool container.
|
||||
pub type PacketBuffer = Pooled<Buffer<{ PACKET_SIZE_MAX }>>;
|
||||
|
@ -99,9 +99,24 @@ pub trait VL1CallerInterface {
|
|||
fn time_clock(&self) -> i64;
|
||||
}
|
||||
|
||||
/// Trait implemented by VL2 to handle messages after they are unwrapped by VL1.
|
||||
pub(crate) trait VL1PacketHandler {
|
||||
/// Handle a packet, returning true if the verb was recognized.
|
||||
/// True should be returned even if the packet is not valid, since the return value is used
|
||||
/// to determine if this is a VL2 or VL1 packet. ERROR and OK should not be handled here but
|
||||
/// in handle_error() and handle_ok() instead.
|
||||
fn handle_packet(&self, peer: &Peer, source_path: &Arc<Path>, forward_secrecy: bool, verb: u8, payload: &Buffer<{ PACKET_SIZE_MAX }>) -> bool;
|
||||
|
||||
/// Handle errors, returning true if the error was recognized.
|
||||
fn handle_error(&self, peer: &Peer, source_path: &Arc<Path>, forward_secrecy: bool, in_re_verb: u8, in_re_packet_id: PacketID, error_code: u8, payload: &Buffer<{ PACKET_SIZE_MAX }>) -> bool;
|
||||
|
||||
/// Handle an OK, returing true if the OK was recognized.
|
||||
fn handle_ok(&self, peer: &Peer, source_path: &Arc<Path>, forward_secrecy: bool, in_re_verb: u8, in_re_packet_id: PacketID, payload: &Buffer<{ PACKET_SIZE_MAX }>) -> bool;
|
||||
}
|
||||
|
||||
#[derive(Default)]
|
||||
struct BackgroundTaskIntervals {
|
||||
whois: IntervalGate<{ Whois::INTERVAL }>,
|
||||
whois: IntervalGate<{ WhoisQueue::INTERVAL }>,
|
||||
}
|
||||
|
||||
pub struct Node {
|
||||
|
@ -110,7 +125,7 @@ pub struct Node {
|
|||
locator: Mutex<Option<Locator>>,
|
||||
paths: DashMap<Endpoint, Arc<Path>>,
|
||||
peers: DashMap<Address, Arc<Peer>>,
|
||||
whois: Whois,
|
||||
whois: WhoisQueue,
|
||||
buffer_pool: Pool<Buffer<{ PACKET_SIZE_MAX }>>,
|
||||
secure_prng: SecureRandom,
|
||||
}
|
||||
|
@ -149,7 +164,7 @@ impl Node {
|
|||
locator: Mutex::new(None),
|
||||
paths: DashMap::new(),
|
||||
peers: DashMap::new(),
|
||||
whois: Whois::new(),
|
||||
whois: WhoisQueue::new(),
|
||||
buffer_pool: Pool::new(64),
|
||||
secure_prng: SecureRandom::get(),
|
||||
})
|
||||
|
@ -205,7 +220,8 @@ impl Node {
|
|||
}
|
||||
|
||||
/// Called when a packet is received on the physical wire.
|
||||
pub fn wire_receive<CI: VL1CallerInterface>(&self, ci: &CI, source_endpoint: &Endpoint, source_local_socket: i64, source_local_interface: i64, mut data: PacketBuffer) {
|
||||
pub fn wire_receive<CI: VL1CallerInterface, PH: VL1PacketHandler>(&self, ci: &CI, ph: &PH, source_endpoint: &Endpoint, source_local_socket: i64, source_local_interface: i64, mut data: PacketBuffer) {
|
||||
/*
|
||||
let _ = data.struct_mut_at::<FragmentHeader>(0).map(|fragment_header| {
|
||||
// NOTE: destination address is located at the same index in both the fragment
|
||||
// header and the full packet header, allowing us to make this decision once.
|
||||
|
@ -216,19 +232,13 @@ impl Node {
|
|||
let path = self.path(source_endpoint, source_local_socket, source_local_interface);
|
||||
if fragment_header.is_fragment() {
|
||||
} else {
|
||||
data.struct_mut_at::<PacketHeader>(0).map(|header| {
|
||||
let source = Address::from(&header.src);
|
||||
|
||||
if header.is_fragmented() {
|
||||
} else {
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
} else {
|
||||
// Packet or fragment is addressed to another node.
|
||||
}
|
||||
});
|
||||
*/
|
||||
}
|
||||
|
||||
/// Get the canonical Path object for a given endpoint and local socket information.
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
use std::sync::atomic::{AtomicI64, Ordering};
|
||||
|
||||
use crate::vl1::Endpoint;
|
||||
use crate::vl1::constants::FRAGMENT_COUNT_MAX;
|
||||
use crate::vl1::constants::{FRAGMENT_COUNT_MAX, FRAGMENT_EXPIRATION};
|
||||
use crate::vl1::fragmentedpacket::FragmentedPacket;
|
||||
use crate::vl1::protocol::{FragmentHeader, PacketID};
|
||||
use crate::vl1::node::PacketBuffer;
|
||||
|
@ -48,46 +48,73 @@ impl Path {
|
|||
self.rxs.lock().last_receive_time_ticks
|
||||
}
|
||||
|
||||
/// Receive a fragment and invoke the handler if a packet appears fully assembled.
|
||||
/// This also updates last receive time, etc.
|
||||
#[inline(always)]
|
||||
pub(crate) fn receive_fragment<F: FnOnce(&mut FragmentedPacket)>(&self, packet_id: PacketID, fragment_no: u8, fragment_expecting_count: u8, packet: PacketBuffer, time_ticks: i64, assembled_packet_handler: F) {
|
||||
if fragment_no < FRAGMENT_COUNT_MAX as u8 {
|
||||
let mut rxs = self.rxs.lock();
|
||||
rxs.last_receive_time_ticks = time_ticks;
|
||||
|
||||
let mut fpcnt = rxs.fragmented_packet_count;
|
||||
let mut fidx = 0;
|
||||
while fpcnt > 0 {
|
||||
let mut f = &mut rxs.fragmented_packets[fidx];
|
||||
// In most situlations this algorithms runs right through and doesn't need to iterate.
|
||||
// If there are no fragments fpcnt will be 0 and the first loop will skip. If there are
|
||||
// no fragments then the second loop won't be needed either since the first slot will
|
||||
// be open. Looping only happens when there are multiple fragments in flight, which is
|
||||
// not a common scenario for peer-to-peer links. The maximum iteration count in the
|
||||
// worst case is only 2*FRAGMENT_COUNT_MAX and the loops are only doing integer
|
||||
// comparisons, so the worst case is still linear.
|
||||
|
||||
let mut fragmented_packets_to_check = rxs.fragmented_packet_count;
|
||||
let mut i = 0;
|
||||
while fragmented_packets_to_check > 0 {
|
||||
let mut f = &mut rxs.fragmented_packets[i];
|
||||
if f.id == packet_id {
|
||||
if f.add(packet, fragment_no, fragment_expecting_count) {
|
||||
if f.add_fragment(packet, fragment_no, fragment_expecting_count) {
|
||||
assembled_packet_handler(f);
|
||||
f.reset();
|
||||
rxs.fragmented_packet_count = rxs.fragmented_packet_count.wrapping_sub(1);
|
||||
f.clear();
|
||||
rxs.fragmented_packet_count -= 1;
|
||||
}
|
||||
return;
|
||||
} else if f.ts_ticks >= 0 {
|
||||
fpcnt = fpcnt.wrapping_sub(1);
|
||||
if (time_ticks - f.ts_ticks) > FRAGMENT_EXPIRATION {
|
||||
f.clear();
|
||||
rxs.fragmented_packet_count -= 1;
|
||||
}
|
||||
fragmented_packets_to_check -= 1;
|
||||
}
|
||||
fidx = fidx.wrapping_add(1);
|
||||
i += 1;
|
||||
}
|
||||
|
||||
let mut oldest_ts = rxs.fragmented_packets[0].ts_ticks;
|
||||
let mut oldest_idx = 0;
|
||||
if oldest_ts >= 0 {
|
||||
let mut oldest_ts = &mut rxs.fragmented_packets[0];
|
||||
let mut oldest_ts_ticks = oldest_ts.ts_ticks;
|
||||
if oldest_ts_ticks >= 0 {
|
||||
for fidx in 1..FRAGMENT_COUNT_MAX {
|
||||
let ts = rxs.fragmented_packets[fidx].ts_ticks;
|
||||
if ts < oldest_ts {
|
||||
let ts = &mut rxs.fragmented_packets[fidx];
|
||||
let tst = ts.ts_ticks;
|
||||
if tst < oldest_ts_ticks {
|
||||
oldest_ts = ts;
|
||||
oldest_idx = fidx;
|
||||
oldest_ts_ticks = tst;
|
||||
if tst < 0 {
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
let mut f = &mut rxs.fragmented_packets[oldest_idx];
|
||||
f.init(packet_id, time_ticks);
|
||||
let _ = f.add(packet, fragment_no, fragment_expecting_count);
|
||||
if oldest_ts_ticks < 0 {
|
||||
rxs.fragmented_packet_count += 1;
|
||||
} else {
|
||||
oldest_ts.clear();
|
||||
}
|
||||
rxs.fragmented_packets[oldest_idx].init(packet_id, time_ticks, packet, fragment_no, fragment_expecting_count);
|
||||
}
|
||||
}
|
||||
|
||||
/// Register receipt of "anything" else which right now includes unfragmented packets and keepalives.
|
||||
#[inline(always)]
|
||||
pub(crate) fn receive_other(&self, time_ticks: i64) {
|
||||
self.rxs.lock().last_receive_time_ticks = time_ticks;
|
||||
}
|
||||
}
|
||||
|
||||
unsafe impl Send for Path {}
|
||||
|
|
|
@ -1,24 +1,93 @@
|
|||
use std::ops::DerefMut;
|
||||
use std::sync::Arc;
|
||||
use std::sync::atomic::{AtomicI64, AtomicU64, AtomicU8};
|
||||
|
||||
use crate::vl1::{Identity, Path};
|
||||
use crate::vl1::fragmentedpacket::FragmentedPacket;
|
||||
use crate::vl1::protocol::{PacketID, PacketHeader};
|
||||
use crate::vl1::node::{VL1CallerInterface, PacketBuffer, Node};
|
||||
|
||||
use parking_lot::Mutex;
|
||||
use aes_gmac_siv::AesGmacSiv;
|
||||
|
||||
const MAX_PATHS: usize = 16;
|
||||
use crate::crypto::c25519::C25519KeyPair;
|
||||
use crate::crypto::hash::SHA384_HASH_SIZE;
|
||||
use crate::crypto::kbkdf::zt_kbkdf_hmac_sha384;
|
||||
use crate::crypto::p521::P521KeyPair;
|
||||
use crate::crypto::poly1305::Poly1305;
|
||||
use crate::crypto::random::next_u64_secure;
|
||||
use crate::crypto::salsa::Salsa;
|
||||
use crate::crypto::secret::Secret;
|
||||
use crate::vl1::{Identity, Path};
|
||||
use crate::vl1::buffer::Buffer;
|
||||
use crate::vl1::constants::*;
|
||||
use crate::vl1::fragmentedpacket::FragmentedPacket;
|
||||
use crate::vl1::node::*;
|
||||
use crate::vl1::protocol::*;
|
||||
|
||||
struct PeerSecrets {
|
||||
// Time secret was created in ticks or -1 for static secrets.
|
||||
create_time_ticks: i64,
|
||||
|
||||
// Number of time secret has been used to encrypt something during this session.
|
||||
encrypt_count: u64,
|
||||
|
||||
// Raw secret itself.
|
||||
secret: Secret<48>,
|
||||
|
||||
// Reusable AES-GMAC-SIV initialized with secret.
|
||||
aes: AesGmacSiv,
|
||||
}
|
||||
|
||||
struct EphemeralKeyPair {
|
||||
// Time ephemeral key pair was created.
|
||||
create_time_ticks: i64,
|
||||
|
||||
// SHA384(c25519 public | p521 public)
|
||||
public_keys_hash: [u8; 48],
|
||||
|
||||
// Curve25519 ECDH key pair.
|
||||
c25519: C25519KeyPair,
|
||||
|
||||
// NIST P-521 ECDH key pair.
|
||||
p521: P521KeyPair,
|
||||
}
|
||||
|
||||
struct TxState {
|
||||
packet_iv_counter: u64,
|
||||
// Time we last sent something to this peer.
|
||||
last_send_time_ticks: i64,
|
||||
paths: [Arc<Path>; MAX_PATHS],
|
||||
|
||||
// Outgoing packet IV counter, starts at a random position.
|
||||
packet_iv_counter: u64,
|
||||
|
||||
// Total bytes sent to this peer during this session.
|
||||
total_bytes: u64,
|
||||
|
||||
// "Eternal" static secret created via identity agreement.
|
||||
static_secret: PeerSecrets,
|
||||
|
||||
// The most recently negotiated ephemeral secret.
|
||||
ephemeral_secret: Option<PeerSecrets>,
|
||||
|
||||
// The current ephemeral key pair we will share with HELLO.
|
||||
ephemeral_pair: Option<EphemeralKeyPair>,
|
||||
|
||||
// Paths to this peer sorted in descending order of quality with None entries at the end.
|
||||
paths: [Option<Arc<Path>>; PEER_MAX_PATHS],
|
||||
}
|
||||
|
||||
struct RxState {
|
||||
// Time we last received something (authenticated) from this peer.
|
||||
last_receive_time_ticks: i64,
|
||||
remote_version: [u8; 4],
|
||||
|
||||
// Total bytes received from this peer during this session.
|
||||
total_bytes: u64,
|
||||
|
||||
// "Eternal" static secret created via identity agreement.
|
||||
static_secret: PeerSecrets,
|
||||
|
||||
// The most recently negotiated ephemeral secret.
|
||||
ephemeral_secret: Option<PeerSecrets>,
|
||||
|
||||
// Remote version as major, minor, revision, build in most-to-least-significant 16-bit chunks.
|
||||
// This is the user-facing software version and is zero if not yet known.
|
||||
remote_version: u64,
|
||||
|
||||
// Remote protocol version or zero if not yet known.
|
||||
remote_protocol_version: u8,
|
||||
}
|
||||
|
||||
|
@ -26,22 +95,245 @@ struct RxState {
|
|||
/// Sending-related and receiving-related fields are locked separately since concurrent
|
||||
/// send/receive is not uncommon.
|
||||
pub struct Peer {
|
||||
// This peer's identity.
|
||||
identity: Identity,
|
||||
|
||||
// Static shared secret computed from agreement with identity.
|
||||
identity_static_secret: [u8; 48],
|
||||
static_secret: Secret<48>,
|
||||
|
||||
// Derived static secret used to encrypt the dictionary part of HELLO.
|
||||
static_secret_hello_dictionary_encrypt: Secret<48>,
|
||||
|
||||
// Derived static secret used to add full HMAC-SHA384 to packets, currently just HELLO.
|
||||
static_secret_packet_hmac: Secret<48>,
|
||||
|
||||
// State used primarily when sending to this peer.
|
||||
txs: Mutex<TxState>,
|
||||
tx: Mutex<TxState>,
|
||||
|
||||
// State used primarily when receiving from this peer.
|
||||
rxs: Mutex<RxState>,
|
||||
rx: Mutex<RxState>,
|
||||
}
|
||||
|
||||
/// 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.
|
||||
#[inline(always)]
|
||||
fn salsa_derive_per_packet_key(key: &Secret<48>, header: &PacketHeader, packet_size: usize) -> Secret<48> {
|
||||
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
|
||||
}
|
||||
|
||||
impl Peer {
|
||||
pub(crate) fn receive_from_singular<CI: VL1CallerInterface>(&self, node: &Node, ci: &CI, header: &PacketHeader, packet: &PacketBuffer) {
|
||||
/// 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) -> Option<Peer> {
|
||||
this_node_identity.agree(&id).map(|static_secret| {
|
||||
let aes_k0 = zt_kbkdf_hmac_sha384(&static_secret.0, KBKDF_KEY_USAGE_LABEL_AES_GMAC_SIV_K0, 0, 0);
|
||||
let aes_k1 = zt_kbkdf_hmac_sha384(&static_secret.0, KBKDF_KEY_USAGE_LABEL_AES_GMAC_SIV_K1, 0, 0);
|
||||
let static_secret_hello_dictionary_encrypt = zt_kbkdf_hmac_sha384(&static_secret.0, KBKDF_KEY_USAGE_LABEL_HELLO_DICTIONARY_ENCRYPT, 0, 0);
|
||||
let static_secret_packet_hmac = zt_kbkdf_hmac_sha384(&static_secret.0, KBKDF_KEY_USAGE_LABEL_PACKET_HMAC, 0, 0);
|
||||
Peer {
|
||||
identity: id,
|
||||
static_secret: static_secret.clone(),
|
||||
static_secret_hello_dictionary_encrypt,
|
||||
static_secret_packet_hmac,
|
||||
tx: Mutex::new(TxState {
|
||||
last_send_time_ticks: 0,
|
||||
packet_iv_counter: next_u64_secure(),
|
||||
total_bytes: 0,
|
||||
static_secret: TxSecret {
|
||||
create_time_ticks: -1,
|
||||
usage_count: 0,
|
||||
secret: static_secret.clone(),
|
||||
aes: AesGmacSiv::new(&aes_k0.0, &aes_k1.0),
|
||||
},
|
||||
ephemeral_secret: None,
|
||||
paths: [None; PEER_MAX_PATHS],
|
||||
ephemeral_pair: None,
|
||||
}),
|
||||
rx: Mutex::new(RxState {
|
||||
last_receive_time_ticks: 0,
|
||||
total_bytes: 0,
|
||||
static_secret: PeerSecrets {
|
||||
create_time_ticks: -1,
|
||||
encrypt_count: 0,
|
||||
secret: static_secret,
|
||||
aes: AesGmacSiv::new(&aes_k0.0, &aes_k1.0),
|
||||
},
|
||||
ephemeral_secret: None,
|
||||
remote_version: 0,
|
||||
remote_protocol_version: 0,
|
||||
}),
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
pub(crate) fn receive_from_fragmented<CI: VL1CallerInterface>(&self, node: &Node, ci: CI, header: &PacketHeader, packet: &FragmentedPacket) {
|
||||
/// 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<CI: VL1CallerInterface, PH: VL1PacketHandler>(&self, node: &Node, ci: &CI, ph: &PH, time_ticks: i64, source_path: &Arc<Path>, header: &PacketHeader, packet: &Buffer<{ PACKET_SIZE_MAX }>, fragments: &[Option<PacketBuffer>]) {
|
||||
let packet_frag0_payload_bytes = packet.as_bytes_after(PACKET_VERB_INDEX).unwrap_or(&[]);
|
||||
if !packet_frag0_payload_bytes.is_empty() {
|
||||
let mut payload: Buffer<{ PACKET_SIZE_MAX }> = Buffer::new();
|
||||
let mut rx = self.rx.lock();
|
||||
|
||||
// When handling incoming packets we try any current ephemeral secret first, and if that
|
||||
// fails we fall back to the static secret. If decryption with an ephemeral secret succeeds
|
||||
// the forward secrecy flag in the receive path is set.
|
||||
let mut secret = rx.ephemeral_secret.as_mut().unwrap_or(&mut rx.static_secret);
|
||||
loop {
|
||||
match header.cipher() {
|
||||
CIPHER_NOCRYPT_POLY1305 => {
|
||||
// Only HELLO is allowed in the clear (but still authenticated).
|
||||
if (packet_frag0_payload_bytes[0] & VERB_MASK) == VERB_VL1_HELLO {
|
||||
let _ = payload.append_bytes(packet_frag0_payload_bytes);
|
||||
|
||||
for f in fragments.iter() {
|
||||
let _ = f.as_ref().map(|f| {
|
||||
let _ = f.as_bytes_after(FRAGMENT_HEADER_SIZE).map(|f| {
|
||||
let _ = payload.append_bytes(f);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
// FIPS note: for FIPS purposes the HMAC-SHA384 tag at the end of V2 HELLOs
|
||||
// will be considered the "real" handshake authentication.
|
||||
let key = salsa_derive_per_packet_key(&secret.secret, header, payload.len());
|
||||
let mut salsa = Salsa::new(&key.0[0..32], header.id_bytes(), true).unwrap();
|
||||
let mut poly1305_key = [0_u8; 32];
|
||||
salsa.crypt_in_place(&mut poly1305_key);
|
||||
let mut poly = Poly1305::new(&poly1305_key).unwrap();
|
||||
poly.update(packet_frag0_payload_bytes);
|
||||
|
||||
if poly.finish()[0..8].eq(&header.message_auth) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
CIPHER_SALSA2012_POLY1305 => {
|
||||
// FIPS note: support for this mode would have to be disabled in FIPS compliant
|
||||
// modes of operation.
|
||||
let key = salsa_derive_per_packet_key(&secret.secret, header, payload.len());
|
||||
let mut salsa = Salsa::new(&key.0[0..32], header.id_bytes(), true).unwrap();
|
||||
let mut poly1305_key = [0_u8; 32];
|
||||
salsa.crypt_in_place(&mut poly1305_key);
|
||||
let mut poly = Poly1305::new(&poly1305_key).unwrap();
|
||||
|
||||
poly.update(packet_frag0_payload_bytes);
|
||||
let _ = payload.append_and_init_bytes(packet_frag0_payload_bytes.len(), |b| salsa.crypt(packet_frag0_payload_bytes, b));
|
||||
for f in fragments.iter() {
|
||||
let _ = f.as_ref().map(|f| {
|
||||
let _ = f.as_bytes_after(FRAGMENT_HEADER_SIZE).map(|f| {
|
||||
poly.update(f);
|
||||
let _ = payload.append_and_init_bytes(f.len(), |b| salsa.crypt(f, b));
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
if poly.finish()[0..8].eq(&header.message_auth) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
CIPHER_AES_GMAC_SIV => {
|
||||
secret.aes.reset();
|
||||
secret.aes.decrypt_init(&header.aes_gmac_siv_tag());
|
||||
secret.aes.decrypt_set_aad(&header.aad_bytes());
|
||||
|
||||
let _ = payload.append_and_init_bytes(packet_frag0_payload_bytes.len(), |b| secret.aes.decrypt(packet_frag0_payload_bytes, b));
|
||||
for f in fragments.iter() {
|
||||
let _ = f.as_ref().map(|f| {
|
||||
let _ = f.as_bytes_after(FRAGMENT_HEADER_SIZE).map(|f| {
|
||||
let _ = payload.append_and_init_bytes(f.len(), |b| secret.aes.decrypt(f, b));
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
if secret.aes.decrypt_finish() {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
_ => {}
|
||||
}
|
||||
|
||||
if (secret as *const PeerSecrets) != (&rx.static_secret as *const PeerSecrets) {
|
||||
payload.clear();
|
||||
secret = &mut rx.static_secret;
|
||||
} else {
|
||||
// Both ephemeral (if any) and static secret have failed, drop packet.
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
// If we make it here we've successfully decrypted and authenticated the packet.
|
||||
|
||||
rx.last_receive_time_ticks = time_ticks;
|
||||
rx.total_bytes += payload.len() as u64;
|
||||
|
||||
let forward_secrecy = (secret as *const PeerSecrets) != (&(rx.static_secret) as *const PeerSecrets);
|
||||
|
||||
// Unlock rx state mutex.
|
||||
drop(rx);
|
||||
|
||||
let _ = payload.u8_at(0).map(|verb| {
|
||||
// For performance reasons we let VL2 handle packets first. It returns false
|
||||
// if it didn't pick up anything.
|
||||
if !ph.handle_packet(self, source_path, forward_secrecy, verb, &payload) {
|
||||
match verb {
|
||||
VERB_VL1_NOP => {}
|
||||
VERB_VL1_HELLO => {}
|
||||
VERB_VL1_ERROR => {}
|
||||
VERB_VL1_OK => {}
|
||||
VERB_VL1_WHOIS => {}
|
||||
VERB_VL1_RENDEZVOUS => {}
|
||||
VERB_VL1_ECHO => {}
|
||||
VERB_VL1_PUSH_DIRECT_PATHS => {}
|
||||
VERB_VL1_USER_MESSAGE => {}
|
||||
VERB_VL1_REMOTE_TRACE => {}
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
/// Get the remote version of this peer: major, minor, revision, and build.
|
||||
/// Returns None if it's not yet known.
|
||||
pub fn version(&self) -> Option<[u16; 4]> {
|
||||
let rv = self.rx.lock().remote_version;
|
||||
if rv != 0 {
|
||||
Some([(rv >> 48) as u16, (rv >> 32) as u16, (rv >> 16) as u16, rv as u16])
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
/// Get the remote protocol version of this peer or None if not yet known.
|
||||
pub fn protocol_version(&self) -> Option<u8> {
|
||||
let pv = self.rx.lock().remote_protocol_version;
|
||||
if pv != 0 {
|
||||
Some(pv)
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,20 +1,33 @@
|
|||
use std::ops::Not;
|
||||
use std::intrinsics::size_of;
|
||||
use std::mem::MaybeUninit;
|
||||
|
||||
use crate::vl1::buffer::{RawObject, Buffer};
|
||||
use crate::vl1::constants::{HEADER_FLAGS_FIELD_MASK_CIPHER, HEADER_FLAGS_FIELD_MASK_HOPS, HEADER_FLAG_FRAGMENTED, FRAGMENT_INDICATOR};
|
||||
use crate::vl1::Address;
|
||||
use crate::vl1::buffer::{Buffer, RawObject};
|
||||
use crate::vl1::constants::*;
|
||||
|
||||
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;
|
||||
pub const VERB_VL1_REMOTE_TRACE: u8 = 0x15;
|
||||
|
||||
/// A unique packet identifier, also the cryptographic nonce.
|
||||
///
|
||||
/// Packet IDs are stored as u64s for efficiency but they should be treated as
|
||||
/// [u8; 8] fields in that their endianness is "wire" endian. If for some reason
|
||||
/// packet IDs need to be portably compared or shared across systems they should
|
||||
/// be treated as bytes not integers.
|
||||
pub type PacketID = u64;
|
||||
|
||||
/// ZeroTier unencrypted outer header
|
||||
/// ZeroTier unencrypted outer packet header
|
||||
///
|
||||
/// This is the header for a complete packet. If the fragmented flag is set, it will
|
||||
/// arrive with one or more fragments that must be assembled to complete it.
|
||||
#[derive(Clone)]
|
||||
#[repr(packed)]
|
||||
pub struct PacketHeader {
|
||||
pub id: PacketID,
|
||||
|
@ -32,9 +45,6 @@ impl PacketHeader {
|
|||
self.flags_cipher_hops & HEADER_FLAGS_FIELD_MASK_CIPHER
|
||||
}
|
||||
|
||||
/// Get this packet's hops field.
|
||||
/// This is the only field in the unencrypted header that is not authenticated, allowing intermediate
|
||||
/// nodes to increment it as they forward packets between indirectly connected peers.
|
||||
#[inline(always)]
|
||||
pub fn hops(&self) -> u8 {
|
||||
self.flags_cipher_hops & HEADER_FLAGS_FIELD_MASK_HOPS
|
||||
|
@ -43,12 +53,9 @@ impl PacketHeader {
|
|||
#[inline(always)]
|
||||
pub fn increment_hops(&mut self) {
|
||||
let f = self.flags_cipher_hops;
|
||||
self.flags_cipher_hops = (f & HEADER_FLAGS_FIELD_MASK_HOPS.not()) | ((f + 1) & HEADER_FLAGS_FIELD_MASK_HOPS);
|
||||
self.flags_cipher_hops = (f & HEADER_FLAGS_FIELD_MASK_HIDE_HOPS) | ((f + 1) & HEADER_FLAGS_FIELD_MASK_HOPS);
|
||||
}
|
||||
|
||||
/// If true, this packet requires one or more fragments to fully assemble.
|
||||
/// The one with the full header is always fragment 0. Note that is_fragment() is checked first
|
||||
/// to see if this IS a fragment.
|
||||
#[inline(always)]
|
||||
pub fn is_fragmented(&self) -> bool {
|
||||
(self.flags_cipher_hops & HEADER_FLAG_FRAGMENTED) != 0
|
||||
|
@ -63,14 +70,41 @@ impl PacketHeader {
|
|||
pub fn source(&self) -> Address {
|
||||
Address::from(&self.src)
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
pub fn id_bytes(&self) -> &[u8; 8] {
|
||||
unsafe { &*(self as *const Self).cast::<[u8; 8]>() }
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
pub fn as_bytes(&self) -> &[u8; PACKET_HEADER_SIZE] {
|
||||
unsafe { &*(self as *const Self).cast::<[u8; PACKET_HEADER_SIZE]>() }
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
pub fn aad_bytes(&self) -> [u8; 11] {
|
||||
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
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
pub fn aes_gmac_siv_tag(&self) -> [u8; 16] {
|
||||
let mut id = unsafe { MaybeUninit::<[u8; 16]>::uninit().assume_init() };
|
||||
id[0..8].copy_from_slice(self.id_bytes());
|
||||
id[8..16].copy_from_slice(&self.message_auth);
|
||||
id
|
||||
}
|
||||
}
|
||||
|
||||
/// ZeroTier fragment header
|
||||
///
|
||||
/// Fragments are indicated by byte 0xff at the start of the source address, which
|
||||
/// is normally illegal since addresses can't begin with that. Fragmented packets
|
||||
/// will arrive with the first fragment carrying a normal header with the fragment
|
||||
/// bit set and remaining fragments being these.
|
||||
#[derive(Clone)]
|
||||
#[repr(packed)]
|
||||
pub struct FragmentHeader {
|
||||
pub id: PacketID, // packet ID
|
||||
|
@ -80,7 +114,7 @@ pub struct FragmentHeader {
|
|||
pub reserved_hops: u8, // rrrrrHHH (3 hops bits, rest reserved)
|
||||
}
|
||||
|
||||
unsafe impl crate::vl1::buffer::RawObject for FragmentHeader {}
|
||||
unsafe impl RawObject for FragmentHeader {}
|
||||
|
||||
impl FragmentHeader {
|
||||
#[inline(always)]
|
||||
|
@ -106,7 +140,7 @@ impl FragmentHeader {
|
|||
#[inline(always)]
|
||||
pub fn increment_hops(&mut self) {
|
||||
let f = self.reserved_hops;
|
||||
self.reserved_hops = (f & HEADER_FLAGS_FIELD_MASK_HOPS.not()) | ((f + 1) & HEADER_FLAGS_FIELD_MASK_HOPS);
|
||||
self.reserved_hops = (f & HEADER_FLAGS_FIELD_MASK_HIDE_HOPS) | ((f + 1) & HEADER_FLAGS_FIELD_MASK_HOPS);
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
|
@ -118,12 +152,28 @@ impl FragmentHeader {
|
|||
#[cfg(test)]
|
||||
mod tests {
|
||||
use std::mem::size_of;
|
||||
use crate::vl1::protocol::{PacketHeader, FragmentHeader};
|
||||
use crate::vl1::constants::{PACKET_HEADER_SIZE, FRAGMENT_SIZE_MIN};
|
||||
|
||||
use crate::vl1::constants::{FRAGMENT_HEADER_SIZE, PACKET_HEADER_SIZE};
|
||||
use crate::vl1::protocol::{FragmentHeader, PacketHeader};
|
||||
|
||||
#[test]
|
||||
fn object_sizing() {
|
||||
fn representation() {
|
||||
assert_eq!(size_of::<PacketHeader>(), PACKET_HEADER_SIZE);
|
||||
assert_eq!(size_of::<FragmentHeader>(), FRAGMENT_SIZE_MIN);
|
||||
assert_eq!(size_of::<FragmentHeader>(), FRAGMENT_HEADER_SIZE);
|
||||
|
||||
let mut foo = [0_u8; 32];
|
||||
unsafe {
|
||||
(*foo.as_mut_ptr().cast::<PacketHeader>()).src[0] = 0xff;
|
||||
assert_eq!((*foo.as_ptr().cast::<FragmentHeader>()).fragment_indicator, 0xff);
|
||||
}
|
||||
|
||||
let bar = PacketHeader{
|
||||
id: 0x0102030405060708_u64.to_be(),
|
||||
dest: [0_u8; 5],
|
||||
src: [0_u8; 5],
|
||||
flags_cipher_hops: 0,
|
||||
message_auth: [0_u8; 8],
|
||||
};
|
||||
assert_eq!(bar.id_bytes(), [1_u8, 2, 3, 4, 5, 6, 7, 8]);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,14 +1,14 @@
|
|||
use std::collections::HashMap;
|
||||
|
||||
use crate::vl1::Address;
|
||||
use crate::vl1::fragmentedpacket::FragmentedPacket;
|
||||
use crate::vl1::node::{VL1CallerInterface, Node, PacketBuffer};
|
||||
use crate::util::gate::IntervalGate;
|
||||
|
||||
use parking_lot::Mutex;
|
||||
use crate::vl1::constants::{WHOIS_RETRY_INTERVAL, WHOIS_RETRY_MAX};
|
||||
|
||||
pub enum QueuedPacket {
|
||||
use crate::util::gate::IntervalGate;
|
||||
use crate::vl1::Address;
|
||||
use crate::vl1::constants::*;
|
||||
use crate::vl1::fragmentedpacket::FragmentedPacket;
|
||||
use crate::vl1::node::{Node, PacketBuffer, VL1CallerInterface};
|
||||
|
||||
pub(crate) enum QueuedPacket {
|
||||
Singular(PacketBuffer),
|
||||
Fragmented(FragmentedPacket)
|
||||
}
|
||||
|
@ -19,11 +19,11 @@ struct WhoisQueueItem {
|
|||
packet_queue: Vec<QueuedPacket>
|
||||
}
|
||||
|
||||
pub struct Whois {
|
||||
pub(crate) struct WhoisQueue {
|
||||
queue: Mutex<HashMap<Address, WhoisQueueItem>>
|
||||
}
|
||||
|
||||
impl Whois {
|
||||
impl WhoisQueue {
|
||||
pub const INTERVAL: i64 = WHOIS_RETRY_INTERVAL;
|
||||
|
||||
pub fn new() -> Self {
|
||||
|
|
Loading…
Add table
Reference in a new issue