diff --git a/zerotier-system-service/src/fastudpsocket.rs b/attic/fastudpsocket.rs similarity index 100% rename from zerotier-system-service/src/fastudpsocket.rs rename to attic/fastudpsocket.rs diff --git a/attic/iblt.rs b/attic/iblt.rs deleted file mode 100644 index 74c433d12..000000000 --- a/attic/iblt.rs +++ /dev/null @@ -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(key: &[u8]) -> u64 { - xorshift64(u64::from_le_bytes((&key[(HS - 8)..HS]).try_into().unwrap())) -} - -#[derive(Clone, PartialEq, Eq)] -struct IBLTEntry { - key_sum: [u8; HS], - check_hash_sum: u64, - count: i64 -} - -impl IBLTEntry { - #[inline(always)] - fn is_singular(&self) -> bool { - if self.count == 1 || self.count == -1 { - calc_check_hash::(&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 { - map: [IBLTEntry; B] -} - -impl IBLT { - 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::(), 0, size_of::()) }; - } - - pub fn 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(&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::(&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(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::(&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 = 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); - } - } -} diff --git a/attic/zerotier-forward-secrecy.md b/attic/zerotier-forward-secrecy.md deleted file mode 100644 index 5fa968d4a..000000000 --- a/attic/zerotier-forward-secrecy.md +++ /dev/null @@ -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/ diff --git a/zerotier-system-service/Cargo.lock b/zerotier-system-service/Cargo.lock index afa482f27..b780f156d 100644 --- a/zerotier-system-service/Cargo.lock +++ b/zerotier-system-service/Cargo.lock @@ -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", diff --git a/zerotier-system-service/Cargo.toml b/zerotier-system-service/Cargo.toml index b725db770..cbdf03018 100644 --- a/zerotier-system-service/Cargo.toml +++ b/zerotier-system-service/Cargo.toml @@ -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"] } diff --git a/zerotier-system-service/src/localconfig.rs b/zerotier-system-service/src/localconfig.rs index 9c4cf26b0..3defcdbc0 100644 --- a/zerotier-system-service/src/localconfig.rs +++ b/zerotier-system-service/src/localconfig.rs @@ -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; diff --git a/zerotier-system-service/src/log.rs b/zerotier-system-service/src/log.rs deleted file mode 100644 index 6766b3a71..000000000 --- a/zerotier-system-service/src/log.rs +++ /dev/null @@ -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, - 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> 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> { - 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>(&mut self, s: S) { - self.log_internal("", s.as_ref()); - } - - pub fn debug>(&mut self, s: S) { - if self.debug { - self.log_internal("DEBUG: ", s.as_ref()); - } - } - - pub fn fatal>(&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)) - } - } -} -*/ diff --git a/zerotier-system-service/src/main.rs b/zerotier-system-service/src/main.rs index ae40a868a..8e3efd603 100644 --- a/zerotier-system-service/src/main.rs +++ b/zerotier-system-service/src/main.rs @@ -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 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, } +#[cfg(any(target_os = "macos"))] +pub fn platform_default_home_path() -> String { + "/Library/Application Support/ZeroTier".into() +} + +async fn async_main(cli_args: Box) -> 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() { 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")) + 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( - 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))) + 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( - 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))), + 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(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( - 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))), + 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(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(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( - 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))), + 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( - 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))), + 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))), ) - .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); - } - std::process::exit(1); - } - let args = args.unwrap(); - if args.subcommand_name().is_none() { - print_help(false); - std::process::exit(1); - } - args + .override_help(help.as_str()) + .override_usage(help.as_str()) + .disable_help_flag(true) + .get_matches_from(std::env::args()) }); - 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()), - }; - - 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 - } - } - }); + std::process::exit(tokio::runtime::Builder::new_multi_thread().enable_all().build().unwrap().block_on(async_main(cli_args))); } diff --git a/zerotier-system-service/src/service.rs b/zerotier-system-service/src/service.rs deleted file mode 100644 index 31c1fbf44..000000000 --- a/zerotier-system-service/src/service.rs +++ /dev/null @@ -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>, - pub online: AtomicBool, - pub all_sockets: Mutex>, - 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> { - 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, local_interface: Option, 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, local_interface: Option) -> bool { - // TODO - true - } - - fn get_path_hints(&self, id: &Identity) -> Option<&[(&Endpoint, Option, Option)]> { - // 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, - #[serde(rename = "httpLocalEndpoints")] - pub http_local_endpoints: Vec, -} - -/// Core ZeroTier service, which is sort of just a container for all the things. -pub(crate) struct Service { - pub(crate) log: Log, - node: Option, - udp_local_endpoints: Mutex>, - http_local_endpoints: Mutex>, - interrupt: Mutex>, - local_config: Mutex>, - store: Arc, - startup_time: i64, - startup_time_monotonic: i64, - run: AtomicBool, - online: AtomicBool, -} - -impl Service { - pub fn local_config(&self) -> Arc { - 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 { - &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 { - 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, local_config: Arc) -> i32 { - let process_exit_value: i32 = 0; - - let mut udp_sockets: BTreeMap = BTreeMap::new(); - let mut http_listeners: BTreeMap = BTreeMap::new(); - let mut loopback_http_listeners: (Option, Option) = (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 = 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::>().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::>().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) -> 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 -} - -*/ diff --git a/zerotier-system-service/src/store.rs b/zerotier-system-service/src/store.rs deleted file mode 100644 index 1f9076dea..000000000 --- a/zerotier-system-service/src/store.rs +++ /dev/null @@ -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, - pub default_log_path: Box, - previous_local_config_on_disk: Mutex, - peers_path: Box, - controller_path: Box, - networks_path: Box, - auth_token_path: Mutex>, - auth_token: Mutex, -} - -/// 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, auth_token_override: &Option) -> std::io::Result { - 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 { - 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> { - 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 = 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 { - 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 { - let mut list: Vec = 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> { - self.read_internal(self.base_path.join(fname)) - } - - pub fn read_file_str(&self, fname: &str) -> std::io::Result { - 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> { - 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::(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> { - 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")) - } - } -} diff --git a/zerotier-system-service/src/utils.rs b/zerotier-system-service/src/utils.rs index d3e89fa98..44d224706 100644 --- a/zerotier-system-service/src/utils.rs +++ b/zerotier-system-service/src/utils.rs @@ -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 { 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 { 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>(path: P, limit: usize) -> std::io::Result> { - let mut v: Vec = 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 { - 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: &O) -> String { serde_json::to_string(o).unwrap_or("null".into()) @@ -125,6 +74,7 @@ pub fn to_json_pretty(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: &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(obj: O, patch: &str, depth_limit: usize) -> Result, serde_json::Error> { serde_json::from_str::(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(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> { + let mut f = File::open(path).await?; + let bytes = f.metadata().await?.len().min(limit as u64) as usize; + let mut v: Vec = 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 { + 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; diff --git a/zerotier-system-service/src/vnic/mod.rs b/zerotier-system-service/src/vnic/mod.rs index a191dee86..2bcc39190 100644 --- a/zerotier-system-service/src/vnic/mod.rs +++ b/zerotier-system-service/src/vnic/mod.rs @@ -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;