mirror of
https://github.com/zerotier/ZeroTierOne.git
synced 2025-06-08 05:23:44 +02:00
A bunch of tightening up code in vl1/node, more CLI work, sketch out CLI for creating root sets.
This commit is contained in:
parent
3f6ce29f22
commit
083e2bc666
15 changed files with 274 additions and 192 deletions
|
@ -19,7 +19,7 @@ metrohash = "^1"
|
|||
dashmap = "^5"
|
||||
parking_lot = "^0"
|
||||
lazy_static = "^1"
|
||||
serde = { version = "^1", features = [], default-features = false }
|
||||
serde = { version = "^1", features = ["derive"], default-features = false }
|
||||
|
||||
[target."cfg(not(windows))".dependencies]
|
||||
libc = "^0"
|
||||
|
|
|
@ -112,6 +112,12 @@ impl<const L: usize> Buffer<L> {
|
|||
&mut self.1[0..self.0]
|
||||
}
|
||||
|
||||
/// Get a mutable reference to the entire buffer regardless of the current 'size'.
|
||||
#[inline(always)]
|
||||
pub unsafe fn entire_buffer_mut(&mut self) -> &mut [u8; L] {
|
||||
&mut self.1
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
pub fn as_ptr(&self) -> *const u8 {
|
||||
self.1.as_ptr()
|
||||
|
|
|
@ -127,7 +127,7 @@ impl<'de> serde::de::Visitor<'de> for AddressVisitor {
|
|||
if v.len() == ADDRESS_SIZE {
|
||||
Address::from_bytes(v).map_or_else(|| Err(E::custom("object too large")), |a| Ok(a))
|
||||
} else {
|
||||
Err(E::custom("object too large"))
|
||||
Err(E::custom("object size incorrect"))
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -220,6 +220,7 @@ impl Marshalable for Endpoint {
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Hash for Endpoint {
|
||||
fn hash<H: Hasher>(&self, state: &mut H) {
|
||||
match self {
|
||||
|
@ -244,7 +245,7 @@ impl Hash for Endpoint {
|
|||
}
|
||||
Endpoint::Ip(ip) => {
|
||||
state.write_u8(TYPE_IP);
|
||||
ip.hash(state);
|
||||
ip.ip_bytes().hash(state);
|
||||
}
|
||||
Endpoint::IpUdp(ip) => {
|
||||
state.write_u8(TYPE_IPUDP);
|
||||
|
|
|
@ -15,10 +15,9 @@ mod dictionary;
|
|||
mod mac;
|
||||
mod path;
|
||||
mod peer;
|
||||
mod rootcluster;
|
||||
mod rootset;
|
||||
|
||||
pub(crate) mod fragmentedpacket;
|
||||
pub(crate) mod hybridkey;
|
||||
pub(crate) mod node;
|
||||
#[allow(unused)]
|
||||
pub(crate) mod protocol;
|
||||
|
@ -34,4 +33,4 @@ pub use mac::MAC;
|
|||
pub use node::{Node, SystemInterface};
|
||||
pub use path::Path;
|
||||
pub use peer::Peer;
|
||||
pub use rootcluster::{Root, RootCluster};
|
||||
pub use rootset::{Root, RootSet};
|
||||
|
|
|
@ -24,7 +24,7 @@ use crate::vl1::path::Path;
|
|||
use crate::vl1::peer::Peer;
|
||||
use crate::vl1::protocol::*;
|
||||
use crate::vl1::whoisqueue::{QueuedPacket, WhoisQueue};
|
||||
use crate::vl1::{Address, Endpoint, Identity, RootCluster};
|
||||
use crate::vl1::{Address, Endpoint, Identity, RootSet};
|
||||
use crate::{PacketBuffer, PacketBufferFactory, PacketBufferPool};
|
||||
|
||||
/// Trait implemented by external code to handle events and provide an interface to the system or application.
|
||||
|
@ -132,6 +132,12 @@ struct BackgroundTaskIntervals {
|
|||
root_hello: IntervalGate<ROOT_HELLO_INTERVAL>,
|
||||
}
|
||||
|
||||
struct RootInfo {
|
||||
roots: HashMap<Arc<Peer>, Vec<Endpoint>>,
|
||||
sets: HashMap<String, RootSet>,
|
||||
sets_modified: bool,
|
||||
}
|
||||
|
||||
/// A VL1 global P2P network node.
|
||||
pub struct Node {
|
||||
/// A random ID generated to identify this particular running instance.
|
||||
|
@ -144,13 +150,13 @@ pub struct Node {
|
|||
intervals: Mutex<BackgroundTaskIntervals>,
|
||||
|
||||
/// Canonicalized network paths, held as Weak<> to be automatically cleaned when no longer in use.
|
||||
paths: DashMap<u128, Weak<Path>>,
|
||||
paths: DashMap<(u64, u64), Weak<Path>>,
|
||||
|
||||
/// Peers with which we are currently communicating.
|
||||
peers: DashMap<Address, Arc<Peer>>,
|
||||
|
||||
/// This node's trusted roots, sorted in ascending order of quality/preference, and cluster definitions.
|
||||
roots: Mutex<(Vec<Arc<Peer>>, Vec<RootCluster>)>,
|
||||
roots: Mutex<RootInfo>,
|
||||
|
||||
/// Current best root.
|
||||
best_root: RwLock<Option<Arc<Peer>>>,
|
||||
|
@ -197,7 +203,11 @@ impl Node {
|
|||
intervals: Mutex::new(BackgroundTaskIntervals::default()),
|
||||
paths: DashMap::new(),
|
||||
peers: DashMap::new(),
|
||||
roots: Mutex::new((Vec::new(), Vec::new())),
|
||||
roots: Mutex::new(RootInfo {
|
||||
roots: HashMap::new(),
|
||||
sets: HashMap::new(),
|
||||
sets_modified: false,
|
||||
}),
|
||||
best_root: RwLock::new(None),
|
||||
whois: WhoisQueue::new(),
|
||||
buffer_pool: PacketBufferPool::new(64, PacketBufferFactory::new()),
|
||||
|
@ -235,89 +245,83 @@ impl Node {
|
|||
let tt = si.time_ticks();
|
||||
|
||||
if intervals.root_sync.gate(tt) {
|
||||
let mut roots_lock = self.roots.lock();
|
||||
let (roots, root_clusters) = &mut *roots_lock;
|
||||
match &mut (*self.roots.lock()) {
|
||||
RootInfo { roots, sets, sets_modified } => {
|
||||
// Sychronize root info with root sets info if the latter has changed.
|
||||
if *sets_modified {
|
||||
*sets_modified = false;
|
||||
roots.clear();
|
||||
let mut colliding_root_addresses = Vec::new(); // see security note below
|
||||
for (_, rc) in sets.iter() {
|
||||
for m in rc.members.iter() {
|
||||
if m.endpoints.is_some() && !colliding_root_addresses.contains(&m.identity.address) {
|
||||
/*
|
||||
* SECURITY NOTE: it should be impossible to get an address/identity collision here unless
|
||||
* the user adds a maliciously crafted root set with an identity that collides another. Under
|
||||
* normal circumstances the root backplane combined with the address PoW should rule this
|
||||
* out. However since we trust roots as identity lookup authorities it's important to take
|
||||
* extra care to check for this case. If it's detected, all roots with the offending
|
||||
* address are ignored/disabled.
|
||||
*
|
||||
* The apparently over-thought functional chain here on peers.entry() is to make access to
|
||||
* the peer map atomic since we use a "lock-free" data structure here (DashMap).
|
||||
*/
|
||||
|
||||
// Look at root cluster definitions and make sure all have corresponding root peers.
|
||||
let mut root_endpoints = HashMap::with_capacity(roots.len() * 2);
|
||||
for rc in root_clusters.iter() {
|
||||
for m in rc.members.iter() {
|
||||
if m.endpoints.is_some() {
|
||||
let endpoints = m.endpoints.as_ref().unwrap();
|
||||
|
||||
/*
|
||||
* SECURITY NOTE: we take extra care to handle the case where we have a peer whose identity
|
||||
* differs from that of a root but whose address is the same. It should be impossible to
|
||||
* make this happen, but we check anyway. It would require a colliding identity to be
|
||||
* approved by one of your existing roots and somehow retrieved via WHOIS, but this would
|
||||
* be hard because this background task loop populates the peer list with specified root
|
||||
* identities before this happens. It could also happen if you have two root cluster
|
||||
* definitions with a colliding address, which would itself be hard to produce and would
|
||||
* probably mean someone is doing something nasty.
|
||||
*
|
||||
* In this case the response is to ignore this root entirely and generate a warning.
|
||||
*/
|
||||
|
||||
// This functional stuff on entry() is to do all this atomically while holding the map's entry
|
||||
// object, since this is a "lock-free" structure.
|
||||
let _ = self
|
||||
.peers
|
||||
.entry(m.identity.address)
|
||||
.or_try_insert_with(|| {
|
||||
Peer::new(&self.identity, m.identity.clone(), tt).map_or(Err(crate::error::UnexpectedError), |new_root| {
|
||||
let new_root = Arc::new(new_root);
|
||||
roots.retain(|r| r.identity.address != m.identity.address); // sanity check, should be impossible
|
||||
roots.push(new_root.clone());
|
||||
Ok(new_root)
|
||||
})
|
||||
})
|
||||
.and_then(|root_peer_entry| {
|
||||
let rp = root_peer_entry.value();
|
||||
if rp.identity.eq(&m.identity) {
|
||||
Ok(root_peer_entry)
|
||||
} else {
|
||||
roots.retain(|r| r.identity.address != m.identity.address);
|
||||
si.event_security_warning(format!("address/identity collision between root {} (from root cluster definition '{}') and known peer {}", m.identity.address.to_string(), rc.name, rp.identity.to_string()).as_str());
|
||||
Err(crate::error::UnexpectedError)
|
||||
let _ = self
|
||||
.peers
|
||||
.entry(m.identity.address)
|
||||
.or_try_insert_with(|| Peer::new(&self.identity, m.identity.clone(), tt).map_or(Err(crate::error::UnexpectedError), |new_root| Ok(Arc::new(new_root))))
|
||||
.and_then(|root_peer_entry| {
|
||||
let rp = root_peer_entry.value();
|
||||
if rp.identity.eq(&m.identity) {
|
||||
Ok(root_peer_entry)
|
||||
} else {
|
||||
colliding_root_addresses.push(m.identity.address);
|
||||
si.event_security_warning(
|
||||
format!("address/identity collision between root {} (from root cluster definition '{}') and known peer {}", m.identity.address.to_string(), rc.name, rp.identity.to_string()).as_str(),
|
||||
);
|
||||
Err(crate::error::UnexpectedError)
|
||||
}
|
||||
})
|
||||
.map(|r| roots.insert(r.value().clone(), m.endpoints.as_ref().unwrap().iter().map(|e| e.clone()).collect()));
|
||||
}
|
||||
})
|
||||
.map(|_| {
|
||||
let _ = root_endpoints.insert(m.identity.address, endpoints);
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Remove all roots not in any current root cluster definition.
|
||||
roots.retain(|r| root_endpoints.contains_key(&r.identity.address));
|
||||
|
||||
// Say HELLO to all roots periodically. For roots we send HELLO to every single endpoint
|
||||
// they have, which is a behavior that differs from normal peers. This allows roots to
|
||||
// e.g. see our IPv4 and our IPv6 address which can be important for us to learn our
|
||||
// external addresses from them.
|
||||
assert!(ROOT_SYNC_INTERVAL_MS <= (ROOT_HELLO_INTERVAL / 2));
|
||||
if intervals.root_hello.gate(tt) {
|
||||
for r in roots.iter() {
|
||||
for ep in root_endpoints.get(&r.identity.address).unwrap().iter() {
|
||||
r.send_hello(si, self, Some(ep));
|
||||
// Say HELLO to all roots periodically. For roots we send HELLO to every single endpoint
|
||||
// they have, which is a behavior that differs from normal peers. This allows roots to
|
||||
// e.g. see our IPv4 and our IPv6 address which can be important for us to learn our
|
||||
// external addresses from them.
|
||||
assert!(ROOT_SYNC_INTERVAL_MS <= (ROOT_HELLO_INTERVAL / 2));
|
||||
if intervals.root_hello.gate(tt) {
|
||||
for (root, endpoints) in roots.iter() {
|
||||
for ep in endpoints.iter() {
|
||||
root.send_hello(si, self, Some(ep));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// The best root is the one that has replied to a HELLO most recently. Since we send HELLOs in unison
|
||||
// this is a proxy for latency and also causes roots that fail to reply to drop out quickly.
|
||||
if !roots.is_empty() {
|
||||
roots.sort_unstable_by(|a, b| a.last_hello_reply_time_ticks.load(Ordering::Relaxed).cmp(&b.last_hello_reply_time_ticks.load(Ordering::Relaxed)));
|
||||
let _ = self.best_root.write().insert(roots.last().unwrap().clone());
|
||||
} else {
|
||||
let _ = self.best_root.write().take();
|
||||
// The best root is the one that has replied to a HELLO most recently. Since we send HELLOs in unison
|
||||
// this is a proxy for latency and also causes roots that fail to reply to drop out quickly.
|
||||
let mut latest_hello_reply = 0;
|
||||
let mut best: Option<&Arc<Peer>> = None;
|
||||
for (r, _) in roots.iter() {
|
||||
let t = r.last_hello_reply_time_ticks.load(Ordering::Relaxed);
|
||||
if t >= latest_hello_reply {
|
||||
latest_hello_reply = t;
|
||||
let _ = best.insert(r);
|
||||
}
|
||||
}
|
||||
*(self.best_root.write()) = best.cloned();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if intervals.peers.gate(tt) {
|
||||
// Service all peers, removing any whose service() method returns false AND that are not
|
||||
// roots. Roots on the other hand remain in the peer list as long as they are roots.
|
||||
self.peers.retain(|_, peer| if peer.service(si, self, tt) { true } else { !self.roots.lock().0.iter().any(|r| Arc::ptr_eq(peer, r)) });
|
||||
self.peers.retain(|_, peer| if peer.service(si, self, tt) { true } else { !self.roots.lock().roots.contains_key(peer) });
|
||||
}
|
||||
|
||||
if intervals.paths.gate(tt) {
|
||||
|
@ -339,7 +343,7 @@ impl Node {
|
|||
if dest == self.identity.address {
|
||||
// Handle packets (seemingly) addressed to this node.
|
||||
|
||||
let path = self.path_to_endpoint(source_endpoint, source_local_socket, source_local_interface);
|
||||
let path = self.canonical_path(source_endpoint, source_local_socket, source_local_interface);
|
||||
path.log_receive_anything(time_ticks);
|
||||
|
||||
if fragment_header.is_fragment() {
|
||||
|
@ -407,16 +411,35 @@ impl Node {
|
|||
|
||||
/// Return true if a peer is a root.
|
||||
pub fn is_peer_root(&self, peer: &Peer) -> bool {
|
||||
self.roots.lock().0.iter().any(|p| Arc::as_ptr(p) == (peer as *const Peer))
|
||||
self.roots.lock().roots.contains_key(peer)
|
||||
}
|
||||
|
||||
pub fn add_update_root_set(&self, rs: RootSet) -> bool {
|
||||
let mut roots = self.roots.lock();
|
||||
let entry = roots.sets.get_mut(&rs.name);
|
||||
if entry.is_some() {
|
||||
let old_rs = entry.unwrap();
|
||||
if rs.should_replace(old_rs) {
|
||||
*old_rs = rs;
|
||||
roots.sets_modified = true;
|
||||
return true;
|
||||
}
|
||||
} else {
|
||||
if rs.verify() {
|
||||
roots.sets.insert(rs.name.clone(), rs);
|
||||
roots.sets_modified = true;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/// Get the canonical Path object for a given endpoint and local socket information.
|
||||
///
|
||||
/// This is a canonicalizing function that returns a unique path object for every tuple
|
||||
/// of endpoint, local socket, and local interface.
|
||||
pub fn path_to_endpoint(&self, ep: &Endpoint, local_socket: Option<NonZeroI64>, local_interface: Option<NonZeroI64>) -> Arc<Path> {
|
||||
let key = Path::local_lookup_key(ep, local_socket, local_interface);
|
||||
let mut path_entry = self.paths.entry(key).or_insert_with(|| Weak::new());
|
||||
pub fn canonical_path(&self, ep: &Endpoint, local_socket: Option<NonZeroI64>, local_interface: Option<NonZeroI64>) -> Arc<Path> {
|
||||
let mut path_entry = self.paths.entry(Path::local_lookup_key(ep, local_socket, local_interface)).or_default();
|
||||
if let Some(path) = path_entry.value().upgrade() {
|
||||
path
|
||||
} else {
|
||||
|
@ -426,7 +449,3 @@ impl Node {
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
unsafe impl Send for Node {}
|
||||
|
||||
unsafe impl Sync for Node {}
|
||||
|
|
|
@ -42,7 +42,7 @@ pub struct Path {
|
|||
|
||||
impl Path {
|
||||
/// Get a 128-bit key to look up this endpoint in the local node path map.
|
||||
pub(crate) fn local_lookup_key(endpoint: &Endpoint, local_socket: Option<NonZeroI64>, local_interface: Option<NonZeroI64>) -> u128 {
|
||||
pub(crate) fn local_lookup_key(endpoint: &Endpoint, local_socket: Option<NonZeroI64>, local_interface: Option<NonZeroI64>) -> (u64, u64) {
|
||||
let mut h = MetroHash128::with_seed(*METROHASH_SEED);
|
||||
h.write_u64(local_socket.map_or(0, |s| s.get() as u64));
|
||||
h.write_u64(local_interface.map_or(0, |s| s.get() as u64));
|
||||
|
@ -89,8 +89,7 @@ impl Path {
|
|||
h.write(fingerprint);
|
||||
}
|
||||
}
|
||||
assert_eq!(std::mem::size_of::<(u64, u64)>(), std::mem::size_of::<u128>());
|
||||
unsafe { std::mem::transmute(h.finish128()) }
|
||||
h.finish128()
|
||||
}
|
||||
|
||||
pub fn new(endpoint: Endpoint, local_socket: Option<NonZeroI64>, local_interface: Option<NonZeroI64>) -> Self {
|
||||
|
|
|
@ -7,6 +7,7 @@
|
|||
*/
|
||||
|
||||
use std::convert::TryInto;
|
||||
use std::hash::{Hash, Hasher};
|
||||
use std::mem::MaybeUninit;
|
||||
use std::num::NonZeroI64;
|
||||
use std::sync::atomic::{AtomicI64, AtomicU64, AtomicU8, Ordering};
|
||||
|
@ -32,8 +33,11 @@ use crate::vl1::{Dictionary, Endpoint, Identity, Path};
|
|||
use crate::{PacketBuffer, VERSION_MAJOR, VERSION_MINOR, VERSION_PROTO, VERSION_REVISION};
|
||||
|
||||
/// A remote peer known to this node.
|
||||
/// Sending-related and receiving-related fields are locked separately since concurrent
|
||||
/// send/receive is not uncommon.
|
||||
///
|
||||
/// NOTE: this implements PartialEq/Eq and Hash in terms of the pointer identity of
|
||||
/// the structure. This means two peers are equal only if they are the same instance in
|
||||
/// memory. This is done because they are only stored in an Arc<> internally and we want
|
||||
/// to use these as efficient hash map keys.
|
||||
pub struct Peer {
|
||||
// This peer's identity.
|
||||
pub(crate) identity: Identity,
|
||||
|
@ -583,6 +587,22 @@ impl Peer {
|
|||
}
|
||||
}
|
||||
|
||||
impl PartialEq for Peer {
|
||||
#[inline(always)]
|
||||
fn eq(&self, other: &Self) -> bool {
|
||||
std::ptr::eq(self as *const Peer, other as *const Peer)
|
||||
}
|
||||
}
|
||||
|
||||
impl Eq for Peer {}
|
||||
|
||||
impl Hash for Peer {
|
||||
#[inline(always)]
|
||||
fn hash<H: Hasher>(&self, state: &mut H) {
|
||||
state.write_usize((self as *const Peer) as usize)
|
||||
}
|
||||
}
|
||||
|
||||
impl BackgroundServicable for Peer {
|
||||
const SERVICE_INTERVAL_MS: i64 = EPHEMERAL_SECRET_REKEY_AFTER_TIME / 10;
|
||||
|
||||
|
|
|
@ -189,23 +189,23 @@ pub const ROOT_HELLO_INTERVAL: i64 = PATH_KEEPALIVE_INTERVAL * 2;
|
|||
/// Proof of work difficulty (threshold) for identity generation.
|
||||
pub const IDENTITY_POW_THRESHOLD: u8 = 17;
|
||||
|
||||
/// Compress a packet and return true if compressed.
|
||||
/// The 'dest' buffer must be empty (will panic otherwise). A return value of false indicates an error or
|
||||
/// that the data was not compressible. The state of the destination buffer is undefined on a return
|
||||
/// value of false.
|
||||
pub fn compress_packet(src: &[u8], dest: &mut Buffer<{ PACKET_SIZE_MAX }>) -> bool {
|
||||
if src.len() > PACKET_VERB_INDEX {
|
||||
debug_assert!(dest.is_empty());
|
||||
let cs = {
|
||||
let d = dest.as_bytes_mut();
|
||||
/// Attempt to compress a packet's payload with LZ4
|
||||
///
|
||||
/// If this returns true the destination buffer will contain a compressed packet. If false is
|
||||
/// returned the contents of 'dest' are entirely undefined. This indicates that the data was not
|
||||
/// compressable or some other error occurred.
|
||||
pub fn compress_packet(src: &[u8], dest: &mut Buffer<PACKET_SIZE_MAX>) -> bool {
|
||||
if src.len() > (PACKET_VERB_INDEX + 16) {
|
||||
let compressed_data_size = {
|
||||
let d = unsafe { dest.entire_buffer_mut() };
|
||||
d[0..PACKET_VERB_INDEX].copy_from_slice(&src[0..PACKET_VERB_INDEX]);
|
||||
d[PACKET_VERB_INDEX] = src[PACKET_VERB_INDEX] | VERB_FLAG_COMPRESSED;
|
||||
lz4_flex::block::compress_into(&src[PACKET_VERB_INDEX + 1..], &mut d[PACKET_VERB_INDEX + 1..])
|
||||
};
|
||||
if cs.is_ok() {
|
||||
let cs = cs.unwrap();
|
||||
if cs > 0 && cs < (src.len() - PACKET_VERB_INDEX) {
|
||||
unsafe { dest.set_size_unchecked(PACKET_VERB_INDEX + 1 + cs) };
|
||||
if compressed_data_size.is_ok() {
|
||||
let compressed_data_size = compressed_data_size.unwrap();
|
||||
if compressed_data_size > 0 && compressed_data_size < (src.len() - PACKET_VERB_INDEX) {
|
||||
unsafe { dest.set_size_unchecked(PACKET_VERB_INDEX + 1 + compressed_data_size) };
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -15,8 +15,10 @@ use crate::vl1::identity::*;
|
|||
use crate::vl1::protocol::PACKET_SIZE_MAX;
|
||||
use crate::vl1::Endpoint;
|
||||
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
/// Description of a member of a root cluster.
|
||||
#[derive(Clone, PartialEq, Eq)]
|
||||
#[derive(Clone, PartialEq, Eq, Serialize, Deserialize)]
|
||||
pub struct Root {
|
||||
/// Full identity of this node.
|
||||
pub identity: Identity,
|
||||
|
@ -24,10 +26,12 @@ pub struct Root {
|
|||
/// Endpoints for this root or None if this is a former member attesting to an update that removes it.
|
||||
pub endpoints: Option<BTreeSet<Endpoint>>,
|
||||
|
||||
/// Signature of entire cluster by this identity.
|
||||
pub cluster_signature: Vec<u8>,
|
||||
/// Signature of entire root set by this identity.
|
||||
#[serde(default)]
|
||||
pub signature: Vec<u8>,
|
||||
|
||||
/// Flags field (currently unused).
|
||||
#[serde(default)]
|
||||
pub flags: u64,
|
||||
}
|
||||
|
||||
|
@ -54,8 +58,8 @@ impl Ord for Root {
|
|||
/// To build a cluster definition first use new(), then use add() to add all members, then have each member
|
||||
/// use sign() to sign its entry. All members must sign after all calls to add() have been made since everyone
|
||||
/// must sign the same definition.
|
||||
#[derive(Clone, PartialEq, Eq)]
|
||||
pub struct RootCluster {
|
||||
#[derive(Clone, PartialEq, Eq, Serialize, Deserialize)]
|
||||
pub struct RootSet {
|
||||
/// An arbitrary name, which could be something like a domain.
|
||||
pub name: String,
|
||||
|
||||
|
@ -67,7 +71,7 @@ pub struct RootCluster {
|
|||
pub members: Vec<Root>,
|
||||
}
|
||||
|
||||
impl RootCluster {
|
||||
impl RootSet {
|
||||
pub fn new(name: String, revision: u64) -> Self {
|
||||
Self { name, revision, members: Vec::new() }
|
||||
}
|
||||
|
@ -90,8 +94,8 @@ impl RootCluster {
|
|||
buf.append_varint(0)?;
|
||||
}
|
||||
if include_signatures {
|
||||
buf.append_varint(m.cluster_signature.len() as u64)?;
|
||||
buf.append_bytes(m.cluster_signature.as_slice())?;
|
||||
buf.append_varint(m.signature.len() as u64)?;
|
||||
buf.append_bytes(m.signature.as_slice())?;
|
||||
}
|
||||
buf.append_varint(m.flags)?;
|
||||
buf.append_varint(0)?; // size of additional fields for future use
|
||||
|
@ -115,7 +119,7 @@ impl RootCluster {
|
|||
|
||||
let tmp = self.marshal_for_signing();
|
||||
for m in self.members.iter() {
|
||||
if m.cluster_signature.is_empty() || !m.identity.verify(tmp.as_bytes(), m.cluster_signature.as_slice()) {
|
||||
if m.signature.is_empty() || !m.identity.verify(tmp.as_bytes(), m.signature.as_slice()) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
@ -135,7 +139,7 @@ impl RootCluster {
|
|||
}
|
||||
tmp
|
||||
}),
|
||||
cluster_signature: Vec::new(),
|
||||
signature: Vec::new(),
|
||||
flags: 0,
|
||||
});
|
||||
self.members.sort();
|
||||
|
@ -157,7 +161,7 @@ impl RootCluster {
|
|||
let _ = self.members.push(Root {
|
||||
identity: unsigned_entry.identity,
|
||||
endpoints: unsigned_entry.endpoints,
|
||||
cluster_signature: signature.unwrap(),
|
||||
signature: signature.unwrap(),
|
||||
flags: unsigned_entry.flags,
|
||||
});
|
||||
self.members.sort();
|
||||
|
@ -208,7 +212,7 @@ impl RootCluster {
|
|||
}
|
||||
}
|
||||
|
||||
impl Marshalable for RootCluster {
|
||||
impl Marshalable for RootSet {
|
||||
const MAX_MARSHAL_SIZE: usize = PACKET_SIZE_MAX;
|
||||
|
||||
#[inline(always)]
|
||||
|
@ -232,7 +236,7 @@ impl Marshalable for RootCluster {
|
|||
let mut m = Root {
|
||||
identity: Identity::unmarshal(buf, cursor)?,
|
||||
endpoints: None,
|
||||
cluster_signature: Vec::new(),
|
||||
signature: Vec::new(),
|
||||
flags: 0,
|
||||
};
|
||||
|
||||
|
@ -246,7 +250,7 @@ impl Marshalable for RootCluster {
|
|||
}
|
||||
|
||||
let signature_size = buf.read_varint(cursor)?;
|
||||
let _ = m.cluster_signature.write_all(buf.read_bytes(signature_size as usize, cursor)?);
|
||||
let _ = m.signature.write_all(buf.read_bytes(signature_size as usize, cursor)?);
|
||||
|
||||
m.flags = buf.read_varint(cursor)?;
|
||||
|
31
zerotier-system-service/Cargo.lock
generated
31
zerotier-system-service/Cargo.lock
generated
|
@ -27,17 +27,6 @@ dependencies = [
|
|||
"critical-section",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "atty"
|
||||
version = "0.2.14"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d9b39be18770d11421cdb1b9947a45dd3f37e93092cbf377614828a319d5fee8"
|
||||
dependencies = [
|
||||
"hermit-abi",
|
||||
"libc",
|
||||
"winapi",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "autocfg"
|
||||
version = "1.1.0"
|
||||
|
@ -122,12 +111,10 @@ version = "3.1.15"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "85a35a599b11c089a7f49105658d089b8f2cf0882993c17daf6de15285c2c35d"
|
||||
dependencies = [
|
||||
"atty",
|
||||
"bitflags",
|
||||
"clap_lex",
|
||||
"indexmap",
|
||||
"strsim",
|
||||
"termcolor",
|
||||
"textwrap",
|
||||
]
|
||||
|
||||
|
@ -821,15 +808,6 @@ dependencies = [
|
|||
"unicode-xid",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "termcolor"
|
||||
version = "1.1.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "bab24d30b911b2376f3a13cc2cd443142f0c81dda04c118693e35b3835757755"
|
||||
dependencies = [
|
||||
"winapi-util",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "textwrap"
|
||||
version = "0.15.0"
|
||||
|
@ -960,15 +938,6 @@ version = "0.4.0"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6"
|
||||
|
||||
[[package]]
|
||||
name = "winapi-util"
|
||||
version = "0.1.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "70ec6ce85bb158151cae5e5c87f95a8e97d2c0c4b001223f33a334e3ce5de178"
|
||||
dependencies = [
|
||||
"winapi",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "winapi-x86_64-pc-windows-gnu"
|
||||
version = "0.4.0"
|
||||
|
|
|
@ -20,8 +20,7 @@ serde = { version = "^1", features = ["derive"], default-features = false }
|
|||
serde_json = { version = "^1", features = ["std"], default-features = false }
|
||||
parking_lot = "^0"
|
||||
lazy_static = "^1"
|
||||
clap = "^3"
|
||||
#async-trait = "^0"
|
||||
clap = { version = "^3", features = ["std", "suggestions"], default-features = false }
|
||||
|
||||
[target."cfg(windows)".dependencies]
|
||||
winapi = { version = "^0", features = ["handleapi", "ws2ipdef", "ws2tcpip"] }
|
||||
|
|
21
zerotier-system-service/src/exitcode.rs
Normal file
21
zerotier-system-service/src/exitcode.rs
Normal file
|
@ -0,0 +1,21 @@
|
|||
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
|
||||
*
|
||||
* (c)2021 ZeroTier, Inc.
|
||||
* https://www.zerotier.com/
|
||||
*/
|
||||
|
||||
// These were taken from BSD sysexits.h to provide some standard.
|
||||
|
||||
pub const OK: i32 = 0;
|
||||
pub const ERR_USAGE: i32 = 64;
|
||||
pub const ERR_DATA_FORMAT: i32 = 65;
|
||||
pub const ERR_NO_INPUT: i32 = 66;
|
||||
pub const ERR_SERVICE_UNAVAILABLE: i32 = 69;
|
||||
pub const ERR_INTERNAL: i32 = 70;
|
||||
pub const ERR_OSERR: i32 = 71;
|
||||
pub const ERR_OSFILE: i32 = 72;
|
||||
pub const ERR_IOERR: i32 = 74;
|
||||
pub const ERR_NOPERM: i32 = 77;
|
||||
pub const ERR_CONFIG: i32 = 78;
|
|
@ -8,20 +8,22 @@
|
|||
|
||||
use std::io::Write;
|
||||
|
||||
use clap::error::{ContextKind, ContextValue};
|
||||
use clap::{Arg, ArgMatches, Command};
|
||||
|
||||
use zerotier_network_hypervisor::{VERSION_MAJOR, VERSION_MINOR, VERSION_REVISION};
|
||||
|
||||
pub mod exitcode;
|
||||
pub mod getifaddrs;
|
||||
pub mod localconfig;
|
||||
pub mod utils;
|
||||
pub mod vnic;
|
||||
|
||||
fn make_help(long_help: bool) -> String {
|
||||
fn make_help() -> String {
|
||||
format!(
|
||||
r###"ZeroTier Network Hypervisor Service Version {}.{}.{}
|
||||
(c)2013-2022 ZeroTier, Inc.
|
||||
Licensed under the Mozilla Public License (MPL) 2.0 (see LICENSE.txt)
|
||||
Licensed under the Mozilla Public License (MPL) 2.0
|
||||
|
||||
Usage: zerotier [-...] <command> [command args]
|
||||
|
||||
|
@ -35,7 +37,6 @@ Global Options:
|
|||
Common Operations:
|
||||
|
||||
help Show this help
|
||||
oldhelp Show v1.x legacy commands
|
||||
version Print version (of this binary)
|
||||
|
||||
· status Show node status and configuration
|
||||
|
@ -66,37 +67,37 @@ Common Operations:
|
|||
|
||||
· join <network> Join a virtual network
|
||||
· leave <network> Leave a virtual network
|
||||
{}"###,
|
||||
VERSION_MAJOR,
|
||||
VERSION_MINOR,
|
||||
VERSION_REVISION,
|
||||
if long_help {
|
||||
r###"
|
||||
|
||||
Advanced Operations:
|
||||
|
||||
service Start local service
|
||||
(usually not invoked manually)
|
||||
|
||||
identity <command> [args]
|
||||
new [c25519 | p384] Create identity (default: c25519)
|
||||
new Create new identity
|
||||
getpublic <?identity> Extract public part of identity
|
||||
fingerprint <?identity> Get an identity's fingerprint
|
||||
validate <?identity> Locally validate an identity
|
||||
sign <?identity> <@file> Sign a file with an identity's key
|
||||
verify <?identity> <@file> <sig> Verify a signature
|
||||
|
||||
· Command (or command with argument type) requires a running node.
|
||||
rootset <command> [args]
|
||||
· trust <@root set> Add or update a root set
|
||||
· untrust <root set name> Stop using a root set
|
||||
· list List root sets in use
|
||||
sign <path> <?identity secret> Sign a root set with an identity
|
||||
|
||||
service Start local service
|
||||
(usually not invoked manually)
|
||||
|
||||
· Command requires a running node to control.
|
||||
@ Argument is the path to a file containing the object.
|
||||
? Argument can be either the object or a path to it (auto-detected).
|
||||
"###
|
||||
} else {
|
||||
""
|
||||
}
|
||||
|
||||
"###,
|
||||
VERSION_MAJOR, VERSION_MINOR, VERSION_REVISION,
|
||||
)
|
||||
}
|
||||
|
||||
pub fn print_help(long_help: bool) {
|
||||
let h = make_help(long_help);
|
||||
pub fn print_help() {
|
||||
let h = make_help();
|
||||
let _ = std::io::stdout().write_all(h.as_bytes());
|
||||
}
|
||||
|
||||
|
@ -112,10 +113,15 @@ pub fn platform_default_home_path() -> String {
|
|||
"/Library/Application Support/ZeroTier".into()
|
||||
}
|
||||
|
||||
#[cfg(any(target_os = "linux"))]
|
||||
pub fn platform_default_home_path() -> String {
|
||||
"/var/lib/zerotier".into()
|
||||
}
|
||||
|
||||
async fn async_main(cli_args: Box<ArgMatches>) -> i32 {
|
||||
let global_cli_flags = GlobalCommandLineFlags {
|
||||
json_output: cli_args.is_present("json"),
|
||||
base_path: cli_args.value_of("path").map_or_else(|| platform_default_home_path(), |p| p.to_string()),
|
||||
base_path: cli_args.value_of("path").map_or_else(platform_default_home_path, |p| p.to_string()),
|
||||
auth_token_path_override: cli_args.value_of("token_path").map(|p| p.to_string()),
|
||||
auth_token_override: cli_args.value_of("token").map(|t| t.to_string()),
|
||||
};
|
||||
|
@ -123,32 +129,32 @@ async fn async_main(cli_args: Box<ArgMatches>) -> i32 {
|
|||
#[allow(unused)]
|
||||
return match cli_args.subcommand() {
|
||||
Some(("help", _)) => {
|
||||
print_help(false);
|
||||
0
|
||||
print_help();
|
||||
exitcode::OK
|
||||
}
|
||||
Some(("oldhelp", _)) => todo!(),
|
||||
Some(("version", _)) => {
|
||||
println!("{}.{}.{}", VERSION_MAJOR, VERSION_MINOR, VERSION_REVISION);
|
||||
0
|
||||
exitcode::OK
|
||||
}
|
||||
Some(("status", _)) => todo!(),
|
||||
Some(("set", sub_cli_args)) => todo!(),
|
||||
Some(("peer", sub_cli_args)) => todo!(),
|
||||
Some(("network", sub_cli_args)) => todo!(),
|
||||
Some(("join", sub_cli_args)) => todo!(),
|
||||
Some(("leave", sub_cli_args)) => todo!(),
|
||||
Some(("set", args)) => todo!(),
|
||||
Some(("peer", args)) => todo!(),
|
||||
Some(("network", args)) => todo!(),
|
||||
Some(("join", args)) => todo!(),
|
||||
Some(("leave", args)) => todo!(),
|
||||
Some(("service", _)) => todo!(),
|
||||
Some(("identity", sub_cli_args)) => todo!(),
|
||||
Some(("identity", args)) => todo!(),
|
||||
Some(("rootset", args)) => todo!(),
|
||||
_ => {
|
||||
print_help(false);
|
||||
1
|
||||
print_help();
|
||||
exitcode::ERR_USAGE
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
fn main() {
|
||||
let cli_args = Box::new({
|
||||
let help = make_help(false);
|
||||
let help = make_help();
|
||||
Command::new("zerotier")
|
||||
.arg(Arg::new("json").short('j'))
|
||||
.arg(Arg::new("path").short('p').takes_value(true))
|
||||
|
@ -180,17 +186,56 @@ fn main() {
|
|||
.subcommand(Command::new("service"))
|
||||
.subcommand(
|
||||
Command::new("identity")
|
||||
.subcommand(Command::new("new").arg(Arg::new("type").possible_value("p384").possible_value("c25519").default_value("c25519").index(1)))
|
||||
.subcommand(Command::new("new"))
|
||||
.subcommand(Command::new("getpublic").arg(Arg::new("identity").index(1).required(true)))
|
||||
.subcommand(Command::new("fingerprint").arg(Arg::new("identity").index(1).required(true)))
|
||||
.subcommand(Command::new("validate").arg(Arg::new("identity").index(1).required(true)))
|
||||
.subcommand(Command::new("sign").arg(Arg::new("identity").index(1).required(true)).arg(Arg::new("path").index(2).required(true)))
|
||||
.subcommand(Command::new("verify").arg(Arg::new("identity").index(1).required(true)).arg(Arg::new("path").index(2).required(true)).arg(Arg::new("signature").index(3).required(true))),
|
||||
)
|
||||
.subcommand(
|
||||
Command::new("rootset")
|
||||
.subcommand(Command::new("trust").arg(Arg::new("path").index(1).required(true)))
|
||||
.subcommand(Command::new("untrust").arg(Arg::new("name").index(1).required(true)))
|
||||
.subcommand(Command::new("list"))
|
||||
.subcommand(Command::new("sign").arg(Arg::new("path").index(1).required(true)).arg(Arg::new("secret").index(2).required(true))),
|
||||
)
|
||||
.override_help(help.as_str())
|
||||
.override_usage(help.as_str())
|
||||
.override_usage("")
|
||||
.disable_version_flag(true)
|
||||
.disable_help_subcommand(false)
|
||||
.disable_help_flag(true)
|
||||
.get_matches_from(std::env::args())
|
||||
.try_get_matches_from(std::env::args())
|
||||
.unwrap_or_else(|e| {
|
||||
if e.kind() == clap::ErrorKind::DisplayHelp {
|
||||
print_help();
|
||||
std::process::exit(exitcode::OK);
|
||||
} else {
|
||||
let mut invalid = String::default();
|
||||
let mut suggested = String::default();
|
||||
for c in e.context() {
|
||||
match c {
|
||||
(ContextKind::SuggestedSubcommand | ContextKind::SuggestedArg, ContextValue::String(name)) => {
|
||||
suggested = name.clone();
|
||||
}
|
||||
(ContextKind::InvalidArg | ContextKind::InvalidSubcommand, ContextValue::String(name)) => {
|
||||
invalid = name.clone();
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
if invalid.is_empty() {
|
||||
println!("Invalid command line. Use 'help' for help.");
|
||||
} else {
|
||||
if suggested.is_empty() {
|
||||
println!("Unrecognized option '{}'. Use 'help' for help.", invalid);
|
||||
} else {
|
||||
println!("Unrecognized option '{}', did you mean {}? Use 'help' for help.", invalid, suggested);
|
||||
}
|
||||
}
|
||||
std::process::exit(exitcode::ERR_USAGE);
|
||||
}
|
||||
})
|
||||
});
|
||||
|
||||
std::process::exit(tokio::runtime::Builder::new_multi_thread().enable_all().build().unwrap().block_on(async_main(cli_args)));
|
||||
|
|
Loading…
Add table
Reference in a new issue