Tons and tons of work on the controller, and some other cleanup.

This commit is contained in:
Adam Ierymenko 2022-10-07 11:53:10 -04:00
parent b7c32c2692
commit dc615e2b04
No known key found for this signature in database
GPG key ID: C8877CF2D7A5D7F3
23 changed files with 1488 additions and 689 deletions

View file

@ -1,175 +0,0 @@
// (c) 2020-2022 ZeroTier, Inc. -- currently propritery pending actual release and licensing. See LICENSE.md.
use std::sync::Arc;
use tokio::time::{Duration, Instant};
use zerotier_network_hypervisor::protocol::{verbs, PacketBuffer};
use zerotier_network_hypervisor::vl1::{HostSystem, Identity, InnerProtocol, PacketHandlerResult, Path, PathFilter, Peer};
use zerotier_network_hypervisor::vl2::NetworkId;
use zerotier_utils::dictionary::Dictionary;
use zerotier_utils::reaper::Reaper;
use zerotier_utils::tokio;
use crate::database::Database;
const REQUEST_TIMEOUT: Duration = Duration::from_secs(10);
pub struct Controller<DatabaseImpl: Database> {
database: Arc<DatabaseImpl>,
reaper: Reaper,
runtime: tokio::runtime::Handle,
}
impl<DatabaseImpl: Database> Controller<DatabaseImpl> {
pub fn new(database: Arc<DatabaseImpl>, runtime: tokio::runtime::Handle) -> Arc<Self> {
Arc::new(Self { database, reaper: Reaper::new(&runtime), runtime })
}
async fn handle_network_config_request<HostSystemImpl: HostSystem>(
database: Arc<DatabaseImpl>,
source: Arc<Peer<HostSystemImpl>>,
source_path: Arc<Path<HostSystemImpl>>,
network_id: NetworkId,
meta_data: Dictionary,
have_revision: Option<u64>,
have_timestamp: Option<u64>,
) {
println!(
"handle_network_config_request {} {} {}",
source.identity.to_string(),
source_path.endpoint.to_string(),
network_id.to_string()
);
if let Ok(Some(network)) = database.get_network(network_id).await {}
}
}
impl<DatabaseImpl: Database> PathFilter for Controller<DatabaseImpl> {
fn should_use_physical_path<HostSystemImpl: HostSystem>(
&self,
_id: &Identity,
_endpoint: &zerotier_network_hypervisor::vl1::Endpoint,
_local_socket: Option<&HostSystemImpl::LocalSocket>,
_local_interface: Option<&HostSystemImpl::LocalInterface>,
) -> bool {
true
}
fn get_path_hints<HostSystemImpl: HostSystem>(
&self,
_id: &Identity,
) -> Option<
Vec<(
zerotier_network_hypervisor::vl1::Endpoint,
Option<HostSystemImpl::LocalSocket>,
Option<HostSystemImpl::LocalInterface>,
)>,
> {
None
}
}
impl<DatabaseImpl: Database> InnerProtocol for Controller<DatabaseImpl> {
fn handle_packet<HostSystemImpl: HostSystem>(
&self,
source: &Arc<Peer<HostSystemImpl>>,
source_path: &Arc<Path<HostSystemImpl>>,
verb: u8,
payload: &PacketBuffer,
) -> PacketHandlerResult {
match verb {
verbs::VL2_VERB_NETWORK_CONFIG_REQUEST => {
let mut cursor = 1;
let network_id = payload.read_u64(&mut cursor);
if network_id.is_err() {
return PacketHandlerResult::Error;
}
let network_id = NetworkId::from_u64(network_id.unwrap());
if network_id.is_none() {
return PacketHandlerResult::Error;
}
let network_id = network_id.unwrap();
let meta_data = if (cursor + 2) < payload.len() {
let meta_data_len = payload.read_u16(&mut cursor);
if meta_data_len.is_err() {
return PacketHandlerResult::Error;
}
if let Ok(d) = payload.read_bytes(meta_data_len.unwrap() as usize, &mut cursor) {
let d = Dictionary::from_bytes(d);
if d.is_none() {
return PacketHandlerResult::Error;
}
d.unwrap()
} else {
return PacketHandlerResult::Error;
}
} else {
Dictionary::new()
};
let (have_revision, have_timestamp) = if (cursor + 16) <= payload.len() {
let r = payload.read_u64(&mut cursor);
let t = payload.read_u64(&mut cursor);
if r.is_err() || t.is_err() {
return PacketHandlerResult::Error;
}
(Some(r.unwrap()), Some(t.unwrap()))
} else {
(None, None)
};
if let Some(deadline) = Instant::now().checked_add(REQUEST_TIMEOUT) {
self.reaper.add(
self.runtime.spawn(Self::handle_network_config_request(
self.database.clone(),
source.clone(),
source_path.clone(),
network_id,
meta_data,
have_revision,
have_timestamp,
)),
deadline,
);
} else {
eprintln!("WARNING: Instant::now() + REQUEST_TIMEOUT overflowed! should be impossible.");
}
PacketHandlerResult::Ok
}
_ => PacketHandlerResult::NotHandled,
}
}
fn handle_error<HostSystemImpl: HostSystem>(
&self,
_source: &Arc<Peer<HostSystemImpl>>,
_source_path: &Arc<Path<HostSystemImpl>>,
_in_re_verb: u8,
_in_re_message_id: u64,
_error_code: u8,
_payload: &PacketBuffer,
_cursor: &mut usize,
) -> PacketHandlerResult {
PacketHandlerResult::NotHandled
}
fn handle_ok<HostSystemImpl: HostSystem>(
&self,
_source: &Arc<Peer<HostSystemImpl>>,
_source_path: &Arc<Path<HostSystemImpl>>,
_in_re_verb: u8,
_in_re_message_id: u64,
_payload: &PacketBuffer,
_cursor: &mut usize,
) -> PacketHandlerResult {
PacketHandlerResult::NotHandled
}
fn should_communicate_with(&self, _: &Identity) -> bool {
true
}
}

View file

@ -2,7 +2,7 @@ use std::error::Error;
use async_trait::async_trait; use async_trait::async_trait;
use zerotier_network_hypervisor::vl1::{Address, NodeStorage}; use zerotier_network_hypervisor::vl1::{Address, InetAddress, NodeStorage};
use zerotier_network_hypervisor::vl2::NetworkId; use zerotier_network_hypervisor::vl2::NetworkId;
use crate::model::*; use crate::model::*;
@ -14,7 +14,40 @@ pub trait Database: Sync + Send + NodeStorage + 'static {
async fn list_members(&self, network_id: NetworkId) -> Result<Vec<Address>, Box<dyn Error>>; async fn list_members(&self, network_id: NetworkId) -> Result<Vec<Address>, Box<dyn Error>>;
async fn get_member(&self, network_id: NetworkId, node_id: Address) -> Result<Option<Member>, Box<dyn Error>>; async fn get_member(&self, network_id: NetworkId, node_id: Address) -> Result<Option<Member>, Box<dyn Error>>;
async fn save_member(&self, obj: &Member) -> Result<(), Box<dyn Error>>; async fn save_member(&self, obj: Member) -> Result<(), Box<dyn Error>>;
/// List members deauthorized after a given time (milliseconds since epoch).
///
/// 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>, Box<dyn 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 m.last_deauthorized_time.unwrap_or(i64::MIN) >= cutoff {
v.push(m.node_id);
}
}
}
Ok(v)
}
/// Check if any member of a network has a given static IP assignment.
///
/// 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, Box<dyn 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 m.ip_assignments.iter().any(|ip2| ip2.ip_bytes().eq(ip.ip_bytes())) {
return Ok(true);
}
}
}
return Ok(false);
}
async fn log_request(&self, obj: &RequestLogItem) -> Result<(), Box<dyn Error>>; async fn log_request(&self, obj: &RequestLogItem) -> Result<(), Box<dyn Error>>;
} }

View file

