Remove some outdated stuff in service, upgrade clap, get it building again.

This commit is contained in:
Adam Ierymenko 2022-05-05 18:10:30 -04:00
parent 40156fd1f3
commit a78b23cf45
No known key found for this signature in database
GPG key ID: C8877CF2D7A5D7F3
12 changed files with 370 additions and 1506 deletions

View file

@ -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);
}
}
}

View file

@ -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/

View file

@ -27,6 +27,17 @@ dependencies = [
"critical-section",
]
[[package]]
name = "atty"
version = "0.2.14"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d9b39be18770d11421cdb1b9947a45dd3f37e93092cbf377614828a319d5fee8"
dependencies = [
"hermit-abi",
"libc",
"winapi",
]
[[package]]
name = "autocfg"
version = "1.1.0"
@ -87,6 +98,12 @@ version = "1.4.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "14c189c53d098945499cdfa7ecc63567cf3886b3332b312a5b4585d8d3a6a610"
[[package]]
name = "bytes"
version = "1.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c4872d67bab6358e59559027aa3b9157c53d9358c51423c17554809a8858e0f8"
[[package]]
name = "cc"
version = "1.0.73"
@ -99,6 +116,30 @@ version = "1.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
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]]
name = "cortex-m"
version = "0.7.4"
@ -152,7 +193,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "391b56fbd302e585b7a9494fb70e40949567b1cf9003a8e4a6041a1687c26573"
dependencies = [
"cfg-if",
"hashbrown",
"hashbrown 0.12.0",
"lock_api",
]
@ -231,7 +272,7 @@ checksum = "8fc3cb4d91f53b50155bdcfd23f6a4c39ae1969c2ae85982b135750cccaf5fce"
dependencies = [
"cfg-if",
"libc",
"wasi",
"wasi 0.9.0+wasi-snapshot-preview1",
]
[[package]]
@ -243,6 +284,12 @@ dependencies = [
"byteorder",
]
[[package]]
name = "hashbrown"
version = "0.11.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ab5ef0d4909ef3724cc8cce6ccc8572c5c817592e9285f5464f8e86f8bd3726e"
[[package]]
name = "hashbrown"
version = "0.12.0"
@ -270,6 +317,16 @@ dependencies = [
"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]]
name = "itoa"
version = "1.0.1"
@ -298,6 +355,15 @@ dependencies = [
"scopeguard",
]
[[package]]
name = "log"
version = "0.4.17"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "abb12e687cfb44aa40f41fc3978ef76448f9b6038cad6aef4259d3c095a2382e"
dependencies = [
"cfg-if",
]
[[package]]
name = "lz4_flex"
version = "0.9.2"
@ -319,6 +385,29 @@ version = "1.0.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
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]]
name = "nb"
version = "0.1.3"
@ -334,6 +423,15 @@ version = "1.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "546c37ac5d9e56f55e73b677106873d9d9f5190605e41a856503623648488cae"
[[package]]
name = "ntapi"
version = "0.3.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c28774a7fd2fbb4f0babd8237ce554b73af68021b5f695a3cebd6c59bac0980f"
dependencies = [
"winapi",
]
[[package]]
name = "num-traits"
version = "0.2.14"
@ -392,6 +490,12 @@ dependencies = [
"vcpkg",
]
[[package]]
name = "os_str_bytes"
version = "6.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8e22443d1643a904602595ba1cd8f7d896afe56d26712531c5ff73a15b2fbf64"
[[package]]
name = "parking_lot"
version = "0.12.0"
@ -415,6 +519,12 @@ dependencies = [
"windows-sys",
]
[[package]]
name = "pin-project-lite"
version = "0.2.9"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e0a7ae3ac2f1173085d398531c705756c94a4c56843785df85a60c1a0afac116"
[[package]]
name = "pkg-config"
version = "0.3.25"
@ -624,6 +734,15 @@ dependencies = [
"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]]
name = "signature"
version = "1.5.0"
@ -636,6 +755,16 @@ version = "1.8.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
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]]
name = "spin"
version = "0.9.2"
@ -657,6 +786,12 @@ version = "1.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a2eb9349b6444b326872e140eb1cf5e7c522154d69e7a0ffb0fb81c06b37543f"
[[package]]
name = "strsim"
version = "0.10.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "73473c0e59e6d5812c5dfe2a064a6444949f089e20eec9a2e5506596494e4623"
[[package]]
name = "subtle"
version = "2.4.1"
@ -686,6 +821,52 @@ dependencies = [
"unicode-xid",
]
[[package]]
name = "termcolor"
version = "1.1.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bab24d30b911b2376f3a13cc2cd443142f0c81dda04c118693e35b3835757755"
dependencies = [
"winapi-util",
]
[[package]]
name = "textwrap"
version = "0.15.0"
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]]
name = "twox-hash"
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"
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]]
name = "winapi"
version = "0.3.9"
@ -773,6 +960,15 @@ version = "0.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6"
[[package]]
name = "winapi-util"
version = "0.1.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "70ec6ce85bb158151cae5e5c87f95a8e97d2c0c4b001223f33a334e3ce5de178"
dependencies = [
"winapi",
]
[[package]]
name = "winapi-x86_64-pc-windows-gnu"
version = "0.4.0"
@ -890,13 +1086,14 @@ dependencies = [
name = "zerotier-system-service"
version = "0.1.0"
dependencies = [
"clap",
"lazy_static",
"libc",
"num-traits",
"num_cpus",
"parking_lot",
"serde",
"serde_json",
"tokio",
"winapi",
"zerotier-core-crypto",
"zerotier-network-hypervisor",

View file

@ -12,14 +12,16 @@ codegen-units = 1
panic = 'abort'
[dependencies]
num-traits = "^0"
zerotier-network-hypervisor = { path = "../zerotier-network-hypervisor" }
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_json = { version = "^1", features = [], default-features = false }
num_cpus = "^1"
serde_json = { version = "^1", features = ["std"], default-features = false }
parking_lot = "^0"
lazy_static = "^1"
clap = "^3"
#async-trait = "^0"
[target."cfg(windows)".dependencies]
winapi = { version = "^0", features = ["handleapi", "ws2ipdef", "ws2tcpip"] }

View file

@ -10,16 +10,16 @@ use std::collections::BTreeMap;
use serde::{Deserialize, Serialize};
use zerotier_network_hypervisor::vl1::identity::NetworkId;
use zerotier_network_hypervisor::vl1::{Address, InetAddress};
use zerotier_network_hypervisor::vl2::NetworkId;
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,
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,
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,
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,
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,
1009, 1023,
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, 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, 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, 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, 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, 1009, 1023,
];
pub const DEFAULT_PORT: u16 = 9993;

View file

@ -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))
}
}
}
*/

