From 36b7986a571f3cef712f59bd85bdfbc2d7c40159 Mon Sep 17 00:00:00 2001 From: Adam Ierymenko Date: Tue, 14 Mar 2023 15:00:29 -0400 Subject: [PATCH] Some cleanup and reorg. --- network-hypervisor/src/protocol.rs | 53 ++++---------------------- network-hypervisor/src/vl1/endpoint.rs | 12 +++--- network-hypervisor/src/vl1/identity.rs | 11 +++--- network-hypervisor/src/vl1/peer.rs | 12 ++---- network-hypervisor/src/vl2/topology.rs | 11 ------ utils/src/base64.rs | 20 ++++++++++ utils/src/blob.rs | 3 +- utils/src/buffer.rs | 1 - utils/src/lib.rs | 16 +------- 9 files changed, 47 insertions(+), 92 deletions(-) create mode 100644 utils/src/base64.rs diff --git a/network-hypervisor/src/protocol.rs b/network-hypervisor/src/protocol.rs index ef68d34e5..b7b1fb1dd 100644 --- a/network-hypervisor/src/protocol.rs +++ b/network-hypervisor/src/protocol.rs @@ -12,8 +12,6 @@ use zerotier_crypto::aes_gmac_siv::AesGmacSiv; use zerotier_crypto::hash::hmac_sha384; use zerotier_crypto::secret::Secret; -use self::v1::VERB_FLAG_COMPRESSED; - /* * Legacy V1 protocol versions: * @@ -95,11 +93,6 @@ pub mod message_type { pub const VL2_NETWORK_CONFIG: u8 = 0x0c; pub const VL2_MULTICAST_GATHER: u8 = 0x0d; - // DR: Data replication protocol (scatter-gather based) - - pub const DR_REQUEST: u8 = 0x1e; - pub const DR_DATA: u8 = 0x1f; - pub fn name(verb: u8) -> &'static str { match verb { VL1_NOP => "VL1_NOP", @@ -115,13 +108,17 @@ pub mod message_type { VL2_NETWORK_CONFIG_REQUEST => "VL2_NETWORK_CONFIG_REQUEST", VL2_NETWORK_CONFIG => "VL2_NETWORK_CONFIG", VL2_MULTICAST_GATHER => "VL2_MULTICAST_GATHER", - DR_REQUEST => "DR_REQUEST", - DR_DATA => "DR_DATA", _ => "???", } } } +/// Verb (inner) flag indicating that the packet's payload (after the verb) is LZ4 compressed. +pub const MESSAGE_FLAG_COMPRESSED: u8 = 0x80; + +/// Mask to get only the verb from the verb + verb flags byte. +pub const MESSAGE_TYPE_MASK: u8 = 0x1f; + /// Default maximum payload size for UDP transport. /// /// This is small enough to traverse numerous weird networks including PPPoE and Google Cloud's @@ -212,18 +209,6 @@ pub mod v1 { /// Byte found at FRAGMENT_INDICATOR_INDEX to indicate a fragment. pub const FRAGMENT_INDICATOR: u8 = 0xff; - /// Verb (inner) flag indicating that the packet's payload (after the verb) is LZ4 compressed. - pub const VERB_FLAG_COMPRESSED: u8 = 0x80; - - /// Verb (inner) flag indicating that payload is authenticated with HMAC-SHA384. - pub const VERB_FLAG_EXTENDED_AUTHENTICATION: u8 = 0x40; - - /// Mask to get only the verb from the verb + verb flags byte. - pub const VERB_MASK: u8 = 0x1f; - - /// Maximum number of verbs that the protocol can support. - pub const VERB_MAX_COUNT: usize = 32; - /// Header (outer) flag indicating that this packet has additional fragments. pub const HEADER_FLAG_FRAGMENTED: u8 = 0x40; @@ -256,30 +241,6 @@ pub mod v1 { /// Maximum number of hops to allow. pub const FORWARD_MAX_HOPS: u8 = 3; - /// Attempt to compress a packet's payload with LZ4 - /// - /// If this returns true the destination buffer will contain a compressed packet. If false is - /// returned the contents of 'dest' are entirely undefined. This indicates that the data was not - /// compressable or some other error occurred. - pub fn compress_packet(src: &[u8], dest: &mut Buffer) -> bool { - if src.len() > (VERB_INDEX + 16) { - let compressed_data_size = { - let d = unsafe { dest.entire_buffer_mut() }; - d[..VERB_INDEX].copy_from_slice(&src[..VERB_INDEX]); - d[VERB_INDEX] = src[VERB_INDEX] | VERB_FLAG_COMPRESSED; - lz4_flex::block::compress_into(&src[VERB_INDEX + 1..], &mut d[VERB_INDEX + 1..]) - }; - if compressed_data_size.is_ok() { - let compressed_data_size = compressed_data_size.unwrap(); - if compressed_data_size > 0 && compressed_data_size < (src.len() - VERB_INDEX) { - unsafe { dest.set_size_unchecked(VERB_INDEX + 1 + compressed_data_size) }; - return true; - } - } - } - return false; - } - /// Set header flag indicating that a packet is fragmented. /// /// This will panic if the buffer provided doesn't contain a proper header. @@ -545,7 +506,7 @@ pub fn compress(payload: &mut [u8]) -> usize { let mut tmp = [0u8; 65536]; if let Ok(mut compressed_size) = lz4_flex::block::compress_into(&payload[1..], &mut tmp) { if compressed_size < (payload.len() - 1) { - payload[0] |= VERB_FLAG_COMPRESSED; + payload[0] |= MESSAGE_FLAG_COMPRESSED; payload[1..(1 + compressed_size)].copy_from_slice(&tmp[..compressed_size]); return 1 + compressed_size; } diff --git a/network-hypervisor/src/vl1/endpoint.rs b/network-hypervisor/src/vl1/endpoint.rs index 21a612f65..0bf307dae 100644 --- a/network-hypervisor/src/vl1/endpoint.rs +++ b/network-hypervisor/src/vl1/endpoint.rs @@ -10,10 +10,10 @@ use crate::vl1::identity::IDENTITY_FINGERPRINT_SIZE; use crate::vl1::inetaddress::InetAddress; use crate::vl1::{Address, MAC}; +use zerotier_utils::base64; use zerotier_utils::buffer::Buffer; use zerotier_utils::error::InvalidFormatError; use zerotier_utils::marshalable::{Marshalable, UnmarshalError}; -use zerotier_utils::{base64_decode_url_nopad, base64_encode_url_nopad}; pub const TYPE_NIL: u8 = 0; pub const TYPE_ZEROTIER: u8 = 1; @@ -319,7 +319,7 @@ impl ToString for Endpoint { fn to_string(&self) -> String { match self { Endpoint::Nil => format!("nil"), - Endpoint::ZeroTier(a, ah) => format!("zt:{}-{}", a.to_string(), base64_encode_url_nopad(ah)), + Endpoint::ZeroTier(a, ah) => format!("zt:{}-{}", a.to_string(), base64::encode_url_nopad(ah)), Endpoint::Ethernet(m) => format!("eth:{}", m.to_string()), Endpoint::WifiDirect(m) => format!("wifip2p:{}", m.to_string()), Endpoint::Bluetooth(m) => format!("bt:{}", m.to_string()), @@ -327,8 +327,8 @@ impl ToString for Endpoint { Endpoint::IpUdp(ip) => format!("udp:{}", ip.to_string()), Endpoint::IpTcp(ip) => format!("tcp:{}", ip.to_string()), Endpoint::Http(url) => format!("url:{}", url.clone()), // http or https - Endpoint::WebRTC(offer) => format!("webrtc:{}", base64_encode_url_nopad(offer.as_slice())), - Endpoint::ZeroTierEncap(a, ah) => format!("zte:{}-{}", a.to_string(), base64_encode_url_nopad(ah)), + Endpoint::WebRTC(offer) => format!("webrtc:{}", base64::encode_url_nopad(offer.as_slice())), + Endpoint::ZeroTierEncap(a, ah) => format!("zte:{}-{}", a.to_string(), base64::encode_url_nopad(ah)), } } } @@ -351,7 +351,7 @@ impl FromStr for Endpoint { let address_and_hash = endpoint_data.split_once("-"); if address_and_hash.is_some() { let (address, hash) = address_and_hash.unwrap(); - if let Some(hash) = base64_decode_url_nopad(hash) { + if let Some(hash) = base64::decode_url_nopad(hash) { if hash.len() == IDENTITY_FINGERPRINT_SIZE { if endpoint_type == "zt" { return Ok(Endpoint::ZeroTier(Address::from_str(address)?, hash.as_slice().try_into().unwrap())); @@ -370,7 +370,7 @@ impl FromStr for Endpoint { "tcp" => return Ok(Endpoint::IpTcp(InetAddress::from_str(endpoint_data)?)), "url" => return Ok(Endpoint::Http(endpoint_data.into())), "webrtc" => { - if let Some(offer) = base64_decode_url_nopad(endpoint_data) { + if let Some(offer) = base64::decode_url_nopad(endpoint_data) { return Ok(Endpoint::WebRTC(offer)); } } diff --git a/network-hypervisor/src/vl1/identity.rs b/network-hypervisor/src/vl1/identity.rs index 9b1a33da2..a50e10f68 100644 --- a/network-hypervisor/src/vl1/identity.rs +++ b/network-hypervisor/src/vl1/identity.rs @@ -16,10 +16,11 @@ use zerotier_crypto::x25519::*; use zerotier_crypto::{hash::*, secure_eq}; use zerotier_utils::arrayvec::ArrayVec; +use zerotier_utils::base64; use zerotier_utils::buffer::Buffer; use zerotier_utils::error::{InvalidFormatError, InvalidParameterError}; +use zerotier_utils::hex; use zerotier_utils::marshalable::{Marshalable, UnmarshalError}; -use zerotier_utils::{base64_decode_url_nopad, base64_encode_url_nopad, hex}; use crate::protocol::{ADDRESS_SIZE, ADDRESS_SIZE_STRING, IDENTITY_POW_THRESHOLD}; use crate::vl1::Address; @@ -492,7 +493,7 @@ impl Identity { &p384.ecdsa_self_signature, &p384.ed25519_self_signature, ); - s.push_str(base64_encode_url_nopad(&p384_joined).as_str()); + s.push_str(base64::encode_url_nopad(&p384_joined).as_str()); if self.secret.is_some() && include_private { let secret = self.secret.as_ref().unwrap(); if secret.p384.is_some() { @@ -502,7 +503,7 @@ impl Identity { p384_secret.ecdsa.secret_key_bytes().as_bytes(), ); s.push(':'); - s.push_str(base64_encode_url_nopad(&p384_secret_joined).as_str()); + s.push_str(base64::encode_url_nopad(&p384_secret_joined).as_str()); } } } @@ -586,8 +587,8 @@ impl FromStr for Identity { let keys = [ hex::from_string(keys[0].unwrap_or("")), hex::from_string(keys[1].unwrap_or("")), - base64_decode_url_nopad(keys[2].unwrap_or("")).unwrap_or_else(|| Vec::new()), - base64_decode_url_nopad(keys[3].unwrap_or("")).unwrap_or_else(|| Vec::new()), + base64::decode_url_nopad(keys[2].unwrap_or("")).unwrap_or_else(|| Vec::new()), + base64::decode_url_nopad(keys[3].unwrap_or("")).unwrap_or_else(|| Vec::new()), ]; if keys[0].len() != C25519_PUBLIC_KEY_SIZE + ED25519_PUBLIC_KEY_SIZE { return Err(InvalidFormatError); diff --git a/network-hypervisor/src/vl1/peer.rs b/network-hypervisor/src/vl1/peer.rs index 774b5a8b0..bb1fb42d0 100644 --- a/network-hypervisor/src/vl1/peer.rs +++ b/network-hypervisor/src/vl1/peer.rs @@ -385,10 +385,6 @@ impl Peer { /// /// If explicit_endpoint is not None the packet will be sent directly to this endpoint. /// Otherwise it will be sent via the best direct or indirect path known. - /// - /// Unlike other messages HELLO is sent partially in the clear and always with the long-lived - /// static identity key. Authentication in old versions is via Poly1305 and in new versions - /// via HMAC-SHA512. pub(crate) fn send_hello(&self, app: &Application, node: &Node, explicit_endpoint: Option<&Endpoint>) -> bool { let mut path = None; let destination = if let Some(explicit_endpoint) = explicit_endpoint { @@ -415,7 +411,7 @@ impl Peer { f.0.dest = self.identity.address.to_bytes(); f.0.src = node.identity.address.to_bytes(); f.0.flags_cipher_hops = v1::CIPHER_NOCRYPT_POLY1305; - f.1.verb = message_type::VL1_HELLO | v1::VERB_FLAG_EXTENDED_AUTHENTICATION; + f.1.verb = message_type::VL1_HELLO; f.1.version_proto = PROTOCOL_VERSION; f.1.version_major = VERSION_MAJOR; f.1.version_minor = VERSION_MINOR; @@ -498,7 +494,7 @@ impl Peer { }; if let Ok(mut verb) = payload.u8_at(0) { - if (verb & v1::VERB_FLAG_COMPRESSED) != 0 { + if (verb & MESSAGE_FLAG_COMPRESSED) != 0 { let mut decompressed_payload = [0u8; v1::SIZE_MAX]; decompressed_payload[0] = verb; if let Ok(dlen) = lz4_flex::block::decompress_into(&payload.as_bytes()[1..], &mut decompressed_payload[1..]) { @@ -523,7 +519,7 @@ impl Peer { } } - verb &= v1::VERB_MASK; // mask off flags + verb &= MESSAGE_TYPE_MASK; // mask off flags debug_event!( app, "[vl1] #{:0>16x} decrypted and authenticated, verb: {} ({:0>2x})", @@ -891,7 +887,7 @@ fn v1_proto_try_aead_decrypt( if cipher == v1::CIPHER_SALSA2012_POLY1305 { salsa.crypt_in_place(payload.as_bytes_mut()); Some(message_id) - } else if (payload.u8_at(0).unwrap_or(0) & v1::VERB_MASK) == message_type::VL1_HELLO { + } else if (payload.u8_at(0).unwrap_or(0) & MESSAGE_TYPE_MASK) == message_type::VL1_HELLO { Some(message_id) } else { // SECURITY: fail if there is no encryption and the message is not HELLO. No other types are allowed diff --git a/network-hypervisor/src/vl2/topology.rs b/network-hypervisor/src/vl2/topology.rs index 6d451ac9e..4b34782e7 100644 --- a/network-hypervisor/src/vl2/topology.rs +++ b/network-hypervisor/src/vl2/topology.rs @@ -1,5 +1,4 @@ use std::borrow::Cow; -use std::io::{Read, Write}; use zerotier_utils::blob::Blob; use zerotier_utils::flatsortedmap::FlatSortedMap; @@ -51,16 +50,6 @@ pub struct Topology<'a> { pub members: FlatSortedMap<'a, Blob, Member<'a>>, } -impl<'a> Topology<'a> { - pub fn new_from_bytes(b: &[u8]) -> Result { - rmp_serde::from_slice(b) - } - - pub fn write_bytes_to(&self, w: &mut W) { - rmp_serde::encode::write_named(w, self).unwrap() - } -} - #[inline(always)] fn u64_zero(i: &u64) -> bool { *i == 0 diff --git a/utils/src/base64.rs b/utils/src/base64.rs new file mode 100644 index 000000000..84cf8d310 --- /dev/null +++ b/utils/src/base64.rs @@ -0,0 +1,20 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at https://mozilla.org/MPL/2.0/. + * + * (c) ZeroTier, Inc. + * https://www.zerotier.com/ + */ + +const BASE64_URL_SAFE_NO_PAD_ENGINE: base64::engine::fast_portable::FastPortable = + base64::engine::fast_portable::FastPortable::from(&base64::alphabet::URL_SAFE, base64::engine::fast_portable::NO_PAD); + +/// Encode base64 using URL-safe alphabet and no padding. +pub fn encode_url_nopad(bytes: &[u8]) -> String { + base64::encode_engine(bytes, &BASE64_URL_SAFE_NO_PAD_ENGINE) +} + +/// Decode base64 using URL-safe alphabet and no padding, or None on error. +pub fn decode_url_nopad(b64: &str) -> Option> { + base64::decode_engine(b64, &BASE64_URL_SAFE_NO_PAD_ENGINE).ok() +} diff --git a/utils/src/blob.rs b/utils/src/blob.rs index 660b35947..85ce65e21 100644 --- a/utils/src/blob.rs +++ b/utils/src/blob.rs @@ -14,7 +14,8 @@ use serde::{Deserialize, Deserializer, Serialize, Serializer}; use crate::hex; -/// Fixed size byte array with Serde serializer/deserializer for sizes over 32 elements and hex to_string(). +/// Fixed size Serde serializable byte array. +/// This makes it easier to deal with blobs larger than 32 bytes (due to serde array limitations) #[repr(transparent)] #[derive(Clone, Eq, PartialEq)] pub struct Blob([u8; L]); diff --git a/utils/src/buffer.rs b/utils/src/buffer.rs index cfffef323..4c148bcfa 100644 --- a/utils/src/buffer.rs +++ b/utils/src/buffer.rs @@ -27,7 +27,6 @@ impl Display for OutOfBoundsError { } impl Debug for OutOfBoundsError { - #[inline(always)] fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { Display::fmt(self, f) } diff --git a/utils/src/lib.rs b/utils/src/lib.rs index 4158587d0..194f7af4e 100644 --- a/utils/src/lib.rs +++ b/utils/src/lib.rs @@ -7,6 +7,7 @@ */ pub mod arrayvec; +pub mod base64; pub mod blob; pub mod buffer; pub mod cast; @@ -36,20 +37,7 @@ pub use tokio; pub use futures_util; /// Initial value that should be used for monotonic tick time variables. -pub const NEVER_HAPPENED_TICKS: i64 = 0; - -const BASE64_URL_SAFE_NO_PAD_ENGINE: base64::engine::fast_portable::FastPortable = - base64::engine::fast_portable::FastPortable::from(&base64::alphabet::URL_SAFE, base64::engine::fast_portable::NO_PAD); - -/// Encode base64 using URL-safe alphabet and no padding. -pub fn base64_encode_url_nopad(bytes: &[u8]) -> String { - base64::encode_engine(bytes, &BASE64_URL_SAFE_NO_PAD_ENGINE) -} - -/// Decode base64 using URL-safe alphabet and no padding, or None on error. -pub fn base64_decode_url_nopad(b64: &str) -> Option> { - base64::decode_engine(b64, &BASE64_URL_SAFE_NO_PAD_ENGINE).ok() -} +pub const NEVER_HAPPENED_TICKS: i64 = i64::MIN; /// Get milliseconds since unix epoch. #[inline]