@ -169,15 +169,15 @@ impl Database for FileDatabase {
} }
} }
async fn save_member(&self, obj: &Member) -> Result<(), Box<dyn Error>> { async fn save_member(&self, obj: Member) -> Result<(), Box<dyn Error>> {
let base_member_path = member_path(&self.base_path, obj.network_id, obj.node_id); let base_member_path = member_path(&self.base_path, obj.network_id, obj.node_id);
if !fs::metadata(&base_member_path).await.is_ok() { if !fs::metadata(&base_member_path).await.is_ok() {
fs::write(base_member_path, to_json_pretty(obj).as_bytes()).await?; fs::write(base_member_path, to_json_pretty(&obj).as_bytes()).await?;
} }
fs::write( fs::write(
member_path(&self.cache_path, obj.network_id, obj.node_id), member_path(&self.cache_path, obj.network_id, obj.node_id),
serde_json::to_vec(obj)?.as_slice(), serde_json::to_vec(&obj)?.as_slice(),
) )
.await?; .await?;
Ok(()) Ok(())

303
controller/src/handler.rs Normal file
View file

@ -0,0 +1,303 @@
// (c) 2020-2022 ZeroTier, Inc. -- currently propritery pending actual release and licensing. See LICENSE.md.
use std::error::Error;
use std::sync::Arc;
use tokio::time::{Duration, Instant};
use zerotier_network_hypervisor::protocol::{verbs, PacketBuffer, DEFAULT_MULTICAST_LIMIT, ZEROTIER_VIRTUAL_NETWORK_DEFAULT_MTU};
use zerotier_network_hypervisor::vl1::{HostSystem, Identity, InnerProtocol, Node, PacketHandlerResult, Path, PathFilter, Peer};
use zerotier_network_hypervisor::vl2::{CertificateOfMembership, CertificateOfOwnership, NetworkConfig, NetworkId, Tag};
use zerotier_utils::dictionary::Dictionary;
use zerotier_utils::error::UnexpectedError;
use zerotier_utils::ms_since_epoch;
use zerotier_utils::reaper::Reaper;
use zerotier_utils::tokio;
use crate::database::Database;
use crate::model::{AuthorizationResult, Member, CREDENTIAL_WINDOW_SIZE_DEFAULT};
const REQUEST_TIMEOUT: Duration = Duration::from_secs(10);
pub struct Handler<DatabaseImpl: Database> {
reaper: Reaper,
runtime: tokio::runtime::Handle,
inner: Arc<Inner<DatabaseImpl>>,
}
impl<DatabaseImpl: Database> Handler<DatabaseImpl> {
pub fn new(database: Arc<DatabaseImpl>, runtime: tokio::runtime::Handle, local_identity: Identity) -> Arc<Self> {
assert!(local_identity.secret.is_some());
Arc::new(Self {
reaper: Reaper::new(&runtime),
runtime,
inner: Arc::new(Inner::<DatabaseImpl> { database, local_identity }),
})
}
}
impl<DatabaseImpl: Database> PathFilter for Handler<DatabaseImpl> {
fn should_use_physical_path<HostSystemImpl: HostSystem>(
&self,
_id: &Identity,
_endpoint: &zerotier_network_hypervisor::vl1::Endpoint,
_local_socket: Option<&HostSystemImpl::LocalSocket>,
_local_interface: Option<&HostSystemImpl::LocalInterface>,
) -> bool {
true
}
fn get_path_hints<HostSystemImpl: HostSystem>(
&self,
_id: &Identity,
) -> Option<
Vec<(
zerotier_network_hypervisor::vl1::Endpoint,
Option<HostSystemImpl::LocalSocket>,
Option<HostSystemImpl::LocalInterface>,
)>,
> {
None
}
}
impl<DatabaseImpl: Database> InnerProtocol for Handler<DatabaseImpl> {
fn handle_packet<HostSystemImpl: HostSystem>(
&self,
_node: &Node<HostSystemImpl>,
source: &Arc<Peer<HostSystemImpl>>,
source_path: &Arc<Path<HostSystemImpl>>,
message_id: u64,
verb: u8,
payload: &PacketBuffer,
) -> PacketHandlerResult {
match verb {
verbs::VL2_VERB_NETWORK_CONFIG_REQUEST => {
let mut cursor = 1;
let network_id = payload.read_u64(&mut cursor);
if network_id.is_err() {
return PacketHandlerResult::Error;
}
let network_id = NetworkId::from_u64(network_id.unwrap());
if network_id.is_none() {
return PacketHandlerResult::Error;
}
let network_id = network_id.unwrap();
let meta_data = if (cursor + 2) < payload.len() {
let meta_data_len = payload.read_u16(&mut cursor);
if meta_data_len.is_err() {
return PacketHandlerResult::Error;
}
if let Ok(d) = payload.read_bytes(meta_data_len.unwrap() as usize, &mut cursor) {
let d = Dictionary::from_bytes(d);
if d.is_none() {
return PacketHandlerResult::Error;
}
d.unwrap()
} else {
return PacketHandlerResult::Error;
}
} else {
Dictionary::new()
};
let (have_revision, have_timestamp) = if (cursor + 16) <= payload.len() {
let r = payload.read_u64(&mut cursor);
let t = payload.read_u64(&mut cursor);
if r.is_err() || t.is_err() {
return PacketHandlerResult::Error;
}
(Some(r.unwrap()), Some(t.unwrap()))
} else {
(None, None)
};
// Launch handler as an async background task.
let (inner, source2, source_path2) = (self.inner.clone(), source.clone(), source_path.clone());
self.reaper.add(
self.runtime.spawn(async move {
// TODO: log errors
let _ = inner.handle_network_config_request(
source2,
source_path2,
message_id,
network_id,
meta_data,
have_revision,
have_timestamp,
);
}),
Instant::now().checked_add(REQUEST_TIMEOUT).unwrap(),
);
PacketHandlerResult::Ok
}
_ => PacketHandlerResult::NotHandled,
}
}
fn handle_error<HostSystemImpl: HostSystem>(
&self,
_node: &Node<HostSystemImpl>,
_source: &Arc<Peer<HostSystemImpl>>,
_source_path: &Arc<Path<HostSystemImpl>>,
_message_id: u64,
_in_re_verb: u8,
_in_re_message_id: u64,
_error_code: u8,
_payload: &PacketBuffer,
_cursor: &mut usize,
) -> PacketHandlerResult {
PacketHandlerResult::NotHandled
}
fn handle_ok<HostSystemImpl: HostSystem>(
&self,
_node: &Node<HostSystemImpl>,
_source: &Arc<Peer<HostSystemImpl>>,
_source_path: &Arc<Path<HostSystemImpl>>,
_message_id: u64,
_in_re_verb: u8,
_in_re_message_id: u64,
_payload: &PacketBuffer,
_cursor: &mut usize,
) -> PacketHandlerResult {
PacketHandlerResult::NotHandled
}
fn should_communicate_with(&self, _: &Identity) -> bool {
true
}
}
struct Inner<DatabaseImpl: Database> {
database: Arc<DatabaseImpl>,
local_identity: Identity,
}
impl<DatabaseImpl: Database> Inner<DatabaseImpl> {
async fn handle_network_config_request<HostSystemImpl: HostSystem>(
self: Arc<Self>,
source: Arc<Peer<HostSystemImpl>>,
_source_path: Arc<Path<HostSystemImpl>>,
_message_id: u64,
network_id: NetworkId,
_meta_data: Dictionary,
_have_revision: Option<u64>,
_have_timestamp: Option<u64>,
) -> Result<(AuthorizationResult, Option<NetworkConfig>), Box<dyn Error>> {
let network = self.database.get_network(network_id).await?;
if network.is_none() {
// TODO: send error
return Ok((AuthorizationResult::Rejected, None));
}
let network = network.unwrap();
let mut member = self.database.get_member(network_id, source.identity.address).await?;
let mut member_changed = false;
let legacy_v1 = source.identity.p384.is_none();
// If we have a member object and a pinned identity, check to make sure it matches.
if let Some(member) = member.as_ref() {
if let Some(pinned_identity) = member.identity.as_ref() {
if !pinned_identity.eq(&source.identity) {
return Ok((AuthorizationResult::RejectedIdentityMismatch, None));
}
}
}
let now = ms_since_epoch();
let mut authorization_result = AuthorizationResult::Rejected;
let mut authorized = member.as_ref().map_or(false, |m| m.authorized());
if !authorized {
if member.is_none() {
if network.learn_members {
let _ = member.insert(Member::new_with_identity(source.identity.clone(), network_id));
member_changed = true;
} else {
return Ok((AuthorizationResult::Rejected, None));
}
}
if !network.private {
authorization_result = AuthorizationResult::ApprovedOnPublicNetwork;
authorized = true;
member.as_mut().unwrap().last_authorized_time = Some(now);
member_changed = true;
}
}
let mut member = member.unwrap();
let nc: Option<NetworkConfig> = if authorized {
// ====================================================================================
// Authorized requests are handled here
// ====================================================================================
// TODO: check SSO
// Figure out time bounds for the certificate to generate.
let max_delta = network.credential_window_size.unwrap_or(CREDENTIAL_WINDOW_SIZE_DEFAULT);
// Get a list of all network members that were deauthorized but are still within the time window.
// These will be issued revocations to remind the node not to speak to them until they fall off.
let deauthed_members_still_in_window = self.database.list_members_deauthorized_after(network.id, now - max_delta).await;
// Check and if necessary auto-assign static IPs for this member.
member_changed |= network.check_zt_ip_assignments(self.database.as_ref(), &mut member).await;
let mut nc = NetworkConfig::new(network_id, source.identity.address);
nc.name = member.name.clone();
nc.private = network.private;
nc.timestamp = now;
nc.max_delta = max_delta;
nc.revision = now as u64;
nc.mtu = network.mtu.unwrap_or(ZEROTIER_VIRTUAL_NETWORK_DEFAULT_MTU as u16);
nc.multicast_limit = network.multicast_limit.unwrap_or(DEFAULT_MULTICAST_LIMIT as u32);
nc.routes = network.ip_routes;
nc.static_ips = member.ip_assignments.clone();
nc.rules = network.rules;
nc.dns = network.dns;
nc.certificate_of_membership = Some(
CertificateOfMembership::new(&self.local_identity, network_id, &source.identity, now, max_delta, legacy_v1)
.ok_or(UnexpectedError)?,
);
let mut coo = CertificateOfOwnership::new(network_id, now, source.identity.address, legacy_v1);
for ip in nc.static_ips.iter() {
coo.add_ip(ip);
}
if !coo.sign(&self.local_identity, &source.identity) {
return Err(Box::new(UnexpectedError));
}
nc.certificates_of_ownership.push(coo);
for (id, value) in member.tags.iter() {
let _ = nc.tags.insert(
*id,
Tag::new(*id, *value, &self.local_identity, network_id, &source.identity, now, legacy_v1).ok_or(UnexpectedError)?,
);
}
// TODO: node info, which isn't supported in v1 so not needed yet
// TODO: revocations!
Some(nc)
// ====================================================================================
} else {
None
};
if member_changed {
self.database.save_member(member).await?;
}
Ok((authorization_result, nc))
}
}

View file

@ -1,6 +1,6 @@
// (c) 2020-2022 ZeroTier, Inc. -- currently propritery pending actual release and licensing. See LICENSE.md. // (c) 2020-2022 ZeroTier, Inc. -- currently propritery pending actual release and licensing. See LICENSE.md.
pub mod controller;
pub mod database; pub mod database;
pub mod filedatabase; pub mod filedatabase;
pub mod handler;
pub mod model; pub mod model;

View file

@ -6,9 +6,9 @@ use std::time::Duration;
use clap::{Arg, Command}; use clap::{Arg, Command};
use zerotier_network_controller::controller::Controller;
use zerotier_network_controller::database::Database; use zerotier_network_controller::database::Database;
use zerotier_network_controller::filedatabase::FileDatabase; use zerotier_network_controller::filedatabase::FileDatabase;
use zerotier_network_controller::handler::Handler;
use zerotier_network_hypervisor::{VERSION_MAJOR, VERSION_MINOR, VERSION_REVISION}; use zerotier_network_hypervisor::{VERSION_MAJOR, VERSION_MINOR, VERSION_REVISION};
use zerotier_utils::exitcode; use zerotier_utils::exitcode;
@ -16,12 +16,12 @@ use zerotier_utils::tokio::runtime::Runtime;
use zerotier_vl1_service::VL1Service; use zerotier_vl1_service::VL1Service;
async fn run<DatabaseImpl: Database>(database: Arc<DatabaseImpl>, runtime: &Runtime) -> i32 { async fn run<DatabaseImpl: Database>(database: Arc<DatabaseImpl>, runtime: &Runtime) -> i32 {
let controller = Controller::new(database.clone(), runtime.handle().clone()); let handler = Handler::new(database.clone(), runtime.handle().clone(), todo!());
let svc = VL1Service::new( let svc = VL1Service::new(
database.clone(), database.clone(),
controller.clone(), handler.clone(),
controller.clone(), handler.clone(),
zerotier_vl1_service::VL1Settings::default(), zerotier_vl1_service::VL1Settings::default(),
); );
if svc.is_ok() { if svc.is_ok() {

View file

@ -1,225 +0,0 @@
// (c) 2020-2022 ZeroTier, Inc. -- currently propritery pending actual release and licensing. See LICENSE.md.
use std::collections::{HashMap, HashSet};
use std::hash::Hash;
use serde::{Deserialize, Serialize};
use zerotier_network_hypervisor::vl1::{Address, Endpoint, Identity, InetAddress};
use zerotier_network_hypervisor::vl2::NetworkId;
/// Static string included in JSON-serializable objects to indicate their object type through the API.
#[derive(Clone, PartialEq, Eq, Serialize, Deserialize)]
pub enum ObjectType {
#[serde(rename = "network")]
Network,
#[serde(rename = "member")]
Member,
}
impl ObjectType {
fn network() -> ObjectType {
Self::Network
}
fn member() -> ObjectType {
Self::Member
}
}
#[derive(Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct Ipv4AssignMode {
pub zt: bool,
}
#[derive(Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct Ipv6AssignMode {
pub zt: bool,
pub rfc4193: bool,
#[serde(rename = "6plane")]
pub _6plane: bool,
}
#[derive(Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct IpAssignmentPool {
#[serde(rename = "ipRangeStart")]
ip_range_start: InetAddress,
#[serde(rename = "ipRangeEnd")]
ip_range_end: InetAddress,
}
#[derive(Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct Tag {
pub id: u32,
pub value: u32,
}
#[derive(Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct Network {
pub id: NetworkId,
pub name: String,
#[serde(rename = "creationTime")]
pub creation_time: i64,
#[serde(rename = "multicastLimit")]
pub multicast_limit: u64,
#[serde(rename = "enableBroadcast")]
pub enable_broadcast: bool,
#[serde(rename = "v4AssignMode")]
pub v4_assign_mode: Ipv4AssignMode,
#[serde(rename = "v6AssignMode")]
pub v6_assign_mode: Ipv6AssignMode,
#[serde(rename = "ipAssignmentPools")]
pub ip_assignment_pools: Vec<IpAssignmentPool>,
#[serde(rename = "rulesSource")]
pub rules_source: String,
pub mtu: u16,
pub private: bool,
#[serde(default = "ObjectType::network")]
pub objtype: ObjectType,
}
impl Hash for Network {
#[inline]
fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
self.id.hash(state)
}
}
#[derive(Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct Member {
#[serde(rename = "address")]
pub node_id: Address,
#[serde(rename = "networkId")]
pub network_id: NetworkId,
pub identity: Option<Identity>,
pub name: String,
pub description: String,
#[serde(rename = "creationTime")]
pub creation_time: i64,
#[serde(rename = "revision")]
pub last_modified_time: i64,
pub authorized: bool,
#[serde(rename = "lastAuthorizedTime")]
pub last_authorized_time: Option<i64>,
#[serde(rename = "lastDeauthorizedTime")]
pub last_deauthorized_time: Option<i64>,
#[serde(rename = "ipAssignments")]
pub ip_assignments: HashSet<InetAddress>,
#[serde(rename = "noAutoAssignIps")]
pub no_auto_assign_ips: bool,
/// If true this member is a full Ethernet bridge.
#[serde(rename = "activeBridge")]
pub bridge: bool,
pub tags: Vec<Tag>,
#[serde(rename = "ssoExempt")]
pub sso_exempt: bool,
/// If true this node is explicitly listed in every member's network configuration.
#[serde(rename = "advertised")]
pub advertised: bool,
/// Most recently generated and signed network configuration for this member in binary format.
#[serde(rename = "networkConfig")]
pub network_config: Option<Vec<u8>>,
/// API object type documentation field, not actually edited/used.
#[serde(default = "ObjectType::member")]
pub objtype: ObjectType,
}
impl Hash for Member {
#[inline]
fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
self.node_id.hash(state);
self.network_id.hash(state);
}
}
/// A complete network with all member configuration information for import/export or blob storage.
#[derive(Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct NetworkExport {
pub network: Network,
pub members: HashMap<Address, Member>,
}
#[derive(Clone, PartialEq, Eq, Serialize, Deserialize)]
#[repr(u8)]
pub enum AuthorizationResult {
#[serde(rename = "r")]
Rejected = 0,
#[serde(rename = "rs")]
RejectedViaSSO = 1,
#[serde(rename = "rt")]
RejectedViaToken = 2,
#[serde(rename = "ro")]
RejectedTooOld = 3,
#[serde(rename = "a")]
Approved = 16,
#[serde(rename = "as")]
ApprovedViaSSO = 17,
#[serde(rename = "at")]
ApprovedViaToken = 18,
}
impl ToString for AuthorizationResult {
fn to_string(&self) -> String {
match self {
Self::Rejected => "rejected",
_ => "",
}
.to_string()
}
}
#[derive(Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct RequestLogItem {
#[serde(rename = "nwid")]
pub network_id: NetworkId,
#[serde(rename = "nid")]
pub node_id: Address,
#[serde(rename = "cid")]
pub controller_node_id: Address,
#[serde(rename = "md")]
pub metadata: Vec<u8>,
#[serde(rename = "ts")]
pub timestamp: i64,
#[serde(rename = "v")]
pub version: (u16, u16, u16, u16),
#[serde(rename = "s")]
pub source_remote_endpoint: Endpoint,
#[serde(rename = "sh")]
pub source_hops: u8,
#[serde(rename = "r")]
pub result: AuthorizationResult,
}
impl ToString for RequestLogItem {
fn to_string(&self) -> String {
format!(
"{} {} {} ts={} v={}.{}.{},{} s={},{} {}",
self.controller_node_id.to_string(),
self.network_id.to_string(),
self.node_id.to_string(),
self.timestamp,
self.version.0,
self.version.1,
self.version.2,
self.version.3,
self.source_remote_endpoint.to_string(),
self.source_hops,
self.result.to_string()
)
}
}

