Lots of Rust stuff.

This commit is contained in:
Adam Ierymenko 2021-08-04 12:31:46 -04:00
parent 617b7c86b6
commit a8da84c055
No known key found for this signature in database
GPG key ID: C8877CF2D7A5D7F3
23 changed files with 613 additions and 191 deletions

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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