mirror of
https://github.com/zerotier/ZeroTierOne.git
synced 2025-06-08 13:33:44 +02:00
Remove some outdated stuff in service, upgrade clap, get it building again.
This commit is contained in:
parent
40156fd1f3
commit
a78b23cf45
12 changed files with 370 additions and 1506 deletions
266
attic/iblt.rs
266
attic/iblt.rs
|
@ -1,266 +0,0 @@
|
||||||
/* 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/
|
|
||||||
*/
|
|
||||||
|
|
||||||
use std::io::{Read, Write};
|
|
||||||
use std::mem::{size_of, transmute, zeroed};
|
|
||||||
use std::ptr::write_bytes;
|
|
||||||
|
|
||||||
use zerotier_core_crypto::varint;
|
|
||||||
|
|
||||||
// max value: 6, 5 was determined to be good via empirical testing
|
|
||||||
const KEY_MAPPING_ITERATIONS: usize = 5;
|
|
||||||
|
|
||||||
#[inline(always)]
|
|
||||||
fn xorshift64(mut x: u64) -> u64 {
|
|
||||||
x ^= x.wrapping_shl(13);
|
|
||||||
x ^= x.wrapping_shr(7);
|
|
||||||
x ^= x.wrapping_shl(17);
|
|
||||||
x
|
|
||||||
}
|
|
||||||
|
|
||||||
#[inline(always)]
|
|
||||||
fn calc_check_hash<const HS: usize>(key: &[u8]) -> u64 {
|
|
||||||
xorshift64(u64::from_le_bytes((&key[(HS - 8)..HS]).try_into().unwrap()))
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Clone, PartialEq, Eq)]
|
|
||||||
struct IBLTEntry<const HS: usize> {
|
|
||||||
key_sum: [u8; HS],
|
|
||||||
check_hash_sum: u64,
|
|
||||||
count: i64
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<const HS: usize> IBLTEntry<HS> {
|
|
||||||
#[inline(always)]
|
|
||||||
fn is_singular(&self) -> bool {
|
|
||||||
if self.count == 1 || self.count == -1 {
|
|
||||||
calc_check_hash::<HS>(&self.key_sum) == self.check_hash_sum
|
|
||||||
} else {
|
|
||||||
false
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// An Invertible Bloom Lookup Table for set reconciliation.
|
|
||||||
///
|
|
||||||
/// This implementation assumes that hashes are random. Hashes must be
|
|
||||||
/// at least 8 bytes in size.
|
|
||||||
#[derive(Clone, PartialEq, Eq)]
|
|
||||||
pub struct IBLT<const HS: usize, const B: usize> {
|
|
||||||
map: [IBLTEntry<HS>; B]
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<const HS: usize, const B: usize> IBLT<HS, B> {
|
|
||||||
pub const BUCKETS: usize = B;
|
|
||||||
|
|
||||||
pub const HASH_SIZE: usize = HS;
|
|
||||||
|
|
||||||
pub fn new() -> Self { unsafe { zeroed() } }
|
|
||||||
|
|
||||||
#[inline(always)]
|
|
||||||
pub fn clear(&mut self) {
|
|
||||||
unsafe { write_bytes((self as *mut Self).cast::<u8>(), 0, size_of::<Self>()) };
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn read<R: Read>(&mut self, r: &mut R) -> std::io::Result<()> {
|
|
||||||
let mut tmp = [0_u8; 8];
|
|
||||||
let mut prev_c = 0_i64;
|
|
||||||
for b in self.map.iter_mut() {
|
|
||||||
r.read_exact(&mut b.key_sum)?;
|
|
||||||
r.read_exact(&mut tmp)?;
|
|
||||||
b.check_hash_sum = u64::from_le_bytes(tmp);
|
|
||||||
let mut c = varint::read(r)?.0 as i64;
|
|
||||||
if (c & 1) == 0 {
|
|
||||||
c = c.wrapping_shr(1);
|
|
||||||
} else {
|
|
||||||
c = -c.wrapping_shr(1);
|
|
||||||
}
|
|
||||||
b.count = c + prev_c;
|
|
||||||
prev_c = b.count;
|
|
||||||
}
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn write<W: Write>(&self, w: &mut W) -> std::io::Result<()> {
|
|
||||||
let mut prev_c = 0_i64;
|
|
||||||
for b in self.map.iter() {
|
|
||||||
w.write_all(&b.key_sum)?;
|
|
||||||
w.write_all(&b.check_hash_sum.to_le_bytes())?;
|
|
||||||
let mut c = (b.count - prev_c).wrapping_shl(1);
|
|
||||||
prev_c = b.count;
|
|
||||||
if c < 0 {
|
|
||||||
c = -c | 1;
|
|
||||||
}
|
|
||||||
varint::write(w, c as u64)?;
|
|
||||||
}
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
fn ins_rem(&mut self, key: &[u8], delta: i64) {
|
|
||||||
assert!(HS >= 8);
|
|
||||||
assert!(key.len() >= HS);
|
|
||||||
let iteration_indices: [u64; 8] = unsafe { transmute(zerotier_core_crypto::hash::SHA512::hash(key)) };
|
|
||||||
let check_hash = calc_check_hash::<HS>(&key);
|
|
||||||
for i in 0..KEY_MAPPING_ITERATIONS {
|
|
||||||
let b = unsafe { self.map.get_unchecked_mut((u64::from_le(iteration_indices[i]) as usize) % B) };
|
|
||||||
for x in 0..HS {
|
|
||||||
b.key_sum[x] ^= key[x];
|
|
||||||
}
|
|
||||||
b.check_hash_sum ^= check_hash;
|
|
||||||
b.count += delta;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[inline(always)]
|
|
||||||
pub fn insert(&mut self, key: &[u8]) { self.ins_rem(key, 1); }
|
|
||||||
|
|
||||||
#[inline(always)]
|
|
||||||
pub fn remove(&mut self, key: &[u8]) { self.ins_rem(key, -1); }
|
|
||||||
|
|
||||||
pub fn subtract(&mut self, other: &Self) {
|
|
||||||
for b in 0..B {
|
|
||||||
let s = &mut self.map[b];
|
|
||||||
let o = &other.map[b];
|
|
||||||
for x in 0..HS {
|
|
||||||
s.key_sum[x] ^= o.key_sum[x];
|
|
||||||
}
|
|
||||||
s.check_hash_sum ^= o.check_hash_sum;
|
|
||||||
s.count += o.count;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn list<F: FnMut(&[u8; HS])>(mut self, mut f: F) -> bool {
|
|
||||||
assert!(HS >= 8);
|
|
||||||
let mut singular_buckets = [0_usize; B];
|
|
||||||
let mut singular_bucket_count = 0_usize;
|
|
||||||
|
|
||||||
for b in 0..B {
|
|
||||||
if self.map[b].is_singular() {
|
|
||||||
singular_buckets[singular_bucket_count] = b;
|
|
||||||
singular_bucket_count += 1;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
while singular_bucket_count > 0 {
|
|
||||||
singular_bucket_count -= 1;
|
|
||||||
let b = &self.map[singular_buckets[singular_bucket_count]];
|
|
||||||
|
|
||||||
if b.is_singular() {
|
|
||||||
let key = b.key_sum.clone();
|
|
||||||
let iteration_indices: [u64; 8] = unsafe { transmute(zerotier_core_crypto::hash::SHA512::hash(&key)) };
|
|
||||||
let check_hash = calc_check_hash::<HS>(&key);
|
|
||||||
|
|
||||||
f(&key);
|
|
||||||
|
|
||||||
for i in 0..KEY_MAPPING_ITERATIONS {
|
|
||||||
let b_idx = (u64::from_le(iteration_indices[i]) as usize) % B;
|
|
||||||
let b = &mut self.map[b_idx];
|
|
||||||
for x in 0..HS {
|
|
||||||
b.key_sum[x] ^= key[x];
|
|
||||||
}
|
|
||||||
b.check_hash_sum ^= check_hash;
|
|
||||||
b.count -= 1;
|
|
||||||
|
|
||||||
if b.is_singular() {
|
|
||||||
if singular_bucket_count >= B {
|
|
||||||
// This would indicate an invalid IBLT.
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
singular_buckets[singular_bucket_count] = b_idx;
|
|
||||||
singular_bucket_count += 1;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[cfg(test)]
|
|
||||||
mod tests {
|
|
||||||
use std::collections::HashSet;
|
|
||||||
|
|
||||||
use zerotier_core_crypto::hash::SHA384;
|
|
||||||
|
|
||||||
use crate::util::iblt::*;
|
|
||||||
|
|
||||||
#[allow(unused_variables)]
|
|
||||||
#[test]
|
|
||||||
fn insert_and_list() {
|
|
||||||
let mut e: HashSet<[u8; 48]> = HashSet::with_capacity(1024);
|
|
||||||
for _ in 0..2 {
|
|
||||||
for expected_cnt in 0..768 {
|
|
||||||
let random_u64 = zerotier_core_crypto::random::xorshift64_random();
|
|
||||||
let mut t: IBLT<48, 1152> = IBLT::new();
|
|
||||||
e.clear();
|
|
||||||
for i in 0..expected_cnt {
|
|
||||||
let k = SHA384::hash(&((i + random_u64) as u64).to_le_bytes());
|
|
||||||
t.insert(&k);
|
|
||||||
e.insert(k);
|
|
||||||
}
|
|
||||||
|
|
||||||
let t_backup = t.clone();
|
|
||||||
assert!(t == t_backup);
|
|
||||||
|
|
||||||
let mut cnt = 0;
|
|
||||||
t.list(|k| {
|
|
||||||
assert!(e.contains(k));
|
|
||||||
cnt += 1;
|
|
||||||
});
|
|
||||||
assert_eq!(cnt, expected_cnt);
|
|
||||||
|
|
||||||
let mut test_buf: Vec<u8> = Vec::new();
|
|
||||||
assert!(t_backup.write(&mut test_buf).is_ok());
|
|
||||||
let mut t_restore: IBLT<48, 1152> = IBLT::new();
|
|
||||||
let mut test_read = test_buf.as_slice();
|
|
||||||
assert!(t_restore.read(&mut test_read).is_ok());
|
|
||||||
|
|
||||||
assert!(t_restore == t_backup);
|
|
||||||
|
|
||||||
cnt = 0;
|
|
||||||
t_restore.list(|k| {
|
|
||||||
assert!(e.contains(k));
|
|
||||||
cnt += 1;
|
|
||||||
});
|
|
||||||
assert_eq!(cnt, expected_cnt);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[allow(unused_variables)]
|
|
||||||
#[test]
|
|
||||||
fn set_reconciliation() {
|
|
||||||
for _ in 0..10 {
|
|
||||||
let random_u64 = zerotier_core_crypto::random::xorshift64_random();
|
|
||||||
let mut alice: IBLT<48, 2048> = IBLT::new();
|
|
||||||
let mut bob: IBLT<48, 2048> = IBLT::new();
|
|
||||||
let mut alice_total = 0_i32;
|
|
||||||
let mut bob_total = 0_i32;
|
|
||||||
for i in 0..1500 {
|
|
||||||
let k = SHA384::hash(&((i ^ random_u64) as u64).to_le_bytes());
|
|
||||||
if (k[0] & 1) == 1 {
|
|
||||||
alice.insert(&k);
|
|
||||||
alice_total += 1;
|
|
||||||
}
|
|
||||||
if (k[0] & 3) == 2 {
|
|
||||||
bob.insert(&k);
|
|
||||||
bob_total += 1;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
alice.subtract(&bob);
|
|
||||||
let mut diff_total = 0_i32;
|
|
||||||
alice.list(|k| {
|
|
||||||
diff_total += 1;
|
|
||||||
});
|
|
||||||
// This is a probabilistic process so we tolerate a little bit of failure. The idea is that each
|
|
||||||
// pass reconciles more and more differences.
|
|
||||||
assert!(((alice_total + bob_total) - diff_total).abs() <= 128);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,65 +0,0 @@
|
||||||
# ZeroTier Forward Secrecy Design (draft)
|
|
||||||
|
|
||||||
Author: Adam Ierymenko / adam.ierymenko@zerotier.com
|
|
||||||
|
|
||||||
## Design Goals
|
|
||||||
|
|
||||||
- Implement security qualities of more modern protocols like Wireguard or Signal.
|
|
||||||
- Support FIPS compliance because we have a bunch of customers who want it.
|
|
||||||
- Continue to use a non-FIPS algorithm too because another huge camp of users are afraid of NIST ECC curves.
|
|
||||||
- Improve hardening against DOS and replay attacks.
|
|
||||||
|
|
||||||
## Algorithms
|
|
||||||
|
|
||||||
- AES-GMAC-SIV for authenticated symmetric encryption (not described here).
|
|
||||||
- HMAC-SHA512 for key derivation.
|
|
||||||
- Curve25519 for asymmetric key agreement
|
|
||||||
- NIST P-521 for asymmetric key agreement
|
|
||||||
- *Maybe* a PQ candidate algorithm to protect against scenarios in which data is warehoused until a QC is available. Considering SIDH, CRYSTALS-KYBER, or NTRU.
|
|
||||||
- Can't easily use the "throw in a static secret" trick used by WireGuard since ZT VL1 sessions are shared among multiple trust boundaries if one is a member of multiple overlapping virtual networks. Which static secret would one use in that case? What if network membership changes? We don't want to set up N totally redundant VL1 sessions between Alice and Bob if they share membership in N networks.
|
|
||||||
|
|
||||||
## Hybrid Cryptography
|
|
||||||
|
|
||||||
Supporting both FIPS and non-FIPS and possibly a PQ algorithm requires the use of hybrid cryptography. This is achieved by performing each KEX in the re-key sequence with multiple algorithms and combining the results. Combination is via HMAC-SHA384(previous, current) where previous is the "key" and current is the "message."
|
|
||||||
|
|
||||||
Exchange uses all algorithms mutually supported by both sides (bitwise AND). This seems less vulnerable to a potential future downgrade attack and to strengthen key agreement overall. All algorithms would have to be broken to break the result of such a chain, making it always as strong as the strongest algorithm.
|
|
||||||
|
|
||||||
FIPS compliance can be achieved by always placing FIPS-compliant algorithms at the end of the list of chained algorithms. So if we are using curve25519 and NIST P-521 the final master key is HMAC-SHA384(curve25519 secret, NIST P-521 secret). FIPS would consider the curve25519 secret a "salt," and FIPS documents do not specify where the salt must originate or if it must be private or public. It doesn't matter cryptographically since the output of HMAC-SHA384(public, secret) is secret.
|
|
||||||
|
|
||||||
*At some point I must write a blog post on smuggling better crypto into FIPS environments through clever use of FIPS primitives.*
|
|
||||||
|
|
||||||
## Session Example
|
|
||||||
|
|
||||||
Notes:
|
|
||||||
|
|
||||||
- Alice starts knowing Bob's identity (which contains long-lived identity key(s)), and vice versa. These are obtained via root nodes which act as identity caches.
|
|
||||||
- Consider public and private keys to actually be bundles of keys containing a key for each algorithm.
|
|
||||||
|
|
||||||
1. Each side sends HELLO containing:
|
|
||||||
- 64-bit counter / nonce
|
|
||||||
- ZeroTier address information (authenticated but not encrypted)
|
|
||||||
- Message type
|
|
||||||
- Sender's ZeroTier identity with their long-lived public key(s)
|
|
||||||
- Encrypted payload section:
|
|
||||||
- Ephemeral public key(s) of initiating node
|
|
||||||
- Wall clock timestamp (milliseconds since epoch)
|
|
||||||
- Monotonic timestamp (milliseconds since some time in the past)
|
|
||||||
- HMAC-SHA512(previous session key) (using static identity key)
|
|
||||||
- Other ZeroTier-related fields (not cryptographically important)
|
|
||||||
- SHA512 hash of recipient's ZeroTier identity
|
|
||||||
- Full HMAC-SHA512 of entire HELLO packet (using static identity key)
|
|
||||||
|
|
||||||
2. Recipients of HELLO respond with OK(HELLO) containing:
|
|
||||||
- Standard ZeroTier encrypted packet headers, addresses, etc.
|
|
||||||
- Ephemeral public key(s) of responding node
|
|
||||||
- Wall clock timestamp (milliseconds since epoch)
|
|
||||||
- Monotonic timestamp (milliseconds since some time in the past)
|
|
||||||
- HMAC-SHA512(new session key) (using static identity key)
|
|
||||||
- Other ZeroTier-related fields (not cryptographically important)
|
|
||||||
- Full HMAC-SHA512 of entire OK(HELLO) packet (using static identity key)
|
|
||||||
|
|
||||||
## Sources
|
|
||||||
|
|
||||||
- https://soatok.blog/2022/01/27/the-controversy-surrounding-hybrid-cryptography/
|
|
||||||
- https://www.wireguard.com/papers/wireguard.pdf
|
|
||||||
- https://www.signal.org/blog/advanced-ratcheting/
|
|
203
zerotier-system-service/Cargo.lock
generated
203
zerotier-system-service/Cargo.lock
generated
|
@ -27,6 +27,17 @@ 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"
|
||||||
|
@ -87,6 +98,12 @@ version = "1.4.3"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "14c189c53d098945499cdfa7ecc63567cf3886b3332b312a5b4585d8d3a6a610"
|
checksum = "14c189c53d098945499cdfa7ecc63567cf3886b3332b312a5b4585d8d3a6a610"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "bytes"
|
||||||
|
version = "1.1.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "c4872d67bab6358e59559027aa3b9157c53d9358c51423c17554809a8858e0f8"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "cc"
|
name = "cc"
|
||||||
version = "1.0.73"
|
version = "1.0.73"
|
||||||
|
@ -99,6 +116,30 @@ version = "1.0.0"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd"
|
checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "clap"
|
||||||
|
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",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "clap_lex"
|
||||||
|
version = "0.2.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "a37c35f1112dad5e6e0b1adaff798507497a18fceeb30cceb3bae7d1427b9213"
|
||||||
|
dependencies = [
|
||||||
|
"os_str_bytes",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "cortex-m"
|
name = "cortex-m"
|
||||||
version = "0.7.4"
|
version = "0.7.4"
|
||||||
|
@ -152,7 +193,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "391b56fbd302e585b7a9494fb70e40949567b1cf9003a8e4a6041a1687c26573"
|
checksum = "391b56fbd302e585b7a9494fb70e40949567b1cf9003a8e4a6041a1687c26573"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"cfg-if",
|
"cfg-if",
|
||||||
"hashbrown",
|
"hashbrown 0.12.0",
|
||||||
"lock_api",
|
"lock_api",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
@ -231,7 +272,7 @@ checksum = "8fc3cb4d91f53b50155bdcfd23f6a4c39ae1969c2ae85982b135750cccaf5fce"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"cfg-if",
|
"cfg-if",
|
||||||
"libc",
|
"libc",
|
||||||
"wasi",
|
"wasi 0.9.0+wasi-snapshot-preview1",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
|
@ -243,6 +284,12 @@ dependencies = [
|
||||||
"byteorder",
|
"byteorder",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "hashbrown"
|
||||||
|
version = "0.11.2"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "ab5ef0d4909ef3724cc8cce6ccc8572c5c817592e9285f5464f8e86f8bd3726e"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "hashbrown"
|
name = "hashbrown"
|
||||||
version = "0.12.0"
|
version = "0.12.0"
|
||||||
|
@ -270,6 +317,16 @@ dependencies = [
|
||||||
"libc",
|
"libc",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "indexmap"
|
||||||
|
version = "1.8.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "0f647032dfaa1f8b6dc29bd3edb7bbef4861b8b8007ebb118d6db284fd59f6ee"
|
||||||
|
dependencies = [
|
||||||
|
"autocfg",
|
||||||
|
"hashbrown 0.11.2",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "itoa"
|
name = "itoa"
|
||||||
version = "1.0.1"
|
version = "1.0.1"
|
||||||
|
@ -298,6 +355,15 @@ dependencies = [
|
||||||
"scopeguard",
|
"scopeguard",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "log"
|
||||||
|
version = "0.4.17"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "abb12e687cfb44aa40f41fc3978ef76448f9b6038cad6aef4259d3c095a2382e"
|
||||||
|
dependencies = [
|
||||||
|
"cfg-if",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "lz4_flex"
|
name = "lz4_flex"
|
||||||
version = "0.9.2"
|
version = "0.9.2"
|
||||||
|
@ -319,6 +385,29 @@ version = "1.0.6"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "3ba553cb19e2acbc54baa16faef215126243fe45e53357a3b2e9f4ebc7b0506c"
|
checksum = "3ba553cb19e2acbc54baa16faef215126243fe45e53357a3b2e9f4ebc7b0506c"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "mio"
|
||||||
|
version = "0.8.2"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "52da4364ffb0e4fe33a9841a98a3f3014fb964045ce4f7a45a398243c8d6b0c9"
|
||||||
|
dependencies = [
|
||||||
|
"libc",
|
||||||
|
"log",
|
||||||
|
"miow",
|
||||||
|
"ntapi",
|
||||||
|
"wasi 0.11.0+wasi-snapshot-preview1",
|
||||||
|
"winapi",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "miow"
|
||||||
|
version = "0.3.7"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "b9f1c5b025cda876f66ef43a113f91ebc9f4ccef34843000e0adf6ebbab84e21"
|
||||||
|
dependencies = [
|
||||||
|
"winapi",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "nb"
|
name = "nb"
|
||||||
version = "0.1.3"
|
version = "0.1.3"
|
||||||
|
@ -334,6 +423,15 @@ version = "1.0.0"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "546c37ac5d9e56f55e73b677106873d9d9f5190605e41a856503623648488cae"
|
checksum = "546c37ac5d9e56f55e73b677106873d9d9f5190605e41a856503623648488cae"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "ntapi"
|
||||||
|
version = "0.3.7"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "c28774a7fd2fbb4f0babd8237ce554b73af68021b5f695a3cebd6c59bac0980f"
|
||||||
|
dependencies = [
|
||||||
|
"winapi",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "num-traits"
|
name = "num-traits"
|
||||||
version = "0.2.14"
|
version = "0.2.14"
|
||||||
|
@ -392,6 +490,12 @@ dependencies = [
|
||||||
"vcpkg",
|
"vcpkg",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "os_str_bytes"
|
||||||
|
version = "6.0.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "8e22443d1643a904602595ba1cd8f7d896afe56d26712531c5ff73a15b2fbf64"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "parking_lot"
|
name = "parking_lot"
|
||||||
version = "0.12.0"
|
version = "0.12.0"
|
||||||
|
@ -415,6 +519,12 @@ dependencies = [
|
||||||
"windows-sys",
|
"windows-sys",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "pin-project-lite"
|
||||||
|
version = "0.2.9"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "e0a7ae3ac2f1173085d398531c705756c94a4c56843785df85a60c1a0afac116"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "pkg-config"
|
name = "pkg-config"
|
||||||
version = "0.3.25"
|
version = "0.3.25"
|
||||||
|
@ -624,6 +734,15 @@ dependencies = [
|
||||||
"opaque-debug",
|
"opaque-debug",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "signal-hook-registry"
|
||||||
|
version = "1.4.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "e51e73328dc4ac0c7ccbda3a494dfa03df1de2f46018127f60c693f2648455b0"
|
||||||
|
dependencies = [
|
||||||
|
"libc",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "signature"
|
name = "signature"
|
||||||
version = "1.5.0"
|
version = "1.5.0"
|
||||||
|
@ -636,6 +755,16 @@ version = "1.8.0"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "f2dd574626839106c320a323308629dcb1acfc96e32a8cba364ddc61ac23ee83"
|
checksum = "f2dd574626839106c320a323308629dcb1acfc96e32a8cba364ddc61ac23ee83"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "socket2"
|
||||||
|
version = "0.4.4"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "66d72b759436ae32898a2af0a14218dbf55efde3feeb170eb623637db85ee1e0"
|
||||||
|
dependencies = [
|
||||||
|
"libc",
|
||||||
|
"winapi",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "spin"
|
name = "spin"
|
||||||
version = "0.9.2"
|
version = "0.9.2"
|
||||||
|
@ -657,6 +786,12 @@ version = "1.1.0"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "a2eb9349b6444b326872e140eb1cf5e7c522154d69e7a0ffb0fb81c06b37543f"
|
checksum = "a2eb9349b6444b326872e140eb1cf5e7c522154d69e7a0ffb0fb81c06b37543f"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "strsim"
|
||||||
|
version = "0.10.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "73473c0e59e6d5812c5dfe2a064a6444949f089e20eec9a2e5506596494e4623"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "subtle"
|
name = "subtle"
|
||||||
version = "2.4.1"
|
version = "2.4.1"
|
||||||
|
@ -686,6 +821,52 @@ 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]]
|
||||||
|
name = "textwrap"
|
||||||
|
version = "0.15.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "b1141d4d61095b28419e22cb0bbf02755f5e54e0526f97f1e3d1d160e60885fb"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "tokio"
|
||||||
|
version = "1.18.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "dce653fb475565de9f6fb0614b28bca8df2c430c0cf84bcd9c843f15de5414cc"
|
||||||
|
dependencies = [
|
||||||
|
"bytes",
|
||||||
|
"libc",
|
||||||
|
"memchr",
|
||||||
|
"mio",
|
||||||
|
"num_cpus",
|
||||||
|
"once_cell",
|
||||||
|
"parking_lot",
|
||||||
|
"pin-project-lite",
|
||||||
|
"signal-hook-registry",
|
||||||
|
"socket2",
|
||||||
|
"tokio-macros",
|
||||||
|
"winapi",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "tokio-macros"
|
||||||
|
version = "1.7.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "b557f72f448c511a979e2564e55d74e6c4432fc96ff4f6241bc6bded342643b7"
|
||||||
|
dependencies = [
|
||||||
|
"proc-macro2",
|
||||||
|
"quote",
|
||||||
|
"syn",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "twox-hash"
|
name = "twox-hash"
|
||||||
version = "1.6.2"
|
version = "1.6.2"
|
||||||
|
@ -757,6 +938,12 @@ version = "0.9.0+wasi-snapshot-preview1"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "cccddf32554fecc6acb585f82a32a72e28b48f8c4c1883ddfeeeaa96f7d8e519"
|
checksum = "cccddf32554fecc6acb585f82a32a72e28b48f8c4c1883ddfeeeaa96f7d8e519"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "wasi"
|
||||||
|
version = "0.11.0+wasi-snapshot-preview1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "winapi"
|
name = "winapi"
|
||||||
version = "0.3.9"
|
version = "0.3.9"
|
||||||
|
@ -773,6 +960,15 @@ 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"
|
||||||
|
@ -890,13 +1086,14 @@ dependencies = [
|
||||||
name = "zerotier-system-service"
|
name = "zerotier-system-service"
|
||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
|
"clap",
|
||||||
"lazy_static",
|
"lazy_static",
|
||||||
"libc",
|
"libc",
|
||||||
"num-traits",
|
"num-traits",
|
||||||
"num_cpus",
|
|
||||||
"parking_lot",
|
"parking_lot",
|
||||||
"serde",
|
"serde",
|
||||||
"serde_json",
|
"serde_json",
|
||||||
|
"tokio",
|
||||||
"winapi",
|
"winapi",
|
||||||
"zerotier-core-crypto",
|
"zerotier-core-crypto",
|
||||||
"zerotier-network-hypervisor",
|
"zerotier-network-hypervisor",
|
||||||
|
|
|
@ -12,14 +12,16 @@ codegen-units = 1
|
||||||
panic = 'abort'
|
panic = 'abort'
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
num-traits = "^0"
|
|
||||||
zerotier-network-hypervisor = { path = "../zerotier-network-hypervisor" }
|
zerotier-network-hypervisor = { path = "../zerotier-network-hypervisor" }
|
||||||
zerotier-core-crypto = { path = "../zerotier-core-crypto" }
|
zerotier-core-crypto = { path = "../zerotier-core-crypto" }
|
||||||
|
num-traits = "^0"
|
||||||
|
tokio = { version = "^1", features = ["full"], default-features = false }
|
||||||
serde = { version = "^1", features = ["derive"], default-features = false }
|
serde = { version = "^1", features = ["derive"], default-features = false }
|
||||||
serde_json = { version = "^1", features = [], default-features = false }
|
serde_json = { version = "^1", features = ["std"], default-features = false }
|
||||||
num_cpus = "^1"
|
|
||||||
parking_lot = "^0"
|
parking_lot = "^0"
|
||||||
lazy_static = "^1"
|
lazy_static = "^1"
|
||||||
|
clap = "^3"
|
||||||
|
#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"] }
|
||||||
|
|
|
@ -10,16 +10,16 @@ use std::collections::BTreeMap;
|
||||||
|
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
|
|
||||||
use zerotier_network_hypervisor::vl1::identity::NetworkId;
|
|
||||||
use zerotier_network_hypervisor::vl1::{Address, InetAddress};
|
use zerotier_network_hypervisor::vl1::{Address, InetAddress};
|
||||||
|
use zerotier_network_hypervisor::vl2::NetworkId;
|
||||||
|
|
||||||
pub const UNASSIGNED_PRIVILEGED_PORTS: [u16; 299] = [
|
pub const UNASSIGNED_PRIVILEGED_PORTS: [u16; 299] = [
|
||||||
4, 6, 8, 10, 12, 14, 15, 16, 26, 28, 30, 32, 34, 36, 40, 60, 269, 270, 271, 272, 273, 274, 275, 276, 277, 278, 279, 285, 288, 289, 290, 291, 292, 293, 294, 295, 296, 297, 298, 299, 300, 301, 302, 303, 304, 305, 306, 307, 323, 324, 325, 326, 327, 328, 329, 330, 331, 332, 334, 335, 336, 337, 338,
|
4, 6, 8, 10, 12, 14, 15, 16, 26, 28, 30, 32, 34, 36, 40, 60, 269, 270, 271, 272, 273, 274, 275, 276, 277, 278, 279, 285, 288, 289, 290, 291, 292, 293, 294, 295, 296, 297, 298, 299, 300, 301, 302, 303, 304, 305, 306, 307, 323, 324, 325, 326, 327, 328,
|
||||||
339, 340, 341, 342, 343, 703, 708, 713, 714, 715, 716, 717, 718, 719, 720, 721, 722, 723, 724, 725, 726, 727, 728, 732, 733, 734, 735, 736, 737, 738, 739, 740, 743, 745, 746, 755, 756, 766, 768, 778, 779, 781, 782, 783, 784, 785, 786, 787, 788, 789, 790, 791, 792, 793, 794, 795, 796, 797, 798,
|
329, 330, 331, 332, 334, 335, 336, 337, 338, 339, 340, 341, 342, 343, 703, 708, 713, 714, 715, 716, 717, 718, 719, 720, 721, 722, 723, 724, 725, 726, 727, 728, 732, 733, 734, 735, 736, 737, 738, 739, 740, 743, 745, 746, 755, 756, 766, 768, 778, 779,
|
||||||
799, 802, 803, 804, 805, 806, 807, 808, 809, 811, 812, 813, 814, 815, 816, 817, 818, 819, 820, 821, 822, 823, 824, 825, 826, 827, 834, 835, 836, 837, 838, 839, 840, 841, 842, 843, 844, 845, 846, 849, 850, 851, 852, 853, 854, 855, 856, 857, 858, 859, 862, 863, 864, 865, 866, 867, 868, 869, 870,
|
781, 782, 783, 784, 785, 786, 787, 788, 789, 790, 791, 792, 793, 794, 795, 796, 797, 798, 799, 802, 803, 804, 805, 806, 807, 808, 809, 811, 812, 813, 814, 815, 816, 817, 818, 819, 820, 821, 822, 823, 824, 825, 826, 827, 834, 835, 836, 837, 838, 839,
|
||||||
871, 872, 874, 875, 876, 877, 878, 879, 880, 881, 882, 883, 884, 885, 889, 890, 891, 892, 893, 894, 895, 896, 897, 898, 899, 904, 905, 906, 907, 908, 909, 910, 911, 914, 915, 916, 917, 918, 919, 920, 921, 922, 923, 924, 925, 926, 927, 928, 929, 930, 931, 932, 933, 934, 935, 936, 937, 938, 939,
|
840, 841, 842, 843, 844, 845, 846, 849, 850, 851, 852, 853, 854, 855, 856, 857, 858, 859, 862, 863, 864, 865, 866, 867, 868, 869, 870, 871, 872, 874, 875, 876, 877, 878, 879, 880, 881, 882, 883, 884, 885, 889, 890, 891, 892, 893, 894, 895, 896, 897,
|
||||||
940, 941, 942, 943, 944, 945, 946, 947, 948, 949, 950, 951, 952, 953, 954, 955, 956, 957, 958, 959, 960, 961, 962, 963, 964, 965, 966, 967, 968, 969, 970, 971, 972, 973, 974, 975, 976, 977, 978, 979, 980, 981, 982, 983, 984, 985, 986, 987, 988, 1001, 1002, 1003, 1004, 1005, 1006, 1007, 1008,
|
898, 899, 904, 905, 906, 907, 908, 909, 910, 911, 914, 915, 916, 917, 918, 919, 920, 921, 922, 923, 924, 925, 926, 927, 928, 929, 930, 931, 932, 933, 934, 935, 936, 937, 938, 939, 940, 941, 942, 943, 944, 945, 946, 947, 948, 949, 950, 951, 952, 953,
|
||||||
1009, 1023,
|
954, 955, 956, 957, 958, 959, 960, 961, 962, 963, 964, 965, 966, 967, 968, 969, 970, 971, 972, 973, 974, 975, 976, 977, 978, 979, 980, 981, 982, 983, 984, 985, 986, 987, 988, 1001, 1002, 1003, 1004, 1005, 1006, 1007, 1008, 1009, 1023,
|
||||||
];
|
];
|
||||||
|
|
||||||
pub const DEFAULT_PORT: u16 = 9993;
|
pub const DEFAULT_PORT: u16 = 9993;
|
||||||
|
|
|
@ -1,169 +0,0 @@
|
||||||
/* 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/
|
|
||||||
*/
|
|
||||||
|
|
||||||
use std::fs::{File, OpenOptions};
|
|
||||||
use std::io::{stderr, Seek, SeekFrom, Write};
|
|
||||||
use std::sync::Arc;
|
|
||||||
|
|
||||||
use parking_lot::Mutex;
|
|
||||||
|
|
||||||
/// It's big it's heavy it's wood.
|
|
||||||
pub struct Log {
|
|
||||||
prefix: String,
|
|
||||||
path: String,
|
|
||||||
file: Option<File>,
|
|
||||||
cur_size: u64,
|
|
||||||
max_size: usize,
|
|
||||||
log_to_stderr: bool,
|
|
||||||
debug: bool,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Log {
|
|
||||||
/// Minimum "maximum size" parameter.
|
|
||||||
const MIN_MAX_SIZE: usize = 1024;
|
|
||||||
|
|
||||||
/// Construct a new logger.
|
|
||||||
///
|
|
||||||
/// If path is empty logs will not be written to files. If log_to_stderr is also
|
|
||||||
/// false then no logs will be output at all.
|
|
||||||
///
|
|
||||||
/// This returns an Arc<Mutex<Log>> suitable for use with the l! and d! macros, which
|
|
||||||
/// expect a mutex guarded instance.
|
|
||||||
pub fn new(path: &str, max_size: usize, log_to_stderr: bool, debug: bool, prefix: &str) -> Arc<Mutex<Log>> {
|
|
||||||
let mut p = String::from(prefix);
|
|
||||||
if !p.is_empty() {
|
|
||||||
p.push(' ');
|
|
||||||
}
|
|
||||||
Arc::new(Mutex::new(Log {
|
|
||||||
prefix: p,
|
|
||||||
path: String::from(path),
|
|
||||||
file: None,
|
|
||||||
cur_size: 0,
|
|
||||||
max_size: max_size.max(Self::MIN_MAX_SIZE),
|
|
||||||
log_to_stderr,
|
|
||||||
debug,
|
|
||||||
}))
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn set_max_size(&mut self, new_max_size: usize) {
|
|
||||||
self.max_size = if new_max_size < Log::MIN_MAX_SIZE { Log::MIN_MAX_SIZE } else { new_max_size };
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn set_log_to_stderr(&mut self, log_to_stderr: bool) {
|
|
||||||
self.log_to_stderr = log_to_stderr;
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn set_debug(&mut self, debug: bool) {
|
|
||||||
self.debug = debug;
|
|
||||||
}
|
|
||||||
|
|
||||||
fn log_internal(&mut self, pfx: &str, s: &str) {
|
|
||||||
if !s.is_empty() {
|
|
||||||
let log_line = format!("{}[{}] {}{}\n", self.prefix.as_str(), chrono::Local::now().format("%Y-%m-%d %H:%M:%S").to_string(), pfx, s);
|
|
||||||
if !self.path.is_empty() {
|
|
||||||
if self.file.is_none() {
|
|
||||||
let f = OpenOptions::new().read(true).write(true).create(true).open(self.path.as_str());
|
|
||||||
if f.is_err() {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
let mut f = f.unwrap();
|
|
||||||
let eof = f.seek(SeekFrom::End(0));
|
|
||||||
if eof.is_err() {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
self.cur_size = eof.unwrap();
|
|
||||||
self.file = Some(f);
|
|
||||||
}
|
|
||||||
|
|
||||||
if self.max_size > 0 && self.cur_size > self.max_size as u64 {
|
|
||||||
self.file = None;
|
|
||||||
self.cur_size = 0;
|
|
||||||
|
|
||||||
let mut old_path = self.path.clone();
|
|
||||||
old_path.push_str(".old");
|
|
||||||
let _ = std::fs::remove_file(old_path.as_str());
|
|
||||||
let _ = std::fs::rename(self.path.as_str(), old_path.as_str());
|
|
||||||
let _ = std::fs::remove_file(self.path.as_str()); // should fail
|
|
||||||
|
|
||||||
let f = OpenOptions::new().read(true).write(true).create(true).open(self.path.as_str());
|
|
||||||
if f.is_err() {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
self.file = Some(f.unwrap());
|
|
||||||
}
|
|
||||||
|
|
||||||
let f = self.file.as_mut().unwrap();
|
|
||||||
let e = f.write_all(log_line.as_bytes());
|
|
||||||
if e.is_err() {
|
|
||||||
eprintln!("ERROR: I/O error writing to log: {}", e.err().unwrap().to_string());
|
|
||||||
self.file = None;
|
|
||||||
} else {
|
|
||||||
let _ = f.flush();
|
|
||||||
self.cur_size += log_line.len() as u64;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if self.log_to_stderr {
|
|
||||||
let _ = stderr().write_all(log_line.as_bytes());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn log<S: AsRef<str>>(&mut self, s: S) {
|
|
||||||
self.log_internal("", s.as_ref());
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn debug<S: AsRef<str>>(&mut self, s: S) {
|
|
||||||
if self.debug {
|
|
||||||
self.log_internal("DEBUG: ", s.as_ref());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn fatal<S: AsRef<str>>(&mut self, s: S) {
|
|
||||||
let ss = s.as_ref();
|
|
||||||
self.log_internal("FATAL: ", ss);
|
|
||||||
eprintln!("FATAL: {}", ss);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[macro_export]
|
|
||||||
macro_rules! log(
|
|
||||||
($logger:expr, $($arg:tt)*) => {
|
|
||||||
$logger.lock().log(format!($($arg)*));
|
|
||||||
}
|
|
||||||
);
|
|
||||||
|
|
||||||
#[macro_export]
|
|
||||||
macro_rules! debug(
|
|
||||||
($logger:expr, $($arg:tt)*) => {
|
|
||||||
$logger.lock().debug(format!($($arg)*));
|
|
||||||
}
|
|
||||||
);
|
|
||||||
|
|
||||||
#[macro_export]
|
|
||||||
macro_rules! fatal(
|
|
||||||
($logger:expr, $($arg:tt)*) => {
|
|
||||||
$logger.lock().fatal(format!($($arg)*));
|
|
||||||
std::process::exit(-1);
|
|
||||||
}
|
|
||||||
);
|
|
||||||
|
|
||||||
/*
|
|
||||||
#[cfg(test)]
|
|
||||||
mod tests {
|
|
||||||
use crate::log::Log;
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn test_log() {
|
|
||||||
let l = Log::new("/tmp/ztlogtest.log", 65536, "");
|
|
||||||
for i in 0..100000 {
|
|
||||||
l.log(format!("line {}", i))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
*/
|
|
|
@ -7,30 +7,20 @@
|
||||||
*/
|
*/
|
||||||
|
|
||||||
use std::io::Write;
|
use std::io::Write;
|
||||||
use std::str::FromStr;
|
|
||||||
|
|
||||||
use clap::{App, Arg, ArgMatches, ErrorKind};
|
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};
|
||||||
|
|
||||||
use crate::store::platform_default_home_path;
|
pub mod getifaddrs;
|
||||||
|
pub mod localconfig;
|
||||||
mod fastudpsocket;
|
pub mod utils;
|
||||||
mod getifaddrs;
|
pub mod vnic;
|
||||||
mod localconfig;
|
|
||||||
#[macro_use]
|
|
||||||
mod log;
|
|
||||||
mod service;
|
|
||||||
mod store;
|
|
||||||
mod utils;
|
|
||||||
mod vnic;
|
|
||||||
|
|
||||||
pub const HTTP_API_OBJECT_SIZE_LIMIT: usize = 131072;
|
|
||||||
|
|
||||||
fn make_help(long_help: bool) -> String {
|
fn make_help(long_help: bool) -> String {
|
||||||
format!(
|
format!(
|
||||||
r###"ZeroTier Network Hypervisor Service Version {}.{}.{}
|
r###"ZeroTier Network Hypervisor Service Version {}.{}.{}
|
||||||
(c)2013-2021 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 (see LICENSE.txt)
|
||||||
|
|
||||||
Usage: zerotier [-...] <command> [command args]
|
Usage: zerotier [-...] <command> [command args]
|
||||||
|
@ -45,7 +35,6 @@ Global Options:
|
||||||
Common Operations:
|
Common Operations:
|
||||||
|
|
||||||
help Show this help
|
help Show this help
|
||||||
longhelp Show help with advanced commands
|
|
||||||
oldhelp Show v1.x legacy commands
|
oldhelp Show v1.x legacy commands
|
||||||
version Print version (of this binary)
|
version Print version (of this binary)
|
||||||
|
|
||||||
|
@ -126,111 +115,100 @@ pub struct GlobalCommandLineFlags {
|
||||||
pub auth_token_override: Option<String>,
|
pub auth_token_override: Option<String>,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[cfg(any(target_os = "macos"))]
|
||||||
|
pub fn platform_default_home_path() -> String {
|
||||||
|
"/Library/Application Support/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()),
|
||||||
|
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()),
|
||||||
|
};
|
||||||
|
|
||||||
|
return match cli_args.subcommand() {
|
||||||
|
Some(("help", _)) => {
|
||||||
|
print_help(false);
|
||||||
|
0
|
||||||
|
}
|
||||||
|
Some(("oldhelp", _)) => todo!(),
|
||||||
|
Some(("version", _)) => {
|
||||||
|
println!("{}.{}.{}", VERSION_MAJOR, VERSION_MINOR, VERSION_REVISION);
|
||||||
|
0
|
||||||
|
}
|
||||||
|
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(("service", _)) => todo!(),
|
||||||
|
Some(("controller", sub_cli_args)) => todo!(),
|
||||||
|
Some(("identity", sub_cli_args)) => todo!(),
|
||||||
|
_ => {
|
||||||
|
print_help(false);
|
||||||
|
1
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
fn main() {
|
fn main() {
|
||||||
let cli_args = Box::new({
|
let cli_args = Box::new({
|
||||||
let help = make_help(false);
|
let help = make_help(false);
|
||||||
let args = App::new("zerotier")
|
Command::new("zerotier")
|
||||||
.arg(Arg::with_name("json").short("j"))
|
.arg(Arg::new("json").short('j'))
|
||||||
.arg(Arg::with_name("path").short("p").takes_value(true))
|
.arg(Arg::new("path").short('p').takes_value(true))
|
||||||
.arg(Arg::with_name("token_path").short("t").takes_value(true))
|
.arg(Arg::new("token_path").short('t').takes_value(true))
|
||||||
.arg(Arg::with_name("token").short("T").takes_value(true))
|
.arg(Arg::new("token").short('T').takes_value(true))
|
||||||
.subcommand(App::new("help"))
|
.subcommand(Command::new("help"))
|
||||||
.subcommand(App::new("version"))
|
.subcommand(Command::new("version"))
|
||||||
.subcommand(App::new("status"))
|
.subcommand(Command::new("status"))
|
||||||
.subcommand(
|
.subcommand(
|
||||||
App::new("set")
|
Command::new("set")
|
||||||
.subcommand(App::new("port").arg(Arg::with_name("port#").index(1).validator(utils::is_valid_port)))
|
.subcommand(Command::new("port").arg(Arg::new("port#").index(1).validator(utils::is_valid_port)))
|
||||||
.subcommand(App::new("secondaryport").arg(Arg::with_name("port#").index(1).validator(utils::is_valid_port)))
|
.subcommand(Command::new("secondaryport").arg(Arg::new("port#").index(1).validator(utils::is_valid_port)))
|
||||||
.subcommand(
|
.subcommand(
|
||||||
App::new("blacklist")
|
Command::new("blacklist")
|
||||||
.subcommand(App::new("cidr").arg(Arg::with_name("ip_bits").index(1)).arg(Arg::with_name("boolean").index(2).validator(utils::is_valid_bool)))
|
.subcommand(Command::new("cidr").arg(Arg::new("ip_bits").index(1)).arg(Arg::new("boolean").index(2).validator(utils::is_valid_bool)))
|
||||||
.subcommand(App::new("if").arg(Arg::with_name("prefix").index(1)).arg(Arg::with_name("boolean").index(2).validator(utils::is_valid_bool))),
|
.subcommand(Command::new("if").arg(Arg::new("prefix").index(1)).arg(Arg::new("boolean").index(2).validator(utils::is_valid_bool))),
|
||||||
)
|
)
|
||||||
.subcommand(App::new("portmap").arg(Arg::with_name("boolean").index(1).validator(utils::is_valid_bool))),
|
.subcommand(Command::new("portmap").arg(Arg::new("boolean").index(1).validator(utils::is_valid_bool))),
|
||||||
)
|
)
|
||||||
.subcommand(App::new("peer").subcommand(App::new("show").arg(Arg::with_name("address").index(1).required(true))).subcommand(App::new("list")).subcommand(App::new("listroots")).subcommand(App::new("try")))
|
.subcommand(Command::new("peer").subcommand(Command::new("show").arg(Arg::new("address").index(1).required(true))).subcommand(Command::new("list")).subcommand(Command::new("listroots")).subcommand(Command::new("try")))
|
||||||
.subcommand(
|
.subcommand(
|
||||||
App::new("network")
|
Command::new("network")
|
||||||
.subcommand(App::new("show").arg(Arg::with_name("nwid").index(1).required(true)))
|
.subcommand(Command::new("show").arg(Arg::new("nwid").index(1).required(true)))
|
||||||
.subcommand(App::new("list"))
|
.subcommand(Command::new("list"))
|
||||||
.subcommand(App::new("set").arg(Arg::with_name("nwid").index(1).required(true)).arg(Arg::with_name("setting").index(2).required(false)).arg(Arg::with_name("value").index(3).required(false))),
|
.subcommand(Command::new("set").arg(Arg::new("nwid").index(1).required(true)).arg(Arg::new("setting").index(2).required(false)).arg(Arg::new("value").index(3).required(false))),
|
||||||
)
|
)
|
||||||
.subcommand(App::new("join").arg(Arg::with_name("nwid").index(1).required(true)))
|
.subcommand(Command::new("join").arg(Arg::new("nwid").index(1).required(true)))
|
||||||
.subcommand(App::new("leave").arg(Arg::with_name("nwid").index(1).required(true)))
|
.subcommand(Command::new("leave").arg(Arg::new("nwid").index(1).required(true)))
|
||||||
.subcommand(App::new("service"))
|
.subcommand(Command::new("service"))
|
||||||
.subcommand(
|
.subcommand(
|
||||||
App::new("controller")
|
Command::new("controller")
|
||||||
.subcommand(App::new("list"))
|
.subcommand(Command::new("list"))
|
||||||
.subcommand(App::new("new"))
|
.subcommand(Command::new("new"))
|
||||||
.subcommand(App::new("set").arg(Arg::with_name("id").index(1).required(true)).arg(Arg::with_name("setting").index(2)).arg(Arg::with_name("value").index(3)))
|
.subcommand(Command::new("set").arg(Arg::new("id").index(1).required(true)).arg(Arg::new("setting").index(2)).arg(Arg::new("value").index(3)))
|
||||||
.subcommand(App::new("show").arg(Arg::with_name("id").index(1).required(true)).arg(Arg::with_name("member").index(2)))
|
.subcommand(Command::new("show").arg(Arg::new("id").index(1).required(true)).arg(Arg::new("member").index(2)))
|
||||||
.subcommand(App::new("auth").arg(Arg::with_name("member").index(1).required(true)))
|
.subcommand(Command::new("auth").arg(Arg::new("member").index(1).required(true)))
|
||||||
.subcommand(App::new("deauth").arg(Arg::with_name("member").index(1).required(true))),
|
.subcommand(Command::new("deauth").arg(Arg::new("member").index(1).required(true))),
|
||||||
)
|
)
|
||||||
.subcommand(
|
.subcommand(
|
||||||
App::new("identity")
|
Command::new("identity")
|
||||||
.subcommand(App::new("new").arg(Arg::with_name("type").possible_value("p384").possible_value("c25519").default_value("c25519").index(1)))
|
.subcommand(Command::new("new").arg(Arg::new("type").possible_value("p384").possible_value("c25519").default_value("c25519").index(1)))
|
||||||
.subcommand(App::new("getpublic").arg(Arg::with_name("identity").index(1).required(true)))
|
.subcommand(Command::new("getpublic").arg(Arg::new("identity").index(1).required(true)))
|
||||||
.subcommand(App::new("fingerprint").arg(Arg::with_name("identity").index(1).required(true)))
|
.subcommand(Command::new("fingerprint").arg(Arg::new("identity").index(1).required(true)))
|
||||||
.subcommand(App::new("validate").arg(Arg::with_name("identity").index(1).required(true)))
|
.subcommand(Command::new("validate").arg(Arg::new("identity").index(1).required(true)))
|
||||||
.subcommand(App::new("sign").arg(Arg::with_name("identity").index(1).required(true)).arg(Arg::with_name("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(App::new("verify").arg(Arg::with_name("identity").index(1).required(true)).arg(Arg::with_name("path").index(2).required(true)).arg(Arg::with_name("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))),
|
||||||
)
|
)
|
||||||
.help(help.as_str())
|
.override_help(help.as_str())
|
||||||
.get_matches_from_safe(std::env::args());
|
.override_usage(help.as_str())
|
||||||
if args.is_err() {
|
.disable_help_flag(true)
|
||||||
let e = args.err().unwrap();
|
.get_matches_from(std::env::args())
|
||||||
if e.kind != ErrorKind::HelpDisplayed {
|
|
||||||
print_help(false);
|
|
||||||
}
|
|
||||||
std::process::exit(1);
|
|
||||||
}
|
|
||||||
let args = args.unwrap();
|
|
||||||
if args.subcommand_name().is_none() {
|
|
||||||
print_help(false);
|
|
||||||
std::process::exit(1);
|
|
||||||
}
|
|
||||||
args
|
|
||||||
});
|
});
|
||||||
|
|
||||||
let global_cli_flags = GlobalCommandLineFlags {
|
std::process::exit(tokio::runtime::Builder::new_multi_thread().enable_all().build().unwrap().block_on(async_main(cli_args)));
|
||||||
json_output: cli_args.is_present("json"),
|
|
||||||
base_path: cli_args.value_of("path").map_or_else(|| platform_default_home_path(), |p| p.into_string()),
|
|
||||||
auth_token_path_override: cli_args.value_of("token_path").map(|p| p.into_string()),
|
|
||||||
auth_token_override: cli_args.value_of("token").map(|t| t.into_string()),
|
|
||||||
};
|
|
||||||
|
|
||||||
std::process::exit({
|
|
||||||
match cli_args.subcommand() {
|
|
||||||
("help", None) => {
|
|
||||||
print_help(false);
|
|
||||||
0
|
|
||||||
}
|
|
||||||
("longhelp", None) => {
|
|
||||||
print_help(true);
|
|
||||||
0
|
|
||||||
}
|
|
||||||
("oldhelp", None) => todo!(),
|
|
||||||
("version", None) => {
|
|
||||||
println!("{}.{}.{}", VERSION_MAJOR, VERSION_MINOR, VERSION_REVISION);
|
|
||||||
0
|
|
||||||
}
|
|
||||||
("status", None) => todo!(),
|
|
||||||
("set", Some(sub_cli_args)) => todo!(),
|
|
||||||
("peer", Some(sub_cli_args)) => todo!(),
|
|
||||||
("network", Some(sub_cli_args)) => todo!(),
|
|
||||||
("join", Some(sub_cli_args)) => todo!(),
|
|
||||||
("leave", Some(sub_cli_args)) => todo!(),
|
|
||||||
("service", None) => {
|
|
||||||
drop(cli_args); // free no longer needed memory before entering service
|
|
||||||
service::run(&global_cli_flags)
|
|
||||||
}
|
|
||||||
("controller", Some(sub_cli_args)) => todo!(),
|
|
||||||
("identity", Some(sub_cli_args)) => todo!(),
|
|
||||||
_ => {
|
|
||||||
print_help(false);
|
|
||||||
1
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,527 +0,0 @@
|
||||||
/* 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/
|
|
||||||
*/
|
|
||||||
|
|
||||||
use std::num::NonZeroI64;
|
|
||||||
use std::sync::atomic::{AtomicBool, AtomicUsize, Ordering};
|
|
||||||
use std::sync::Arc;
|
|
||||||
|
|
||||||
use parking_lot::Mutex;
|
|
||||||
|
|
||||||
use crate::fastudpsocket::{fast_udp_socket_sendto, FastUDPRawOsSocket};
|
|
||||||
use zerotier_network_hypervisor::vl1::{Endpoint, Identity, IdentityType, Node, VL1SystemInterface};
|
|
||||||
use zerotier_network_hypervisor::vl2::SwitchInterface;
|
|
||||||
use zerotier_network_hypervisor::{Interface, NetworkHypervisor};
|
|
||||||
|
|
||||||
use crate::localconfig::LocalConfig;
|
|
||||||
use crate::log::Log;
|
|
||||||
use crate::store::{StateObjectType, Store};
|
|
||||||
use crate::utils::{ms_monotonic, ms_since_epoch};
|
|
||||||
use crate::GlobalCommandLineFlags;
|
|
||||||
|
|
||||||
struct ServiceInterface {
|
|
||||||
pub store: Store,
|
|
||||||
pub config: Arc<Mutex<LocalConfig>>,
|
|
||||||
pub online: AtomicBool,
|
|
||||||
pub all_sockets: Mutex<Vec<FastUDPRawOsSocket>>,
|
|
||||||
pub all_sockets_spin_ptr: AtomicUsize,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl VL1SystemInterface for ServiceInterface {
|
|
||||||
fn event_node_is_up(&self) {}
|
|
||||||
|
|
||||||
fn event_node_is_down(&self) {}
|
|
||||||
|
|
||||||
fn event_identity_collision(&self) {}
|
|
||||||
|
|
||||||
#[inline(always)]
|
|
||||||
fn event_online_status_change(&self, online: bool) {
|
|
||||||
self.online.store(online, Ordering::Relaxed);
|
|
||||||
}
|
|
||||||
|
|
||||||
fn event_user_message(&self, source: &Identity, message_type: u64, message: &[u8]) {}
|
|
||||||
|
|
||||||
#[inline(always)]
|
|
||||||
fn load_node_identity(&self) -> Option<Vec<u8>> {
|
|
||||||
self.store.load_object(StateObjectType::IdentitySecret, &[]).map_or(None, |b| Some(b))
|
|
||||||
}
|
|
||||||
|
|
||||||
fn save_node_identity(&self, _: &Identity, public: &[u8], secret: &[u8]) {
|
|
||||||
let _ = self.store.store_object(StateObjectType::IdentityPublic, &[], public);
|
|
||||||
let _ = self.store.store_object(StateObjectType::IdentitySecret, &[], secret);
|
|
||||||
}
|
|
||||||
|
|
||||||
#[inline(always)]
|
|
||||||
fn wire_send(&self, endpoint: &Endpoint, local_socket: Option<NonZeroI64>, local_interface: Option<NonZeroI64>, data: &[&[u8]], packet_ttl: u8) -> bool {
|
|
||||||
match endpoint {
|
|
||||||
Endpoint::IpUdp(ip) => {
|
|
||||||
local_socket.map_or_else(
|
|
||||||
|| {
|
|
||||||
let ptr = self.all_sockets_spin_ptr.fetch_add(1, Ordering::Relaxed);
|
|
||||||
let all_sockets = self.all_sockets.lock();
|
|
||||||
if !all_sockets.is_empty() {
|
|
||||||
let s = unsafe { all_sockets.get_unchecked(ptr % all_sockets.len()) }.clone();
|
|
||||||
drop(all_sockets); // release mutex
|
|
||||||
fast_udp_socket_sendto(&s, ip, data, packet_ttl);
|
|
||||||
true
|
|
||||||
} else {
|
|
||||||
false
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|local_socket| {
|
|
||||||
fast_udp_socket_sendto(&local_socket, ip, data, packet_ttl);
|
|
||||||
true
|
|
||||||
},
|
|
||||||
)
|
|
||||||
}
|
|
||||||
_ => false,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn check_path(&self, id: &Identity, endpoint: &Endpoint, local_socket: Option<NonZeroI64>, local_interface: Option<NonZeroI64>) -> bool {
|
|
||||||
// TODO
|
|
||||||
true
|
|
||||||
}
|
|
||||||
|
|
||||||
fn get_path_hints(&self, id: &Identity) -> Option<&[(&Endpoint, Option<NonZeroI64>, Option<NonZeroI64>)]> {
|
|
||||||
// TODO
|
|
||||||
None
|
|
||||||
}
|
|
||||||
|
|
||||||
#[inline(always)]
|
|
||||||
fn time_ticks(&self) -> i64 {
|
|
||||||
ms_monotonic()
|
|
||||||
}
|
|
||||||
|
|
||||||
#[inline(always)]
|
|
||||||
fn time_clock(&self) -> i64 {
|
|
||||||
ms_since_epoch()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl SwitchInterface for ServiceInterface {}
|
|
||||||
|
|
||||||
//impl Interface for ServiceInterface {}
|
|
||||||
|
|
||||||
pub fn run(global_cli_flags: &GlobalCommandLineFlags) -> i32 {
|
|
||||||
let store = Store::new(global_cli_flags.base_path.as_str(), &global_cli_flags.auth_token_path_override, &global_cli_flags.auth_token_override);
|
|
||||||
if store.is_err() {}
|
|
||||||
|
|
||||||
let si = ServiceInterface {
|
|
||||||
store: store.unwrap(),
|
|
||||||
config: Arc::new(Mutex::new(LocalConfig::default())),
|
|
||||||
online: AtomicBool::new(false),
|
|
||||||
all_sockets: Mutex::new(Vec::new()),
|
|
||||||
all_sockets_spin_ptr: AtomicUsize::new(0),
|
|
||||||
};
|
|
||||||
|
|
||||||
let node = Node::new(&si, Some(IdentityType::C25519));
|
|
||||||
|
|
||||||
0
|
|
||||||
}
|
|
||||||
|
|
||||||
/*
|
|
||||||
use std::cell::Cell;
|
|
||||||
use std::collections::BTreeMap;
|
|
||||||
use std::net::{SocketAddr, Ipv4Addr, IpAddr, Ipv6Addr};
|
|
||||||
use std::sync::{Arc, Mutex, Weak};
|
|
||||||
use std::sync::atomic::{AtomicBool, Ordering};
|
|
||||||
use std::time::Duration;
|
|
||||||
|
|
||||||
use zerotier_network_hypervisor::vl1::{Address, Identity, InetAddress, MAC, PacketBuffer};
|
|
||||||
use zerotier_network_hypervisor::vl1::inetaddress::IpScope;
|
|
||||||
use zerotier_network_hypervisor::{CallerInterface, Node};
|
|
||||||
|
|
||||||
use futures::StreamExt;
|
|
||||||
use serde::{Serialize, Deserialize};
|
|
||||||
|
|
||||||
use crate::fastudpsocket::*;
|
|
||||||
use crate::getifaddrs;
|
|
||||||
use crate::localconfig::*;
|
|
||||||
use crate::log::Log;
|
|
||||||
use crate::network::Network;
|
|
||||||
use crate::store::Store;
|
|
||||||
use crate::utils::{ms_since_epoch, ms_monotonic};
|
|
||||||
use crate::httplistener::HttpListener;
|
|
||||||
|
|
||||||
const CONFIG_CHECK_INTERVAL: i64 = 5000;
|
|
||||||
|
|
||||||
/// ServiceStatus is the object returned by the API /status endpoint
|
|
||||||
#[derive(Serialize, Deserialize, Clone, PartialEq, Eq)]
|
|
||||||
pub struct ServiceStatus {
|
|
||||||
#[serde(rename = "objectType")]
|
|
||||||
pub object_type: String,
|
|
||||||
pub address: Address,
|
|
||||||
pub clock: i64,
|
|
||||||
#[serde(rename = "startTime")]
|
|
||||||
pub start_time: i64,
|
|
||||||
pub uptime: i64,
|
|
||||||
pub config: LocalConfig,
|
|
||||||
pub online: bool,
|
|
||||||
#[serde(rename = "publicIdentity")]
|
|
||||||
pub public_identity: Identity,
|
|
||||||
pub version: String,
|
|
||||||
#[serde(rename = "versionMajor")]
|
|
||||||
pub version_major: i32,
|
|
||||||
#[serde(rename = "versionMinor")]
|
|
||||||
pub version_minor: i32,
|
|
||||||
#[serde(rename = "versionRev")]
|
|
||||||
pub version_revision: i32,
|
|
||||||
#[serde(rename = "versionBuild")]
|
|
||||||
pub version_build: i32,
|
|
||||||
#[serde(rename = "udpLocalEndpoints")]
|
|
||||||
pub udp_local_endpoints: Vec<InetAddress>,
|
|
||||||
#[serde(rename = "httpLocalEndpoints")]
|
|
||||||
pub http_local_endpoints: Vec<InetAddress>,
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Core ZeroTier service, which is sort of just a container for all the things.
|
|
||||||
pub(crate) struct Service {
|
|
||||||
pub(crate) log: Log,
|
|
||||||
node: Option<Node>,
|
|
||||||
udp_local_endpoints: Mutex<Vec<InetAddress>>,
|
|
||||||
http_local_endpoints: Mutex<Vec<InetAddress>>,
|
|
||||||
interrupt: Mutex<futures::channel::mpsc::Sender<()>>,
|
|
||||||
local_config: Mutex<Arc<LocalConfig>>,
|
|
||||||
store: Arc<Store>,
|
|
||||||
startup_time: i64,
|
|
||||||
startup_time_monotonic: i64,
|
|
||||||
run: AtomicBool,
|
|
||||||
online: AtomicBool,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Service {
|
|
||||||
pub fn local_config(&self) -> Arc<LocalConfig> {
|
|
||||||
self.local_config.lock().unwrap().clone()
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn set_local_config(&self, new_lc: LocalConfig) {
|
|
||||||
*(self.local_config.lock().unwrap()) = Arc::new(new_lc);
|
|
||||||
}
|
|
||||||
|
|
||||||
#[inline(always)]
|
|
||||||
pub fn store(&self) -> &Arc<Store> {
|
|
||||||
&self.store
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn online(&self) -> bool {
|
|
||||||
self.online.load(Ordering::Relaxed)
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn shutdown(&self) {
|
|
||||||
self.run.store(false, Ordering::Relaxed);
|
|
||||||
let _ = self.interrupt.lock().unwrap().try_send(());
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Get service status for API, or None if a shutdown is in progress.
|
|
||||||
pub fn status(&self) -> Option<ServiceStatus> {
|
|
||||||
let ver = zerotier_core::version();
|
|
||||||
self.node().map(|node| {
|
|
||||||
ServiceStatus {
|
|
||||||
object_type: "status".to_owned(),
|
|
||||||
address: node.address(),
|
|
||||||
clock: ms_since_epoch(),
|
|
||||||
start_time: self.startup_time,
|
|
||||||
uptime: ms_monotonic() - self.startup_time_monotonic,
|
|
||||||
config: (*self.local_config()).clone(),
|
|
||||||
online: self.online(),
|
|
||||||
public_identity: node.identity().clone(),
|
|
||||||
version: format!("{}.{}.{}", ver.0, ver.1, ver.2),
|
|
||||||
version_major: ver.0,
|
|
||||||
version_minor: ver.1,
|
|
||||||
version_revision: ver.2,
|
|
||||||
version_build: ver.3,
|
|
||||||
udp_local_endpoints: self.udp_local_endpoints.lock().unwrap().clone(),
|
|
||||||
http_local_endpoints: self.http_local_endpoints.lock().unwrap().clone(),
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
unsafe impl Send for Service {}
|
|
||||||
|
|
||||||
unsafe impl Sync for Service {}
|
|
||||||
|
|
||||||
async fn run_async(store: Arc<Store>, local_config: Arc<LocalConfig>) -> i32 {
|
|
||||||
let process_exit_value: i32 = 0;
|
|
||||||
|
|
||||||
let mut udp_sockets: BTreeMap<InetAddress, FastUDPSocket> = BTreeMap::new();
|
|
||||||
let mut http_listeners: BTreeMap<InetAddress, HttpListener> = BTreeMap::new();
|
|
||||||
let mut loopback_http_listeners: (Option<HttpListener>, Option<HttpListener>) = (None, None); // 127.0.0.1, ::1
|
|
||||||
|
|
||||||
let (interrupt_tx, mut interrupt_rx) = futures::channel::mpsc::channel::<()>(1);
|
|
||||||
let service = Arc::new(Service {
|
|
||||||
log: Log::new(
|
|
||||||
if local_config.settings.log.path.as_ref().is_some() {
|
|
||||||
local_config.settings.log.path.as_ref().unwrap().as_str()
|
|
||||||
} else {
|
|
||||||
store.default_log_path.to_str().unwrap()
|
|
||||||
},
|
|
||||||
local_config.settings.log.max_size,
|
|
||||||
local_config.settings.log.stderr,
|
|
||||||
local_config.settings.log.debug,
|
|
||||||
"",
|
|
||||||
),
|
|
||||||
node: None,
|
|
||||||
udp_local_endpoints: Mutex::new(Vec::new()),
|
|
||||||
http_local_endpoints: Mutex::new(Vec::new()),
|
|
||||||
interrupt: Mutex::new(interrupt_tx),
|
|
||||||
local_config: Mutex::new(local_config),
|
|
||||||
store: store.clone(),
|
|
||||||
startup_time: ms_since_epoch(),
|
|
||||||
startup_time_monotonic: ms_monotonic(),
|
|
||||||
run: AtomicBool::new(true),
|
|
||||||
online: AtomicBool::new(false),
|
|
||||||
});
|
|
||||||
|
|
||||||
let node = Node::new(service.clone(), ms_since_epoch(), ms_monotonic());
|
|
||||||
if node.is_err() {
|
|
||||||
service.log.fatal(format!("error initializing node: {}", node.err().unwrap().to_str()));
|
|
||||||
return 1;
|
|
||||||
}
|
|
||||||
let node = Arc::new(node.ok().unwrap());
|
|
||||||
service._node.replace(Arc::downgrade(&node));
|
|
||||||
|
|
||||||
let mut local_config = service.local_config();
|
|
||||||
|
|
||||||
let mut ticks: i64 = ms_monotonic();
|
|
||||||
let mut loop_delay = zerotier_core::NODE_BACKGROUND_TASKS_MAX_INTERVAL;
|
|
||||||
let mut last_checked_config: i64 = 0;
|
|
||||||
while service.run.load(Ordering::Relaxed) {
|
|
||||||
let loop_delay_start = ms_monotonic();
|
|
||||||
tokio::select! {
|
|
||||||
_ = tokio::time::sleep(Duration::from_millis(loop_delay as u64)) => {
|
|
||||||
ticks = ms_monotonic();
|
|
||||||
let actual_delay = ticks - loop_delay_start;
|
|
||||||
if actual_delay > ((loop_delay as i64) * 4_i64) {
|
|
||||||
l!(service.log, "likely sleep/wake detected due to excessive loop delay, cycling links...");
|
|
||||||
// TODO: handle likely sleep/wake or other system interruption
|
|
||||||
}
|
|
||||||
},
|
|
||||||
_ = interrupt_rx.next() => {
|
|
||||||
d!(service.log, "inner loop delay interrupted!");
|
|
||||||
if !service.run.load(Ordering::Relaxed) {
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
ticks = ms_monotonic();
|
|
||||||
},
|
|
||||||
_ = tokio::signal::ctrl_c() => {
|
|
||||||
l!(service.log, "exit signal received, shutting down...");
|
|
||||||
service.run.store(false, Ordering::Relaxed);
|
|
||||||
break;
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
if (ticks - last_checked_config) >= CONFIG_CHECK_INTERVAL {
|
|
||||||
last_checked_config = ticks;
|
|
||||||
|
|
||||||
let mut bindings_changed = false;
|
|
||||||
|
|
||||||
let _ = store.read_local_conf(true).map(|new_config| new_config.map(|new_config| {
|
|
||||||
d!(service.log, "local.conf changed on disk, reloading.");
|
|
||||||
service.set_local_config(new_config);
|
|
||||||
}));
|
|
||||||
|
|
||||||
let next_local_config = service.local_config();
|
|
||||||
if local_config.settings.primary_port != next_local_config.settings.primary_port {
|
|
||||||
loopback_http_listeners.0 = None;
|
|
||||||
loopback_http_listeners.1 = None;
|
|
||||||
bindings_changed = true;
|
|
||||||
}
|
|
||||||
if local_config.settings.log.max_size != next_local_config.settings.log.max_size {
|
|
||||||
service.log.set_max_size(next_local_config.settings.log.max_size);
|
|
||||||
}
|
|
||||||
if local_config.settings.log.stderr != next_local_config.settings.log.stderr {
|
|
||||||
service.log.set_log_to_stderr(next_local_config.settings.log.stderr);
|
|
||||||
}
|
|
||||||
if local_config.settings.log.debug != next_local_config.settings.log.debug {
|
|
||||||
service.log.set_debug(next_local_config.settings.log.debug);
|
|
||||||
}
|
|
||||||
local_config = next_local_config;
|
|
||||||
|
|
||||||
let mut loopback_dev_name = String::new();
|
|
||||||
let mut system_addrs: BTreeMap<InetAddress, String> = BTreeMap::new();
|
|
||||||
getifaddrs::for_each_address(|addr: &InetAddress, dev: &str| {
|
|
||||||
match addr.ip_scope() {
|
|
||||||
IpScope::Global | IpScope::Private | IpScope::PseudoPrivate | IpScope::Shared => {
|
|
||||||
if !local_config.settings.is_interface_blacklisted(dev) {
|
|
||||||
let mut a = addr.clone();
|
|
||||||
a.set_port(local_config.settings.primary_port);
|
|
||||||
system_addrs.insert(a, String::from(dev));
|
|
||||||
if local_config.settings.secondary_port.is_some() {
|
|
||||||
let mut a = addr.clone();
|
|
||||||
a.set_port(local_config.settings.secondary_port.unwrap());
|
|
||||||
system_addrs.insert(a, String::from(dev));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
IpScope::Loopback => {
|
|
||||||
if loopback_dev_name.is_empty() {
|
|
||||||
loopback_dev_name.push_str(dev);
|
|
||||||
}
|
|
||||||
},
|
|
||||||
_ => {},
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
// TODO: need to also inform the core about these IPs...
|
|
||||||
|
|
||||||
for k in udp_sockets.keys().filter_map(|a| if system_addrs.contains_key(a) { None } else { Some(a.clone()) }).collect::<Vec<InetAddress>>().iter() {
|
|
||||||
l!(service.log, "unbinding UDP socket at {} (address no longer exists on system or port has changed)", k.to_string());
|
|
||||||
udp_sockets.remove(k);
|
|
||||||
bindings_changed = true;
|
|
||||||
}
|
|
||||||
for a in system_addrs.iter() {
|
|
||||||
if !udp_sockets.contains_key(a.0) {
|
|
||||||
let _ = FastUDPSocket::new(a.1.as_str(), a.0, |raw_socket: &FastUDPRawOsSocket, from_address: &InetAddress, data: PacketBuffer| {
|
|
||||||
// TODO: incoming packet handler
|
|
||||||
}).map_or_else(|e| {
|
|
||||||
l!(service.log, "error binding UDP socket to {}: {}", a.0.to_string(), e.to_string());
|
|
||||||
}, |s| {
|
|
||||||
l!(service.log, "bound UDP socket at {}", a.0.to_string());
|
|
||||||
udp_sockets.insert(a.0.clone(), s);
|
|
||||||
bindings_changed = true;
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
let mut udp_primary_port_bind_failure = true;
|
|
||||||
let mut udp_secondary_port_bind_failure = local_config.settings.secondary_port.is_some();
|
|
||||||
for s in udp_sockets.iter() {
|
|
||||||
if s.0.port() == local_config.settings.primary_port {
|
|
||||||
udp_primary_port_bind_failure = false;
|
|
||||||
if !udp_secondary_port_bind_failure {
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if s.0.port() == local_config.settings.secondary_port.unwrap() {
|
|
||||||
udp_secondary_port_bind_failure = false;
|
|
||||||
if !udp_primary_port_bind_failure {
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if udp_primary_port_bind_failure {
|
|
||||||
if local_config.settings.auto_port_search {
|
|
||||||
// TODO: port hunting
|
|
||||||
} else {
|
|
||||||
l!(service.log, "WARNING: failed to bind to any address at primary port {}", local_config.settings.primary_port);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if udp_secondary_port_bind_failure {
|
|
||||||
if local_config.settings.auto_port_search {
|
|
||||||
// TODO: port hunting
|
|
||||||
} else {
|
|
||||||
l!(service.log, "WARNING: failed to bind to any address at secondary port {}", local_config.settings.secondary_port.unwrap_or(0));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
for k in http_listeners.keys().filter_map(|a| if system_addrs.contains_key(a) { None } else { Some(a.clone()) }).collect::<Vec<InetAddress>>().iter() {
|
|
||||||
l!(service.log, "closing HTTP listener at {} (address no longer exists on system or port has changed)", k.to_string());
|
|
||||||
http_listeners.remove(k);
|
|
||||||
bindings_changed = true;
|
|
||||||
}
|
|
||||||
for a in system_addrs.iter() {
|
|
||||||
if !http_listeners.contains_key(a.0) {
|
|
||||||
let sa = a.0.to_socketaddr();
|
|
||||||
if sa.is_some() {
|
|
||||||
let wl = HttpListener::new(a.1.as_str(), sa.unwrap(), &service).await.map_or_else(|e| {
|
|
||||||
l!(service.log, "error creating HTTP listener at {}: {}", a.0.to_string(), e.to_string());
|
|
||||||
}, |l| {
|
|
||||||
l!(service.log, "created HTTP listener at {}", a.0.to_string());
|
|
||||||
http_listeners.insert(a.0.clone(), l);
|
|
||||||
bindings_changed = true;
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if loopback_http_listeners.0.is_none() {
|
|
||||||
let _ = HttpListener::new(loopback_dev_name.as_str(), SocketAddr::new(IpAddr::from(Ipv4Addr::LOCALHOST), local_config.settings.primary_port), &service).await.map(|wl| {
|
|
||||||
loopback_http_listeners.0 = Some(wl);
|
|
||||||
let _ = store.write_uri(format!("http://127.0.0.1:{}/", local_config.settings.primary_port).as_str());
|
|
||||||
bindings_changed = true;
|
|
||||||
});
|
|
||||||
}
|
|
||||||
if loopback_http_listeners.1.is_none() {
|
|
||||||
let _ = HttpListener::new(loopback_dev_name.as_str(), SocketAddr::new(IpAddr::from(Ipv6Addr::LOCALHOST), local_config.settings.primary_port), &service).await.map(|wl| {
|
|
||||||
loopback_http_listeners.1 = Some(wl);
|
|
||||||
if loopback_http_listeners.0.is_none() {
|
|
||||||
let _ = store.write_uri(format!("http://[::1]:{}/", local_config.settings.primary_port).as_str());
|
|
||||||
}
|
|
||||||
bindings_changed = true;
|
|
||||||
});
|
|
||||||
}
|
|
||||||
if loopback_http_listeners.0.is_none() && loopback_http_listeners.1.is_none() {
|
|
||||||
// TODO: port hunting
|
|
||||||
l!(service.log, "CRITICAL: unable to create HTTP endpoint on 127.0.0.1/{} or ::1/{}, service control API will not work!", local_config.settings.primary_port, local_config.settings.primary_port);
|
|
||||||
}
|
|
||||||
|
|
||||||
if bindings_changed {
|
|
||||||
{
|
|
||||||
let mut udp_local_endpoints = service.udp_local_endpoints.lock().unwrap();
|
|
||||||
udp_local_endpoints.clear();
|
|
||||||
for ep in udp_sockets.iter() {
|
|
||||||
udp_local_endpoints.push(ep.0.clone());
|
|
||||||
}
|
|
||||||
udp_local_endpoints.sort();
|
|
||||||
}
|
|
||||||
{
|
|
||||||
let mut http_local_endpoints = service.http_local_endpoints.lock().unwrap();
|
|
||||||
http_local_endpoints.clear();
|
|
||||||
for ep in http_listeners.iter() {
|
|
||||||
http_local_endpoints.push(ep.0.clone());
|
|
||||||
}
|
|
||||||
if loopback_http_listeners.0.is_some() {
|
|
||||||
http_local_endpoints.push(InetAddress::new_ipv4_loopback(loopback_http_listeners.0.as_ref().unwrap().address.port()));
|
|
||||||
}
|
|
||||||
if loopback_http_listeners.1.is_some() {
|
|
||||||
http_local_endpoints.push(InetAddress::new_ipv6_loopback(loopback_http_listeners.1.as_ref().unwrap().address.port()));
|
|
||||||
}
|
|
||||||
http_local_endpoints.sort();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Run background task handler in ZeroTier core.
|
|
||||||
loop_delay = node.process_background_tasks(ms_since_epoch(), ticks);
|
|
||||||
}
|
|
||||||
|
|
||||||
l!(service.log, "shutting down normally.");
|
|
||||||
|
|
||||||
drop(udp_sockets);
|
|
||||||
drop(http_listeners);
|
|
||||||
drop(loopback_http_listeners);
|
|
||||||
drop(node);
|
|
||||||
drop(service);
|
|
||||||
|
|
||||||
process_exit_value
|
|
||||||
}
|
|
||||||
|
|
||||||
pub(crate) fn run(store: Arc<Store>) -> i32 {
|
|
||||||
let local_config = Arc::new(store.read_local_conf_or_default());
|
|
||||||
|
|
||||||
if store.auth_token(true).is_err() {
|
|
||||||
eprintln!("FATAL: error writing new web API authorization token (likely permission problem).");
|
|
||||||
return 1;
|
|
||||||
}
|
|
||||||
if store.write_pid().is_err() {
|
|
||||||
eprintln!("FATAL: error writing to directory '{}': unable to write zerotier.pid (likely permission problem).", store.base_path.to_str().unwrap());
|
|
||||||
return 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
let rt = tokio::runtime::Builder::new_current_thread().enable_all().build().unwrap();
|
|
||||||
let store2 = store.clone();
|
|
||||||
let process_exit_value = rt.block_on(async move { run_async(store2, local_config).await });
|
|
||||||
rt.shutdown_timeout(Duration::from_millis(500));
|
|
||||||
|
|
||||||
store.erase_pid();
|
|
||||||
|
|
||||||
process_exit_value
|
|
||||||
}
|
|
||||||
|
|
||||||
*/
|
|
|
@ -1,275 +0,0 @@
|
||||||
/* 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/
|
|
||||||
*/
|
|
||||||
|
|
||||||
use std::ffi::CString;
|
|
||||||
use std::io::{Read, Write};
|
|
||||||
use std::path::{Path, PathBuf};
|
|
||||||
use std::str::FromStr;
|
|
||||||
use std::sync::Mutex;
|
|
||||||
|
|
||||||
use crate::localconfig::LocalConfig;
|
|
||||||
|
|
||||||
use zerotier_network_hypervisor::vl1::identity::NetworkId;
|
|
||||||
|
|
||||||
const ZEROTIER_PID: &'static str = "zerotier.pid";
|
|
||||||
const ZEROTIER_URI: &'static str = "zerotier.uri";
|
|
||||||
const LOCAL_CONF: &'static str = "local.conf";
|
|
||||||
const AUTHTOKEN_SECRET: &'static str = "authtoken.secret";
|
|
||||||
const SERVICE_LOG: &'static str = "service.log";
|
|
||||||
|
|
||||||
#[derive(Clone, Copy)]
|
|
||||||
pub enum StateObjectType {
|
|
||||||
IdentityPublic,
|
|
||||||
IdentitySecret,
|
|
||||||
NetworkConfig,
|
|
||||||
Peer,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[cfg(any(target_os = "macos", target_os = "ios"))]
|
|
||||||
pub fn platform_default_home_path() -> String {
|
|
||||||
"/Library/Application Support/ZeroTier".into_string()
|
|
||||||
}
|
|
||||||
|
|
||||||
/// In-filesystem data store for configuration and objects.
|
|
||||||
pub(crate) struct Store {
|
|
||||||
pub base_path: Box<Path>,
|
|
||||||
pub default_log_path: Box<Path>,
|
|
||||||
previous_local_config_on_disk: Mutex<String>,
|
|
||||||
peers_path: Box<Path>,
|
|
||||||
controller_path: Box<Path>,
|
|
||||||
networks_path: Box<Path>,
|
|
||||||
auth_token_path: Mutex<Box<Path>>,
|
|
||||||
auth_token: Mutex<String>,
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Restrict file permissions using OS-specific code in osdep/OSUtils.cpp.
|
|
||||||
pub fn lock_down_file(path: &str) {
|
|
||||||
// TODO: need both Windows and Unix implementations
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Store {
|
|
||||||
const MAX_OBJECT_SIZE: usize = 262144; // sanity limit
|
|
||||||
|
|
||||||
pub fn new(base_path: &str, auth_token_path_override: &Option<String>, auth_token_override: &Option<String>) -> std::io::Result<Store> {
|
|
||||||
let bp = Path::new(base_path);
|
|
||||||
let _ = std::fs::create_dir_all(bp);
|
|
||||||
let md = bp.metadata()?;
|
|
||||||
if !md.is_dir() || md.permissions().readonly() {
|
|
||||||
return Err(std::io::Error::new(std::io::ErrorKind::PermissionDenied, "base path does not exist or is not writable"));
|
|
||||||
}
|
|
||||||
|
|
||||||
let s = Store {
|
|
||||||
base_path: bp.to_path_buf().into_boxed_path(),
|
|
||||||
default_log_path: bp.join(SERVICE_LOG).into_boxed_path(),
|
|
||||||
previous_local_config_on_disk: Mutex::new(String::new()),
|
|
||||||
peers_path: bp.join("peers.d").into_boxed_path(),
|
|
||||||
controller_path: bp.join("controller.d").into_boxed_path(),
|
|
||||||
networks_path: bp.join("networks.d").into_boxed_path(),
|
|
||||||
auth_token_path: Mutex::new(auth_token_path_override.map_or_else(|| bp.join(AUTHTOKEN_SECRET).into_boxed_path(), |auth_token_path_override| PathBuf::from(auth_token_path_override).into_boxed_path())),
|
|
||||||
auth_token: Mutex::new(auth_token_override.map_or_else(|| String::new(), |auth_token_override| auth_token_override)),
|
|
||||||
};
|
|
||||||
|
|
||||||
let _ = std::fs::create_dir_all(&s.peers_path);
|
|
||||||
let _ = std::fs::create_dir_all(&s.controller_path);
|
|
||||||
let _ = std::fs::create_dir_all(&s.networks_path);
|
|
||||||
|
|
||||||
Ok(s)
|
|
||||||
}
|
|
||||||
|
|
||||||
fn make_obj_path_internal(&self, obj_type: StateObjectType, obj_id: &[u64]) -> Option<PathBuf> {
|
|
||||||
match obj_type {
|
|
||||||
StateObjectType::IdentityPublic => Some(self.base_path.join("identity.public")),
|
|
||||||
StateObjectType::IdentitySecret => Some(self.base_path.join("identity.secret")),
|
|
||||||
StateObjectType::NetworkConfig => {
|
|
||||||
if obj_id.len() < 1 {
|
|
||||||
None
|
|
||||||
} else {
|
|
||||||
Some(self.networks_path.join(format!("{:0>16x}.conf", obj_id[0])))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
StateObjectType::Peer => {
|
|
||||||
if obj_id.len() < 1 {
|
|
||||||
None
|
|
||||||
} else {
|
|
||||||
Some(self.peers_path.join(format!("{:0>10x}.peer", obj_id[0])))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn read_internal(&self, path: PathBuf) -> std::io::Result<Vec<u8>> {
|
|
||||||
let fmd = path.metadata()?;
|
|
||||||
if fmd.is_file() {
|
|
||||||
let flen = fmd.len();
|
|
||||||
if flen <= Store::MAX_OBJECT_SIZE as u64 {
|
|
||||||
let mut f = std::fs::File::open(path)?;
|
|
||||||
let mut buf: Vec<u8> = Vec::new();
|
|
||||||
buf.reserve(flen as usize);
|
|
||||||
let rs = f.read_to_end(&mut buf)?;
|
|
||||||
buf.resize(rs as usize, 0);
|
|
||||||
return Ok(buf);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Err(std::io::Error::new(std::io::ErrorKind::NotFound, "does not exist or is not readable"))
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn auth_token(&self, generate_if_missing: bool) -> std::io::Result<String> {
|
|
||||||
let mut token = self.auth_token.lock().unwrap();
|
|
||||||
if token.is_empty() {
|
|
||||||
let p = self.auth_token_path.lock().unwrap();
|
|
||||||
let ps = p.to_str().unwrap();
|
|
||||||
|
|
||||||
let token2 = self.read_file(ps).map_or(String::new(), |sb| String::from_utf8(sb).unwrap_or(String::new()).trim().to_string());
|
|
||||||
if token2.is_empty() {
|
|
||||||
if generate_if_missing {
|
|
||||||
let mut rb = [0_u8; 32];
|
|
||||||
zerotier_core_crypto::random::fill_bytes_secure(&mut rb);
|
|
||||||
token.reserve(rb.len());
|
|
||||||
for b in rb.iter() {
|
|
||||||
if *b > 127_u8 {
|
|
||||||
token.push((65 + (*b % 26)) as char); // A..Z
|
|
||||||
} else {
|
|
||||||
token.push((97 + (*b % 26)) as char); // a..z
|
|
||||||
}
|
|
||||||
}
|
|
||||||
let res = self.write_file(ps, token.as_bytes());
|
|
||||||
if res.is_err() {
|
|
||||||
token.clear();
|
|
||||||
Err(res.err().unwrap())
|
|
||||||
} else {
|
|
||||||
lock_down_file(ps);
|
|
||||||
Ok(token.clone())
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
Err(std::io::Error::new(std::io::ErrorKind::NotFound, ""))
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
*token = token2;
|
|
||||||
Ok(token.clone())
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
Ok(token.clone())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn list_joined_networks(&self) -> Vec<NetworkId> {
|
|
||||||
let mut list: Vec<NetworkId> = Vec::new();
|
|
||||||
let d = std::fs::read_dir(self.networks_path.as_ref());
|
|
||||||
if d.is_ok() {
|
|
||||||
for de in d.unwrap() {
|
|
||||||
if de.is_ok() {
|
|
||||||
let nn = de.unwrap().file_name();
|
|
||||||
let n = nn.to_str().unwrap_or("");
|
|
||||||
if n.len() == 21 && n.ends_with(".conf") {
|
|
||||||
// ################.conf
|
|
||||||
let nwid = u64::from_str_radix(&n[0..16], 16);
|
|
||||||
if nwid.is_ok() {
|
|
||||||
list.push(NetworkId(nwid.unwrap()));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
list
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn read_file(&self, fname: &str) -> std::io::Result<Vec<u8>> {
|
|
||||||
self.read_internal(self.base_path.join(fname))
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn read_file_str(&self, fname: &str) -> std::io::Result<String> {
|
|
||||||
let data = self.read_file(fname)?;
|
|
||||||
let data = String::from_utf8(data);
|
|
||||||
if data.is_err() {
|
|
||||||
return Err(std::io::Error::new(std::io::ErrorKind::InvalidData, data.err().unwrap().to_string()));
|
|
||||||
}
|
|
||||||
Ok(data.unwrap())
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn write_file(&self, fname: &str, data: &[u8]) -> std::io::Result<()> {
|
|
||||||
std::fs::OpenOptions::new().write(true).truncate(true).create(true).open(self.base_path.join(fname))?.write_all(data)
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn read_local_conf(&self, skip_if_unchanged: bool) -> Option<std::io::Result<LocalConfig>> {
|
|
||||||
let data = self.read_file_str(LOCAL_CONF);
|
|
||||||
if data.is_err() {
|
|
||||||
return Some(Err(data.err().unwrap()));
|
|
||||||
}
|
|
||||||
let data = data.unwrap();
|
|
||||||
if skip_if_unchanged {
|
|
||||||
let mut prev = self.previous_local_config_on_disk.lock().unwrap();
|
|
||||||
if prev.eq(&data) {
|
|
||||||
return None;
|
|
||||||
}
|
|
||||||
*prev = data.clone();
|
|
||||||
} else {
|
|
||||||
*(self.previous_local_config_on_disk.lock().unwrap()) = data.clone();
|
|
||||||
}
|
|
||||||
let lc = serde_json::from_str::<LocalConfig>(data.as_str());
|
|
||||||
if lc.is_err() {
|
|
||||||
return Some(Err(std::io::Error::new(std::io::ErrorKind::InvalidData, lc.err().unwrap())));
|
|
||||||
}
|
|
||||||
Some(Ok(lc.unwrap()))
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn read_local_conf_or_default(&self) -> LocalConfig {
|
|
||||||
let lc = self.read_local_conf(false);
|
|
||||||
if lc.is_some() {
|
|
||||||
let lc = lc.unwrap();
|
|
||||||
if lc.is_ok() {
|
|
||||||
return lc.unwrap();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
LocalConfig::default()
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn write_local_conf(&self, lc: &LocalConfig) -> std::io::Result<()> {
|
|
||||||
let json = serde_json::to_string(lc).unwrap();
|
|
||||||
self.write_file(LOCAL_CONF, json.as_bytes())
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn write_pid(&self) -> std::io::Result<()> {
|
|
||||||
let pid = unsafe { libc::getpid() }.to_string();
|
|
||||||
self.write_file(ZEROTIER_PID, pid.as_bytes())
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn erase_pid(&self) {
|
|
||||||
let _ = std::fs::remove_file(self.base_path.join(ZEROTIER_PID));
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn load_object(&self, obj_type: StateObjectType, obj_id: &[u64]) -> std::io::Result<Vec<u8>> {
|
|
||||||
let obj_path = self.make_obj_path_internal(obj_type, obj_id);
|
|
||||||
if obj_path.is_some() {
|
|
||||||
return self.read_internal(obj_path.unwrap());
|
|
||||||
}
|
|
||||||
Err(std::io::Error::new(std::io::ErrorKind::NotFound, "does not exist or is not readable"))
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn erase_object(&self, obj_type: StateObjectType, obj_id: &[u64]) {
|
|
||||||
let obj_path = self.make_obj_path_internal(obj_type, obj_id);
|
|
||||||
if obj_path.is_some() {
|
|
||||||
let _ = std::fs::remove_file(obj_path.unwrap());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn store_object(&self, obj_type: StateObjectType, obj_id: &[u64], obj_data: &[u8]) -> std::io::Result<()> {
|
|
||||||
let obj_path = self.make_obj_path_internal(obj_type, obj_id);
|
|
||||||
if obj_path.is_some() {
|
|
||||||
let obj_path = obj_path.unwrap();
|
|
||||||
std::fs::OpenOptions::new().write(true).truncate(true).create(true).open(&obj_path)?.write_all(obj_data)?;
|
|
||||||
|
|
||||||
if obj_type.is_secret() {
|
|
||||||
lock_down_file(obj_path.to_str().unwrap());
|
|
||||||
}
|
|
||||||
|
|
||||||
Ok(())
|
|
||||||
} else {
|
|
||||||
Err(std::io::Error::new(std::io::ErrorKind::InvalidInput, "object type or ID not valid"))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -6,30 +6,37 @@
|
||||||
* https://www.zerotier.com/
|
* https://www.zerotier.com/
|
||||||
*/
|
*/
|
||||||
|
|
||||||
use std::borrow::Borrow;
|
|
||||||
use std::fs::File;
|
|
||||||
use std::io::Read;
|
|
||||||
use std::path::Path;
|
|
||||||
use std::str::FromStr;
|
use std::str::FromStr;
|
||||||
use std::time::UNIX_EPOCH;
|
use std::time::{Instant, SystemTime, UNIX_EPOCH};
|
||||||
|
|
||||||
use serde::de::DeserializeOwned;
|
use serde::de::DeserializeOwned;
|
||||||
use serde::Serialize;
|
use serde::Serialize;
|
||||||
|
|
||||||
use zerotier_core_crypto::hex;
|
use lazy_static::lazy_static;
|
||||||
|
|
||||||
|
use tokio::fs::File;
|
||||||
|
use tokio::io::{AsyncRead, AsyncReadExt};
|
||||||
|
|
||||||
use zerotier_network_hypervisor::vl1::Identity;
|
use zerotier_network_hypervisor::vl1::Identity;
|
||||||
|
|
||||||
//use crate::osdep;
|
lazy_static! {
|
||||||
|
static ref STARTUP_INSTANT: Instant = Instant::now();
|
||||||
pub fn ms_since_epoch() -> i64 {
|
|
||||||
std::time::SystemTime::now().duration_since(UNIX_EPOCH).unwrap().as_millis() as i64
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn ms_monotonic() -> i64 {}
|
/// Get milliseconds since unix epoch.
|
||||||
|
pub fn ms_since_epoch() -> i64 {
|
||||||
|
SystemTime::now().duration_since(UNIX_EPOCH).unwrap().as_millis() as i64
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Get milliseconds since an arbitrary time in the past, guaranteed to monotonically increase.
|
||||||
|
pub fn ms_monotonic() -> i64 {
|
||||||
|
Instant::now().duration_since(*STARTUP_INSTANT).as_millis() as i64
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns true if the string starts with [yY1tT] or false for [nN0fF].
|
||||||
pub fn parse_bool(v: &str) -> Result<bool, String> {
|
pub fn parse_bool(v: &str) -> Result<bool, String> {
|
||||||
if !v.is_empty() {
|
if !v.is_empty() {
|
||||||
match v.chars().next().unwrap() {
|
match v.trim().chars().next().unwrap() {
|
||||||
'y' | 'Y' | '1' | 't' | 'T' => {
|
'y' | 'Y' | '1' | 't' | 'T' => {
|
||||||
return Ok(true);
|
return Ok(true);
|
||||||
}
|
}
|
||||||
|
@ -42,78 +49,20 @@ pub fn parse_bool(v: &str) -> Result<bool, String> {
|
||||||
Err(format!("invalid boolean value: '{}'", v))
|
Err(format!("invalid boolean value: '{}'", v))
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn is_valid_bool(v: String) -> Result<(), String> {
|
/// Returns a non-error if a string is a valid boolean.
|
||||||
parse_bool(v.as_str()).map(|_| ())
|
pub fn is_valid_bool(v: &str) -> Result<(), String> {
|
||||||
|
parse_bool(v).map(|_| ())
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn is_valid_port(v: String) -> Result<(), String> {
|
/// Returns a non-error if the string is a valid port number.
|
||||||
let i = u16::from_str(v.as_str()).unwrap_or(0);
|
pub fn is_valid_port(v: &str) -> Result<(), String> {
|
||||||
if i >= 1 {
|
let i = isize::from_str(v).unwrap_or(0);
|
||||||
|
if i >= 0x0001 && i <= 0xffff {
|
||||||
return Ok(());
|
return Ok(());
|
||||||
}
|
}
|
||||||
Err(format!("invalid TCP/IP port number: {}", v))
|
Err(format!("invalid TCP/IP port number: {}", v))
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Convenience function to read up to limit bytes from a file.
|
|
||||||
/// If the file is larger than limit, the excess is not read.
|
|
||||||
pub fn read_limit<P: AsRef<Path>>(path: P, limit: usize) -> std::io::Result<Vec<u8>> {
|
|
||||||
let mut v: Vec<u8> = Vec::new();
|
|
||||||
let _ = File::open(path)?.take(limit as u64).read_to_end(&mut v)?;
|
|
||||||
Ok(v)
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Read an identity as either a literal or from a file.
|
|
||||||
pub fn parse_cli_identity(input: &str, validate: bool) -> Result<Identity, String> {
|
|
||||||
let parse_func = |s: &str| {
|
|
||||||
Identity::new_from_string(s).map_or_else(
|
|
||||||
|e| Err(format!("invalid identity: {}", e.to_str())),
|
|
||||||
|id| {
|
|
||||||
if !validate || id.validate() {
|
|
||||||
Ok(id)
|
|
||||||
} else {
|
|
||||||
Err(String::from("invalid identity: local validation failed"))
|
|
||||||
}
|
|
||||||
},
|
|
||||||
)
|
|
||||||
};
|
|
||||||
if Path::new(input).exists() {
|
|
||||||
read_limit(input, 16384).map_or_else(|e| Err(e.to_string()), |v| String::from_utf8(v).map_or_else(|e| Err(e.to_string()), |s| parse_func(s.as_str())))
|
|
||||||
} else {
|
|
||||||
parse_func(input)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Create a new HTTP authorization nonce by encrypting the current time.
|
|
||||||
/// The key used to encrypt the current time is random and is re-created for
|
|
||||||
/// each execution of the process. By decrypting this nonce when it is returned,
|
|
||||||
/// the client and server may check the age of a digest auth exchange.
|
|
||||||
pub fn create_http_auth_nonce(timestamp: i64) -> String {
|
|
||||||
let mut nonce_plaintext: [u64; 2] = [timestamp as u64, timestamp as u64];
|
|
||||||
unsafe {
|
|
||||||
//osdep::encryptHttpAuthNonce(nonce_plaintext.as_mut_ptr().cast());
|
|
||||||
hex::to_string(&nonce_plaintext.as_ptr().cast::<[u8]>())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Decrypt HTTP auth nonce encrypted by this process and return the timestamp.
|
|
||||||
/// This returns zero if the input was not valid.
|
|
||||||
pub fn decrypt_http_auth_nonce(nonce: &str) -> i64 {
|
|
||||||
let nonce = hex::from_string(nonce.trim());
|
|
||||||
if !nonce.is_err() {
|
|
||||||
let mut nonce = nonce.unwrap();
|
|
||||||
if nonce.len() == 16 {
|
|
||||||
unsafe {
|
|
||||||
//osdep::decryptHttpAuthNonce(nonce.as_mut_ptr().cast());
|
|
||||||
let nonce = *nonce.as_ptr().cast::<[u64; 2]>();
|
|
||||||
if nonce[0] == nonce[1] {
|
|
||||||
return nonce[0] as i64;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Shortcut to use serde_json to serialize an object, returns "null" on error.
|
/// Shortcut to use serde_json to serialize an object, returns "null" on error.
|
||||||
pub fn to_json<O: serde::Serialize>(o: &O) -> String {
|
pub fn to_json<O: serde::Serialize>(o: &O) -> String {
|
||||||
serde_json::to_string(o).unwrap_or("null".into())
|
serde_json::to_string(o).unwrap_or("null".into())
|
||||||
|
@ -125,6 +74,7 @@ pub fn to_json_pretty<O: serde::Serialize>(o: &O) -> String {
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Recursively patch a JSON object.
|
/// Recursively patch a JSON object.
|
||||||
|
///
|
||||||
/// This is slightly different from a usual JSON merge. For objects in the target their fields
|
/// This is slightly different from a usual JSON merge. For objects in the target their fields
|
||||||
/// are updated by recursively calling json_patch if the same field is present in the source.
|
/// are updated by recursively calling json_patch if the same field is present in the source.
|
||||||
/// If the source tries to set an object to something other than another object, this is ignored.
|
/// If the source tries to set an object to something other than another object, this is ignored.
|
||||||
|
@ -133,7 +83,7 @@ pub fn to_json_pretty<O: serde::Serialize>(o: &O) -> String {
|
||||||
pub fn json_patch(target: &mut serde_json::value::Value, source: &serde_json::value::Value, depth_limit: usize) {
|
pub fn json_patch(target: &mut serde_json::value::Value, source: &serde_json::value::Value, depth_limit: usize) {
|
||||||
if target.is_object() {
|
if target.is_object() {
|
||||||
if source.is_object() {
|
if source.is_object() {
|
||||||
let mut target = target.as_object_mut().unwrap();
|
let target = target.as_object_mut().unwrap();
|
||||||
let source = source.as_object().unwrap();
|
let source = source.as_object().unwrap();
|
||||||
for kv in target.iter_mut() {
|
for kv in target.iter_mut() {
|
||||||
let _ = source.get(kv.0).map(|new_value| {
|
let _ = source.get(kv.0).map(|new_value| {
|
||||||
|
@ -154,13 +104,14 @@ pub fn json_patch(target: &mut serde_json::value::Value, source: &serde_json::va
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Patch a serializable object with the fields present in a JSON object.
|
/// Patch a serializable object with the fields present in a JSON object.
|
||||||
|
///
|
||||||
/// If there are no changes, None is returned. The depth limit is passed through to json_patch and
|
/// If there are no changes, None is returned. The depth limit is passed through to json_patch and
|
||||||
/// should be set to a sanity check value to prevent overflows.
|
/// should be set to a sanity check value to prevent overflows.
|
||||||
pub fn json_patch_object<O: Serialize + DeserializeOwned + Eq>(obj: O, patch: &str, depth_limit: usize) -> Result<Option<O>, serde_json::Error> {
|
pub fn json_patch_object<O: Serialize + DeserializeOwned + Eq>(obj: O, patch: &str, depth_limit: usize) -> Result<Option<O>, serde_json::Error> {
|
||||||
serde_json::from_str::<serde_json::value::Value>(patch).map_or_else(
|
serde_json::from_str::<serde_json::value::Value>(patch).map_or_else(
|
||||||
|e| Err(e),
|
|e| Err(e),
|
||||||
|patch| {
|
|patch| {
|
||||||
serde_json::value::to_value(obj.borrow()).map_or_else(
|
serde_json::value::to_value(&obj).map_or_else(
|
||||||
|e| Err(e),
|
|e| Err(e),
|
||||||
|mut obj_value| {
|
|mut obj_value| {
|
||||||
json_patch(&mut obj_value, &patch, depth_limit);
|
json_patch(&mut obj_value, &patch, depth_limit);
|
||||||
|
@ -180,6 +131,44 @@ pub fn json_patch_object<O: Serialize + DeserializeOwned + Eq>(obj: O, patch: &s
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Convenience function to read up to limit bytes from a file.
|
||||||
|
///
|
||||||
|
/// If the file is larger than limit, the excess is not read.
|
||||||
|
pub async fn read_limit(path: &str, limit: usize) -> std::io::Result<Vec<u8>> {
|
||||||
|
let mut f = File::open(path).await?;
|
||||||
|
let bytes = f.metadata().await?.len().min(limit as u64) as usize;
|
||||||
|
let mut v: Vec<u8> = Vec::with_capacity(bytes);
|
||||||
|
v.resize(bytes, 0);
|
||||||
|
f.read_exact(v.as_mut_slice()).await?;
|
||||||
|
Ok(v)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns true if the file exists and is a regular file (or a link to one).
|
||||||
|
pub async fn file_exists(path: &str) -> bool {
|
||||||
|
tokio::fs::metadata(path).await.is_ok()
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Read an identity as either a literal or from a file.
|
||||||
|
pub async fn parse_cli_identity(input: &str, validate: bool) -> Result<Identity, String> {
|
||||||
|
let parse_func = |s: &str| {
|
||||||
|
Identity::from_str(s).map_or_else(
|
||||||
|
|e| Err(format!("invalid identity: {}", e.to_string())),
|
||||||
|
|id| {
|
||||||
|
if !validate || id.validate_identity() {
|
||||||
|
Ok(id)
|
||||||
|
} else {
|
||||||
|
Err(String::from("invalid identity: local validation failed"))
|
||||||
|
}
|
||||||
|
},
|
||||||
|
)
|
||||||
|
};
|
||||||
|
if file_exists(input).await {
|
||||||
|
read_limit(input, 16384).await.map_or_else(|e| Err(e.to_string()), |v| String::from_utf8(v).map_or_else(|e| Err(e.to_string()), |s| parse_func(s.as_str())))
|
||||||
|
} else {
|
||||||
|
parse_func(input)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod tests {
|
||||||
use crate::utils::ms_monotonic;
|
use crate::utils::ms_monotonic;
|
||||||
|
|
|
@ -6,8 +6,8 @@
|
||||||
* https://www.zerotier.com/
|
* https://www.zerotier.com/
|
||||||
*/
|
*/
|
||||||
|
|
||||||
mod common;
|
//mod common;
|
||||||
mod vnic;
|
mod vnic;
|
||||||
|
|
||||||
#[cfg(target_os = "macos")]
|
//#[cfg(target_os = "macos")]
|
||||||
mod mac_feth_tap;
|
//mod mac_feth_tap;
|
||||||
|
|
Loading…
Add table
Reference in a new issue