View file

@ -0,0 +1,108 @@
// (c) 2020-2022 ZeroTier, Inc. -- currently propritery pending actual release and licensing. See LICENSE.md.
use std::collections::{HashMap, HashSet};
use std::hash::Hash;
use serde::{Deserialize, Serialize};
use zerotier_network_hypervisor::vl1::{Address, Identity, InetAddress};
use zerotier_network_hypervisor::vl2::NetworkId;
use crate::model::ObjectType;
#[derive(Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct Member {
#[serde(rename = "address")]
pub node_id: Address,
#[serde(rename = "networkId")]
pub network_id: NetworkId,
/// Pinned full member identity, if known.
pub identity: Option<Identity>,
/// A short name that can also be used for DNS, etc.
#[serde(default)]
pub name: String,
/// Time member was most recently authorized, None for 'never'.
#[serde(rename = "lastAuthorizedTime")]
pub last_authorized_time: Option<i64>,
/// Time member was most recently deauthorized, None for 'never'.
#[serde(rename = "lastDeauthorizedTime")]
pub last_deauthorized_time: Option<i64>,
/// ZeroTier-managed IP assignments.
#[serde(rename = "ipAssignments")]
#[serde(default)]
pub ip_assignments: HashSet<InetAddress>,
/// If true, do not auto-assign IPs in the controller.
#[serde(rename = "noAutoAssignIps")]
#[serde(default)]
pub no_auto_assign_ips: bool,
/// If true this member is a full Ethernet bridge.
#[serde(rename = "activeBridge")]
#[serde(default)]
pub bridge: bool,
/// Tags that can be used in rule evaluation for ACL-like behavior.
#[serde(default)]
pub tags: HashMap<u32, u32>,
/// Member is exempt from SSO, authorization managed conventionally.
#[serde(rename = "ssoExempt")]
#[serde(default)]
pub sso_exempt: bool,
/// If true this node is explicitly listed in every member's network configuration.
/// This is only supported for V2 nodes.
#[serde(rename = "advertised")]
#[serde(default)]
pub advertised: bool,
/// API object type documentation field, not actually edited/used.
#[serde(default = "ObjectType::member")]
pub objtype: ObjectType,
}
impl Member {
pub fn new_with_identity(identity: Identity, network_id: NetworkId) -> Self {
Self {
node_id: identity.address,
network_id,
identity: Some(identity),
name: String::new(),
last_authorized_time: None,
last_deauthorized_time: None,
ip_assignments: HashSet::new(),
no_auto_assign_ips: false,
bridge: false,
tags: HashMap::new(),
sso_exempt: false,
advertised: false,
objtype: ObjectType::Member,
}
}
/// 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
.map_or(false, |la| self.last_deauthorized_time.map_or(true, |ld| la > ld))
}
}
impl Hash for Member {
#[inline]
fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
self.node_id.hash(state);
self.network_id.hash(state);
}
}
#[derive(Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct Tag {
pub id: u32,
pub value: u32,
}

130
controller/src/model/mod.rs Normal file
View file

@ -0,0 +1,130 @@
// (c) 2020-2022 ZeroTier, Inc. -- currently propritery pending actual release and licensing. See LICENSE.md.
mod member;
mod network;
pub use member::*;
pub use network::*;
use std::collections::HashMap;
use serde::{Deserialize, Serialize};
use zerotier_network_hypervisor::vl1::{Address, Endpoint};
use zerotier_network_hypervisor::vl2::NetworkId;
/// A complete network with all member configuration information for import/export or blob storage.
#[derive(Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct NetworkExport {
pub network: Network,
pub members: HashMap<Address, Member>,
}
/// Static string included in JSON-serializable objects to indicate their object type through the API.
#[derive(Clone, PartialEq, Eq, Serialize, Deserialize)]
pub enum ObjectType {
#[serde(rename = "network")]
Network,
#[serde(rename = "member")]
Member,
}
impl ObjectType {
fn network() -> ObjectType {
Self::Network
}
fn member() -> ObjectType {
Self::Member
}
}
#[derive(Clone, PartialEq, Eq, Serialize, Deserialize)]
#[repr(u8)]
pub enum AuthorizationResult {
#[serde(rename = "r")]
Rejected = 0,
#[serde(rename = "rs")]
RejectedViaSSO = 1,
#[serde(rename = "rt")]
RejectedViaToken = 2,
#[serde(rename = "ro")]
RejectedTooOld = 3,
#[serde(rename = "re")]
RejectedDueToError = 4,
#[serde(rename = "rm")]
RejectedIdentityMismatch = 5,
#[serde(rename = "a")]
Approved = 128,
#[serde(rename = "as")]
ApprovedViaSSO = 129,
#[serde(rename = "at")]
ApprovedViaToken = 130,
#[serde(rename = "ap")]
ApprovedOnPublicNetwork = 131,
}
impl AuthorizationResult {
pub fn as_str(&self) -> &'static str {
// These short codes should match the serde enum names above.
match self {
Self::Rejected => "r",
Self::RejectedViaSSO => "rs",
Self::RejectedViaToken => "rt",
Self::RejectedTooOld => "ro",
Self::RejectedDueToError => "re",
Self::RejectedIdentityMismatch => "rm",
Self::Approved => "a",
Self::ApprovedViaSSO => "as",
Self::ApprovedViaToken => "at",
Self::ApprovedOnPublicNetwork => "ap",
}
}
}
impl ToString for AuthorizationResult {
#[inline(always)]
fn to_string(&self) -> String {
self.as_str().to_string()
}
}
#[derive(Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct RequestLogItem {
#[serde(rename = "nwid")]
pub network_id: NetworkId,
#[serde(rename = "nid")]
pub node_id: Address,
#[serde(rename = "cid")]
pub controller_node_id: Address,
#[serde(rename = "md")]
pub metadata: Vec<u8>,
#[serde(rename = "ts")]
pub timestamp: i64,
#[serde(rename = "v")]
pub version: (u16, u16, u16, u16),
#[serde(rename = "s")]
pub source_remote_endpoint: Endpoint,
#[serde(rename = "sh")]
pub source_hops: u8,
#[serde(rename = "r")]
pub result: AuthorizationResult,
}
impl ToString for RequestLogItem {
fn to_string(&self) -> String {
format!(
"{} {} {} ts={} v={}.{}.{},{} s={},{} {}",
self.controller_node_id.to_string(),
self.network_id.to_string(),
self.node_id.to_string(),
self.timestamp,
self.version.0,
self.version.1,
self.version.2,
self.version.3,
self.source_remote_endpoint.to_string(),
self.source_hops,
self.result.to_string()
)
}
}

View file

@ -0,0 +1,180 @@
// (c) 2020-2022 ZeroTier, Inc. -- currently propritery pending actual release and licensing. See LICENSE.md.
use std::collections::{HashMap, HashSet};
use std::hash::Hash;
use serde::{Deserialize, Serialize};
use zerotier_network_hypervisor::vl1::InetAddress;
use zerotier_network_hypervisor::vl2::networkconfig::IpRoute;
use zerotier_network_hypervisor::vl2::{NetworkId, Rule};
use crate::database::Database;
use crate::model::{Member, ObjectType};
pub const CREDENTIAL_WINDOW_SIZE_DEFAULT: i64 = 1000 * 60 * 60;
#[derive(Clone, PartialEq, Eq, Serialize, Deserialize, Default)]
pub struct Ipv4AssignMode {
pub zt: bool,
}
#[derive(Clone, PartialEq, Eq, Serialize, Deserialize, Default)]
pub struct Ipv6AssignMode {
pub zt: bool,
pub rfc4193: bool,
#[serde(rename = "6plane")]
pub _6plane: bool,
}
#[derive(Clone, PartialEq, Eq, Serialize, Deserialize, Hash)]
pub struct IpAssignmentPool {
#[serde(rename = "ipRangeStart")]
ip_range_start: InetAddress,
#[serde(rename = "ipRangeEnd")]
ip_range_end: InetAddress,
}
/// Virtual network configuration.
///
/// This contains only fields of relevance to the controller. Other fields can be tracked by various
/// database implementations such as row last modified, creation time, ownership in an admin panel, etc.
#[derive(Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct Network {
pub id: NetworkId,
/// Network name that's sent to network members
#[serde(default)]
pub name: String,
/// Guideline for the maximum number of multicast recipients on a network (not a hard limit).
/// Setting to zero disables multicast entirely. The default is used if this is not set.
#[serde(rename = "multicastLimit")]
pub multicast_limit: Option<u32>,
/// If true, this network supports ff:ff:ff:ff:ff:ff Ethernet broadcast.
#[serde(rename = "enableBroadcast")]
#[serde(default = "troo")]
pub enable_broadcast: bool,
/// Auto IP assignment mode(s) for IPv4 addresses.
#[serde(rename = "v4AssignMode")]
#[serde(default)]
pub v4_assign_mode: Ipv4AssignMode,
/// Auto IP assignment mode(s) for IPv6 addresses.
#[serde(rename = "v6AssignMode")]
#[serde(default)]
pub v6_assign_mode: Ipv6AssignMode,
/// IPv4 or IPv6 auto-assignment pools available, must be present to use 'zt' mode.
#[serde(rename = "ipAssignmentPools")]
#[serde(default)]
pub ip_assignment_pools: HashSet<IpAssignmentPool>,
/// IPv4 or IPv6 routes to advertise.
#[serde(default)]
pub ip_routes: HashSet<IpRoute>,
/// DNS records to push to members.
pub dns: HashMap<String, HashSet<InetAddress>>,
/// Network rule set.
#[serde(default)]
pub rules: Vec<Rule>,
/// If set this overrides the default "agreement" window for certificates and credentials.
///
/// Making it smaller causes deauthorized nodes to fall out of the window more rapidly but can
/// come at the expense of reliability if it's too short for everyone to update their certs
/// on time from the controller. Note that revocations are also used to deauthorize nodes
/// promptly, so nodes will still deauthorize quickly even if the window is long.
///
/// Usually this does not need to be changed.
#[serde(rename = "credentialWindowSize")]
pub credential_window_size: Option<i64>,
/// MTU inside the virtual network, default of 2800 is used if not set.
pub mtu: Option<u16>,
/// If true the network has access control, which is usually what you want.
#[serde(default = "troo")]
pub private: bool,
/// If true this network will add not-authorized members for anyone who requests a config.
#[serde(default = "troo")]
pub learn_members: bool,
/// Static object type field for use with API.
#[serde(default = "ObjectType::network")]
pub objtype: ObjectType,
}
impl Hash for Network {
#[inline]
fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
self.id.hash(state)
}
}
#[inline(always)]
fn troo() -> bool {
true
}
impl Network {
/// Check member IP assignments and return 'true' if IP assignments were created or modified.
pub async fn check_zt_ip_assignments<DatabaseImpl: Database>(&self, database: &DatabaseImpl, member: &mut Member) -> bool {
let mut modified = false;
if self.v4_assign_mode.zt {
if !member.ip_assignments.iter().any(|ip| ip.is_ipv4()) {
'ip_search: for pool in self.ip_assignment_pools.iter() {
if pool.ip_range_start.is_ipv4() && pool.ip_range_end.is_ipv4() {
let mut ip_ptr = u32::from_be_bytes(pool.ip_range_start.ip_bytes().try_into().unwrap());
let ip_end = u32::from_be_bytes(pool.ip_range_end.ip_bytes().try_into().unwrap());
while ip_ptr < ip_end {
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 !database.is_ip_assigned(self.id, &ip).await.unwrap_or(true) {
modified = true;
let _ = member.ip_assignments.insert(ip);
break 'ip_search;
}
}
}
ip_ptr += 1;
}
}
}
}
}
if self.v6_assign_mode.zt {
if !member.ip_assignments.iter().any(|ip| ip.is_ipv6()) {
'ip_search: for pool in self.ip_assignment_pools.iter() {
if pool.ip_range_start.is_ipv6() && pool.ip_range_end.is_ipv6() {
let mut ip_ptr = u128::from_be_bytes(pool.ip_range_start.ip_bytes().try_into().unwrap());
let ip_end = u128::from_be_bytes(pool.ip_range_end.ip_bytes().try_into().unwrap());
while ip_ptr < ip_end {
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 !database.is_ip_assigned(self.id, &ip).await.unwrap_or(true) {
modified = true;
let _ = member.ip_assignments.insert(ip);
break 'ip_search;
}
}
}
ip_ptr += 1;
}
}
}
}
}
modified
}
}

