Updates to controller, get rid of compile errors w/Address and NetworkId changes.

This commit is contained in:
Adam Ierymenko 2023-03-27 15:16:05 -04:00
parent ed309b9f18
commit c7ba67c717
18 changed files with 242 additions and 241 deletions

View file

@ -33,13 +33,13 @@ impl Cache {
let networks = db.list_networks().await?;
for network_id in networks {
if let Some(network) = db.get_network(network_id).await? {
let network_entry = by_nwid.entry(network_id).or_insert_with(|| (network, Mutex::new(HashMap::new())));
if let Some(network) = db.get_network(&network_id).await? {
let network_entry = by_nwid.entry(network_id.clone()).or_insert_with(|| (network, Mutex::new(HashMap::new())));
let mut by_node_id = network_entry.1.lock().unwrap();
let members = db.list_members(network_id).await?;
let members = db.list_members(&network_id).await?;
for node_id in members {
if let Some(member) = db.get_member(network_id, node_id).await? {
let _ = by_node_id.insert(node_id, member);
if let Some(member) = db.get_member(&network_id, &node_id).await? {
let _ = by_node_id.insert(node_id.clone(), member);
}
}
}
@ -59,7 +59,7 @@ impl Cache {
(false, None)
}
} else {
let _ = by_nwid.insert(network.id, (network.clone(), Mutex::new(HashMap::new())));
let _ = by_nwid.insert(network.id.clone(), (network.clone(), Mutex::new(HashMap::new())));
(true, None)
}
}
@ -78,7 +78,7 @@ impl Cache {
(false, None)
}
} else {
let _ = by_node_id.insert(member.node_id, member);
let _ = by_node_id.insert(member.node_id.clone(), member);
(true, None)
}
} else {

View file

@ -6,9 +6,8 @@ use std::sync::{Arc, Mutex, RwLock, Weak};
use tokio::time::{Duration, Instant};
use zerotier_crypto::secure_eq;
use zerotier_network_hypervisor::protocol;
use zerotier_network_hypervisor::protocol::{PacketBuffer, DEFAULT_MULTICAST_LIMIT, ZEROTIER_VIRTUAL_NETWORK_DEFAULT_MTU};
use zerotier_network_hypervisor::protocol::PacketBuffer;
use zerotier_network_hypervisor::vl1::identity::{Identity, IdentitySecret};
use zerotier_network_hypervisor::vl1::*;
use zerotier_network_hypervisor::vl2;
@ -16,10 +15,8 @@ use zerotier_network_hypervisor::vl2::multicastauthority::MulticastAuthority;
use zerotier_network_hypervisor::vl2::v1::networkconfig::*;
use zerotier_network_hypervisor::vl2::v1::Revocation;
use zerotier_network_hypervisor::vl2::NetworkId;
use zerotier_utils::blob::Blob;
use zerotier_utils::buffer::OutOfBoundsError;
use zerotier_utils::cast::cast_ref;
use zerotier_utils::error::InvalidParameterError;
use zerotier_utils::reaper::Reaper;
use zerotier_utils::tokio;
use zerotier_utils::{ms_monotonic, ms_since_epoch};
@ -38,6 +35,7 @@ pub struct Controller {
reaper: Reaper,
runtime: tokio::runtime::Handle,
database: Arc<dyn Database>,
local_identity: IdentitySecret,
/// Handler for MULTICAST_LIKE and MULTICAST_GATHER messages.
multicast_authority: MulticastAuthority,
@ -51,6 +49,7 @@ pub struct Controller {
}
impl Controller {
/*
/// Start an inner protocol handler answer ZeroTier VL2 network controller queries.
///
/// The start() method must be called once the service this will run within is also created.
@ -121,6 +120,7 @@ impl Controller {
}
}));
}
*/
/// Launched as a task when the DB informs us of a change.
async fn handle_change_notification(self: Arc<Self>, change: Change) {
@ -162,7 +162,7 @@ impl Controller {
if peer.is_v2() {
todo!()
} else {
let config_data = if let Some(config_dict) = config.v1_proto_to_dictionary(&self.local_identity) {
let config_data = if let Some(config_dict) = config.v1_proto_to_dictionary(&self.local_identity.public) {
config_dict.to_bytes()
} else {
eprintln!("WARNING: unexpected error serializing network config into V1 format dictionary");
@ -173,7 +173,7 @@ impl Controller {
return Err(OutOfBoundsError); // abort
}
packet.append_u64(config.network_id.into())?;
packet.append_u64(config.network_id.to_legacy_u64())?;
packet.append_u16(config_data.len() as u16)?;
packet.append_bytes(config_data.as_slice())?;
@ -196,7 +196,7 @@ impl Controller {
debug_assert!(send_count <= (u16::MAX as usize));
peer.send(
host_system.as_ref(),
host_system.node(),
&host_system.node,
None,
time_ticks,
|packet| -> Result<(), OutOfBoundsError> {
@ -209,7 +209,7 @@ impl Controller {
packet.append_u16(send_count as u16)?;
for _ in 0..send_count {
let r = revocations.pop().unwrap();
packet.append_bytes(r.v1_proto_to_bytes(self.local_identity.address).as_bytes())?;
packet.append_bytes(r.v1_proto_to_bytes(&self.local_identity.public.address).as_bytes())?;
}
packet.append_u16(0)?;
@ -226,12 +226,19 @@ impl Controller {
async fn deauthorize_member(&self, member: &Member) {
let time_clock = ms_since_epoch();
let mut revocations = Vec::with_capacity(1);
if let Ok(all_network_members) = self.database.list_members(member.network_id).await {
if let Ok(all_network_members) = self.database.list_members(&member.network_id).await {
for m in all_network_members.iter() {
if member.node_id != *m {
if let Some(peer) = self.service.read().unwrap().upgrade().and_then(|s| s.node().peer(*m)) {
if let Some(peer) = self.service.read().unwrap().upgrade().and_then(|s| s.node.peer(m)) {
revocations.clear();
Revocation::new(&member.network_id, time_clock, &member.node_id, m, &self.local_identity, false).map(|r| revocations.push(r));
revocations.push(Revocation::new(
&member.network_id,
time_clock,
&member.node_id,
m,
&self.local_identity,
false,
));
self.send_revocations(&peer, &mut revocations);
}
}
@ -249,56 +256,18 @@ impl Controller {
async fn authorize(
self: &Arc<Self>,
source_identity: &Valid<Identity>,
network_id: NetworkId,
network_id: &NetworkId,
time_clock: i64,
) -> Result<(AuthenticationResult, Option<NetworkConfig>), Box<dyn Error + Send + Sync>> {
let network = self.database.get_network(network_id).await?;
let network = self.database.get_network(&network_id).await?;
if network.is_none() {
return Ok((AuthenticationResult::Rejected, None));
}
let network = network.unwrap();
let mut member = self.database.get_member(network_id, source_identity.address).await?;
let mut member = self.database.get_member(&network_id, &source_identity.address).await?;
let mut member_changed = false;
// SECURITY WARNING: this is a critical code path where members of networks are authorized.
// Read and modify with extreme care.
// If we have a member object and a pinned identity, check to make sure it matches. Also accept
// upgraded identities to replace old versions if they are properly formed and their signatures
// all check out (see Identity::is_upgraded_from()). Note that we do not pin the identity here
// if it is unspecified. That's not done until we fully authorize this member, since we don't
// want to have a way to somehow pin the wrong person's identity (if someone manages to somehow
// create a colliding identity and get it to us).
if let Some(member) = member.as_mut() {
if let Some(pinned_identity) = member.identity.as_ref() {
if !pinned_identity.eq(&source_identity) {
if source_identity.is_upgraded_from(pinned_identity) {
// Upgrade identity types if we have a V2 identity upgraded from a V1 identity.
let _ = member.identity.replace(source_identity.clone_without_secret());
let _ = member.identity_fingerprint.replace(Blob::from(source_identity.fingerprint));
member_changed = true;
} else {
return Ok((AuthenticationResult::RejectedIdentityMismatch, None));
}
}
}
if let Some(pinned_fingerprint) = member.identity_fingerprint.as_ref() {
if secure_eq(pinned_fingerprint.as_bytes(), &source_identity.fingerprint) {
if member.identity.is_none() {
// Learn the FULL identity if the fingerprint is pinned and they match. This
// lets us add members by address/fingerprint with full SHA384 identity
// verification instead of just by short address.
let _ = member.identity.replace(source_identity.clone_without_secret());
member_changed = true;
}
} else {
return Ok((AuthenticationResult::RejectedIdentityMismatch, None));
}
}
}
let mut authentication_result = AuthenticationResult::Rejected;
// This is the main "authorized" state of the member record. If it is true then the member is allowed,
@ -310,7 +279,7 @@ impl Controller {
if !member_authorized {
if member.is_none() {
if network.learn_members.unwrap_or(true) {
let _ = member.insert(Member::new_with_identity(source_identity.as_ref().clone(), network_id));
let _ = member.insert(Member::new(source_identity.address.clone(), network_id.clone()));
member_changed = true;
} else {
return Ok((AuthenticationResult::Rejected, None));
@ -351,40 +320,20 @@ impl Controller {
// We should not be able to make it here if this is still false.
assert!(member_authorized);
// Pin member identity if not pinned already. This is analogous to SSH "trust on first use" except
// that the ZeroTier address is akin to the host name. Once we've seen the full identity once then
// it becomes truly "impossible" to collide the address. (Unless you can break ECC and SHA384.)
if member.identity.is_none() {
let _ = member.identity.replace(source_identity.clone_without_secret());
debug_assert!(member.identity_fingerprint.is_none());
let _ = member.identity_fingerprint.replace(Blob::from(source_identity.fingerprint));
member_changed = true;
}
// Make sure these agree. It should be impossible to end up with a member that's authorized and
// whose identity and identity fingerprint don't match.
if !secure_eq(
&member.identity.as_ref().unwrap().fingerprint,
member.identity_fingerprint.as_ref().unwrap().as_bytes(),
) {
debug_assert!(false);
return Ok((AuthenticationResult::RejectedDueToError, None));
}
// Figure out TTL for credentials (time window in V1).
let credential_ttl = network.credential_ttl.unwrap_or(CREDENTIAL_WINDOW_SIZE_DEFAULT);
// Check and if necessary auto-assign static IPs for this member.
member_changed |= network.assign_ip_addresses(self.database.as_ref(), &mut member).await;
let mut nc = NetworkConfig::new(network_id, source_identity.address);
let mut nc = NetworkConfig::new(network_id.clone(), source_identity.address.clone());
nc.name = network.name.clone();
nc.private = network.private;
nc.timestamp = time_clock;
nc.multicast_limit = network.multicast_limit.unwrap_or(DEFAULT_MULTICAST_LIMIT as u32);
nc.multicast_limit = network.multicast_limit.unwrap_or(protocol::v1::DEFAULT_MULTICAST_LIMIT as u32);
nc.multicast_like_expire = Some(protocol::VL2_DEFAULT_MULTICAST_LIKE_EXPIRE as u32);
nc.mtu = network.mtu.unwrap_or(ZEROTIER_VIRTUAL_NETWORK_DEFAULT_MTU as u16);
nc.mtu = network.mtu.unwrap_or(protocol::ZEROTIER_VIRTUAL_NETWORK_DEFAULT_MTU as u16);
nc.routes = network.ip_routes.iter().cloned().collect();
nc.static_ips = member.ip_assignments.iter().cloned().collect();
@ -394,7 +343,7 @@ impl Controller {
// connectivity between valid members.
if let Ok(mut deauthed_members_still_in_window) = self
.database
.list_members_deauthorized_after(network.id, time_clock - (credential_ttl as i64))
.list_members_deauthorized_after(&network.id, time_clock - (credential_ttl as i64))
.await
{
if !deauthed_members_still_in_window.is_empty() {
@ -402,7 +351,8 @@ impl Controller {
nc.rules.reserve(deauthed_members_still_in_window.len() + 1);
let mut or = false;
for dead in deauthed_members_still_in_window.iter() {
nc.rules.push(vl2::rule::Rule::match_source_zerotier_address(false, or, *dead));
nc.rules
.push(vl2::rule::Rule::match_source_zerotier_address(false, or, dead.to_partial()));
or = true;
}
nc.rules.push(vl2::rule::Rule::action_drop());
@ -425,40 +375,34 @@ impl Controller {
// If this network supports V1 nodes we have to include V1 credentials. Otherwise we can skip
// the overhead (bandwidth and CPU) of generating these.
if let Some(com) =
vl2::v1::CertificateOfMembership::new(&self.local_identity, &network_id, &source_identity, time_clock, credential_ttl)
{
let mut v1cred = V1Credentials {
revision: time_clock as u64,
max_delta: credential_ttl,
certificate_of_membership: com,
certificates_of_ownership: Vec::new(),
tags: HashMap::new(),
};
let com = vl2::v1::CertificateOfMembership::new(&self.local_identity, &network_id, &source_identity, time_clock, credential_ttl);
let mut v1cred = V1Credentials {
revision: time_clock as u64,
max_delta: credential_ttl,
certificate_of_membership: com,
certificates_of_ownership: Vec::new(),
tags: HashMap::new(),
};
if !nc.static_ips.is_empty() {
let mut coo = vl2::v1::CertificateOfOwnership::new(&network_id, time_clock, &source_identity.address);
for ip in nc.static_ips.iter() {
coo.add_ip(ip);
}
if !coo.sign(&self.local_identity, &source_identity) {
return Ok((AuthenticationResult::RejectedDueToError, None));
}
v1cred.certificates_of_ownership.push(coo);
if !nc.static_ips.is_empty() {
let mut coo = vl2::v1::CertificateOfOwnership::new(&network_id, time_clock, &source_identity.address);
for ip in nc.static_ips.iter() {
coo.add_ip(ip);
}
for (id, value) in member.tags.iter() {
let tag = vl2::v1::Tag::new(*id, *value, &self.local_identity, network_id, &source_identity, time_clock);
if tag.is_none() {
return Ok((AuthenticationResult::RejectedDueToError, None));
}
let _ = v1cred.tags.insert(*id, tag.unwrap());
if !coo.sign(&self.local_identity, &source_identity) {
return Ok((AuthenticationResult::RejectedDueToError, None));
}
nc.v1_credentials = Some(v1cred);
} else {
return Ok((AuthenticationResult::RejectedDueToError, None));
v1cred.certificates_of_ownership.push(coo);
}
for (id, value) in member.tags.iter() {
let _ = v1cred.tags.insert(
*id,
vl2::v1::Tag::new(*id, *value, &self.local_identity, &network_id, &source_identity, time_clock),
);
}
nc.v1_credentials = Some(v1cred);
}
if source_identity.p384.is_some() {
@ -471,9 +415,9 @@ impl Controller {
.recently_authorized
.write()
.unwrap()
.entry(source_identity.fingerprint)
.entry(source_identity.address.clone())
.or_default()
.insert(network_id, ms_monotonic() + (credential_ttl as i64));
.insert(network_id.clone(), ms_monotonic() + (credential_ttl as i64));
Some(nc)
} else {
@ -508,7 +452,7 @@ impl InnerProtocolLayer for Controller {
if network_id.is_err() {
return PacketHandlerResult::Error;
}
let network_id = NetworkId::from_u64(network_id.unwrap());
let network_id = NetworkId::from_legacy_u64(network_id.unwrap()).ok();
if network_id.is_none() {
return PacketHandlerResult::Error;
}
@ -516,10 +460,10 @@ impl InnerProtocolLayer for Controller {
debug_event!(
app,
"[vl2] NETWORK_CONFIG_REQUEST from {}({}) for {:0>16x}",
"[vl2] NETWORK_CONFIG_REQUEST from {}({}) for {}",
source.identity.address.to_string(),
source_path.endpoint.to_string(),
u64::from(network_id)
network_id.to_string()
);
let metadata = if (cursor + 2) < payload.len() {
@ -540,15 +484,14 @@ impl InnerProtocolLayer for Controller {
let (self2, source, source_remote_endpoint) = (self.self_ref.upgrade().unwrap(), source.clone(), source_path.endpoint.clone());
self.reaper.add(
self.runtime.spawn(async move {
let node_id = source.identity.address;
let node_fingerprint = Blob::from(source.identity.fingerprint);
let node_id = source.identity.address.clone();
let now = ms_since_epoch();
let (result, config) = match self2.authorize(&source.identity, network_id, now).await {
let (result, config) = match self2.authorize(&source.identity, &network_id, now).await {
Result::Ok((result, Some(config))) => {
//println!("{}", serde_yaml::to_string(&config).unwrap());
let app = self2.service.read().unwrap().upgrade().unwrap();
self2.send_network_config(app.as_ref(), app.node(), cast_ref(source.as_ref()).unwrap(), &config, Some(message_id));
self2.send_network_config(app.as_ref(), &app.node, cast_ref(source.as_ref()).unwrap(), &config, Some(message_id));
(result, Some(config))
}
Result::Ok((result, None)) => (result, None),
@ -565,8 +508,7 @@ impl InnerProtocolLayer for Controller {
.log_request(RequestLogItem {
network_id,
node_id,
node_fingerprint,
controller_node_id: self2.local_identity.address,
controller_node_id: self2.local_identity.public.address.clone(),
metadata,
peer_version: source.version(),
peer_protocol_version: source.protocol_version(),
@ -589,7 +531,7 @@ impl InnerProtocolLayer for Controller {
let time_ticks = ms_monotonic();
self.multicast_authority.handle_vl2_multicast_like(
|network_id, identity| {
auth.get(&identity.fingerprint)
auth.get(&identity.address)
.map_or(false, |t| t.get(&network_id).map_or(false, |t| *t > time_ticks))
},
time_ticks,
@ -605,7 +547,7 @@ impl InnerProtocolLayer for Controller {
let time_ticks = ms_monotonic();
self.multicast_authority.handle_vl2_multicast_gather(
|network_id, identity| {
auth.get(&identity.fingerprint)
auth.get(&identity.address)
.map_or(false, |t| t.get(&network_id).map_or(false, |t| *t > time_ticks))
},
time_ticks,

View file

@ -23,11 +23,11 @@ pub enum Change {
#[async_trait]
pub trait Database: Sync + Send + 'static {
async fn list_networks(&self) -> Result<Vec<NetworkId>, Error>;
async fn get_network(&self, id: NetworkId) -> Result<Option<Network>, Error>;
async fn get_network(&self, id: &NetworkId) -> Result<Option<Network>, Error>;
async fn save_network(&self, obj: Network, generate_change_notification: bool) -> Result<(), Error>;
async fn list_members(&self, network_id: NetworkId) -> Result<Vec<Address>, Error>;
async fn get_member(&self, network_id: NetworkId, node_id: Address) -> Result<Option<Member>, Error>;
async fn list_members(&self, network_id: &NetworkId) -> Result<Vec<Address>, Error>;
async fn get_member(&self, network_id: &NetworkId, node_id: &Address) -> Result<Option<Member>, Error>;
async fn save_member(&self, obj: Member, generate_change_notification: bool) -> Result<(), Error>;
/// Get a receiver that can be used to receive changes made to networks and members, if supported.
@ -49,11 +49,11 @@ pub trait Database: Sync + Send + 'static {
///
/// The default trait implementation uses a brute force method. This should be reimplemented if a
/// more efficient way is available.
async fn list_members_deauthorized_after(&self, network_id: NetworkId, cutoff: i64) -> Result<Vec<Address>, Error> {
async fn list_members_deauthorized_after(&self, network_id: &NetworkId, cutoff: i64) -> Result<Vec<Address>, Error> {
let mut v = Vec::new();
let members = self.list_members(network_id).await?;
for a in members.iter() {
if let Some(m) = self.get_member(network_id, *a).await? {
if let Some(m) = self.get_member(network_id, a).await? {
if m.last_deauthorized_time.unwrap_or(i64::MIN) >= cutoff {
v.push(m.node_id);
}
@ -66,10 +66,10 @@ pub trait Database: Sync + Send + 'static {
///
/// The default trait implementation uses a brute force method. This should be reimplemented if a
/// more efficient way is available.
async fn is_ip_assigned(&self, network_id: NetworkId, ip: &InetAddress) -> Result<bool, Error> {
async fn is_ip_assigned(&self, network_id: &NetworkId, ip: &InetAddress) -> Result<bool, Error> {
let members = self.list_members(network_id).await?;
for a in members.iter() {
if let Some(m) = self.get_member(network_id, *a).await? {
if let Some(m) = self.get_member(network_id, a).await? {
if m.ip_assignments.iter().any(|ip2| secure_eq(ip2.ip_bytes(), ip.ip_bytes())) {
return Ok(true);
}

View file

@ -1,4 +1,5 @@
use std::path::{Path, PathBuf};
use std::str::FromStr;
use std::sync::{Arc, Mutex, Weak};
use async_trait::async_trait;
@ -60,11 +61,12 @@ impl FileDatabase {
match event.kind {
notify::EventKind::Create(_) | notify::EventKind::Modify(_) | notify::EventKind::Remove(_) => {
if let Some(db) = db_weak.lock().unwrap().upgrade() {
let controller_address2 = controller_address.clone();
db.clone().tasks.add(
runtime.spawn(async move {
if let Some(path0) = event.paths.first() {
if let Some((record_type, network_id, node_id)) =
Self::record_type_from_path(controller_address, path0.as_path())
Self::record_type_from_path(controller_address2, path0.as_path())
{
// Paths to objects that were deleted or changed. Changed includes adding new objects.
let mut deleted = None;
@ -195,11 +197,11 @@ impl FileDatabase {
Ok(db)
}
fn network_path(&self, network_id: NetworkId) -> PathBuf {
fn network_path(&self, network_id: &NetworkId) -> PathBuf {
self.base_path.join(format!("N{:06x}", network_id.network_no())).join("config.yaml")
}
fn member_path(&self, network_id: NetworkId, member_id: Address) -> PathBuf {
fn member_path(&self, network_id: &NetworkId, member_id: &Address) -> PathBuf {
self.base_path
.join(format!("N{:06x}", network_id.network_no()))
.join(format!("M{}.yaml", member_id.to_string()))
@ -217,7 +219,7 @@ impl FileDatabase {
fn record_type_from_path(controller_address: Address, p: &Path) -> Option<(RecordType, NetworkId, Option<Address>)> {
let parent = p.parent()?.file_name()?.to_string_lossy();
if parent.len() == 7 && (parent.starts_with("N") || parent.starts_with('n')) {
let network_id = NetworkId::from_controller_and_network_no(controller_address, u64::from_str_radix(&parent[1..], 16).ok()?)?;
let network_id = NetworkId::Full(controller_address, u32::from_str_radix(&parent[1..], 16).ok()?);
if let Some(file_name) = p.file_name().map(|p| p.to_string_lossy().to_lowercase()) {
if file_name.eq("config.yaml") {
return Some((RecordType::Network, network_id, None));
@ -225,7 +227,7 @@ impl FileDatabase {
return Some((
RecordType::Member,
network_id,
Some(Address::from_u64(u64::from_str_radix(&file_name.as_str()[1..11], 16).unwrap_or(0))?),
Some(Address::from_str(&file_name.as_str()[1..file_name.len() - 5]).ok()?),
));
}
}
@ -244,7 +246,6 @@ impl Drop for FileDatabase {
impl Database for FileDatabase {
async fn list_networks(&self) -> Result<Vec<NetworkId>, Error> {
let mut networks = Vec::new();
let controller_address_shift24 = u64::from(self.local_identity.address).wrapping_shl(24);
let mut dir = fs::read_dir(&self.base_path).await?;
while let Ok(Some(ent)) = dir.next_entry().await {
if ent.file_type().await.map_or(false, |t| t.is_dir()) {
@ -252,10 +253,8 @@ impl Database for FileDatabase {
let name = osname.to_string_lossy();
if name.len() == 7 && name.starts_with("N") {
if fs::metadata(ent.path().join("config.yaml")).await.is_ok() {
if let Ok(nwid_last24bits) = u64::from_str_radix(&name[1..], 16) {
if let Some(nwid) = NetworkId::from_u64(controller_address_shift24 | nwid_last24bits) {
networks.push(nwid);
}
if let Ok(network_no) = u32::from_str_radix(&name[1..], 16) {
networks.push(NetworkId::Full(self.controller_address.clone(), network_no));
}
}
}
@ -264,13 +263,13 @@ impl Database for FileDatabase {
Ok(networks)
}
async fn get_network(&self, id: NetworkId) -> Result<Option<Network>, Error> {
async fn get_network(&self, id: &NetworkId) -> Result<Option<Network>, Error> {
let mut network = Self::load_object::<Network>(self.network_path(id).as_path()).await?;
if let Some(network) = network.as_mut() {
// FileDatabase stores networks by their "network number" and automatically adapts their IDs
// if the controller's identity changes. This is done to make it easy to just clone networks,
// including storing them in "git."
let network_id_should_be = network.id.change_network_controller(self.local_identity.address);
let network_id_should_be = NetworkId::Full(self.controller_address.clone(), network.id.network_no());
if network.id != network_id_should_be {
network.id = network_id_should_be;
let _ = self.save_network(network.clone(), false).await?;
@ -283,25 +282,22 @@ impl Database for FileDatabase {
if !generate_change_notification {
let _ = self.cache.on_network_updated(obj.clone());
}
let base_network_path = self.network_path(obj.id);
let base_network_path = self.network_path(&obj.id);
let _ = fs::create_dir_all(base_network_path.parent().unwrap()).await;
let _ = fs::write(base_network_path, serde_yaml::to_string(&obj)?.as_bytes()).await?;
return Ok(());
}
async fn list_members(&self, network_id: NetworkId) -> Result<Vec<Address>, Error> {
async fn list_members(&self, network_id: &NetworkId) -> Result<Vec<Address>, Error> {
let mut members = Vec::new();
let mut dir = fs::read_dir(self.base_path.join(format!("N{:06x}", network_id.network_no()))).await?;
while let Ok(Some(ent)) = dir.next_entry().await {
if ent.file_type().await.map_or(false, |t| t.is_file() || t.is_symlink()) {
let osname = ent.file_name();
let name = osname.to_string_lossy();
if name.len() == (zerotier_network_hypervisor::protocol::ADDRESS_SIZE_STRING + 6) && name.starts_with("M") && name.ends_with(".yaml")
{
if let Ok(member_address) = u64::from_str_radix(&name[1..11], 16) {
if let Some(member_address) = Address::from_u64(member_address) {
members.push(member_address);
}
if name.starts_with("M") && name.ends_with(".yaml") {
if let Ok(member_address) = Address::from_str(&name[1..name.len() - 5]) {
members.push(member_address);
}
}
}
@ -309,12 +305,12 @@ impl Database for FileDatabase {
Ok(members)
}
async fn get_member(&self, network_id: NetworkId, node_id: Address) -> Result<Option<Member>, Error> {
let mut member = Self::load_object::<Member>(self.member_path(network_id, node_id).as_path()).await?;
async fn get_member(&self, network_id: &NetworkId, node_id: &Address) -> Result<Option<Member>, Error> {
let mut member = Self::load_object::<Member>(self.member_path(&network_id, node_id).as_path()).await?;
if let Some(member) = member.as_mut() {
if member.network_id != network_id {
if member.network_id.eq(network_id) {
// Also auto-update member network IDs, see get_network().
member.network_id = network_id;
member.network_id = network_id.clone();
self.save_member(member.clone(), false).await?;
}
}
@ -325,7 +321,7 @@ impl Database for FileDatabase {
if !generate_change_notification {
let _ = self.cache.on_member_updated(obj.clone());
}
let base_member_path = self.member_path(obj.network_id, obj.node_id);
let base_member_path = self.member_path(&obj.network_id, &obj.node_id);
let _ = fs::create_dir_all(base_member_path.parent().unwrap()).await;
let _ = fs::write(base_member_path, serde_yaml::to_string(&obj)?.as_bytes()).await?;
Ok(())
@ -348,6 +344,7 @@ mod tests {
use std::sync::atomic::{AtomicUsize, Ordering};
use zerotier_network_hypervisor::vl1::identity::Identity;
/* TODO
#[allow(unused)]
#[test]
fn test_db() {
@ -397,10 +394,11 @@ mod tests {
sleep(Duration::from_millis(100)).await;
zerotier_utils::tokio::task::yield_now().await;
let test_member2 = db.get_member(network_id, node_id).await.unwrap().unwrap();
let test_member2 = db.get_member(&network_id, &node_id).await.unwrap().unwrap();
assert!(test_member == test_member2);
}
});
}
}
*/
}

View file

@ -5,29 +5,17 @@ use std::hash::Hash;
use serde::{Deserialize, Serialize};
use zerotier_network_hypervisor::vl1::identity::Identity;
use zerotier_network_hypervisor::vl1::{Address, InetAddress};
use zerotier_network_hypervisor::vl2::NetworkId;
use zerotier_utils::blob::Blob;
#[derive(Clone, PartialEq, Eq, Serialize, Deserialize, Debug)]
pub struct Member {
#[serde(rename = "address")]
pub node_id: Address,
#[serde(rename = "networkId")]
pub network_id: NetworkId,
/// Pinned full member identity fingerprint, if known.
/// If this is set but 'identity' is not, the 'identity' field will be set on first request
/// but an identity not matching this fingerprint will not be accepted. This allows a member
/// to be created with an address and a fingerprint for full SHA384 identity specification.
#[serde(skip_serializing_if = "Option::is_none")]
pub identity_fingerprint: Option<Blob<{ Identity::FINGERPRINT_SIZE }>>,
/// Pinned full member identity, if known.
#[serde(skip_serializing_if = "Option::is_none")]
pub identity: Option<Identity>,
/// A short name that can also be used for DNS, etc.
#[serde(skip_serializing_if = "String::is_empty")]
#[serde(default)]
@ -81,12 +69,10 @@ pub struct Member {
impl Member {
/// Create a new network member without specifying a "pinned" identity.
pub fn new_without_identity(node_id: Address, network_id: NetworkId) -> Self {
pub fn new(node_id: Address, network_id: NetworkId) -> Self {
Self {
node_id,
network_id,
identity: None,
identity_fingerprint: None,
name: String::new(),
last_authorized_time: None,
last_deauthorized_time: None,
@ -99,13 +85,6 @@ impl Member {
}
}
pub fn new_with_identity(identity: Identity, network_id: NetworkId) -> Self {
let mut tmp = Self::new_without_identity(identity.address, network_id);
tmp.identity_fingerprint = Some(Blob::from(identity.fingerprint));
tmp.identity = Some(identity);
tmp
}
/// Check whether this member is authorized, which is true if the last authorized time is after last deauthorized time.
pub fn authorized(&self) -> bool {
self.last_authorized_time

View file

@ -13,7 +13,6 @@ use serde::{Deserialize, Serialize};
use zerotier_network_hypervisor::vl1::{Address, Endpoint};
use zerotier_network_hypervisor::vl2::v1::networkconfig::NetworkConfig;
use zerotier_network_hypervisor::vl2::NetworkId;
use zerotier_utils::blob::Blob;
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub enum RecordType {
@ -92,8 +91,6 @@ pub struct RequestLogItem {
pub network_id: NetworkId,
#[serde(rename = "n")]
pub node_id: Address,
#[serde(rename = "nf")]
pub node_fingerprint: Blob<48>,
#[serde(rename = "c")]
pub controller_node_id: Address,

View file

@ -173,7 +173,7 @@ impl Network {
for route in self.ip_routes.iter() {
let ip = InetAddress::from_ip_port(&ip_ptr.to_be_bytes(), route.target.port()); // IP/bits
if ip.is_within(&route.target) {
if let Ok(is_ip_assigned) = database.is_ip_assigned(self.id, &ip).await {
if let Ok(is_ip_assigned) = database.is_ip_assigned(&self.id, &ip).await {
if !is_ip_assigned {
modified = true;
let _ = member.ip_assignments.insert(ip);
@ -201,7 +201,7 @@ impl Network {
for route in self.ip_routes.iter() {
let ip = InetAddress::from_ip_port(&ip_ptr.to_be_bytes(), route.target.port()); // IP/bits
if ip.is_within(&route.target) {
if let Ok(is_ip_assigned) = database.is_ip_assigned(self.id, &ip).await {
if let Ok(is_ip_assigned) = database.is_ip_assigned(&self.id, &ip).await {
if !is_ip_assigned {
modified = true;
let _ = member.ip_assignments.insert(ip);

View file

@ -129,21 +129,12 @@ pub const UDP_DEFAULT_MTU: usize = 1432;
/// Default MTU inside VL2 virtual networks.
pub const ZEROTIER_VIRTUAL_NETWORK_DEFAULT_MTU: usize = 2800;
/// Default multicast limit if not set in the network.
pub const DEFAULT_MULTICAST_LIMIT: usize = 32;
/// Length of an address in bytes.
pub const ADDRESS_SIZE: usize = 5;
/// Length of an address in string format.
pub const ADDRESS_SIZE_STRING: usize = 10;
/// Bit mask for address bits in a u64.
pub const ADDRESS_MASK: u64 = 0xffffffffff;
pub mod v1 {
use super::*;
/// Default multicast limit if not set in the network.
pub const DEFAULT_MULTICAST_LIMIT: usize = 32;
/// Size of packet header that lies outside the encryption envelope.
pub const HEADER_SIZE: usize = 27;

View file

@ -1,6 +1,7 @@
// (c) 2020-2022 ZeroTier, Inc. -- currently proprietary pending actual release and licensing. See LICENSE.md.
use std::array::TryFromSliceError;
use std::fmt::Debug;
use std::io::Write;
use std::str::FromStr;
@ -383,6 +384,13 @@ impl PartialOrd for Identity {
}
}
impl Debug for Identity {
#[inline]
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.write_str(self.to_string().as_str())
}
}
impl Marshalable for Identity {
const MAX_MARSHAL_SIZE: usize = packed::V2_PUBLIC_SIZE;

View file

@ -22,6 +22,8 @@ use crate::vl1::Valid;
use crate::vl1::{Address, Endpoint, Path};
use crate::{VERSION_MAJOR, VERSION_MINOR, VERSION_REVISION};
use super::PartialAddress;
pub(crate) const SERVICE_INTERVAL_MS: i64 = 10000;
pub struct Peer<Application: ApplicationLayer + ?Sized> {
@ -766,16 +768,16 @@ impl<Application: ApplicationLayer + ?Sized> Peer<Application> {
) -> PacketHandlerResult {
if node.this_node_is_root() || app.should_respond_to(&self.identity) {
let mut addresses = payload.as_bytes();
while addresses.len() >= ADDRESS_SIZE {
while addresses.len() >= PartialAddress::LEGACY_SIZE_BYTES {
if !self
.send(app, node, None, time_ticks, |packet| {
while addresses.len() >= ADDRESS_SIZE && (packet.len() + Identity::MAX_MARSHAL_SIZE) <= UDP_DEFAULT_MTU {
if let Ok(zt_address) = Address::from_bytes(&addresses[..ADDRESS_SIZE]) {
while addresses.len() >= PartialAddress::LEGACY_SIZE_BYTES && (packet.len() + Identity::MAX_MARSHAL_SIZE) <= UDP_DEFAULT_MTU {
if let Ok(zt_address) = Address::from_bytes(&addresses[..PartialAddress::LEGACY_SIZE_BYTES]) {
if let Some(peer) = node.peer(&zt_address) {
peer.identity.write_bytes(packet, !self.is_v2())?;
}
}
addresses = &addresses[ADDRESS_SIZE..];
addresses = &addresses[PartialAddress::LEGACY_SIZE_BYTES..];
}
Ok(())
})

View file

@ -122,7 +122,7 @@ impl MulticastAuthority {
let in_this_packet = gathered
.len()
.clamp(1, (packet.capacity() - packet.len()) / protocol::ADDRESS_SIZE)
.clamp(1, (packet.capacity() - packet.len()) / PartialAddress::LEGACY_SIZE_BYTES)
.min(u16::MAX as usize);
packet.append_u16(in_this_packet as u16)?;

View file

@ -19,6 +19,9 @@ pub enum NetworkId {
}
impl NetworkId {
/// Maximum network number on a controller (24 bits)
pub const MAX_NETWORK_NO: u32 = 0xffffff;
pub fn to_bytes(&self) -> Vec<u8> {
match self {
Self::Legacy(nwid) => nwid.to_be_bytes().to_vec(),
@ -44,6 +47,14 @@ impl NetworkId {
}
}
/// Get the 24-bit network number on the network's controller.
pub fn network_no(&self) -> u32 {
match self {
Self::Legacy(nwid) => (*nwid & 0xffffff) as u32,
Self::Full(_, nwid) => *nwid & 0xffffff,
}
}
pub fn from_legacy_u64(nwid: u64) -> Result<Self, InvalidParameterError> {
let _ = PartialAddress::from_legacy_address_u64(nwid)?; // check validity of address portion
Ok(Self::Legacy(nwid))
@ -58,10 +69,10 @@ impl NetworkId {
}
/// Convert this into a legacy network ID in u64 form, or return itself if already a legacy ID.
pub(crate) fn to_legacy_u64(&self) -> u64 {
pub fn to_legacy_u64(&self) -> u64 {
match self {
Self::Legacy(nwid) => *nwid,
Self::Full(controller, nw) => controller.legacy_u64().wrapping_shl(24) | ((*nw & 0xffffff) as u64),
Self::Full(controller, nw) => controller.legacy_u64().wrapping_shl(24) | ((*nw & Self::MAX_NETWORK_NO) as u64),
}
}
}
@ -70,7 +81,7 @@ impl ToString for NetworkId {
fn to_string(&self) -> String {
match self {
Self::Legacy(nwid) => hex::to_string_u64(*nwid, false),
Self::Full(controller, nw) => format!("{:08x}@{}", *nw, controller.to_string()),
Self::Full(controller, nw) => format!("{:06x}@{}", *nw & Self::MAX_NETWORK_NO, controller.to_string()),
}
}
}
@ -87,7 +98,7 @@ impl FromStr for NetworkId {
let mut controller = None;
for ss in s.split('@') {
if fno == 0 {
net_no = hex::from_string_u64(ss);
net_no = u32::from_str_radix(ss, 16).map_err(|_| InvalidParameterError("invalid network ID"))?;
} else if fno == 1 {
controller = Some(Address::from_str(ss)?);
} else {

View file

@ -9,8 +9,7 @@ use phf::phf_map;
use zerotier_utils::buffer::{Buffer, OutOfBoundsError};
use zerotier_utils::marshalable::{Marshalable, UnmarshalError};
use crate::protocol;
use crate::vl1::{Address, InetAddress, PartialAddress, MAC};
use crate::vl1::{InetAddress, PartialAddress, MAC};
#[allow(unused)]
pub const RULES_ENGINE_REVISION: u8 = 1;
@ -235,7 +234,7 @@ impl Rule {
Self { t: action::DROP, v: RuleValue::default() }
}
pub fn action_tee(address: Address, flags: u32, length: u16) -> Self {
pub fn action_tee(address: PartialAddress, flags: u32, length: u16) -> Self {
Self {
t: action::TEE,
v: RuleValue {
@ -244,7 +243,7 @@ impl Rule {
}
}
pub fn action_watch(address: Address, flags: u32, length: u16) -> Self {
pub fn action_watch(address: PartialAddress, flags: u32, length: u16) -> Self {
Self {
t: action::TEE,
v: RuleValue {
@ -253,7 +252,7 @@ impl Rule {
}
}
pub fn action_redirect(address: Address, flags: u32, length: u16) -> Self {
pub fn action_redirect(address: PartialAddress, flags: u32, length: u16) -> Self {
Self {
t: action::TEE,
v: RuleValue {
@ -270,14 +269,14 @@ impl Rule {
Self { t: action::PRIORITY, v: RuleValue { qos_bucket } }
}
pub fn match_source_zerotier_address(not: bool, or: bool, address: Address) -> Self {
pub fn match_source_zerotier_address(not: bool, or: bool, address: PartialAddress) -> Self {
Self {
t: t(not, or, match_cond::SOURCE_ZEROTIER_ADDRESS),
v: RuleValue { zt: address.legacy_u64() },
}
}
pub fn match_dest_zerotier_address(not: bool, or: bool, address: Address) -> Self {
pub fn match_dest_zerotier_address(not: bool, or: bool, address: PartialAddress) -> Self {
Self {
t: t(not, or, match_cond::DEST_ZEROTIER_ADDRESS),
v: RuleValue { zt: address.legacy_u64() },
@ -462,7 +461,7 @@ impl Marshalable for Rule {
}
match_cond::SOURCE_ZEROTIER_ADDRESS | match_cond::DEST_ZEROTIER_ADDRESS => {
buf.append_u8(5)?;
buf.append_bytes(&self.v.zt.to_be_bytes()[..protocol::ADDRESS_SIZE])?;
buf.append_bytes(&self.v.zt.to_be_bytes()[..PartialAddress::LEGACY_SIZE_BYTES])?;
}
match_cond::VLAN_ID => {
buf.append_u8(2)?;
@ -562,7 +561,7 @@ impl Marshalable for Rule {
r.v.qos_bucket = buf.read_u8(cursor)?;
}
match_cond::SOURCE_ZEROTIER_ADDRESS | match_cond::DEST_ZEROTIER_ADDRESS => {
let a = buf.read_bytes_fixed::<{ protocol::ADDRESS_SIZE }>(cursor)?;
let a = buf.read_bytes_fixed::<{ PartialAddress::LEGACY_SIZE_BYTES }>(cursor)?;
r.v.zt = a[0].wrapping_shl(32) as u64
| a[1].wrapping_shl(24) as u64
| a[2].wrapping_shl(16) as u64

View file

@ -168,9 +168,9 @@ impl CertificateOfOwnership {
}
/// Sign certificate of ownership for use by V1 nodes.
pub fn sign(&mut self, issuer_address: &Address, issuer: &IdentitySecret, issued_to: &Identity) -> bool {
pub fn sign(&mut self, issuer: &IdentitySecret, issued_to: &Identity) -> bool {
self.issued_to = issued_to.address.legacy_u64();
if let Some(to_sign) = self.internal_to_bytes(true, issuer_address) {
if let Some(to_sign) = self.internal_to_bytes(true, &issuer.public.address) {
self.signature = issuer.sign(&to_sign.as_slice());
return true;
}

View file

@ -21,15 +21,7 @@ pub struct Revocation {
}
impl Revocation {
pub fn new(
network_id: &NetworkId,
threshold: i64,
target: &Address,
issued_to: &Address,
signer_address: &Address,
signer: &IdentitySecret,
fast_propagate: bool,
) -> Self {
pub fn new(network_id: &NetworkId, threshold: i64, target: &Address, issued_to: &Address, signer: &IdentitySecret, fast_propagate: bool) -> Self {
let mut r = Self {
network_id: network_id.to_legacy_u64(),
threshold,
@ -38,7 +30,7 @@ impl Revocation {
signature: ArrayVec::new(),
fast_propagate,
};
r.signature = signer.sign(r.internal_to_bytes(true, signer_address).as_bytes());
r.signature = signer.sign(r.internal_to_bytes(true, &signer.public.address).as_bytes());
r
}

View file

@ -21,15 +21,7 @@ pub struct Tag {
}
impl Tag {
pub fn new(
id: u32,
value: u32,
issuer_address: &Address,
issuer: &IdentitySecret,
network_id: &NetworkId,
issued_to: &Identity,
timestamp: i64,
) -> Self {
pub fn new(id: u32, value: u32, issuer: &IdentitySecret, network_id: &NetworkId, issued_to: &Identity, timestamp: i64) -> Self {
let mut tag = Self {
network_id: network_id.to_legacy_u64(),
timestamp,
@ -38,7 +30,7 @@ impl Tag {
value,
signature: Blob::default(),
};
let to_sign = tag.internal_to_bytes(true, issuer_address);
let to_sign = tag.internal_to_bytes(true, &issuer.public.address);
tag.signature.as_mut().copy_from_slice(issuer.sign(to_sign.as_ref()).as_bytes());
tag
}

View file

@ -204,6 +204,34 @@ impl<T, const C: usize> ArrayVec<T, C> {
C - self.s
}
#[inline(always)]
pub fn iter(&self) -> impl DoubleEndedIterator<Item = &T> {
self.as_ref().iter()
}
#[inline(always)]
pub fn iter_mut(&mut self) -> impl DoubleEndedIterator<Item = &mut T> {
self.as_mut().iter_mut()
}
#[inline(always)]
pub fn first(&self) -> Option<&T> {
if self.s != 0 {
Some(unsafe { self.a.get_unchecked(0).assume_init_ref() })
} else {
None
}
}
#[inline(always)]
pub fn last(&self) -> Option<&T> {
if self.s != 0 {
Some(unsafe { self.a.get_unchecked(self.s - 1).assume_init_ref() })
} else {
None
}
}
#[inline]
pub fn pop(&mut self) -> Option<T> {
if self.s > 0 {

62
utils/src/basex.rs Normal file
View file

@ -0,0 +1,62 @@
use super::arrayvec::ArrayVec;
fn big_div_rem<const C: usize>(n: &mut ArrayVec<u32, C>, d: u32) -> u32 {
while let Some(&0) = n.last() {
n.pop();
}
let d = d as u64;
let mut rem = 0;
for word in n.iter_mut().rev() {
let temp = (rem as u64).wrapping_shl(32) | (*word as u64);
let (a, b) = (temp / d, temp % d);
*word = a as u32;
rem = b as u32;
}
while let Some(&0) = n.last() {
n.pop();
}
rem
}
fn big_add<const C: usize>(n: &mut ArrayVec<u32, C>, i: u32) {
debug_assert!(i <= (u32::MAX - 1));
debug_assert!(!n.is_empty());
debug_assert!(n.iter().any(|x| *x != 0));
let mut carry = false;
for word in n.iter_mut() {
(*word, carry) = word.overflowing_add(i.wrapping_add(carry as u32));
}
if carry {
n.push(1);
}
}
fn big_mul<const C: usize>(n: &mut ArrayVec<u32, C>, m: u32) {
while let Some(&0) = n.last() {
n.pop();
}
let m = m as u64;
let mut carry = 0;
for word in n.iter_mut() {
let temp = (*word as u64).wrapping_mul(m).wrapping_add(carry);
*word = (temp & 0xffffffff) as u32;
carry = temp.wrapping_shr(32);
}
if carry > 0 {
n.push(carry as u32);
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn div_rem() {
let mut n = ArrayVec::<u32, 4>::new();
n.push_slice(&[0xdeadbeef, 0xfeedfeed, 0xcafebabe, 0xf00dd00d]);
let rem = big_div_rem(&mut n, 63);
let nn = n.as_ref();
assert!(nn[0] == 0xaa23440b && nn[1] == 0xa696103c && nn[2] == 0x89513fea && nn[3] == 0x03cf7514 && rem == 58);
}
}