mirror of
https://github.com/zerotier/ZeroTierOne.git
synced 2025-06-08 13:33: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"
|
dashmap = "^5"
|
||||||
parking_lot = "^0"
|
parking_lot = "^0"
|
||||||
lazy_static = "^1"
|
lazy_static = "^1"
|
||||||
serde = { version = "^1", features = [], default-features = false }
|
serde = { version = "^1", features = ["derive"], default-features = false }
|
||||||
|
|
||||||
[target."cfg(not(windows))".dependencies]
|
[target."cfg(not(windows))".dependencies]
|
||||||
libc = "^0"
|
libc = "^0"
|
||||||
|
|
|
@ -112,6 +112,12 @@ impl<const L: usize> Buffer<L> {
|
||||||
&mut self.1[0..self.0]
|
&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)]
|
#[inline(always)]
|
||||||
pub fn as_ptr(&self) -> *const u8 {
|
pub fn as_ptr(&self) -> *const u8 {
|
||||||
self.1.as_ptr()
|
self.1.as_ptr()
|
||||||
|
|
|
@ -127,7 +127,7 @@ impl<'de> serde::de::Visitor<'de> for AddressVisitor {
|
||||||
if v.len() == ADDRESS_SIZE {
|
if v.len() == ADDRESS_SIZE {
|
||||||
Address::from_bytes(v).map_or_else(|| Err(E::custom("object too large")), |a| Ok(a))
|
Address::from_bytes(v).map_or_else(|| Err(E::custom("object too large")), |a| Ok(a))
|
||||||
} else {
|
} 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 {
|
impl Hash for Endpoint {
|
||||||
fn hash<H: Hasher>(&self, state: &mut H) {
|
fn hash<H: Hasher>(&self, state: &mut H) {
|
||||||
match self {
|
match self {
|
||||||
|
@ -244,7 +245,7 @@ impl Hash for Endpoint {
|
||||||
}
|
}
|
||||||
Endpoint::Ip(ip) => {
|
Endpoint::Ip(ip) => {
|
||||||
state.write_u8(TYPE_IP);
|
state.write_u8(TYPE_IP);
|
||||||
ip.hash(state);
|
ip.ip_bytes().hash(state);
|
||||||
}
|
}
|
||||||
Endpoint::IpUdp(ip) => {
|
Endpoint::IpUdp(ip) => {
|
||||||
state.write_u8(TYPE_IPUDP);
|
state.write_u8(TYPE_IPUDP);
|
||||||
|
|
|
@ -15,10 +15,9 @@ mod dictionary;
|
||||||
mod mac;
|
mod mac;
|
||||||
mod path;
|
mod path;
|
||||||
mod peer;
|
mod peer;
|
||||||
mod rootcluster;
|
mod rootset;
|
||||||
|
|
||||||
pub(crate) mod fragmentedpacket;
|
pub(crate) mod fragmentedpacket;
|
||||||
pub(crate) mod hybridkey;
|
|
||||||
pub(crate) mod node;
|
pub(crate) mod node;
|
||||||
#[allow(unused)]
|
#[allow(unused)]
|
||||||
pub(crate) mod protocol;
|
pub(crate) mod protocol;
|
||||||
|
@ -34,4 +33,4 @@ pub use mac::MAC;
|
||||||
pub use node::{Node, SystemInterface};
|
pub use node::{Node, SystemInterface};
|
||||||
pub use path::Path;
|
pub use path::Path;
|
||||||
pub use peer::Peer;
|
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::peer::Peer;
|
||||||
use crate::vl1::protocol::*;
|
use crate::vl1::protocol::*;
|
||||||
use crate::vl1::whoisqueue::{QueuedPacket, WhoisQueue};
|
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};
|
use crate::{PacketBuffer, PacketBufferFactory, PacketBufferPool};
|
||||||
|
|
||||||
/// Trait implemented by external code to handle events and provide an interface to the system or application.
|
/// 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>,
|
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.
|
/// A VL1 global P2P network node.
|
||||||
pub struct Node {
|
pub struct Node {
|
||||||
/// A random ID generated to identify this particular running instance.
|
/// A random ID generated to identify this particular running instance.
|
||||||
|
@ -144,13 +150,13 @@ pub struct Node {
|
||||||
intervals: Mutex<BackgroundTaskIntervals>,
|
intervals: Mutex<BackgroundTaskIntervals>,
|
||||||
|
|
||||||
/// Canonicalized network paths, held as Weak<> to be automatically cleaned when no longer in use.
|
/// 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 with which we are currently communicating.
|
||||||
peers: DashMap<Address, Arc<Peer>>,
|
peers: DashMap<Address, Arc<Peer>>,
|
||||||
|
|
||||||
/// This node's trusted roots, sorted in ascending order of quality/preference, and cluster definitions.
|
/// 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.
|
/// Current best root.
|
||||||
best_root: RwLock<Option<Arc<Peer>>>,
|
best_root: RwLock<Option<Arc<Peer>>>,
|
||||||
|
@ -197,7 +203,11 @@ impl Node {
|
||||||
intervals: Mutex::new(BackgroundTaskIntervals::default()),
|
intervals: Mutex::new(BackgroundTaskIntervals::default()),
|
||||||
paths: DashMap::new(),
|
paths: DashMap::new(),
|
||||||
peers: 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),
|
best_root: RwLock::new(None),
|
||||||
whois: WhoisQueue::new(),
|
whois: WhoisQueue::new(),
|
||||||
buffer_pool: PacketBufferPool::new(64, PacketBufferFactory::new()),
|
buffer_pool: PacketBufferPool::new(64, PacketBufferFactory::new()),
|
||||||
|
@ -235,61 +245,49 @@ impl Node {
|
||||||
let tt = si.time_ticks();
|
let tt = si.time_ticks();
|
||||||
|
|
||||||
if intervals.root_sync.gate(tt) {
|
if intervals.root_sync.gate(tt) {
|
||||||
let mut roots_lock = self.roots.lock();
|
match &mut (*self.roots.lock()) {
|
||||||
let (roots, root_clusters) = &mut *roots_lock;
|
RootInfo { roots, sets, sets_modified } => {
|
||||||
|
// Sychronize root info with root sets info if the latter has changed.
|
||||||
// Look at root cluster definitions and make sure all have corresponding root peers.
|
if *sets_modified {
|
||||||
let mut root_endpoints = HashMap::with_capacity(roots.len() * 2);
|
*sets_modified = false;
|
||||||
for rc in root_clusters.iter() {
|
roots.clear();
|
||||||
|
let mut colliding_root_addresses = Vec::new(); // see security note below
|
||||||
|
for (_, rc) in sets.iter() {
|
||||||
for m in rc.members.iter() {
|
for m in rc.members.iter() {
|
||||||
if m.endpoints.is_some() {
|
if m.endpoints.is_some() && !colliding_root_addresses.contains(&m.identity.address) {
|
||||||
let endpoints = m.endpoints.as_ref().unwrap();
|
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* SECURITY NOTE: we take extra care to handle the case where we have a peer whose identity
|
* SECURITY NOTE: it should be impossible to get an address/identity collision here unless
|
||||||
* differs from that of a root but whose address is the same. It should be impossible to
|
* the user adds a maliciously crafted root set with an identity that collides another. Under
|
||||||
* make this happen, but we check anyway. It would require a colliding identity to be
|
* normal circumstances the root backplane combined with the address PoW should rule this
|
||||||
* approved by one of your existing roots and somehow retrieved via WHOIS, but this would
|
* out. However since we trust roots as identity lookup authorities it's important to take
|
||||||
* be hard because this background task loop populates the peer list with specified root
|
* extra care to check for this case. If it's detected, all roots with the offending
|
||||||
* identities before this happens. It could also happen if you have two root cluster
|
* address are ignored/disabled.
|
||||||
* 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.
|
* 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).
|
||||||
*/
|
*/
|
||||||
|
|
||||||
// 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
|
let _ = self
|
||||||
.peers
|
.peers
|
||||||
.entry(m.identity.address)
|
.entry(m.identity.address)
|
||||||
.or_try_insert_with(|| {
|
.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))))
|
||||||
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| {
|
.and_then(|root_peer_entry| {
|
||||||
let rp = root_peer_entry.value();
|
let rp = root_peer_entry.value();
|
||||||
if rp.identity.eq(&m.identity) {
|
if rp.identity.eq(&m.identity) {
|
||||||
Ok(root_peer_entry)
|
Ok(root_peer_entry)
|
||||||
} else {
|
} else {
|
||||||
roots.retain(|r| r.identity.address != m.identity.address);
|
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());
|
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)
|
Err(crate::error::UnexpectedError)
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
.map(|_| {
|
.map(|r| roots.insert(r.value().clone(), m.endpoints.as_ref().unwrap().iter().map(|e| e.clone()).collect()));
|
||||||
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
|
// 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
|
// they have, which is a behavior that differs from normal peers. This allows roots to
|
||||||
|
@ -297,27 +295,33 @@ impl Node {
|
||||||
// external addresses from them.
|
// external addresses from them.
|
||||||
assert!(ROOT_SYNC_INTERVAL_MS <= (ROOT_HELLO_INTERVAL / 2));
|
assert!(ROOT_SYNC_INTERVAL_MS <= (ROOT_HELLO_INTERVAL / 2));
|
||||||
if intervals.root_hello.gate(tt) {
|
if intervals.root_hello.gate(tt) {
|
||||||
for r in roots.iter() {
|
for (root, endpoints) in roots.iter() {
|
||||||
for ep in root_endpoints.get(&r.identity.address).unwrap().iter() {
|
for ep in endpoints.iter() {
|
||||||
r.send_hello(si, self, Some(ep));
|
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
|
// 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.
|
// this is a proxy for latency and also causes roots that fail to reply to drop out quickly.
|
||||||
if !roots.is_empty() {
|
let mut latest_hello_reply = 0;
|
||||||
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 mut best: Option<&Arc<Peer>> = None;
|
||||||
let _ = self.best_root.write().insert(roots.last().unwrap().clone());
|
for (r, _) in roots.iter() {
|
||||||
} else {
|
let t = r.last_hello_reply_time_ticks.load(Ordering::Relaxed);
|
||||||
let _ = self.best_root.write().take();
|
if t >= latest_hello_reply {
|
||||||
|
latest_hello_reply = t;
|
||||||
|
let _ = best.insert(r);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
*(self.best_root.write()) = best.cloned();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if intervals.peers.gate(tt) {
|
if intervals.peers.gate(tt) {
|
||||||
// Service all peers, removing any whose service() method returns false AND that are not
|
// 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.
|
// 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) {
|
if intervals.paths.gate(tt) {
|
||||||
|
@ -339,7 +343,7 @@ impl Node {
|
||||||
if dest == self.identity.address {
|
if dest == self.identity.address {
|
||||||
// Handle packets (seemingly) addressed to this node.
|
// 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);
|
path.log_receive_anything(time_ticks);
|
||||||
|
|
||||||
if fragment_header.is_fragment() {
|
if fragment_header.is_fragment() {
|
||||||
|
@ -407,16 +411,35 @@ impl Node {
|
||||||
|
|
||||||
/// Return true if a peer is a root.
|
/// Return true if a peer is a root.
|
||||||
pub fn is_peer_root(&self, peer: &Peer) -> bool {
|
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.
|
/// 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
|
/// This is a canonicalizing function that returns a unique path object for every tuple
|
||||||
/// of endpoint, local socket, and local interface.
|
/// 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> {
|
pub fn canonical_path(&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(Path::local_lookup_key(ep, local_socket, local_interface)).or_default();
|
||||||
let mut path_entry = self.paths.entry(key).or_insert_with(|| Weak::new());
|
|
||||||
if let Some(path) = path_entry.value().upgrade() {
|
if let Some(path) = path_entry.value().upgrade() {
|
||||||
path
|
path
|
||||||
} else {
|
} 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 {
|
impl Path {
|
||||||
/// Get a 128-bit key to look up this endpoint in the local node path map.
|
/// 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);
|
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_socket.map_or(0, |s| s.get() as u64));
|
||||||
h.write_u64(local_interface.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);
|
h.write(fingerprint);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
assert_eq!(std::mem::size_of::<(u64, u64)>(), std::mem::size_of::<u128>());
|
h.finish128()
|
||||||
unsafe { std::mem::transmute(h.finish128()) }
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn new(endpoint: Endpoint, local_socket: Option<NonZeroI64>, local_interface: Option<NonZeroI64>) -> Self {
|
pub fn new(endpoint: Endpoint, local_socket: Option<NonZeroI64>, local_interface: Option<NonZeroI64>) -> Self {
|
||||||
|
|
|
@ -7,6 +7,7 @@
|
||||||
*/
|
*/
|
||||||
|
|
||||||
use std::convert::TryInto;
|
use std::convert::TryInto;
|
||||||
|
use std::hash::{Hash, Hasher};
|
||||||
use std::mem::MaybeUninit;
|
use std::mem::MaybeUninit;
|
||||||
use std::num::NonZeroI64;
|
use std::num::NonZeroI64;
|
||||||
use std::sync::atomic::{AtomicI64, AtomicU64, AtomicU8, Ordering};
|
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};
|
use crate::{PacketBuffer, VERSION_MAJOR, VERSION_MINOR, VERSION_PROTO, VERSION_REVISION};
|
||||||
|
|
||||||
/// A remote peer known to this node.
|
/// 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 {
|
pub struct Peer {
|
||||||
// This peer's identity.
|
// This peer's identity.
|
||||||
pub(crate) identity: 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 {
|
impl BackgroundServicable for Peer {
|
||||||
const SERVICE_INTERVAL_MS: i64 = EPHEMERAL_SECRET_REKEY_AFTER_TIME / 10;
|
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.
|
/// Proof of work difficulty (threshold) for identity generation.
|
||||||
pub const IDENTITY_POW_THRESHOLD: u8 = 17;
|
pub const IDENTITY_POW_THRESHOLD: u8 = 17;
|
||||||
|
|
||||||
/// Compress a packet and return true if compressed.
|
/// Attempt to compress a packet's payload with LZ4
|
||||||
/// 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
|
/// If this returns true the destination buffer will contain a compressed packet. If false is
|
||||||
/// value of false.
|
/// returned the contents of 'dest' are entirely undefined. This indicates that the data was not
|
||||||
pub fn compress_packet(src: &[u8], dest: &mut Buffer<{ PACKET_SIZE_MAX }>) -> bool {
|
/// compressable or some other error occurred.
|
||||||
if src.len() > PACKET_VERB_INDEX {
|
pub fn compress_packet(src: &[u8], dest: &mut Buffer<PACKET_SIZE_MAX>) -> bool {
|
||||||
debug_assert!(dest.is_empty());
|
if src.len() > (PACKET_VERB_INDEX + 16) {
|
||||||
let cs = {
|
let compressed_data_size = {
|
||||||
let d = dest.as_bytes_mut();
|
let d = unsafe { dest.entire_buffer_mut() };
|
||||||
d[0..PACKET_VERB_INDEX].copy_from_slice(&src[0..PACKET_VERB_INDEX]);
|
d[0..PACKET_VERB_INDEX].copy_from_slice(&src[0..PACKET_VERB_INDEX]);
|
||||||
d[PACKET_VERB_INDEX] = src[PACKET_VERB_INDEX] | VERB_FLAG_COMPRESSED;
|
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..])
|
lz4_flex::block::compress_into(&src[PACKET_VERB_INDEX + 1..], &mut d[PACKET_VERB_INDEX + 1..])
|
||||||
};
|
};
|
||||||
if cs.is_ok() {
|
if compressed_data_size.is_ok() {
|
||||||
let cs = cs.unwrap();
|
let compressed_data_size = compressed_data_size.unwrap();
|
||||||
if cs > 0 && cs < (src.len() - PACKET_VERB_INDEX) {
|
if compressed_data_size > 0 && compressed_data_size < (src.len() - PACKET_VERB_INDEX) {
|
||||||
unsafe { dest.set_size_unchecked(PACKET_VERB_INDEX + 1 + cs) };
|
unsafe { dest.set_size_unchecked(PACKET_VERB_INDEX + 1 + compressed_data_size) };
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -15,8 +15,10 @@ use crate::vl1::identity::*;
|
||||||
use crate::vl1::protocol::PACKET_SIZE_MAX;
|
use crate::vl1::protocol::PACKET_SIZE_MAX;
|
||||||
use crate::vl1::Endpoint;
|
use crate::vl1::Endpoint;
|
||||||
|
|
||||||
|
use serde::{Deserialize, Serialize};
|
||||||
|
|
||||||
/// Description of a member of a root cluster.
|
/// Description of a member of a root cluster.
|
||||||
#[derive(Clone, PartialEq, Eq)]
|
#[derive(Clone, PartialEq, Eq, Serialize, Deserialize)]
|
||||||
pub struct Root {
|
pub struct Root {
|
||||||
/// Full identity of this node.
|
/// Full identity of this node.
|
||||||
pub identity: Identity,
|
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.
|
/// Endpoints for this root or None if this is a former member attesting to an update that removes it.
|
||||||
pub endpoints: Option<BTreeSet<Endpoint>>,
|
pub endpoints: Option<BTreeSet<Endpoint>>,
|
||||||
|
|
||||||
/// Signature of entire cluster by this identity.
|
/// Signature of entire root set by this identity.
|
||||||
pub cluster_signature: Vec<u8>,
|
#[serde(default)]
|
||||||
|
pub signature: Vec<u8>,
|
||||||
|
|
||||||
/// Flags field (currently unused).
|
/// Flags field (currently unused).
|
||||||
|
#[serde(default)]
|
||||||
pub flags: u64,
|
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
|
/// 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
|
/// 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.
|
/// must sign the same definition.
|
||||||
#[derive(Clone, PartialEq, Eq)]
|
#[derive(Clone, PartialEq, Eq, Serialize, Deserialize)]
|
||||||
pub struct RootCluster {
|
pub struct RootSet {
|
||||||
/// An arbitrary name, which could be something like a domain.
|
/// An arbitrary name, which could be something like a domain.
|
||||||
pub name: String,
|
pub name: String,
|
||||||
|
|
||||||
|
@ -67,7 +71,7 @@ pub struct RootCluster {
|
||||||
pub members: Vec<Root>,
|
pub members: Vec<Root>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl RootCluster {
|
impl RootSet {
|
||||||
pub fn new(name: String, revision: u64) -> Self {
|
pub fn new(name: String, revision: u64) -> Self {
|
||||||
Self { name, revision, members: Vec::new() }
|
Self { name, revision, members: Vec::new() }
|
||||||
}
|
}
|
||||||
|
@ -90,8 +94,8 @@ impl RootCluster {
|
||||||
buf.append_varint(0)?;
|
buf.append_varint(0)?;
|
||||||
}
|
}
|
||||||
if include_signatures {
|
if include_signatures {
|
||||||
buf.append_varint(m.cluster_signature.len() as u64)?;
|
buf.append_varint(m.signature.len() as u64)?;
|
||||||
buf.append_bytes(m.cluster_signature.as_slice())?;
|
buf.append_bytes(m.signature.as_slice())?;
|
||||||
}
|
}
|
||||||
buf.append_varint(m.flags)?;
|
buf.append_varint(m.flags)?;
|
||||||
buf.append_varint(0)?; // size of additional fields for future use
|
buf.append_varint(0)?; // size of additional fields for future use
|
||||||
|
@ -115,7 +119,7 @@ impl RootCluster {
|
||||||
|
|
||||||
let tmp = self.marshal_for_signing();
|
let tmp = self.marshal_for_signing();
|
||||||
for m in self.members.iter() {
|
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;
|
return false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -135,7 +139,7 @@ impl RootCluster {
|
||||||
}
|
}
|
||||||
tmp
|
tmp
|
||||||
}),
|
}),
|
||||||
cluster_signature: Vec::new(),
|
signature: Vec::new(),
|
||||||
flags: 0,
|
flags: 0,
|
||||||
});
|
});
|
||||||
self.members.sort();
|
self.members.sort();
|
||||||
|
@ -157,7 +161,7 @@ impl RootCluster {
|
||||||
let _ = self.members.push(Root {
|
let _ = self.members.push(Root {
|
||||||
identity: unsigned_entry.identity,
|
identity: unsigned_entry.identity,
|
||||||
endpoints: unsigned_entry.endpoints,
|
endpoints: unsigned_entry.endpoints,
|
||||||
cluster_signature: signature.unwrap(),
|
signature: signature.unwrap(),
|
||||||
flags: unsigned_entry.flags,
|
flags: unsigned_entry.flags,
|
||||||
});
|
});
|
||||||
self.members.sort();
|
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;
|
const MAX_MARSHAL_SIZE: usize = PACKET_SIZE_MAX;
|
||||||
|
|
||||||
#[inline(always)]
|
#[inline(always)]
|
||||||
|
@ -232,7 +236,7 @@ impl Marshalable for RootCluster {
|
||||||
let mut m = Root {
|
let mut m = Root {
|
||||||
identity: Identity::unmarshal(buf, cursor)?,
|
identity: Identity::unmarshal(buf, cursor)?,
|
||||||
endpoints: None,
|
endpoints: None,
|
||||||
cluster_signature: Vec::new(),
|
signature: Vec::new(),
|
||||||
flags: 0,
|
flags: 0,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -246,7 +250,7 @@ impl Marshalable for RootCluster {
|
||||||
}
|
}
|
||||||
|
|
||||||
let signature_size = buf.read_varint(cursor)?;
|
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)?;
|
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",
|
"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]]
|
[[package]]
|
||||||
name = "autocfg"
|
name = "autocfg"
|
||||||
version = "1.1.0"
|
version = "1.1.0"
|
||||||
|
@ -122,12 +111,10 @@ version = "3.1.15"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "85a35a599b11c089a7f49105658d089b8f2cf0882993c17daf6de15285c2c35d"
|
checksum = "85a35a599b11c089a7f49105658d089b8f2cf0882993c17daf6de15285c2c35d"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"atty",
|
|
||||||
"bitflags",
|
"bitflags",
|
||||||
"clap_lex",
|
"clap_lex",
|
||||||
"indexmap",
|
"indexmap",
|
||||||
"strsim",
|
"strsim",
|
||||||
"termcolor",
|
|
||||||
"textwrap",
|
"textwrap",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
@ -821,15 +808,6 @@ dependencies = [
|
||||||
"unicode-xid",
|
"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]]
|
[[package]]
|
||||||
name = "textwrap"
|
name = "textwrap"
|
||||||
version = "0.15.0"
|
version = "0.15.0"
|
||||||
|
@ -960,15 +938,6 @@ version = "0.4.0"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6"
|
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]]
|
[[package]]
|
||||||
name = "winapi-x86_64-pc-windows-gnu"
|
name = "winapi-x86_64-pc-windows-gnu"
|
||||||
version = "0.4.0"
|
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 }
|
serde_json = { version = "^1", features = ["std"], default-features = false }
|
||||||
parking_lot = "^0"
|
parking_lot = "^0"
|
||||||
lazy_static = "^1"
|
lazy_static = "^1"
|
||||||
clap = "^3"
|
clap = { version = "^3", features = ["std", "suggestions"], default-features = false }
|
||||||
#async-trait = "^0"
|
|
||||||
|
|
||||||
[target."cfg(windows)".dependencies]
|
[target."cfg(windows)".dependencies]
|
||||||
winapi = { version = "^0", features = ["handleapi", "ws2ipdef", "ws2tcpip"] }
|
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 std::io::Write;
|
||||||
|
|
||||||
|
use clap::error::{ContextKind, ContextValue};
|
||||||
use clap::{Arg, ArgMatches, Command};
|
use clap::{Arg, ArgMatches, Command};
|
||||||
|
|
||||||
use zerotier_network_hypervisor::{VERSION_MAJOR, VERSION_MINOR, VERSION_REVISION};
|
use zerotier_network_hypervisor::{VERSION_MAJOR, VERSION_MINOR, VERSION_REVISION};
|
||||||
|
|
||||||
|
pub mod exitcode;
|
||||||
pub mod getifaddrs;
|
pub mod getifaddrs;
|
||||||
pub mod localconfig;
|
pub mod localconfig;
|
||||||
pub mod utils;
|
pub mod utils;
|
||||||
pub mod vnic;
|
pub mod vnic;
|
||||||
|
|
||||||
fn make_help(long_help: bool) -> String {
|
fn make_help() -> String {
|
||||||
format!(
|
format!(
|
||||||
r###"ZeroTier Network Hypervisor Service Version {}.{}.{}
|
r###"ZeroTier Network Hypervisor Service Version {}.{}.{}
|
||||||
(c)2013-2022 ZeroTier, Inc.
|
(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]
|
Usage: zerotier [-...] <command> [command args]
|
||||||
|
|
||||||
|
@ -35,7 +37,6 @@ Global Options:
|
||||||
Common Operations:
|
Common Operations:
|
||||||
|
|
||||||
help Show this help
|
help Show this help
|
||||||
oldhelp Show v1.x legacy commands
|
|
||||||
version Print version (of this binary)
|
version Print version (of this binary)
|
||||||
|
|
||||||
· status Show node status and configuration
|
· status Show node status and configuration
|
||||||
|
@ -66,37 +67,37 @@ Common Operations:
|
||||||
|
|
||||||
· join <network> Join a virtual network
|
· join <network> Join a virtual network
|
||||||
· leave <network> Leave a virtual network
|
· leave <network> Leave a virtual network
|
||||||
{}"###,
|
|
||||||
VERSION_MAJOR,
|
|
||||||
VERSION_MINOR,
|
|
||||||
VERSION_REVISION,
|
|
||||||
if long_help {
|
|
||||||
r###"
|
|
||||||
Advanced Operations:
|
Advanced Operations:
|
||||||
|
|
||||||
service Start local service
|
|
||||||
(usually not invoked manually)
|
|
||||||
|
|
||||||
identity <command> [args]
|
identity <command> [args]
|
||||||
new [c25519 | p384] Create identity (default: c25519)
|
new Create new identity
|
||||||
getpublic <?identity> Extract public part of identity
|
getpublic <?identity> Extract public part of identity
|
||||||
fingerprint <?identity> Get an identity's fingerprint
|
fingerprint <?identity> Get an identity's fingerprint
|
||||||
validate <?identity> Locally validate an identity
|
validate <?identity> Locally validate an identity
|
||||||
sign <?identity> <@file> Sign a file with an identity's key
|
sign <?identity> <@file> Sign a file with an identity's key
|
||||||
verify <?identity> <@file> <sig> Verify a signature
|
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 is the path to a file containing the object.
|
||||||
? Argument can be either the object or a path to it (auto-detected).
|
? 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) {
|
pub fn print_help() {
|
||||||
let h = make_help(long_help);
|
let h = make_help();
|
||||||
let _ = std::io::stdout().write_all(h.as_bytes());
|
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()
|
"/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 {
|
async fn async_main(cli_args: Box<ArgMatches>) -> i32 {
|
||||||
let global_cli_flags = GlobalCommandLineFlags {
|
let global_cli_flags = GlobalCommandLineFlags {
|
||||||
json_output: cli_args.is_present("json"),
|
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_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()),
|
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)]
|
#[allow(unused)]
|
||||||
return match cli_args.subcommand() {
|
return match cli_args.subcommand() {
|
||||||
Some(("help", _)) => {
|
Some(("help", _)) => {
|
||||||
print_help(false);
|
print_help();
|
||||||
0
|
exitcode::OK
|
||||||
}
|
}
|
||||||
Some(("oldhelp", _)) => todo!(),
|
|
||||||
Some(("version", _)) => {
|
Some(("version", _)) => {
|
||||||
println!("{}.{}.{}", VERSION_MAJOR, VERSION_MINOR, VERSION_REVISION);
|
println!("{}.{}.{}", VERSION_MAJOR, VERSION_MINOR, VERSION_REVISION);
|
||||||
0
|
exitcode::OK
|
||||||
}
|
}
|
||||||
Some(("status", _)) => todo!(),
|
Some(("status", _)) => todo!(),
|
||||||
Some(("set", sub_cli_args)) => todo!(),
|
Some(("set", args)) => todo!(),
|
||||||
Some(("peer", sub_cli_args)) => todo!(),
|
Some(("peer", args)) => todo!(),
|
||||||
Some(("network", sub_cli_args)) => todo!(),
|
Some(("network", args)) => todo!(),
|
||||||
Some(("join", sub_cli_args)) => todo!(),
|
Some(("join", args)) => todo!(),
|
||||||
Some(("leave", sub_cli_args)) => todo!(),
|
Some(("leave", args)) => todo!(),
|
||||||
Some(("service", _)) => todo!(),
|
Some(("service", _)) => todo!(),
|
||||||
Some(("identity", sub_cli_args)) => todo!(),
|
Some(("identity", args)) => todo!(),
|
||||||
|
Some(("rootset", args)) => todo!(),
|
||||||
_ => {
|
_ => {
|
||||||
print_help(false);
|
print_help();
|
||||||
1
|
exitcode::ERR_USAGE
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
fn main() {
|
fn main() {
|
||||||
let cli_args = Box::new({
|
let cli_args = Box::new({
|
||||||
let help = make_help(false);
|
let help = make_help();
|
||||||
Command::new("zerotier")
|
Command::new("zerotier")
|
||||||
.arg(Arg::new("json").short('j'))
|
.arg(Arg::new("json").short('j'))
|
||||||
.arg(Arg::new("path").short('p').takes_value(true))
|
.arg(Arg::new("path").short('p').takes_value(true))
|
||||||
|
@ -180,17 +186,56 @@ fn main() {
|
||||||
.subcommand(Command::new("service"))
|
.subcommand(Command::new("service"))
|
||||||
.subcommand(
|
.subcommand(
|
||||||
Command::new("identity")
|
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("getpublic").arg(Arg::new("identity").index(1).required(true)))
|
||||||
.subcommand(Command::new("fingerprint").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("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("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("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_help(help.as_str())
|
||||||
.override_usage(help.as_str())
|
.override_usage("")
|
||||||
|
.disable_version_flag(true)
|
||||||
|
.disable_help_subcommand(false)
|
||||||
.disable_help_flag(true)
|
.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)));
|
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