View file

@ -111,6 +111,12 @@ pub mod verbs {
/// two fragments. /// two fragments.
pub const UDP_DEFAULT_MTU: usize = 1432; 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. /// Length of an address in bytes.
pub const ADDRESS_SIZE: usize = 5; pub const ADDRESS_SIZE: usize = 5;

View file

@ -156,6 +156,8 @@ impl Identity {
+ P384_ECDSA_SIGNATURE_SIZE + P384_ECDSA_SIGNATURE_SIZE
+ P384_ECDSA_SIGNATURE_SIZE; + P384_ECDSA_SIGNATURE_SIZE;
pub const FINGERPRINT_SIZE: usize = IDENTITY_FINGERPRINT_SIZE;
const ALGORITHM_X25519: u8 = 0x01; const ALGORITHM_X25519: u8 = 0x01;
const ALGORITHM_EC_NIST_P384: u8 = 0x02; const ALGORITHM_EC_NIST_P384: u8 = 0x02;
const FLAG_INCLUDES_SECRETS: u8 = 0x80; const FLAG_INCLUDES_SECRETS: u8 = 0x80;

View file

@ -134,8 +134,10 @@ pub trait InnerProtocol: Sync + Send + 'static {
/// Do not attempt to handle OK or ERROR. Instead implement handle_ok() and handle_error(). /// Do not attempt to handle OK or ERROR. Instead implement handle_ok() and handle_error().
fn handle_packet<HostSystemImpl: HostSystem>( fn handle_packet<HostSystemImpl: HostSystem>(
&self, &self,
node: &Node<HostSystemImpl>,
source: &Arc<Peer<HostSystemImpl>>, source: &Arc<Peer<HostSystemImpl>>,
source_path: &Arc<Path<HostSystemImpl>>, source_path: &Arc<Path<HostSystemImpl>>,
message_id: u64,
verb: u8, verb: u8,
payload: &PacketBuffer, payload: &PacketBuffer,
) -> PacketHandlerResult; ) -> PacketHandlerResult;
@ -143,8 +145,10 @@ pub trait InnerProtocol: Sync + Send + 'static {
/// Handle errors, returning true if the error was recognized. /// Handle errors, returning true if the error was recognized.
fn handle_error<HostSystemImpl: HostSystem>( fn handle_error<HostSystemImpl: HostSystem>(
&self, &self,
node: &Node<HostSystemImpl>,
source: &Arc<Peer<HostSystemImpl>>, source: &Arc<Peer<HostSystemImpl>>,
source_path: &Arc<Path<HostSystemImpl>>, source_path: &Arc<Path<HostSystemImpl>>,
message_id: u64,
in_re_verb: u8, in_re_verb: u8,
in_re_message_id: u64, in_re_message_id: u64,
error_code: u8, error_code: u8,
@ -155,8 +159,10 @@ pub trait InnerProtocol: Sync + Send + 'static {
/// Handle an OK, returing true if the OK was recognized. /// Handle an OK, returing true if the OK was recognized.
fn handle_ok<HostSystemImpl: HostSystem>( fn handle_ok<HostSystemImpl: HostSystem>(
&self, &self,
node: &Node<HostSystemImpl>,
source: &Arc<Peer<HostSystemImpl>>, source: &Arc<Peer<HostSystemImpl>>,
source_path: &Arc<Path<HostSystemImpl>>, source_path: &Arc<Path<HostSystemImpl>>,
message_id: u64,
in_re_verb: u8, in_re_verb: u8,
in_re_message_id: u64, in_re_message_id: u64,
payload: &PacketBuffer, payload: &PacketBuffer,
@ -1044,8 +1050,10 @@ impl InnerProtocol for DummyInnerProtocol {
#[inline(always)] #[inline(always)]
fn handle_packet<HostSystemImpl: HostSystem>( fn handle_packet<HostSystemImpl: HostSystem>(
&self, &self,
_node: &Node<HostSystemImpl>,
_source: &Arc<Peer<HostSystemImpl>>, _source: &Arc<Peer<HostSystemImpl>>,
_source_path: &Arc<Path<HostSystemImpl>>, _source_path: &Arc<Path<HostSystemImpl>>,
_message_id: u64,
_verb: u8, _verb: u8,
_payload: &PacketBuffer, _payload: &PacketBuffer,
) -> PacketHandlerResult { ) -> PacketHandlerResult {
@ -1055,8 +1063,10 @@ impl InnerProtocol for DummyInnerProtocol {
#[inline(always)] #[inline(always)]
fn handle_error<HostSystemImpl: HostSystem>( fn handle_error<HostSystemImpl: HostSystem>(
&self, &self,
_node: &Node<HostSystemImpl>,
_source: &Arc<Peer<HostSystemImpl>>, _source: &Arc<Peer<HostSystemImpl>>,
_source_path: &Arc<Path<HostSystemImpl>>, _source_path: &Arc<Path<HostSystemImpl>>,
_message_id: u64,
_in_re_verb: u8, _in_re_verb: u8,
_in_re_message_id: u64, _in_re_message_id: u64,
_error_code: u8, _error_code: u8,
@ -1069,8 +1079,10 @@ impl InnerProtocol for DummyInnerProtocol {
#[inline(always)] #[inline(always)]
fn handle_ok<HostSystemImpl: HostSystem>( fn handle_ok<HostSystemImpl: HostSystem>(
&self, &self,
_node: &Node<HostSystemImpl>,
_source: &Arc<Peer<HostSystemImpl>>, _source: &Arc<Peer<HostSystemImpl>>,
_source_path: &Arc<Path<HostSystemImpl>>, _source_path: &Arc<Path<HostSystemImpl>>,
_message_id: u64,
_in_re_verb: u8, _in_re_verb: u8,
_in_re_message_id: u64, _in_re_message_id: u64,
_payload: &PacketBuffer, _payload: &PacketBuffer,

View file

@ -293,7 +293,7 @@ impl<HostSystemImpl: HostSystem> Peer<HostSystemImpl> {
}; };
let mut aes_gmac_siv = self.v1_proto_static_secret.aes_gmac_siv.get(); let mut aes_gmac_siv = self.v1_proto_static_secret.aes_gmac_siv.get();
aes_gmac_siv.encrypt_init(&self.v1_proto_next_message_id().to_ne_bytes()); aes_gmac_siv.encrypt_init(&self.v1_proto_next_message_id().to_be_bytes());
aes_gmac_siv.encrypt_set_aad(&v1::get_packet_aad_bytes( aes_gmac_siv.encrypt_set_aad(&v1::get_packet_aad_bytes(
self.identity.address, self.identity.address,
node.identity.address, node.identity.address,
@ -326,7 +326,7 @@ impl<HostSystemImpl: HostSystem> Peer<HostSystemImpl> {
&self.v1_proto_static_secret, &self.v1_proto_static_secret,
{ {
let header = packet.struct_mut_at::<v1::PacketHeader>(0).unwrap(); let header = packet.struct_mut_at::<v1::PacketHeader>(0).unwrap();
header.id = self.v1_proto_next_message_id().to_ne_bytes(); header.id = self.v1_proto_next_message_id().to_be_bytes();
header.dest = self.identity.address.to_bytes(); header.dest = self.identity.address.to_bytes();
header.src = node.identity.address.to_bytes(); header.src = node.identity.address.to_bytes();
header.flags_cipher_hops = flags_cipher_hops; header.flags_cipher_hops = flags_cipher_hops;
@ -531,13 +531,14 @@ impl<HostSystemImpl: HostSystem> Peer<HostSystemImpl> {
packet_header.hops(), packet_header.hops(),
&payload, &payload,
), ),
verbs::VL1_ERROR => self.handle_incoming_error(host_system, inner, node, time_ticks, source_path, &payload), verbs::VL1_ERROR => self.handle_incoming_error(host_system, inner, node, time_ticks, source_path, message_id, &payload),
verbs::VL1_OK => self.handle_incoming_ok( verbs::VL1_OK => self.handle_incoming_ok(
host_system, host_system,
inner, inner,
node, node,
time_ticks, time_ticks,
source_path, source_path,
message_id,
packet_header.hops(), packet_header.hops(),
path_is_known, path_is_known,
&payload, &payload,
@ -551,7 +552,7 @@ impl<HostSystemImpl: HostSystem> Peer<HostSystemImpl> {
self.handle_incoming_push_direct_paths(host_system, node, time_ticks, source_path, &payload) self.handle_incoming_push_direct_paths(host_system, node, time_ticks, source_path, &payload)
} }
verbs::VL1_USER_MESSAGE => self.handle_incoming_user_message(host_system, node, time_ticks, source_path, &payload), verbs::VL1_USER_MESSAGE => self.handle_incoming_user_message(host_system, node, time_ticks, source_path, &payload),
_ => inner.handle_packet(self, &source_path, verb, &payload), _ => inner.handle_packet(node, self, &source_path, message_id, verb, &payload),
}; };
} }
} }
@ -623,20 +624,23 @@ impl<HostSystemImpl: HostSystem> Peer<HostSystemImpl> {
self: &Arc<Self>, self: &Arc<Self>,
_: &HostSystemImpl, _: &HostSystemImpl,
inner: &InnerProtocolImpl, inner: &InnerProtocolImpl,
_: &Node<HostSystemImpl>, node: &Node<HostSystemImpl>,
_: i64, _: i64,
source_path: &Arc<Path<HostSystemImpl>>, source_path: &Arc<Path<HostSystemImpl>>,
message_id: u64,
payload: &PacketBuffer, payload: &PacketBuffer,
) -> PacketHandlerResult { ) -> PacketHandlerResult {
let mut cursor = 0; let mut cursor = 0;
if let Ok(error_header) = payload.read_struct::<v1::message_component_structs::ErrorHeader>(&mut cursor) { if let Ok(error_header) = payload.read_struct::<v1::message_component_structs::ErrorHeader>(&mut cursor) {
let in_re_message_id: MessageId = u64::from_ne_bytes(error_header.in_re_message_id); let in_re_message_id: MessageId = u64::from_be_bytes(error_header.in_re_message_id);
if self.message_id_counter.load(Ordering::Relaxed).wrapping_sub(in_re_message_id) <= PACKET_RESPONSE_COUNTER_DELTA_MAX { if self.message_id_counter.load(Ordering::Relaxed).wrapping_sub(in_re_message_id) <= PACKET_RESPONSE_COUNTER_DELTA_MAX {
match error_header.in_re_verb { match error_header.in_re_verb {
_ => { _ => {
return inner.handle_error( return inner.handle_error(
node,
self, self,
&source_path, &source_path,
message_id,
error_header.in_re_verb, error_header.in_re_verb,
in_re_message_id, in_re_message_id,
error_header.error_code, error_header.error_code,
@ -657,6 +661,7 @@ impl<HostSystemImpl: HostSystem> Peer<HostSystemImpl> {
node: &Node<HostSystemImpl>, node: &Node<HostSystemImpl>,
time_ticks: i64, time_ticks: i64,
source_path: &Arc<Path<HostSystemImpl>>, source_path: &Arc<Path<HostSystemImpl>>,
message_id: u64,
hops: u8, hops: u8,
path_is_known: bool, path_is_known: bool,
payload: &PacketBuffer, payload: &PacketBuffer,
@ -730,7 +735,16 @@ impl<HostSystemImpl: HostSystem> Peer<HostSystemImpl> {
} }
_ => { _ => {
return inner.handle_ok(self, &source_path, ok_header.in_re_verb, in_re_message_id, payload, &mut cursor); return inner.handle_ok(
node,
self,
&source_path,
message_id,
ok_header.in_re_verb,
in_re_message_id,
payload,
&mut cursor,
);
} }
} }
} }
@ -890,7 +904,7 @@ fn v1_proto_try_aead_decrypt(
let (mut salsa, poly1305_key) = v1_proto_salsa_poly_create(secret, packet_header, payload.len() + v1::HEADER_SIZE); let (mut salsa, poly1305_key) = v1_proto_salsa_poly_create(secret, packet_header, payload.len() + v1::HEADER_SIZE);
let mac = poly1305::compute(&poly1305_key, &payload.as_bytes()); let mac = poly1305::compute(&poly1305_key, &payload.as_bytes());
if mac[0..8].eq(&packet_header.mac) { if mac[0..8].eq(&packet_header.mac) {
let message_id = u64::from_ne_bytes(packet_header.id); let message_id = u64::from_be_bytes(packet_header.id);
if cipher == v1::CIPHER_SALSA2012_POLY1305 { if cipher == v1::CIPHER_SALSA2012_POLY1305 {
salsa.crypt_in_place(payload.as_bytes_mut()); salsa.crypt_in_place(payload.as_bytes_mut());
Some(message_id) Some(message_id)
@ -941,7 +955,7 @@ fn v1_proto_try_aead_decrypt(
// AES-GMAC-SIV encrypts the packet ID too as part of its computation of a single // AES-GMAC-SIV encrypts the packet ID too as part of its computation of a single
// opaque 128-bit tag, so to get the original packet ID we have to grab it from the // opaque 128-bit tag, so to get the original packet ID we have to grab it from the
// decrypted tag. // decrypted tag.
Some(u64::from_ne_bytes(*array_range::<u8, 16, 0, 8>(tag))) Some(u64::from_be_bytes(*array_range::<u8, 16, 0, 8>(tag)))
} else { } else {
None None
} }

View file

@ -8,25 +8,63 @@ use crate::vl2::NetworkId;
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
use zerotier_crypto::hash::SHA384; use zerotier_crypto::hash::SHA384;
use zerotier_crypto::verified::Verified;
use zerotier_utils::arrayvec::ArrayVec; use zerotier_utils::arrayvec::ArrayVec;
use zerotier_utils::blob::Blob; use zerotier_utils::blob::Blob;
use zerotier_utils::error::InvalidParameterError;
use zerotier_utils::memory; use zerotier_utils::memory;
#[derive(Clone, Serialize, Deserialize, PartialEq, Eq)] #[derive(Clone, Serialize, Deserialize, PartialEq, Eq)]
pub struct CertificateOfMembership { pub struct CertificateOfMembership {
pub network_id: NetworkId, pub network_id: NetworkId,
pub issued_to: Address,
pub timestamp: i64, pub timestamp: i64,
pub max_delta: i64, pub max_delta: i64,
pub issued_to_fingerprint: Blob<48>, pub issued_to: Address,
pub v1: Option<[u8; 32]>, pub issued_to_fingerprint: Blob<{ identity::IDENTITY_FINGERPRINT_SIZE }>,
pub signed_by: Address,
pub signature: ArrayVec<u8, { identity::IDENTITY_MAX_SIGNATURE_SIZE }>, pub signature: ArrayVec<u8, { identity::IDENTITY_MAX_SIGNATURE_SIZE }>,
pub version: u8,
} }
impl CertificateOfMembership { impl CertificateOfMembership {
/// Generate the first three "qualifiers" for V1 nodes. /// Create a new signed certificate of membership.
fn v1_proto_write_first_3_qualifiers(&self, q: &mut [u64]) { /// None is returned if an error occurs, such as the issuer missing its secrets.
pub fn new(
issuer: &Identity,
network_id: NetworkId,
issued_to: &Identity,
timestamp: i64,
max_delta: i64,
legacy_v1: bool,
) -> Option<Self> {
let mut com = CertificateOfMembership {
network_id,
timestamp,
max_delta,
issued_to: issued_to.address,
issued_to_fingerprint: Blob::default(),
signature: ArrayVec::new(),
version: 0,
};
if legacy_v1 {
com.issued_to_fingerprint = Blob::from(Self::v1_proto_issued_to_fingerprint(issued_to, Some(issuer.address)));
com.version = 1;
if let Some(signature) = issuer.sign(&com.v1_proto_get_qualifier_bytes(), true) {
com.signature = signature;
Some(com)
} else {
None
}
} else {
com.issued_to_fingerprint = Blob::from(issued_to.fingerprint);
com.version = 2;
todo!()
}
}
fn v1_proto_get_qualifier_bytes(&self) -> [u8; 168] {
assert_eq!(self.version, 1);
let mut q = [0u64; 21];
q[0] = 0; q[0] = 0;
q[1] = self.timestamp.to_be() as u64; q[1] = self.timestamp.to_be() as u64;
q[2] = self.max_delta.to_be() as u64; q[2] = self.max_delta.to_be() as u64;
@ -38,68 +76,130 @@ impl CertificateOfMembership {
let a: u64 = self.issued_to.into(); let a: u64 = self.issued_to.into();
q[7] = a.to_be(); q[7] = a.to_be();
q[8] = 0xffffffffffffffffu64; // no to_be needed q[8] = 0xffffffffffffffffu64; // no to_be needed
let fp = self.issued_to_fingerprint.as_bytes();
q[9] = 3;
q[10] = u64::from_ne_bytes(fp[0..8].try_into().unwrap());
q[11] = 0xffffffffffffffffu64;
q[12] = 4;
q[13] = u64::from_ne_bytes(fp[8..16].try_into().unwrap());
q[14] = 0xffffffffffffffffu64;
q[15] = 5;
q[16] = u64::from_ne_bytes(fp[16..24].try_into().unwrap());
q[17] = 0xffffffffffffffffu64;
q[18] = 6;
q[19] = u64::from_ne_bytes(fp[24..32].try_into().unwrap());
q[20] = 0xffffffffffffffffu64;
*memory::as_byte_array(&q)
} }
/// Generate all the qualifiers for V1 nodes, which is part of marshaling for those. fn v1_proto_issued_to_fingerprint(issued_to: &Identity, signed_by: Option<Address>) -> [u8; 48] {
fn v1_proto_get_qualifier_bytes(&self) -> Option<[u8; 168]> {
self.v1.as_ref().map(|v1| {
let mut q = [0u64; 21];
self.v1_proto_write_first_3_qualifiers(&mut q);
q[9] = 3;
q[10] = u64::from_ne_bytes(v1[0..8].try_into().unwrap());
q[11] = 0xffffffffffffffffu64;
q[12] = 4;
q[13] = u64::from_ne_bytes(v1[8..16].try_into().unwrap());
q[14] = 0xffffffffffffffffu64;
q[15] = 5;
q[16] = u64::from_ne_bytes(v1[16..24].try_into().unwrap());
q[17] = 0xffffffffffffffffu64;
q[18] = 6;
q[19] = u64::from_ne_bytes(v1[24..32].try_into().unwrap());
q[20] = 0xffffffffffffffffu64;
*memory::as_byte_array(&q)
})
}
/// Sign this certificate of membership for use by V1 nodes.
///
/// This should be used in conjunction with v1_proto_to_bytes() to generate a COM for v1
/// nodes. This sets the issued_to and v1 fields.
pub fn v1_proto_sign(&mut self, issuer: &Identity, issued_to: &Identity) -> bool {
let mut v1_signee_hasher = SHA384::new(); let mut v1_signee_hasher = SHA384::new();
v1_signee_hasher.update(&issued_to.address.to_bytes()); v1_signee_hasher.update(&issued_to.address.to_bytes());
v1_signee_hasher.update(&issued_to.x25519); v1_signee_hasher.update(&issued_to.x25519);
v1_signee_hasher.update(&issued_to.ed25519); v1_signee_hasher.update(&issued_to.ed25519);
let v1_signee_hash = v1_signee_hasher.finish(); let mut fp = v1_signee_hasher.finish();
fp[32..].fill(0);
let mut to_sign = [0u64; 9]; if let Some(signed_by) = signed_by {
self.v1_proto_write_first_3_qualifiers(&mut to_sign); fp[32..38].copy_from_slice(&signed_by.to_bytes());
if let Some(signature) = issuer.sign(memory::as_byte_array::<[u64; 9], 27>(&to_sign), true) {
self.issued_to = issued_to.address;
self.v1 = Some(v1_signee_hash[..32].try_into().unwrap());
self.signed_by = issuer.address;
self.signature = signature;
true
} else {
false
} }
fp
} }
/// Get this certificate of membership encoded in the format expected by old V1 nodes. /// Get this certificate of membership in byte encoded format.
pub fn v1_proto_to_bytes(&self) -> Option<Vec<u8>> { pub fn to_bytes(&self) -> Option<Vec<u8>> {
if self.signature.is_empty() || self.v1.is_none() { if self.version == 1 {
return None; if self.signature.len() == 96 {
let mut v: Vec<u8> = Vec::with_capacity(3 + 168 + 5 + 96);
v.push(1); // version byte from v1 protocol
v.push(0);
v.push(7); // 7 qualifiers, big-endian 16-bit
let _ = v.write_all(&self.v1_proto_get_qualifier_bytes());
let _ = v.write_all(&self.issued_to_fingerprint.as_bytes()[32..38]); // issuer address
let _ = v.write_all(self.signature.as_bytes());
return Some(v);
}
} else if self.version == 2 {
todo!()
} }
let mut v: Vec<u8> = Vec::with_capacity(384); return None;
v.push(1); }
v.push(0);
v.push(7); // 7 qualifiers, big-endian 16-bit /// Decode a V1 legacy format certificate of membership in byte format.
let _ = v.write_all(&self.v1_proto_get_qualifier_bytes().unwrap()); pub fn v1_proto_from_bytes(mut b: &[u8]) -> Result<Self, InvalidParameterError> {
let _ = v.write_all(&self.signed_by.to_bytes()); if b.len() <= 3 || b[0] != 1 {
let _ = v.write_all(self.signature.as_bytes()); return Err(InvalidParameterError("version mismatch"));
return Some(v); }
let qualifier_count = (b[1] as usize).wrapping_shl(8) | (b[2] as usize);
b = &b[3..];
if b.len() < ((qualifier_count * 24) + (5 + 96)) {
return Err(InvalidParameterError("incomplete"));
}
let (mut network_id, mut issued_to, mut timestamp, mut max_delta, mut v1_fingerprint) = (0, 0, 0, 0, [0u8; 48]);
for _ in 0..qualifier_count {
let qt = u64::from_be_bytes(b[..8].try_into().unwrap());
let q: [u8; 8] = b[8..16].try_into().unwrap();
let qd = u64::from_be_bytes(b[16..24].try_into().unwrap());
match qt {
0 => {
timestamp = i64::from_be_bytes(q);
max_delta = qd as i64;
}
1 => {
network_id = u64::from_be_bytes(q);
}
2 => {
issued_to = u64::from_be_bytes(q);
}
3 => {
v1_fingerprint[0..8].copy_from_slice(&q);
}
4 => {
v1_fingerprint[8..16].copy_from_slice(&q);
}
5 => {
v1_fingerprint[16..24].copy_from_slice(&q);
}
6 => {
v1_fingerprint[24..32].copy_from_slice(&q);
}
_ => {}
}
b = &b[24..];
}
v1_fingerprint[32..38].copy_from_slice(&b[..5]); // issuer address
b = &b[5..];
Ok(Self {
network_id: NetworkId::from_u64(network_id).ok_or(InvalidParameterError("invalid network ID"))?,
timestamp,
max_delta,
issued_to: Address::from_u64(issued_to).ok_or(InvalidParameterError("invalid issued to address"))?,
issued_to_fingerprint: Blob::from(v1_fingerprint),
signature: {
let mut s = ArrayVec::new();
s.push_slice(&b[..96]);
s
},
version: 1,
})
}
/// Verify this certificate of membership.
pub fn verify(self, issuer: &Identity, expect_issued_to: &Identity) -> Option<Verified<Self>> {
if self.version == 1 {
if Self::v1_proto_issued_to_fingerprint(expect_issued_to, None).eq(&self.issued_to_fingerprint.as_bytes()[..32]) {
if issuer.verify(&self.v1_proto_get_qualifier_bytes(), self.signature.as_bytes()) {
return Some(Verified(self));
}
}
} else if self.version == 2 {
todo!()
}
return None;
} }
} }

View file

@ -1,3 +1,4 @@
use std::collections::HashSet;
use std::io::Write; use std::io::Write;
use crate::vl1::{Address, Identity, InetAddress, MAC}; use crate::vl1::{Address, Identity, InetAddress, MAC};
@ -6,27 +7,71 @@ use crate::vl2::NetworkId;
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
use zerotier_utils::arrayvec::ArrayVec; use zerotier_utils::arrayvec::ArrayVec;
use zerotier_utils::blob::Blob;
use zerotier_utils::error::InvalidParameterError;
#[derive(Clone, Serialize, Deserialize, PartialEq, Eq)] #[derive(Clone, Serialize, Deserialize, PartialEq, Eq, Hash)]
pub enum Thing { pub enum Thing {
Ip(InetAddress), Ipv4([u8; 4]),
Ipv6([u8; 16]),
Mac(MAC), Mac(MAC),
} }
impl Thing {
/// Get the type ID for this "thing."
pub fn type_id(&self) -> u8 {
match self {
Self::Mac(_) => 1,
Self::Ipv4(_) => 2,
Self::Ipv6(_) => 3,
}
}
}
#[derive(Clone, Serialize, Deserialize, PartialEq, Eq)] #[derive(Clone, Serialize, Deserialize, PartialEq, Eq)]
pub struct CertificateOfOwnership { pub struct CertificateOfOwnership {
pub network_id: NetworkId, pub network_id: NetworkId,
pub timestamp: i64, pub timestamp: i64,
pub flags: u64, pub things: HashSet<Thing>,
pub id: u32,
pub things: Vec<Thing>,
pub issued_to: Address, pub issued_to: Address,
pub signed_by: Address, pub issued_to_fingerprint: Blob<{ Identity::FINGERPRINT_SIZE }>,
pub signature: ArrayVec<u8, { crate::vl1::identity::IDENTITY_MAX_SIGNATURE_SIZE }>, pub signature: ArrayVec<u8, { crate::vl1::identity::IDENTITY_MAX_SIGNATURE_SIZE }>,
pub version: u8,
} }
impl CertificateOfOwnership { impl CertificateOfOwnership {
fn internal_v1_proto_to_bytes(&self, for_sign: bool) -> Option<Vec<u8>> { /// Create a new empty and unsigned certificate.
pub fn new(network_id: NetworkId, timestamp: i64, issued_to: Address, legacy_v1: bool) -> Self {
Self {
network_id,
timestamp,
things: HashSet::with_capacity(4),
issued_to,
issued_to_fingerprint: Blob::default(),
signature: ArrayVec::new(),
version: if legacy_v1 {
1
} else {
2
},
}
}
/// Add an IP address to this certificate.
pub fn add_ip(&mut self, ip: &InetAddress) {
if ip.is_ipv4() {
let _ = self.things.insert(Thing::Ipv4(ip.ip_bytes().try_into().unwrap()));
} else if ip.is_ipv6() {
let _ = self.things.insert(Thing::Ipv6(ip.ip_bytes().try_into().unwrap()));
}
}
/// Add a MAC address to this certificate.
pub fn add_mac(&mut self, mac: MAC) {
let _ = self.things.insert(Thing::Mac(mac));
}
fn internal_v1_proto_to_bytes(&self, for_sign: bool, signed_by: Address) -> Option<Vec<u8>> {
if self.things.len() > 0xffff || self.signature.len() != 96 { if self.things.len() > 0xffff || self.signature.len() != 96 {
return None; return None;
} }
@ -36,23 +81,18 @@ impl CertificateOfOwnership {
} }
let _ = v.write_all(&self.network_id.to_bytes()); let _ = v.write_all(&self.network_id.to_bytes());
let _ = v.write_all(&self.timestamp.to_be_bytes()); let _ = v.write_all(&self.timestamp.to_be_bytes());
let _ = v.write_all(&self.flags.to_be_bytes()); let _ = v.write_all(&[0u8, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1]); // obsolete flags and ID fields
let _ = v.write_all(&self.id.to_be_bytes());
let _ = v.write_all(&(self.things.len() as u16).to_be_bytes()); let _ = v.write_all(&(self.things.len() as u16).to_be_bytes());
for t in self.things.iter() { for t in self.things.iter() {
match t { match t {
Thing::Ip(ip) => { Thing::Ipv4(ip) => {
if ip.is_ipv4() { v.push(2);
v.push(2); let _ = v.write_all(ip);
let mut tmp = [0u8; 16]; let _ = v.write_all(&[0u8; 12]);
tmp[..4].copy_from_slice(&ip.ip_bytes()); }
let _ = v.write_all(&tmp); Thing::Ipv6(ip) => {
} else if ip.is_ipv6() { v.push(3);
v.push(3); let _ = v.write_all(ip);
let _ = v.write_all(ip.ip_bytes());
} else {
return None;
}
} }
Thing::Mac(m) => { Thing::Mac(m) => {
v.push(1); v.push(1);
@ -63,7 +103,7 @@ impl CertificateOfOwnership {
} }
} }
let _ = v.write_all(&self.issued_to.to_bytes()); let _ = v.write_all(&self.issued_to.to_bytes());
let _ = v.write_all(&self.signed_by.to_bytes()); let _ = v.write_all(&signed_by.to_bytes());
if for_sign { if for_sign {
v.push(0); v.push(0);
v.push(0); v.push(0);
@ -80,18 +120,75 @@ impl CertificateOfOwnership {
} }
#[inline(always)] #[inline(always)]
pub fn v1_proto_to_bytes(&self) -> Option<Vec<u8>> { pub fn v1_proto_to_bytes(&self, signed_by: Address) -> Option<Vec<u8>> {
self.internal_v1_proto_to_bytes(false) self.internal_v1_proto_to_bytes(false, signed_by)
} }
pub fn v1_proto_sign(&mut self, issuer: &Identity, issued_to: &Identity) -> bool { /// Decode a V1 legacy format certificate of ownership in byte format.
self.issued_to = issued_to.address; /// The certificate and the current position slice are returned so multiple certs can be easily read from a buffer.
self.signed_by = issuer.address; pub fn v1_proto_from_bytes(mut b: &[u8]) -> Result<(Self, &[u8]), InvalidParameterError> {
if let Some(to_sign) = self.internal_v1_proto_to_bytes(true) { if b.len() < 30 {
if let Some(signature) = issuer.sign(&to_sign.as_slice(), true) { return Err(InvalidParameterError("incomplete"));
self.signature = signature; }
return true; let network_id = u64::from_be_bytes(b[0..8].try_into().unwrap());
let timestamp = i64::from_be_bytes(b[8..16].try_into().unwrap());
let thing_count = u16::from_be_bytes(b[28..30].try_into().unwrap());
let mut things: HashSet<Thing> = HashSet::with_capacity(thing_count as usize);
b = &b[30..];
for _ in 0..thing_count {
if b.len() < 17 {
return Err(InvalidParameterError("incomplete"));
} }
match b[0] {
1 => {
let _ = things.insert(Thing::Mac(MAC::from_bytes(&b[1..7]).ok_or(InvalidParameterError("invalid MAC"))?));
}
2 => {
let _ = things.insert(Thing::Ipv4(b[1..5].try_into().unwrap()));
}
3 => {
let _ = things.insert(Thing::Ipv6(b[1..17].try_into().unwrap()));
}
_ => {
return Err(InvalidParameterError("unknown thing type"));
}
}
b = &b[17..];
}
const END_LEN: usize = 5 + 5 + 3 + 96 + 2;
if b.len() < END_LEN {
return Err(InvalidParameterError("incomplete"));
}
Ok((
Self {
network_id: NetworkId::from_u64(network_id).ok_or(InvalidParameterError("invalid network ID"))?,
timestamp,
things,
issued_to: Address::from_bytes(&b[..5]).ok_or(InvalidParameterError("invalid address"))?,
issued_to_fingerprint: Blob::default(),
signature: {
let mut s = ArrayVec::new();
s.push_slice(&b[13..109]);
s
},
version: 1,
},
&b[END_LEN..],
))
}
/// Sign certificate of ownership for use by V1 nodes.
pub fn sign(&mut self, issuer: &Identity, issued_to: &Identity) -> bool {
if self.version == 1 {
self.issued_to = issued_to.address;
if let Some(to_sign) = self.internal_v1_proto_to_bytes(true, issuer.address) {
if let Some(signature) = issuer.sign(&to_sign.as_slice(), true) {
self.signature = signature;
return true;
}
}
} else if self.version == 2 {
todo!()
} }
return false; return false;
} }

View file

@ -3,12 +3,13 @@
mod certificateofmembership; mod certificateofmembership;
mod certificateofownership; mod certificateofownership;
mod multicastgroup; mod multicastgroup;
mod networkconfig;
mod networkid; mod networkid;
mod rule; mod rule;
mod switch; mod switch;
mod tag; mod tag;
pub mod networkconfig;
pub use certificateofmembership::CertificateOfMembership; pub use certificateofmembership::CertificateOfMembership;
pub use certificateofownership::CertificateOfOwnership; pub use certificateofownership::CertificateOfOwnership;
pub use multicastgroup::MulticastGroup; pub use multicastgroup::MulticastGroup;

View file

@ -25,7 +25,6 @@ impl From<MAC> for MulticastGroup {
} }
impl Ord for MulticastGroup { impl Ord for MulticastGroup {
#[inline(always)]
fn cmp(&self, other: &Self) -> Ordering { fn cmp(&self, other: &Self) -> Ordering {
let o = self.mac.cmp(&other.mac); let o = self.mac.cmp(&other.mac);
match o { match o {

View file

@ -1,19 +1,329 @@
// (c) 2020-2022 ZeroTier, Inc. -- currently propritery pending actual release and licensing. See LICENSE.md. // (c) 2020-2022 ZeroTier, Inc. -- currently propritery pending actual release and licensing. See LICENSE.md.
use std::collections::HashMap; use std::collections::{HashMap, HashSet};
use std::io::Write; use std::io::Write;
use std::str::FromStr;
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
use crate::vl1::{Address, InetAddress}; use crate::vl1::{Address, Identity, InetAddress};
use crate::vl2::certificateofmembership::CertificateOfMembership; use crate::vl2::certificateofmembership::CertificateOfMembership;
use crate::vl2::certificateofownership::CertificateOfOwnership; use crate::vl2::certificateofownership::CertificateOfOwnership;
use crate::vl2::rule::Rule; use crate::vl2::rule::Rule;
use crate::vl2::tag::Tag; use crate::vl2::tag::Tag;
use crate::vl2::NetworkId;
use zerotier_utils::buffer::Buffer; use zerotier_utils::buffer::Buffer;
use zerotier_utils::dictionary::Dictionary; use zerotier_utils::dictionary::Dictionary;
use zerotier_utils::marshalable::{Marshalable, UnmarshalError}; use zerotier_utils::error::InvalidParameterError;
use zerotier_utils::marshalable::Marshalable;
/// Network configuration object sent to nodes by network controllers.
#[derive(Clone, Serialize, Deserialize, PartialEq, Eq)]
pub struct NetworkConfig {
pub network_id: NetworkId,
pub issued_to: Address,
pub name: String,
pub motd: String,
pub private: bool,
pub timestamp: i64,
pub max_delta: i64,
pub revision: u64,
pub mtu: u16,
pub multicast_limit: u32,
pub routes: HashSet<IpRoute>,
pub static_ips: HashSet<InetAddress>,
pub rules: Vec<Rule>,
pub dns: HashMap<String, HashSet<InetAddress>>,
pub certificate_of_membership: Option<CertificateOfMembership>, // considered invalid if None
pub certificates_of_ownership: Vec<CertificateOfOwnership>,
pub tags: HashMap<u32, Tag>,
pub node_info: HashMap<Address, NodeInfo>,
pub central_url: String,
pub sso: Option<SSOAuthConfiguration>,
}
impl NetworkConfig {
pub fn new(network_id: NetworkId, issued_to: Address) -> Self {
Self {
network_id,
issued_to,
name: String::new(),
motd: String::new(),
private: true,
timestamp: 0,
max_delta: 0,
revision: 0,
mtu: 0,
multicast_limit: 0,
routes: HashSet::new(),
static_ips: HashSet::new(),
rules: Vec::new(),
dns: HashMap::new(),
certificate_of_membership: None,
certificates_of_ownership: Vec::new(),
tags: HashMap::new(),
node_info: HashMap::new(),
central_url: String::new(),
sso: None,
}
}
/// Encode a network configuration for sending to V1 nodes.
pub fn v1_proto_to_dictionary(&self, controller_identity: &Identity) -> Option<Dictionary> {
let mut d = Dictionary::new();
d.set_str(
proto_v1_field_name::network_config::NETWORK_ID,
self.network_id.to_string().as_str(),
);
if !self.name.is_empty() {
d.set_str(proto_v1_field_name::network_config::NAME, self.name.as_str());
}
d.set_str(proto_v1_field_name::network_config::ISSUED_TO, self.issued_to.to_string().as_str());
d.set_str(
proto_v1_field_name::network_config::TYPE,
if self.private {
"0"
} else {
"1"
},
);
d.set_u64(proto_v1_field_name::network_config::TIMESTAMP, self.timestamp as u64);
d.set_u64(proto_v1_field_name::network_config::MAX_DELTA, self.max_delta as u64);
d.set_u64(proto_v1_field_name::network_config::REVISION, self.revision);
d.set_u64(proto_v1_field_name::network_config::MTU, self.mtu as u64);
d.set_u64(proto_v1_field_name::network_config::MULTICAST_LIMIT, self.multicast_limit as u64);
if !self.routes.is_empty() {
let r: Vec<IpRoute> = self.routes.iter().cloned().collect();
d.set_bytes(
proto_v1_field_name::network_config::ROUTES,
IpRoute::marshal_multiple_to_bytes(r.as_slice()).unwrap(),
);
}
if !self.static_ips.is_empty() {
let ips: Vec<InetAddress> = self.static_ips.iter().cloned().collect();
d.set_bytes(
proto_v1_field_name::network_config::STATIC_IPS,
InetAddress::marshal_multiple_to_bytes(ips.as_slice()).unwrap(),
);
}
if !self.rules.is_empty() {
d.set_bytes(
proto_v1_field_name::network_config::RULES,
Rule::marshal_multiple_to_bytes(self.rules.as_slice()).unwrap(),
);
}
if !self.dns.is_empty() {
// NOTE: v1 nodes only support one DNS server per network! If there is more than
// one the first will be picked, whichever that is (it's a set). The UI should not
// allow a user to add more than one unless this is a v2-only network.
let mut dns_bin: Vec<u8> = Vec::with_capacity(256);
if let Some((name, servers)) = self.dns.iter().next() {
let mut name_bytes = name.as_bytes();
name_bytes = &name_bytes[..name_bytes.len().min(127)];
let _ = dns_bin.write_all(name_bytes);
for _ in 0..(128 - name_bytes.len()) {
dns_bin.push(0);
}
for s in servers.iter() {
if let Ok(s) = s.to_buffer::<64>() {
let _ = dns_bin.write_all(s.as_bytes());
}
}
}
d.set_bytes(proto_v1_field_name::network_config::DNS, dns_bin);
}
d.set_bytes(
proto_v1_field_name::network_config::CERTIFICATE_OF_MEMBERSHIP,
self.certificate_of_membership.as_ref()?.to_bytes()?,
);
if !self.certificates_of_ownership.is_empty() {
let mut certs = Vec::with_capacity(self.certificates_of_ownership.len() * 256);
for c in self.certificates_of_ownership.iter() {
let _ = certs.write_all(c.v1_proto_to_bytes(controller_identity.address)?.as_slice());
}
d.set_bytes(proto_v1_field_name::network_config::CERTIFICATES_OF_OWNERSHIP, certs);
}
if !self.tags.is_empty() {
let mut certs = Vec::with_capacity(self.tags.len() * 256);
for (_, t) in self.tags.iter() {
let _ = certs.write_all(t.v1_proto_to_bytes(controller_identity.address)?.as_slice());
}
d.set_bytes(proto_v1_field_name::network_config::TAGS, certs);
}
// node_info is not supported by V1 nodes
if !self.central_url.is_empty() {
d.set_str(proto_v1_field_name::network_config::CENTRAL_URL, self.central_url.as_str());
}
if let Some(sso) = self.sso.as_ref() {
d.set_bool(proto_v1_field_name::network_config::SSO_ENABLED, true);
d.set_u64(proto_v1_field_name::network_config::SSO_VERSION, sso.version as u64);
d.set_str(
proto_v1_field_name::network_config::SSO_AUTHENTICATION_URL,
sso.authentication_url.as_str(),
);
d.set_u64(
proto_v1_field_name::network_config::SSO_AUTHENTICATION_EXPIRY_TIME,
sso.authentication_expiry_time as u64,
);
d.set_str(proto_v1_field_name::network_config::SSO_ISSUER_URL, sso.issuer_url.as_str());
d.set_str(proto_v1_field_name::network_config::SSO_NONCE, sso.nonce.as_str());
d.set_str(proto_v1_field_name::network_config::SSO_STATE, sso.state.as_str());
d.set_str(proto_v1_field_name::network_config::SSO_CLIENT_ID, sso.client_id.as_str());
} else {
d.set_bool(proto_v1_field_name::network_config::SSO_ENABLED, false);
}
Some(d)
}
/// Decode a V1 format network configuration.
pub fn v1_proto_from_dictionary(d: &Dictionary) -> Result<NetworkConfig, InvalidParameterError> {
let nwid = NetworkId::from_str(
d.get_str(proto_v1_field_name::network_config::NETWORK_ID)
.ok_or(InvalidParameterError("missing network ID"))?,
)
.map_err(|_| InvalidParameterError("invalid network ID"))?;
let issued_to_address = Address::from_str(
d.get_str(proto_v1_field_name::network_config::ISSUED_TO)
.ok_or(InvalidParameterError("missing address"))?,
)
.map_err(|_| InvalidParameterError("invalid address"))?;
let mut nc = Self::new(nwid, issued_to_address);
d.get_str(proto_v1_field_name::network_config::NAME)
.map(|x| nc.name = x.to_string());
nc.private = d.get_str(proto_v1_field_name::network_config::TYPE).map_or(true, |x| x == "1");
nc.timestamp = d
.get_i64(proto_v1_field_name::network_config::TIMESTAMP)
.ok_or(InvalidParameterError("missing timestamp"))?;
nc.max_delta = d.get_i64(proto_v1_field_name::network_config::MAX_DELTA).unwrap_or(0);
nc.revision = d.get_u64(proto_v1_field_name::network_config::REVISION).unwrap_or(0);
nc.mtu = d
.get_u64(proto_v1_field_name::network_config::MTU)
.unwrap_or(crate::protocol::ZEROTIER_VIRTUAL_NETWORK_DEFAULT_MTU as u64) as u16;
nc.multicast_limit = d.get_u64(proto_v1_field_name::network_config::MULTICAST_LIMIT).unwrap_or(0) as u32;
if let Some(routes_bin) = d.get_bytes(proto_v1_field_name::network_config::ROUTES) {
for r in IpRoute::unmarshal_multiple_from_bytes(routes_bin)
.map_err(|_| InvalidParameterError("invalid route object(s)"))?
.drain(..)
{
let _ = nc.routes.insert(r);
}
}
if let Some(static_ips_bin) = d.get_bytes(proto_v1_field_name::network_config::STATIC_IPS) {
for ip in InetAddress::unmarshal_multiple_from_bytes(static_ips_bin)
.map_err(|_| InvalidParameterError("invalid route object(s)"))?
.drain(..)
{
let _ = nc.static_ips.insert(ip);
}
}
if let Some(rules_bin) = d.get_bytes(proto_v1_field_name::network_config::RULES) {
nc.rules = Rule::unmarshal_multiple_from_bytes(rules_bin).map_err(|_| InvalidParameterError("invalid route object(s)"))?;
}
if let Some(dns_bin) = d.get_bytes(proto_v1_field_name::network_config::DNS) {
if dns_bin.len() > 128 && dns_bin.len() < 1024 {
let mut name = String::with_capacity(64);
for i in 0..128 {
if dns_bin[i] == 0 {
break;
} else {
name.push(dns_bin[i] as char);
}
}
if !name.is_empty() {
let mut tmp: Buffer<1024> = Buffer::new();
let _ = tmp.append_bytes(&dns_bin[128..]);
let mut servers = HashSet::new();
let mut cursor = 0;
while cursor < tmp.len() {
if let Ok(s) = InetAddress::unmarshal(&tmp, &mut cursor) {
let _ = servers.insert(s);
} else {
break;
}
}
if !servers.is_empty() {
let _ = nc.dns.insert(name, servers);
}
}
}
}
nc.certificate_of_membership = Some(CertificateOfMembership::v1_proto_from_bytes(
d.get_bytes(proto_v1_field_name::network_config::CERTIFICATE_OF_MEMBERSHIP)
.ok_or(InvalidParameterError("missing certificate of membership"))?,
)?);
if let Some(mut coo_bin) = d.get_bytes(proto_v1_field_name::network_config::CERTIFICATES_OF_OWNERSHIP) {
while !coo_bin.is_empty() {
let c = CertificateOfOwnership::v1_proto_from_bytes(coo_bin)?;
nc.certificates_of_ownership.push(c.0);
coo_bin = c.1;
}
}
if let Some(mut tag_bin) = d.get_bytes(proto_v1_field_name::network_config::TAGS) {
while !tag_bin.is_empty() {
let t = Tag::v1_proto_from_bytes(tag_bin)?;
let _ = nc.tags.insert(t.0.id, t.0);
tag_bin = t.1;
}
}
if let Some(central_url) = d.get_str(proto_v1_field_name::network_config::CENTRAL_URL) {
nc.central_url = central_url.to_string();
}
if d.get_bool(proto_v1_field_name::network_config::SSO_ENABLED).unwrap_or(false) {
nc.sso = Some(SSOAuthConfiguration {
version: d.get_u64(proto_v1_field_name::network_config::SSO_VERSION).unwrap_or(0) as u32,
authentication_url: d
.get_str(proto_v1_field_name::network_config::SSO_AUTHENTICATION_URL)
.unwrap_or("")
.to_string(),
authentication_expiry_time: d
.get_i64(proto_v1_field_name::network_config::SSO_AUTHENTICATION_EXPIRY_TIME)
.unwrap_or(0),
issuer_url: d
.get_str(proto_v1_field_name::network_config::SSO_ISSUER_URL)
.unwrap_or("")
.to_string(),
nonce: d.get_str(proto_v1_field_name::network_config::SSO_NONCE).unwrap_or("").to_string(),
state: d.get_str(proto_v1_field_name::network_config::SSO_STATE).unwrap_or("").to_string(),
client_id: d
.get_str(proto_v1_field_name::network_config::SSO_CLIENT_ID)
.unwrap_or("")
.to_string(),
})
}
Ok(nc)
}
}
#[allow(unused)] #[allow(unused)]
mod proto_v1_field_name { mod proto_v1_field_name {
@ -27,7 +337,6 @@ mod proto_v1_field_name {
pub const MULTICAST_LIMIT: &'static str = "ml"; pub const MULTICAST_LIMIT: &'static str = "ml";
pub const TYPE: &'static str = "t"; pub const TYPE: &'static str = "t";
pub const NAME: &'static str = "n"; pub const NAME: &'static str = "n";
pub const MOTD: &'static str = "motd";
pub const MTU: &'static str = "mtu"; pub const MTU: &'static str = "mtu";
pub const MAX_DELTA: &'static str = "ctmd"; pub const MAX_DELTA: &'static str = "ctmd";
pub const CERTIFICATE_OF_MEMBERSHIP: &'static str = "C"; pub const CERTIFICATE_OF_MEMBERSHIP: &'static str = "C";
@ -72,129 +381,6 @@ pub struct SSOAuthConfiguration {
pub client_id: String, pub client_id: String,
} }
/// Network configuration object sent to nodes by network controllers.
#[derive(Clone, Serialize, Deserialize, PartialEq, Eq)]
pub struct NetworkConfig {
pub id: u64,
pub name: String,
pub motd: String,
pub issued_to: Address,
pub private: bool,
pub timestamp: i64,
pub max_delta: i64,
pub revision: u64,
pub mtu: u16,
pub multicast_limit: u32,
pub routes: Vec<IpRoute>,
pub static_ips: Vec<InetAddress>,
pub rules: Vec<Rule>,
pub dns: Vec<Nameserver>,
pub certificate_of_membership: CertificateOfMembership,
pub certificates_of_ownership: Vec<CertificateOfOwnership>,
pub tags: Vec<Tag>,
pub node_info: HashMap<Address, NodeInfo>,
pub central_url: String,
pub sso: Option<SSOAuthConfiguration>,
}
impl NetworkConfig {
pub fn v1_proto_to_dictionary(&self) -> Option<Dictionary> {
let mut d = Dictionary::new();
d.set_u64(proto_v1_field_name::network_config::NETWORK_ID, self.id);
if !self.name.is_empty() {
d.set_str(proto_v1_field_name::network_config::NAME, self.name.as_str());
}
if !self.motd.is_empty() {
d.set_str(proto_v1_field_name::network_config::MOTD, self.motd.as_str());
}
d.set_str(proto_v1_field_name::network_config::ISSUED_TO, self.issued_to.to_string().as_str());
d.set_str(
proto_v1_field_name::network_config::TYPE,
if self.private {
"0"
} else {
"1"
},
);
d.set_u64(proto_v1_field_name::network_config::TIMESTAMP, self.timestamp as u64);
d.set_u64(proto_v1_field_name::network_config::MAX_DELTA, self.max_delta as u64);
d.set_u64(proto_v1_field_name::network_config::REVISION, self.revision);
d.set_u64(proto_v1_field_name::network_config::MTU, self.mtu as u64);
d.set_u64(proto_v1_field_name::network_config::MULTICAST_LIMIT, self.multicast_limit as u64);
if !self.routes.is_empty() {
d.set_bytes(
proto_v1_field_name::network_config::ROUTES,
IpRoute::marshal_multiple_to_bytes(self.routes.as_slice()).unwrap(),
);
}
if !self.static_ips.is_empty() {
d.set_bytes(
proto_v1_field_name::network_config::STATIC_IPS,
InetAddress::marshal_multiple_to_bytes(self.static_ips.as_slice()).unwrap(),
);
}
if !self.rules.is_empty() {
d.set_bytes(
proto_v1_field_name::network_config::RULES,
Rule::marshal_multiple_to_bytes(self.rules.as_slice()).unwrap(),
);
}
if !self.dns.is_empty() {
d.set_bytes(
proto_v1_field_name::network_config::DNS,
Nameserver::marshal_multiple_to_bytes(self.dns.as_slice()).unwrap(),
);
}
d.set_bytes(
proto_v1_field_name::network_config::CERTIFICATE_OF_MEMBERSHIP,
self.certificate_of_membership.v1_proto_to_bytes()?,
);
if !self.certificates_of_ownership.is_empty() {
let mut certs = Vec::with_capacity(self.certificates_of_ownership.len() * 256);
for c in self.certificates_of_ownership.iter() {
let _ = certs.write_all(c.v1_proto_to_bytes()?.as_slice());
}
d.set_bytes(proto_v1_field_name::network_config::CERTIFICATES_OF_OWNERSHIP, certs);
}
if !self.tags.is_empty() {
let mut certs = Vec::with_capacity(self.certificates_of_ownership.len() * 256);
for t in self.tags.iter() {
let _ = certs.write_all(t.v1_proto_to_bytes()?.as_slice());
}
d.set_bytes(proto_v1_field_name::network_config::TAGS, certs);
}
// node_info is not supported by V1 nodes
if !self.central_url.is_empty() {
d.set_str(proto_v1_field_name::network_config::CENTRAL_URL, self.central_url.as_str());
}
if let Some(sso) = self.sso.as_ref() {
d.set_bool(proto_v1_field_name::network_config::SSO_ENABLED, true);
d.set_u64(proto_v1_field_name::network_config::SSO_VERSION, sso.version as u64);
d.set_str(
proto_v1_field_name::network_config::SSO_AUTHENTICATION_URL,
sso.authentication_url.as_str(),
);
d.set_u64(
proto_v1_field_name::network_config::SSO_AUTHENTICATION_EXPIRY_TIME,
sso.authentication_expiry_time as u64,
);
d.set_str(proto_v1_field_name::network_config::SSO_ISSUER_URL, sso.issuer_url.as_str());
d.set_str(proto_v1_field_name::network_config::SSO_NONCE, sso.nonce.as_str());
d.set_str(proto_v1_field_name::network_config::SSO_STATE, sso.state.as_str());
d.set_str(proto_v1_field_name::network_config::SSO_CLIENT_ID, sso.client_id.as_str());
} else {
d.set_bool(proto_v1_field_name::network_config::SSO_ENABLED, false);
}
Some(d)
}
}
/// Information about nodes on the network that can be included in a network config. /// Information about nodes on the network that can be included in a network config.
#[derive(Clone, Serialize, Deserialize, PartialEq, Eq)] #[derive(Clone, Serialize, Deserialize, PartialEq, Eq)]
pub struct NodeInfo { pub struct NodeInfo {
@ -205,7 +391,7 @@ pub struct NodeInfo {
} }
/// Statically pushed L3 IP routes included with a network configuration. /// Statically pushed L3 IP routes included with a network configuration.
#[derive(Clone, Serialize, Deserialize, PartialEq, Eq)] #[derive(Clone, Serialize, Deserialize, PartialEq, Eq, Hash)]
pub struct IpRoute { pub struct IpRoute {
pub target: InetAddress, pub target: InetAddress,
pub via: Option<InetAddress>, pub via: Option<InetAddress>,
@ -250,42 +436,3 @@ impl Marshalable for IpRoute {
}) })
} }
} }
/// ZeroTier-pushed DNS nameserver configuration.
#[derive(Clone, Serialize, Deserialize, PartialEq, Eq)]
pub struct Nameserver {
pub ip: Vec<InetAddress>,
pub domain: String,
}
impl Marshalable for Nameserver {
const MAX_MARSHAL_SIZE: usize = 128 + InetAddress::MAX_MARSHAL_SIZE;
fn marshal<const BL: usize>(&self, buf: &mut Buffer<BL>) -> Result<(), UnmarshalError> {
let domain_bytes = self.domain.as_bytes();
let domain_bytes_len = domain_bytes.len().min(127);
let mut domain_bytes_pad128 = [0_u8; 128];
domain_bytes_pad128[..domain_bytes_len].copy_from_slice(&domain_bytes[..domain_bytes_len]);
buf.append_bytes_fixed(&domain_bytes_pad128)?;
buf.append_bytes(InetAddress::marshal_multiple_to_bytes(self.ip.as_slice()).unwrap().as_slice())?;
Ok(())
}
fn unmarshal<const BL: usize>(buf: &Buffer<BL>, cursor: &mut usize) -> Result<Self, UnmarshalError> {
let domain_bytes_pad128: &[u8; 128] = buf.read_bytes_fixed(cursor)?;
let mut domain_bytes_len = 0;
for i in 0..128 {
if domain_bytes_pad128[i] == 0 {
domain_bytes_len = i;
break;
}
}
if domain_bytes_len == 0 {
return Err(UnmarshalError::InvalidData);
}
Ok(Nameserver {
ip: InetAddress::unmarshal_multiple(buf, cursor, buf.len())?,
domain: String::from_utf8_lossy(&domain_bytes_pad128[..domain_bytes_len]).to_string(),
})
}
}

View file

@ -740,7 +740,7 @@ struct HumanReadableRule<'a> {
impl<'a> HumanReadableRule<'a> { impl<'a> HumanReadableRule<'a> {
fn to_rule(&self) -> Option<Rule> { fn to_rule(&self) -> Option<Rule> {
if let Some(t) = HR_NAME_TO_RULE_TYPE.get(self._type) { if let Some(t) = HR_NAME_TO_RULE_TYPE.get(self._type.to_uppercase().as_str()) {
let mut r = Rule::default(); let mut r = Rule::default();
r.t = r.t =
*t | if self.not.unwrap_or(false) { *t | if self.not.unwrap_or(false) {

View file

@ -3,7 +3,7 @@
use std::sync::Arc; use std::sync::Arc;
use crate::protocol::PacketBuffer; use crate::protocol::PacketBuffer;
use crate::vl1::node::{HostSystem, InnerProtocol, PacketHandlerResult}; use crate::vl1::node::{HostSystem, InnerProtocol, Node, PacketHandlerResult};
use crate::vl1::{Identity, Path, Peer}; use crate::vl1::{Identity, Path, Peer};
pub trait SwitchInterface: Sync + Send {} pub trait SwitchInterface: Sync + Send {}
@ -13,8 +13,10 @@ pub struct Switch {}
impl InnerProtocol for Switch { impl InnerProtocol for Switch {
fn handle_packet<HostSystemImpl: HostSystem>( fn handle_packet<HostSystemImpl: HostSystem>(
&self, &self,
node: &Node<HostSystemImpl>,
source: &Arc<Peer<HostSystemImpl>>, source: &Arc<Peer<HostSystemImpl>>,
source_path: &Arc<Path<HostSystemImpl>>, source_path: &Arc<Path<HostSystemImpl>>,
message_id: u64,
verb: u8, verb: u8,
payload: &PacketBuffer, payload: &PacketBuffer,
) -> PacketHandlerResult { ) -> PacketHandlerResult {
@ -23,8 +25,10 @@ impl InnerProtocol for Switch {
fn handle_error<HostSystemImpl: HostSystem>( fn handle_error<HostSystemImpl: HostSystem>(
&self, &self,
node: &Node<HostSystemImpl>,
source: &Arc<Peer<HostSystemImpl>>, source: &Arc<Peer<HostSystemImpl>>,
source_path: &Arc<Path<HostSystemImpl>>, source_path: &Arc<Path<HostSystemImpl>>,
message_id: u64,
in_re_verb: u8, in_re_verb: u8,
in_re_message_id: u64, in_re_message_id: u64,
error_code: u8, error_code: u8,
@ -36,8 +40,10 @@ impl InnerProtocol for Switch {
fn handle_ok<HostSystemImpl: HostSystem>( fn handle_ok<HostSystemImpl: HostSystem>(
&self, &self,
node: &Node<HostSystemImpl>,
source: &Arc<Peer<HostSystemImpl>>, source: &Arc<Peer<HostSystemImpl>>,
source_path: &Arc<Path<HostSystemImpl>>, source_path: &Arc<Path<HostSystemImpl>>,
message_id: u64,
in_re_verb: u8, in_re_verb: u8,
in_re_message_id: u64, in_re_message_id: u64,
payload: &PacketBuffer, payload: &PacketBuffer,

View file

@ -8,6 +8,8 @@ use crate::vl2::NetworkId;
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
use zerotier_utils::arrayvec::ArrayVec; use zerotier_utils::arrayvec::ArrayVec;
use zerotier_utils::blob::Blob;
use zerotier_utils::error::InvalidParameterError;
#[derive(Clone, Serialize, Deserialize, PartialEq, Eq)] #[derive(Clone, Serialize, Deserialize, PartialEq, Eq)]
pub struct Tag { pub struct Tag {
@ -16,13 +18,49 @@ pub struct Tag {
pub network_id: NetworkId, pub network_id: NetworkId,
pub timestamp: i64, pub timestamp: i64,
pub issued_to: Address, pub issued_to: Address,
pub signed_by: Address, pub issued_to_fingerprint: Blob<{ Identity::FINGERPRINT_SIZE }>,
pub signature: ArrayVec<u8, { identity::IDENTITY_MAX_SIGNATURE_SIZE }>, pub signature: ArrayVec<u8, { identity::IDENTITY_MAX_SIGNATURE_SIZE }>,
pub version: u8,
} }
impl Tag { impl Tag {
fn internal_v1_proto_to_bytes(&self, for_sign: bool) -> Option<Vec<u8>> { pub fn new(
if self.signature.len() == 96 { id: u32,
value: u32,
issuer: &Identity,
network_id: NetworkId,
issued_to: &Identity,
timestamp: i64,
legacy_v1: bool,
) -> Option<Self> {
let mut tag = Self {
id,
value,
network_id,
timestamp,
issued_to: issued_to.address,
issued_to_fingerprint: Blob::from(issued_to.fingerprint),
signature: ArrayVec::new(),
version: if legacy_v1 {
1
} else {
2
},
};
if legacy_v1 {
let to_sign = tag.internal_v1_proto_to_bytes(true, issuer.address)?;
if let Some(signature) = issuer.sign(to_sign.as_slice(), true) {
tag.signature = signature;
return Some(tag);
}
} else {
todo!()
}
return None;
}
fn internal_v1_proto_to_bytes(&self, for_sign: bool, signed_by: Address) -> Option<Vec<u8>> {
if self.version == 1 && self.signature.len() == 96 {
let mut v = Vec::with_capacity(256); let mut v = Vec::with_capacity(256);
if for_sign { if for_sign {
let _ = v.write_all(&[0x7f; 8]); let _ = v.write_all(&[0x7f; 8]);
@ -32,7 +70,7 @@ impl Tag {
let _ = v.write_all(&self.id.to_be_bytes()); let _ = v.write_all(&self.id.to_be_bytes());
let _ = v.write_all(&self.value.to_be_bytes()); let _ = v.write_all(&self.value.to_be_bytes());
let _ = v.write_all(&self.issued_to.to_bytes()); let _ = v.write_all(&self.issued_to.to_bytes());
let _ = v.write_all(&self.signed_by.to_bytes()); let _ = v.write_all(&signed_by.to_bytes());
if !for_sign { if !for_sign {
v.push(1); v.push(1);
v.push(0); v.push(0);
@ -50,14 +88,13 @@ impl Tag {
} }
#[inline(always)] #[inline(always)]
pub fn v1_proto_to_bytes(&self) -> Option<Vec<u8>> { pub fn v1_proto_to_bytes(&self, signed_by: Address) -> Option<Vec<u8>> {
self.internal_v1_proto_to_bytes(false) self.internal_v1_proto_to_bytes(false, signed_by)
} }
pub fn v1_proto_sign(&mut self, issuer: &Identity, issued_to: &Identity) -> bool { pub fn v1_proto_sign(&mut self, issuer: &Identity, issued_to: &Identity) -> bool {
self.issued_to = issued_to.address; self.issued_to = issued_to.address;
self.signed_by = issuer.address; if let Some(to_sign) = self.internal_v1_proto_to_bytes(true, issuer.address) {
if let Some(to_sign) = self.internal_v1_proto_to_bytes(true) {
if let Some(signature) = issuer.sign(&to_sign.as_slice(), true) { if let Some(signature) = issuer.sign(&to_sign.as_slice(), true) {
self.signature = signature; self.signature = signature;
return true; return true;
@ -65,4 +102,28 @@ impl Tag {
} }
return false; return false;
} }
pub fn v1_proto_from_bytes(b: &[u8]) -> Result<(Self, &[u8]), InvalidParameterError> {
const LEN: usize = 8 + 8 + 4 + 4 + 5 + 5 + 3 + 96 + 2;
if b.len() < LEN {
return Err(InvalidParameterError("incomplete"));
}
Ok((
Self {
id: u32::from_be_bytes(b[16..20].try_into().unwrap()),
value: u32::from_be_bytes(b[20..24].try_into().unwrap()),
network_id: NetworkId::from_bytes(&b[0..8]).ok_or(InvalidParameterError("invalid network ID"))?,
timestamp: i64::from_be_bytes(b[8..16].try_into().unwrap()),
issued_to: Address::from_bytes(&b[24..29]).ok_or(InvalidParameterError("invalid address"))?,
issued_to_fingerprint: Blob::default(),
signature: {
let mut s = ArrayVec::new();
s.push_slice(&b[37..133]);
s
},
version: 1,
},
&b[LEN..],
))
}
} }

View file

@ -64,7 +64,7 @@ impl<T, const C: usize> GatherArray<T, C> {
} }
impl<T, const C: usize> Drop for GatherArray<T, C> { impl<T, const C: usize> Drop for GatherArray<T, C> {
#[inline(always)] #[inline]
fn drop(&mut self) { fn drop(&mut self) {
let have = self.have_bits; let have = self.have_bits;
for i in 0..self.goal { for i in 0..self.goal {