View file

@ -7,30 +7,20 @@
*/
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 crate::store::platform_default_home_path;
mod fastudpsocket;
mod getifaddrs;
mod localconfig;
#[macro_use]
mod log;
mod service;
mod store;
mod utils;
mod vnic;
pub const HTTP_API_OBJECT_SIZE_LIMIT: usize = 131072;
pub mod getifaddrs;
pub mod localconfig;
pub mod utils;
pub mod vnic;
fn make_help(long_help: bool) -> String {
format!(
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)
Usage: zerotier [-...] <command> [command args]
@ -45,7 +35,6 @@ Global Options:
Common Operations:
help Show this help
longhelp Show help with advanced commands
oldhelp Show v1.x legacy commands
version Print version (of this binary)
@ -126,111 +115,100 @@ pub struct GlobalCommandLineFlags {
pub auth_token_override: Option<String>,
}
fn main() {
let cli_args = Box::new({
let help = make_help(false);
let args = App::new("zerotier")
.arg(Arg::with_name("json").short("j"))
.arg(Arg::with_name("path").short("p").takes_value(true))
.arg(Arg::with_name("token_path").short("t").takes_value(true))
.arg(Arg::with_name("token").short("T").takes_value(true))
.subcommand(App::new("help"))
.subcommand(App::new("version"))
.subcommand(App::new("status"))
.subcommand(
App::new("set")
.subcommand(App::new("port").arg(Arg::with_name("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(
App::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(App::new("if").arg(Arg::with_name("prefix").index(1)).arg(Arg::with_name("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(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(
App::new("network")
.subcommand(App::new("show").arg(Arg::with_name("nwid").index(1).required(true)))
.subcommand(App::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(App::new("join").arg(Arg::with_name("nwid").index(1).required(true)))
.subcommand(App::new("leave").arg(Arg::with_name("nwid").index(1).required(true)))
.subcommand(App::new("service"))
.subcommand(
App::new("controller")
.subcommand(App::new("list"))
.subcommand(App::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(App::new("show").arg(Arg::with_name("id").index(1).required(true)).arg(Arg::with_name("member").index(2)))
.subcommand(App::new("auth").arg(Arg::with_name("member").index(1).required(true)))
.subcommand(App::new("deauth").arg(Arg::with_name("member").index(1).required(true))),
)
.subcommand(
App::new("identity")
.subcommand(App::new("new").arg(Arg::with_name("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(App::new("fingerprint").arg(Arg::with_name("identity").index(1).required(true)))
.subcommand(App::new("validate").arg(Arg::with_name("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(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))),
)
.help(help.as_str())
.get_matches_from_safe(std::env::args());
if args.is_err() {
let e = args.err().unwrap();
if e.kind != ErrorKind::HelpDisplayed {
print_help(false);
#[cfg(any(target_os = "macos"))]
pub fn platform_default_home_path() -> String {
"/Library/Application Support/ZeroTier".into()
}
std::process::exit(1);
}
let args = args.unwrap();
if args.subcommand_name().is_none() {
print_help(false);
std::process::exit(1);
}
args
});
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.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()),
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()),
};
std::process::exit({
match cli_args.subcommand() {
("help", None) => {
return match cli_args.subcommand() {
Some(("help", _)) => {
print_help(false);
0
}
("longhelp", None) => {
print_help(true);
0
}
("oldhelp", None) => todo!(),
("version", None) => {
Some(("oldhelp", _)) => todo!(),
Some(("version", _)) => {
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!(),
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() {
let cli_args = Box::new({
let help = make_help(false);
Command::new("zerotier")
.arg(Arg::new("json").short('j'))
.arg(Arg::new("path").short('p').takes_value(true))
.arg(Arg::new("token_path").short('t').takes_value(true))
.arg(Arg::new("token").short('T').takes_value(true))
.subcommand(Command::new("help"))
.subcommand(Command::new("version"))
.subcommand(Command::new("status"))
.subcommand(
Command::new("set")
.subcommand(Command::new("port").arg(Arg::new("port#").index(1).validator(utils::is_valid_port)))
.subcommand(Command::new("secondaryport").arg(Arg::new("port#").index(1).validator(utils::is_valid_port)))
.subcommand(
Command::new("blacklist")
.subcommand(Command::new("cidr").arg(Arg::new("ip_bits").index(1)).arg(Arg::new("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(Command::new("portmap").arg(Arg::new("boolean").index(1).validator(utils::is_valid_bool))),
)
.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(
Command::new("network")
.subcommand(Command::new("show").arg(Arg::new("nwid").index(1).required(true)))
.subcommand(Command::new("list"))
.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(Command::new("join").arg(Arg::new("nwid").index(1).required(true)))
.subcommand(Command::new("leave").arg(Arg::new("nwid").index(1).required(true)))
.subcommand(Command::new("service"))
.subcommand(
Command::new("controller")
.subcommand(Command::new("list"))
.subcommand(Command::new("new"))
.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(Command::new("show").arg(Arg::new("id").index(1).required(true)).arg(Arg::new("member").index(2)))
.subcommand(Command::new("auth").arg(Arg::new("member").index(1).required(true)))
.subcommand(Command::new("deauth").arg(Arg::new("member").index(1).required(true))),
)
.subcommand(
Command::new("identity")
.subcommand(Command::new("new").arg(Arg::new("type").possible_value("p384").possible_value("c25519").default_value("c25519").index(1)))
.subcommand(Command::new("getpublic").arg(Arg::new("identity").index(1).required(true)))
.subcommand(Command::new("fingerprint").arg(Arg::new("identity").index(1).required(true)))
.subcommand(Command::new("validate").arg(Arg::new("identity").index(1).required(true)))
.subcommand(Command::new("sign").arg(Arg::new("identity").index(1).required(true)).arg(Arg::new("path").index(2).required(true)))
.subcommand(Command::new("verify").arg(Arg::new("identity").index(1).required(true)).arg(Arg::new("path").index(2).required(true)).arg(Arg::new("signature").index(3).required(true))),
)
.override_help(help.as_str())
.override_usage(help.as_str())
.disable_help_flag(true)
.get_matches_from(std::env::args())
});
std::process::exit(tokio::runtime::Builder::new_multi_thread().enable_all().build().unwrap().block_on(async_main(cli_args)));
}

View file

@ -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
}
*/

View file

@ -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"))
}
}
}

View file

@ -6,30 +6,37 @@
* 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::time::UNIX_EPOCH;
use std::time::{Instant, SystemTime, UNIX_EPOCH};
use serde::de::DeserializeOwned;
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 crate::osdep;
pub fn ms_since_epoch() -> i64 {
std::time::SystemTime::now().duration_since(UNIX_EPOCH).unwrap().as_millis() as i64
lazy_static! {
static ref STARTUP_INSTANT: Instant = Instant::now();
}
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> {
if !v.is_empty() {
match v.chars().next().unwrap() {
match v.trim().chars().next().unwrap() {
'y' | 'Y' | '1' | 't' | 'T' => {
return Ok(true);
}
@ -42,78 +49,20 @@ pub fn parse_bool(v: &str) -> Result<bool, String> {
Err(format!("invalid boolean value: '{}'", v))
}
pub fn is_valid_bool(v: String) -> Result<(), String> {
parse_bool(v.as_str()).map(|_| ())
/// Returns a non-error if a string is a valid boolean.
pub fn is_valid_bool(v: &str) -> Result<(), String> {
parse_bool(v).map(|_| ())
}
pub fn is_valid_port(v: String) -> Result<(), String> {
let i = u16::from_str(v.as_str()).unwrap_or(0);
if i >= 1 {
/// Returns a non-error if the string is a valid port number.
pub fn is_valid_port(v: &str) -> Result<(), String> {
let i = isize::from_str(v).unwrap_or(0);
if i >= 0x0001 && i <= 0xffff {
return Ok(());
}
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.
pub fn to_json<O: serde::Serialize>(o: &O) -> String {
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.
///
/// 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.
/// 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) {
if target.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();
for kv in target.iter_mut() {
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.
///
/// 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.
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(
|e| Err(e),
|patch| {
serde_json::value::to_value(obj.borrow()).map_or_else(
serde_json::value::to_value(&obj).map_or_else(
|e| Err(e),
|mut obj_value| {
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)]
mod tests {
use crate::utils::ms_monotonic;

View file

@ -6,8 +6,8 @@
* https://www.zerotier.com/
*/
mod common;
//mod common;
mod vnic;
#[cfg(target_os = "macos")]
mod mac_feth_tap;
//#[cfg(target_os = "macos")]
//mod mac_feth_tap;