Some VL1 work, and reorg the header in ZSSP to make backward compatibility easy.

This commit is contained in:
Adam Ierymenko 2022-09-13 21:27:47 -04:00
parent 115c1c6c56
commit 887585f6fa
No known key found for this signature in database
GPG key ID: C8877CF2D7A5D7F3
4 changed files with 115 additions and 129 deletions

View file

@ -427,11 +427,11 @@ impl<H: Host> Session<H> {
let fragment_size = fragment_data_size + HEADER_SIZE;
c.crypt(&data[..fragment_data_size], &mut mtu_buffer[HEADER_SIZE..fragment_size]);
data = &data[fragment_data_size..];
armor_header(mtu_buffer, &self.header_check_cipher);
set_header_mac(mtu_buffer, &self.header_check_cipher);
send(&mut mtu_buffer[..fragment_size]);
debug_assert!(header[7].wrapping_shr(2) < 63);
header[7] += 0x04; // increment fragment number
debug_assert!(header[15].wrapping_shr(2) < 63);
header[15] += 0x04; // increment fragment number
mtu_buffer[..HEADER_SIZE].copy_from_slice(&header);
if data.len() <= last_fragment_data_mtu {
@ -445,7 +445,7 @@ impl<H: Host> Session<H> {
c.crypt(data, &mut mtu_buffer[HEADER_SIZE..gcm_tag_idx]);
mtu_buffer[gcm_tag_idx..packet_len].copy_from_slice(&c.finish_encrypt());
armor_header(mtu_buffer, &self.header_check_cipher);
set_header_mac(mtu_buffer, &self.header_check_cipher);
send(&mut mtu_buffer[..packet_len]);
key.return_send_cipher(c);
@ -562,11 +562,17 @@ impl<H: Host> ReceiveContext<H> {
return Err(Error::InvalidPacket);
}
if let Some(local_session_id) = SessionId::new_from_u64(memory::u64_from_le_bytes(incoming_packet) & SessionId::MAX_BIT_MASK) {
let counter = memory::u32_from_le_bytes(incoming_packet);
let packet_type_fragment_info = memory::u16_from_le_bytes(&incoming_packet[14..16]);
let packet_type = (packet_type_fragment_info & 0x0f) as u8;
let fragment_count = ((packet_type_fragment_info.wrapping_shr(4) + 1) as u8) & 63;
let fragment_no = packet_type_fragment_info.wrapping_shr(10) as u8;
if let Some(local_session_id) =
SessionId::new_from_u64(memory::u64_from_le_bytes(&incoming_packet[8..16]) & SessionId::MAX_BIT_MASK)
{
if let Some(session) = host.session_lookup(local_session_id) {
if let Some((packet_type, fragment_count, fragment_no, counter)) =
dearmor_header(incoming_packet, &session.header_check_cipher)
{
if check_header_mac(incoming_packet, &session.header_check_cipher) {
let pseudoheader = Pseudoheader::make(u64::from(local_session_id), packet_type, counter);
if fragment_count > 1 {
if fragment_count <= (MAX_FRAGMENTS as u8) && fragment_no < fragment_count {
@ -613,9 +619,7 @@ impl<H: Host> ReceiveContext<H> {
}
} else {
unlikely_branch();
if let Some((packet_type, fragment_count, fragment_no, counter)) =
dearmor_header(incoming_packet, &self.incoming_init_header_check_cipher)
{
if check_header_mac(incoming_packet, &self.incoming_init_header_check_cipher) {
let pseudoheader = Pseudoheader::make(0, packet_type, counter);
if fragment_count > 1 {
let mut defrag = self.initial_offer_defrag.lock();
@ -1152,7 +1156,7 @@ impl<H: Host> ReceiveContext<H> {
reply_buf[HEADER_SIZE..].copy_from_slice(&c.finish_encrypt());
key.return_send_cipher(c);
armor_header(&mut reply_buf, &session.header_check_cipher);
set_header_mac(&mut reply_buf, &session.header_check_cipher);
send(&mut reply_buf);
let mut state = RwLockUpgradableReadGuard::upgrade(state);
@ -1351,6 +1355,15 @@ fn create_initial_offer<SendFunction: FnMut(&mut [u8])>(
}))
}
#[derive(Copy, Clone)]
#[repr(C, packed)]
struct PackedHeader {
counter: u32,
recipient_session_id_low32: u32,
recipient_session_id_high16_packet_type_fragment_info: u32,
zero: u32,
}
#[inline(always)]
fn create_packet_header(
header: &mut [u8],
@ -1365,17 +1378,20 @@ fn create_packet_header(
debug_assert!(header.len() >= HEADER_SIZE);
debug_assert!(mtu >= MIN_MTU);
debug_assert!(packet_len >= MIN_PACKET_SIZE);
debug_assert!(fragment_count <= MAX_FRAGMENTS);
debug_assert!(fragment_count > 0);
debug_assert!(packet_type <= 0x0f); // packet type is 4 bits
debug_assert!(recipient_session_id <= 0xffffffffffff); // session ID is 48 bits
if fragment_count <= MAX_FRAGMENTS {
header[0..8].copy_from_slice(
&(recipient_session_id | (packet_type as u64).wrapping_shl(48) | ((fragment_count - 1) as u64).wrapping_shl(52)).to_le_bytes(),
);
header[8..12].copy_from_slice(&counter.to_u32().to_le_bytes());
header[12..16].fill(0);
header[..16].copy_from_slice(memory::as_byte_array::<[u32; 4], 16>(&[
counter.to_u32().to_le(),
0,
(recipient_session_id as u32).to_le(),
((recipient_session_id.wrapping_shr(32) as u32)
| (packet_type as u32).wrapping_shl(16)
| ((fragment_count - 1) as u32).wrapping_shl(20))
.to_le(),
]));
Ok(())
} else {
unlikely_branch();
@ -1395,11 +1411,11 @@ fn send_with_fragmentation<SendFunction: FnMut(&mut [u8])>(
let mut header: [u8; 16] = packet[..HEADER_SIZE].try_into().unwrap();
loop {
let fragment = &mut packet[fragment_start..fragment_end];
armor_header(fragment, header_check_cipher);
set_header_mac(fragment, header_check_cipher);
send(fragment);
if fragment_end < packet_len {
debug_assert!(header[7].wrapping_shr(2) < 63);
header[7] += 0x04; // increment fragment number
debug_assert!(header[15].wrapping_shr(2) < 63);
header[15] += 0x04; // increment fragment number
fragment_start = fragment_end - HEADER_SIZE;
fragment_end = (fragment_start + mtu).min(packet_len);
packet[fragment_start..(fragment_start + HEADER_SIZE)].copy_from_slice(&header);
@ -1410,45 +1426,21 @@ fn send_with_fragmentation<SendFunction: FnMut(&mut [u8])>(
}
}
/// Encrypt everything in header after session ID using AES-CTR and the second 16 bytes as a nonce.
/// The last four bytes of the header must be zero, so this also embeds a small header MAC.
/// Set 32-bit header MAC.
#[inline(always)]
fn armor_header(packet: &mut [u8], header_check_cipher: &Aes) {
fn set_header_mac(packet: &mut [u8], header_check_cipher: &Aes) {
debug_assert!(packet.len() >= MIN_PACKET_SIZE);
let mut header_pad = 0u128.to_ne_bytes();
header_check_cipher.encrypt_block(&packet[16..32], &mut header_pad);
packet[SESSION_ID_SIZE..HEADER_SIZE]
.iter_mut()
.zip(header_pad.iter())
.for_each(|(x, y)| *x ^= *y);
let mut header_mac = 0u128.to_ne_bytes();
header_check_cipher.encrypt_block(&packet[8..24], &mut header_mac);
packet[4..8].copy_from_slice(&header_mac[..4]);
}
/// Dearmor the armored part of the header and return it if the 32-bit MAC matches.
fn dearmor_header(packet: &[u8], header_check_cipher: &Aes) -> Option<(u8, u8, u8, u32)> {
/// Check 32-bit header MAC on an incoming packet.
fn check_header_mac(packet: &[u8], header_check_cipher: &Aes) -> bool {
debug_assert!(packet.len() >= MIN_PACKET_SIZE);
let mut header_pad = 0u128.to_ne_bytes();
header_check_cipher.encrypt_block(&packet[16..32], &mut header_pad);
let header_pad = u128::from_ne_bytes(header_pad);
#[cfg(target_endian = "little")]
let (header_0_8, header_8_16) = {
let header = memory::u128_from_ne_bytes(packet) ^ header_pad.wrapping_shl(48);
(header as u64, header.wrapping_shr(64) as u64)
};
#[cfg(target_endian = "big")]
let (header_0_8, header_8_16) = {
let header = memory::u128_from_ne_bytes(packet) ^ header_pad.wrapping_shr(48);
((header.wrapping_shr(64) as u64).swap_bytes(), (header as u64).swap_bytes())
};
if header_8_16.wrapping_shr(32) == 0 {
let packet_type = (header_0_8.wrapping_shr(48) as u8) & 15;
let fragment_count = ((header_0_8.wrapping_shr(52) as u8) & 63).wrapping_add(1);
let fragment_no = (header_0_8.wrapping_shr(58) as u8) & 63;
Some((packet_type, fragment_count, fragment_no, header_8_16 as u32))
} else {
None
}
let mut header_mac = 0u128.to_ne_bytes();
header_check_cipher.encrypt_block(&packet[8..24], &mut header_mac);
memory::u32_from_ne_bytes(&packet[4..8]) == memory::u32_from_ne_bytes(&header_mac)
}
/// Parse KEY_OFFER and KEY_COUNTER_OFFER starting after the unencrypted public key part.

View file

@ -155,7 +155,7 @@ struct BackgroundTaskIntervals {
struct RootInfo<SI: SystemInterface> {
sets: HashMap<String, RootSet>,
roots: HashMap<Arc<Peer<SI>>, Vec<Endpoint>>,
my_root_sets: Option<Vec<u8>>,
this_root_sets: Option<Vec<u8>>,
sets_modified: bool,
online: bool,
}
@ -278,7 +278,7 @@ impl<SI: SystemInterface> Node<SI> {
roots: RwLock::new(RootInfo {
sets: HashMap::new(),
roots: HashMap::new(),
my_root_sets: None,
this_root_sets: None,
sets_modified: false,
online: false,
}),
@ -504,7 +504,7 @@ impl<SI: SystemInterface> Node<SI> {
if !old_root_identities.eq(&new_root_identities) {
let mut roots = self.roots.write();
roots.roots = new_roots;
roots.my_root_sets = my_root_sets;
roots.this_root_sets = my_root_sets;
si.event(Event::UpdatedRoots(old_root_identities, new_root_identities));
}
}
@ -729,14 +729,17 @@ impl<SI: SystemInterface> Node<SI> {
}
}
/// Get the current "best" root from among this node's trusted roots.
pub fn best_root(&self) -> Option<Arc<Peer<SI>>> {
self.best_root.read().clone()
}
/// Check whether this peer is a root according to any root set trusted by this node.
pub fn is_peer_root(&self, peer: &Peer<SI>) -> bool {
self.roots.read().roots.keys().any(|p| p.identity.eq(&peer.identity))
}
/// Called when a remote node sends us a root set update.
pub(crate) fn remote_update_root_set(&self, received_from: &Identity, rs: RootSet) {
let mut roots = self.roots.write();
if let Some(entry) = roots.sets.get_mut(&rs.name) {
@ -763,23 +766,27 @@ impl<SI: SystemInterface> Node<SI> {
return false;
}
/// Returns whether or not this node has any root sets defined.
pub fn has_roots_defined(&self) -> bool {
self.roots.read().sets.iter().any(|rs| !rs.1.members.is_empty())
}
/// Get the root sets that this node trusts.
pub fn root_sets(&self) -> Vec<RootSet> {
self.roots.read().sets.values().cloned().collect()
}
pub(crate) fn my_root_sets(&self) -> Option<Vec<u8>> {
self.roots.read().my_root_sets.clone()
/// Get the root set(s) to which this node belongs if it is a root.
pub(crate) fn this_root_sets_as_bytes(&self) -> Option<Vec<u8>> {
self.roots.read().this_root_sets.clone()
}
#[allow(unused)]
pub(crate) fn this_node_is_root(&self) -> bool {
self.roots.read().my_root_sets.is_some()
/// Returns true if this node is a member of a root set (that it knows about).
pub fn this_node_is_root(&self) -> bool {
self.roots.read().this_root_sets.is_some()
}
/// Get the canonical Path object corresponding to an endpoint.
pub(crate) fn canonical_path(
&self,
ep: &Endpoint,
@ -788,13 +795,13 @@ impl<SI: SystemInterface> Node<SI> {
time_ticks: i64,
) -> Arc<Path<SI>> {
if let Some(path) = self.paths.read().get(&PathKey::Ref(ep, local_socket)) {
return path.clone();
path.clone()
} else {
self.paths
.write()
.entry(PathKey::Copied(ep.clone(), local_socket.clone()))
.or_insert_with(|| Arc::new(Path::new(ep.clone(), local_socket.clone(), local_interface.clone(), time_ticks)))
.clone()
}
return self
.paths
.write()
.entry(PathKey::Copied(ep.clone(), local_socket.clone()))
.or_insert_with(|| Arc::new(Path::new(ep.clone(), local_socket.clone(), local_interface.clone(), time_ticks)))
.clone();
}
}

View file

@ -8,7 +8,7 @@ use std::sync::{Arc, Weak};
use parking_lot::{Mutex, RwLock};
use zerotier_crypto::poly1305;
use zerotier_crypto::random::next_u64_secure;
use zerotier_crypto::random;
use zerotier_crypto::salsa::Salsa;
use zerotier_crypto::secret::Secret;
use zerotier_utils::memory::array_range;
@ -25,50 +25,35 @@ use crate::{VERSION_MAJOR, VERSION_MINOR, VERSION_REVISION};
pub(crate) const SERVICE_INTERVAL_MS: i64 = 10000;
pub struct Peer<SI: SystemInterface> {
pub identity: Identity,
static_symmetric_key: SymmetricSecret,
paths: Mutex<Vec<PeerPath<SI>>>,
pub(crate) last_send_time_ticks: AtomicI64,
pub(crate) last_receive_time_ticks: AtomicI64,
pub(crate) last_hello_reply_time_ticks: AtomicI64,
pub(crate) last_forward_time_ticks: AtomicI64,
pub(crate) create_time_ticks: i64,
random_ticks_offset: u32,
message_id_counter: AtomicU64,
remote_node_info: RwLock<RemoteNodeInfo>,
}
struct PeerPath<SI: SystemInterface> {
path: Weak<Path<SI>>,
last_receive_time_ticks: i64,
}
struct RemoteNodeInfo {
remote_instance_id: [u8; 16],
reported_local_endpoints: HashMap<Endpoint, i64>,
remote_version: u64,
remote_protocol_version: u8,
remote_version: (u8, u8, u16),
}
/// A remote peer known to this node.
///
/// Equality and hashing is implemented in terms of the identity.
pub struct Peer<SI: SystemInterface> {
// This peer's identity.
pub identity: Identity,
// Static shared secret computed from agreement with identity.
identity_symmetric_key: SymmetricSecret,
// Paths sorted in descending order of quality / preference.
paths: Mutex<Vec<PeerPath<SI>>>,
// Statistics and times of events.
pub(crate) last_send_time_ticks: AtomicI64,
pub(crate) last_receive_time_ticks: AtomicI64,
pub(crate) last_hello_reply_time_ticks: AtomicI64,
pub(crate) last_forward_time_ticks: AtomicI64,
pub(crate) last_incoming_message_id: AtomicU64,
pub(crate) create_time_ticks: i64,
// A random offset added to ticks sent to this peer to avoid disclosing the actual tick count.
random_ticks_offset: u64,
// Counter for assigning sequential message IDs.
message_id_counter: AtomicU64,
// Other information reported by remote node.
remote_node_info: RwLock<RemoteNodeInfo>,
}
/// Attempt AEAD packet encryption and MAC validation. Returns message ID on success.
/// Attempt ZeroTier V1 protocol AEAD packet encryption and MAC validation. Returns message ID on success.
fn try_aead_decrypt(
secret: &SymmetricSecret,
packet_frag0_payload_bytes: &[u8],
@ -185,7 +170,7 @@ impl<SI: SystemInterface> Peer<SI> {
this_node_identity.agree(&id).map(|static_secret| -> Self {
Self {
identity: id,
identity_symmetric_key: SymmetricSecret::new(static_secret),
static_symmetric_key: SymmetricSecret::new(static_secret),
paths: Mutex::new(Vec::with_capacity(4)),
last_send_time_ticks: AtomicI64::new(crate::util::NEVER_HAPPENED_TICKS),
last_receive_time_ticks: AtomicI64::new(crate::util::NEVER_HAPPENED_TICKS),
@ -193,29 +178,23 @@ impl<SI: SystemInterface> Peer<SI> {
last_hello_reply_time_ticks: AtomicI64::new(crate::util::NEVER_HAPPENED_TICKS),
last_incoming_message_id: AtomicU64::new(0),
create_time_ticks: time_ticks,
random_ticks_offset: next_u64_secure(),
message_id_counter: AtomicU64::new(next_u64_secure()),
random_ticks_offset: random::xorshift64_random() as u32,
message_id_counter: AtomicU64::new(random::xorshift64_random()),
remote_node_info: RwLock::new(RemoteNodeInfo {
remote_instance_id: [0_u8; 16],
reported_local_endpoints: HashMap::new(),
remote_version: 0,
remote_protocol_version: 0,
remote_version: (0, 0, 0),
}),
}
})
}
/// Get the remote version of this peer: major, minor, revision, and build.
/// Get the remote version of this peer: major, minor, revision.
/// Returns None if it's not yet known.
pub fn version(&self) -> Option<[u16; 4]> {
pub fn version(&self) -> Option<(u8, u8, u16)> {
let rv = self.remote_node_info.read().remote_version;
if rv != 0 {
Some([
rv.wrapping_shr(48) as u16,
rv.wrapping_shr(32) as u16,
rv.wrapping_shr(16) as u16,
rv as u16,
])
if rv.0 != 0 || rv.1 != 0 || rv.2 != 0 {
Some(rv)
} else {
None
}
@ -433,7 +412,7 @@ impl<SI: SystemInterface> Peer<SI> {
v1::CIPHER_AES_GMAC_SIV
};
let mut aes_gmac_siv = self.identity_symmetric_key.aes_gmac_siv.get();
let mut aes_gmac_siv = self.static_symmetric_key.aes_gmac_siv.get();
aes_gmac_siv.encrypt_init(&self.next_message_id().to_ne_bytes());
aes_gmac_siv.encrypt_set_aad(&v1::get_packet_aad_bytes(
self.identity.address,
@ -543,14 +522,14 @@ impl<SI: SystemInterface> Peer<SI> {
f.1.version_major = VERSION_MAJOR;
f.1.version_minor = VERSION_MINOR;
f.1.version_revision = VERSION_REVISION.to_be_bytes();
f.1.timestamp = (time_ticks as u64).wrapping_add(self.random_ticks_offset).to_be_bytes();
f.1.timestamp = (time_ticks as u64).wrapping_add(self.random_ticks_offset as u64).to_be_bytes();
}
debug_assert_eq!(packet.len(), 41);
assert!(packet.append_bytes((&node.identity.to_public_bytes()).into()).is_ok());
let (_, poly1305_key) = salsa_poly_create(
&self.identity_symmetric_key,
&self.static_symmetric_key,
packet.struct_at::<v1::PacketHeader>(0).unwrap(),
packet.len(),
);
@ -611,7 +590,7 @@ impl<SI: SystemInterface> Peer<SI> {
let mut payload = PacketBuffer::new();
let message_id = if let Some(message_id2) = try_aead_decrypt(
&self.identity_symmetric_key,
&self.static_symmetric_key,
packet_frag0_payload_bytes,
packet_header,
fragments,
@ -684,9 +663,6 @@ impl<SI: SystemInterface> Peer<SI> {
verbs::VL1_USER_MESSAGE => self.handle_incoming_user_message(si, node, time_ticks, source_path, &payload).await,
_ => ph.handle_packet(self, &source_path, verb, &payload).await,
} {
// This needs to be saved AFTER processing the packet since some message types may use it to try to filter for replays.
self.last_incoming_message_id.store(message_id, Ordering::Relaxed);
return true;
}
}
@ -721,9 +697,11 @@ impl<SI: SystemInterface> Peer<SI> {
{
let mut remote_node_info = self.remote_node_info.write();
remote_node_info.remote_protocol_version = hello_fixed_headers.version_proto;
remote_node_info.remote_version = (hello_fixed_headers.version_major as u64).wrapping_shl(48)
| (hello_fixed_headers.version_minor as u64).wrapping_shl(32)
| (u16::from_be_bytes(hello_fixed_headers.version_revision) as u64).wrapping_shl(16);
remote_node_info.remote_version = (
hello_fixed_headers.version_major,
hello_fixed_headers.version_minor,
u16::from_be_bytes(hello_fixed_headers.version_revision),
);
}
let mut packet = PacketBuffer::new();

View file

@ -80,6 +80,11 @@ pub mod verbs {
pub const VL1_PUSH_DIRECT_PATHS: u8 = 0x10;
pub const VL1_USER_MESSAGE: u8 = 0x14;
pub const VL2_VERB_MULTICAST_LIKE: u8 = 0x09;
pub const VL2_VERB_NETWORK_CONFIG_REQUEST: u8 = 0x0b;
pub const VL2_VERB_NETWORK_CONFIG: u8 = 0x0c;
pub const VL2_VERB_MULTICAST_GATHER: u8 = 0x0d;
pub fn name(verb: u8) -> &'static str {
match verb {
VL1_NOP => "VL1_NOP",
@ -91,6 +96,10 @@ pub mod verbs {
VL1_ECHO => "VL1_ECHO",
VL1_PUSH_DIRECT_PATHS => "VL1_PUSH_DIRECT_PATHS",
VL1_USER_MESSAGE => "VL1_USER_MESSAGE",
VL2_VERB_MULTICAST_LIKE => "VL2_VERB_MULTICAST_LIKE",
VL2_VERB_NETWORK_CONFIG_REQUEST => "VL2_VERB_NETWORK_CONFIG_REQUEST",
VL2_VERB_NETWORK_CONFIG => "VL2_VERB_NETWORK_CONFIG",
VL2_VERB_MULTICAST_GATHER => "VL2_VERB_MULTICAST_GATHER",
_ => "???",
}
}