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", "critical-section",
] ]
[[package]]
name = "atty"
version = "0.2.14"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d9b39be18770d11421cdb1b9947a45dd3f37e93092cbf377614828a319d5fee8"
dependencies = [
"hermit-abi",
"libc",
"winapi",
]
[[package]] [[package]]
name = "autocfg" name = "autocfg"
version = "1.1.0" version = "1.1.0"
@ -87,6 +98,12 @@ version = "1.4.3"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "14c189c53d098945499cdfa7ecc63567cf3886b3332b312a5b4585d8d3a6a610" checksum = "14c189c53d098945499cdfa7ecc63567cf3886b3332b312a5b4585d8d3a6a610"
[[package]]
name = "bytes"
version = "1.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c4872d67bab6358e59559027aa3b9157c53d9358c51423c17554809a8858e0f8"
[[package]] [[package]]
name = "cc" name = "cc"
version = "1.0.73" version = "1.0.73"
@ -99,6 +116,30 @@ version = "1.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd"
[[package]]
name = "clap"
version = "3.1.15"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "85a35a599b11c089a7f49105658d089b8f2cf0882993c17daf6de15285c2c35d"
dependencies = [
"atty",
"bitflags",
"clap_lex",
"indexmap",
"strsim",
"termcolor",
"textwrap",
]
[[package]]
name = "clap_lex"
version = "0.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a37c35f1112dad5e6e0b1adaff798507497a18fceeb30cceb3bae7d1427b9213"
dependencies = [
"os_str_bytes",
]
[[package]] [[package]]
name = "cortex-m" name = "cortex-m"
version = "0.7.4" version = "0.7.4"
@ -152,7 +193,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "391b56fbd302e585b7a9494fb70e40949567b1cf9003a8e4a6041a1687c26573" checksum = "391b56fbd302e585b7a9494fb70e40949567b1cf9003a8e4a6041a1687c26573"
dependencies = [ dependencies = [
"cfg-if", "cfg-if",
"hashbrown", "hashbrown 0.12.0",
"lock_api", "lock_api",
] ]
@ -231,7 +272,7 @@ checksum = "8fc3cb4d91f53b50155bdcfd23f6a4c39ae1969c2ae85982b135750cccaf5fce"
dependencies = [ dependencies = [
"cfg-if", "cfg-if",
"libc", "libc",
"wasi", "wasi 0.9.0+wasi-snapshot-preview1",
] ]
[[package]] [[package]]
@ -243,6 +284,12 @@ dependencies = [
"byteorder", "byteorder",
] ]
[[package]]
name = "hashbrown"
version = "0.11.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ab5ef0d4909ef3724cc8cce6ccc8572c5c817592e9285f5464f8e86f8bd3726e"
[[package]] [[package]]
name = "hashbrown" name = "hashbrown"
version = "0.12.0" version = "0.12.0"
@ -270,6 +317,16 @@ dependencies = [
"libc", "libc",
] ]
[[package]]
name = "indexmap"
version = "1.8.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0f647032dfaa1f8b6dc29bd3edb7bbef4861b8b8007ebb118d6db284fd59f6ee"
dependencies = [
"autocfg",
"hashbrown 0.11.2",
]
[[package]] [[package]]
name = "itoa" name = "itoa"
version = "1.0.1" version = "1.0.1"
@ -298,6 +355,15 @@ dependencies = [
"scopeguard", "scopeguard",
] ]
[[package]]
name = "log"
version = "0.4.17"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "abb12e687cfb44aa40f41fc3978ef76448f9b6038cad6aef4259d3c095a2382e"
dependencies = [
"cfg-if",
]
[[package]] [[package]]
name = "lz4_flex" name = "lz4_flex"
version = "0.9.2" version = "0.9.2"
@ -319,6 +385,29 @@ version = "1.0.6"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3ba553cb19e2acbc54baa16faef215126243fe45e53357a3b2e9f4ebc7b0506c" checksum = "3ba553cb19e2acbc54baa16faef215126243fe45e53357a3b2e9f4ebc7b0506c"
[[package]]
name = "mio"
version = "0.8.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "52da4364ffb0e4fe33a9841a98a3f3014fb964045ce4f7a45a398243c8d6b0c9"
dependencies = [
"libc",
"log",
"miow",
"ntapi",
"wasi 0.11.0+wasi-snapshot-preview1",
"winapi",
]
[[package]]
name = "miow"
version = "0.3.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b9f1c5b025cda876f66ef43a113f91ebc9f4ccef34843000e0adf6ebbab84e21"
dependencies = [
"winapi",
]
[[package]] [[package]]
name = "nb" name = "nb"
version = "0.1.3" version = "0.1.3"
@ -334,6 +423,15 @@ version = "1.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "546c37ac5d9e56f55e73b677106873d9d9f5190605e41a856503623648488cae" checksum = "546c37ac5d9e56f55e73b677106873d9d9f5190605e41a856503623648488cae"
[[package]]
name = "ntapi"
version = "0.3.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c28774a7fd2fbb4f0babd8237ce554b73af68021b5f695a3cebd6c59bac0980f"
dependencies = [
"winapi",
]
[[package]] [[package]]
name = "num-traits" name = "num-traits"
version = "0.2.14" version = "0.2.14"
@ -392,6 +490,12 @@ dependencies = [
"vcpkg", "vcpkg",
] ]
[[package]]
name = "os_str_bytes"
version = "6.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8e22443d1643a904602595ba1cd8f7d896afe56d26712531c5ff73a15b2fbf64"
[[package]] [[package]]
name = "parking_lot" name = "parking_lot"
version = "0.12.0" version = "0.12.0"
@ -415,6 +519,12 @@ dependencies = [
"windows-sys", "windows-sys",
] ]
[[package]]
name = "pin-project-lite"
version = "0.2.9"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e0a7ae3ac2f1173085d398531c705756c94a4c56843785df85a60c1a0afac116"
[[package]] [[package]]
name = "pkg-config" name = "pkg-config"
version = "0.3.25" version = "0.3.25"
@ -624,6 +734,15 @@ dependencies = [
"opaque-debug", "opaque-debug",
] ]
[[package]]
name = "signal-hook-registry"
version = "1.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e51e73328dc4ac0c7ccbda3a494dfa03df1de2f46018127f60c693f2648455b0"
dependencies = [
"libc",
]
[[package]] [[package]]
name = "signature" name = "signature"
version = "1.5.0" version = "1.5.0"
@ -636,6 +755,16 @@ version = "1.8.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f2dd574626839106c320a323308629dcb1acfc96e32a8cba364ddc61ac23ee83" checksum = "f2dd574626839106c320a323308629dcb1acfc96e32a8cba364ddc61ac23ee83"
[[package]]
name = "socket2"
version = "0.4.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "66d72b759436ae32898a2af0a14218dbf55efde3feeb170eb623637db85ee1e0"
dependencies = [
"libc",
"winapi",
]
[[package]] [[package]]
name = "spin" name = "spin"
version = "0.9.2" version = "0.9.2"
@ -657,6 +786,12 @@ version = "1.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a2eb9349b6444b326872e140eb1cf5e7c522154d69e7a0ffb0fb81c06b37543f" checksum = "a2eb9349b6444b326872e140eb1cf5e7c522154d69e7a0ffb0fb81c06b37543f"
[[package]]
name = "strsim"
version = "0.10.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "73473c0e59e6d5812c5dfe2a064a6444949f089e20eec9a2e5506596494e4623"
[[package]] [[package]]
name = "subtle" name = "subtle"
version = "2.4.1" version = "2.4.1"
@ -686,6 +821,52 @@ dependencies = [
"unicode-xid", "unicode-xid",
] ]
[[package]]
name = "termcolor"
version = "1.1.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bab24d30b911b2376f3a13cc2cd443142f0c81dda04c118693e35b3835757755"
dependencies = [
"winapi-util",
]
[[package]]
name = "textwrap"
version = "0.15.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b1141d4d61095b28419e22cb0bbf02755f5e54e0526f97f1e3d1d160e60885fb"
[[package]]
name = "tokio"
version = "1.18.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "dce653fb475565de9f6fb0614b28bca8df2c430c0cf84bcd9c843f15de5414cc"
dependencies = [
"bytes",
"libc",
"memchr",
"mio",
"num_cpus",
"once_cell",
"parking_lot",
"pin-project-lite",
"signal-hook-registry",
"socket2",
"tokio-macros",
"winapi",
]
[[package]]
name = "tokio-macros"
version = "1.7.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b557f72f448c511a979e2564e55d74e6c4432fc96ff4f6241bc6bded342643b7"
dependencies = [
"proc-macro2",
"quote",
"syn",
]
[[package]] [[package]]
name = "twox-hash" name = "twox-hash"
version = "1.6.2" version = "1.6.2"
@ -757,6 +938,12 @@ version = "0.9.0+wasi-snapshot-preview1"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "cccddf32554fecc6acb585f82a32a72e28b48f8c4c1883ddfeeeaa96f7d8e519" checksum = "cccddf32554fecc6acb585f82a32a72e28b48f8c4c1883ddfeeeaa96f7d8e519"
[[package]]
name = "wasi"
version = "0.11.0+wasi-snapshot-preview1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423"
[[package]] [[package]]
name = "winapi" name = "winapi"
version = "0.3.9" version = "0.3.9"
@ -773,6 +960,15 @@ version = "0.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6"
[[package]]
name = "winapi-util"
version = "0.1.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "70ec6ce85bb158151cae5e5c87f95a8e97d2c0c4b001223f33a334e3ce5de178"
dependencies = [
"winapi",
]
[[package]] [[package]]
name = "winapi-x86_64-pc-windows-gnu" name = "winapi-x86_64-pc-windows-gnu"
version = "0.4.0" version = "0.4.0"
@ -890,13 +1086,14 @@ dependencies = [
name = "zerotier-system-service" name = "zerotier-system-service"
version = "0.1.0" version = "0.1.0"
dependencies = [ dependencies = [
"clap",
"lazy_static", "lazy_static",
"libc", "libc",
"num-traits", "num-traits",
"num_cpus",
"parking_lot", "parking_lot",
"serde", "serde",
"serde_json", "serde_json",
"tokio",
"winapi", "winapi",
"zerotier-core-crypto", "zerotier-core-crypto",
"zerotier-network-hypervisor", "zerotier-network-hypervisor",

View file

@ -12,14 +12,16 @@ codegen-units = 1
panic = 'abort' panic = 'abort'
[dependencies] [dependencies]
num-traits = "^0"
zerotier-network-hypervisor = { path = "../zerotier-network-hypervisor" } zerotier-network-hypervisor = { path = "../zerotier-network-hypervisor" }
zerotier-core-crypto = { path = "../zerotier-core-crypto" } zerotier-core-crypto = { path = "../zerotier-core-crypto" }
num-traits = "^0"
tokio = { version = "^1", features = ["full"], default-features = false }
serde = { version = "^1", features = ["derive"], default-features = false } serde = { version = "^1", features = ["derive"], default-features = false }
serde_json = { version = "^1", features = [], default-features = false } serde_json = { version = "^1", features = ["std"], default-features = false }
num_cpus = "^1"
parking_lot = "^0" parking_lot = "^0"
lazy_static = "^1" lazy_static = "^1"
clap = "^3"
#async-trait = "^0"
[target."cfg(windows)".dependencies] [target."cfg(windows)".dependencies]
winapi = { version = "^0", features = ["handleapi", "ws2ipdef", "ws2tcpip"] } winapi = { version = "^0", features = ["handleapi", "ws2ipdef", "ws2tcpip"] }

View file

@ -10,16 +10,16 @@ use std::collections::BTreeMap;
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
use zerotier_network_hypervisor::vl1::identity::NetworkId;
use zerotier_network_hypervisor::vl1::{Address, InetAddress}; use zerotier_network_hypervisor::vl1::{Address, InetAddress};
use zerotier_network_hypervisor::vl2::NetworkId;
pub const UNASSIGNED_PRIVILEGED_PORTS: [u16; 299] = [ pub const UNASSIGNED_PRIVILEGED_PORTS: [u16; 299] = [
4, 6, 8, 10, 12, 14, 15, 16, 26, 28, 30, 32, 34, 36, 40, 60, 269, 270, 271, 272, 273, 274, 275, 276, 277, 278, 279, 285, 288, 289, 290, 291, 292, 293, 294, 295, 296, 297, 298, 299, 300, 301, 302, 303, 304, 305, 306, 307, 323, 324, 325, 326, 327, 328, 329, 330, 331, 332, 334, 335, 336, 337, 338, 4, 6, 8, 10, 12, 14, 15, 16, 26, 28, 30, 32, 34, 36, 40, 60, 269, 270, 271, 272, 273, 274, 275, 276, 277, 278, 279, 285, 288, 289, 290, 291, 292, 293, 294, 295, 296, 297, 298, 299, 300, 301, 302, 303, 304, 305, 306, 307, 323, 324, 325, 326, 327, 328,
339, 340, 341, 342, 343, 703, 708, 713, 714, 715, 716, 717, 718, 719, 720, 721, 722, 723, 724, 725, 726, 727, 728, 732, 733, 734, 735, 736, 737, 738, 739, 740, 743, 745, 746, 755, 756, 766, 768, 778, 779, 781, 782, 783, 784, 785, 786, 787, 788, 789, 790, 791, 792, 793, 794, 795, 796, 797, 798, 329, 330, 331, 332, 334, 335, 336, 337, 338, 339, 340, 341, 342, 343, 703, 708, 713, 714, 715, 716, 717, 718, 719, 720, 721, 722, 723, 724, 725, 726, 727, 728, 732, 733, 734, 735, 736, 737, 738, 739, 740, 743, 745, 746, 755, 756, 766, 768, 778, 779,
799, 802, 803, 804, 805, 806, 807, 808, 809, 811, 812, 813, 814, 815, 816, 817, 818, 819, 820, 821, 822, 823, 824, 825, 826, 827, 834, 835, 836, 837, 838, 839, 840, 841, 842, 843, 844, 845, 846, 849, 850, 851, 852, 853, 854, 855, 856, 857, 858, 859, 862, 863, 864, 865, 866, 867, 868, 869, 870, 781, 782, 783, 784, 785, 786, 787, 788, 789, 790, 791, 792, 793, 794, 795, 796, 797, 798, 799, 802, 803, 804, 805, 806, 807, 808, 809, 811, 812, 813, 814, 815, 816, 817, 818, 819, 820, 821, 822, 823, 824, 825, 826, 827, 834, 835, 836, 837, 838, 839,
871, 872, 874, 875, 876, 877, 878, 879, 880, 881, 882, 883, 884, 885, 889, 890, 891, 892, 893, 894, 895, 896, 897, 898, 899, 904, 905, 906, 907, 908, 909, 910, 911, 914, 915, 916, 917, 918, 919, 920, 921, 922, 923, 924, 925, 926, 927, 928, 929, 930, 931, 932, 933, 934, 935, 936, 937, 938, 939, 840, 841, 842, 843, 844, 845, 846, 849, 850, 851, 852, 853, 854, 855, 856, 857, 858, 859, 862, 863, 864, 865, 866, 867, 868, 869, 870, 871, 872, 874, 875, 876, 877, 878, 879, 880, 881, 882, 883, 884, 885, 889, 890, 891, 892, 893, 894, 895, 896, 897,
940, 941, 942, 943, 944, 945, 946, 947, 948, 949, 950, 951, 952, 953, 954, 955, 956, 957, 958, 959, 960, 961, 962, 963, 964, 965, 966, 967, 968, 969, 970, 971, 972, 973, 974, 975, 976, 977, 978, 979, 980, 981, 982, 983, 984, 985, 986, 987, 988, 1001, 1002, 1003, 1004, 1005, 1006, 1007, 1008, 898, 899, 904, 905, 906, 907, 908, 909, 910, 911, 914, 915, 916, 917, 918, 919, 920, 921, 922, 923, 924, 925, 926, 927, 928, 929, 930, 931, 932, 933, 934, 935, 936, 937, 938, 939, 940, 941, 942, 943, 944, 945, 946, 947, 948, 949, 950, 951, 952, 953,
1009, 1023, 954, 955, 956, 957, 958, 959, 960, 961, 962, 963, 964, 965, 966, 967, 968, 969, 970, 971, 972, 973, 974, 975, 976, 977, 978, 979, 980, 981, 982, 983, 984, 985, 986, 987, 988, 1001, 1002, 1003, 1004, 1005, 1006, 1007, 1008, 1009, 1023,
]; ];
pub const DEFAULT_PORT: u16 = 9993; pub const DEFAULT_PORT: u16 = 9993;

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::io::Write;
use std::str::FromStr;
use clap::{App, Arg, ArgMatches, ErrorKind}; use clap::{Arg, ArgMatches, Command};
use zerotier_network_hypervisor::{VERSION_MAJOR, VERSION_MINOR, VERSION_REVISION}; use zerotier_network_hypervisor::{VERSION_MAJOR, VERSION_MINOR, VERSION_REVISION};
use crate::store::platform_default_home_path; pub mod getifaddrs;
pub mod localconfig;
mod fastudpsocket; pub mod utils;
mod getifaddrs; pub mod vnic;
mod localconfig;
#[macro_use]
mod log;
mod service;
mod store;
mod utils;
mod vnic;
pub const HTTP_API_OBJECT_SIZE_LIMIT: usize = 131072;
fn make_help(long_help: bool) -> String { fn make_help(long_help: bool) -> String {
format!( format!(
r###"ZeroTier Network Hypervisor Service Version {}.{}.{} r###"ZeroTier Network Hypervisor Service Version {}.{}.{}
(c)2013-2021 ZeroTier, Inc. (c)2013-2022 ZeroTier, Inc.
Licensed under the Mozilla Public License (MPL) 2.0 (see LICENSE.txt) Licensed under the Mozilla Public License (MPL) 2.0 (see LICENSE.txt)
Usage: zerotier [-...] <command> [command args] Usage: zerotier [-...] <command> [command args]
@ -45,7 +35,6 @@ Global Options:
Common Operations: Common Operations:
help Show this help help Show this help
longhelp Show help with advanced commands
oldhelp Show v1.x legacy commands oldhelp Show v1.x legacy commands
version Print version (of this binary) version Print version (of this binary)
@ -126,111 +115,100 @@ pub struct GlobalCommandLineFlags {
pub auth_token_override: Option<String>, pub auth_token_override: Option<String>,
} }
#[cfg(any(target_os = "macos"))]
pub fn platform_default_home_path() -> String {
"/Library/Application Support/ZeroTier".into()
}
async fn async_main(cli_args: Box<ArgMatches>) -> i32 {
let global_cli_flags = GlobalCommandLineFlags {
json_output: cli_args.is_present("json"),
base_path: cli_args.value_of("path").map_or_else(|| platform_default_home_path(), |p| p.to_string()),
auth_token_path_override: cli_args.value_of("token_path").map(|p| p.to_string()),
auth_token_override: cli_args.value_of("token").map(|t| t.to_string()),
};
return match cli_args.subcommand() {
Some(("help", _)) => {
print_help(false);
0
}
Some(("oldhelp", _)) => todo!(),
Some(("version", _)) => {
println!("{}.{}.{}", VERSION_MAJOR, VERSION_MINOR, VERSION_REVISION);
0
}
Some(("status", _)) => todo!(),
Some(("set", sub_cli_args)) => todo!(),
Some(("peer", sub_cli_args)) => todo!(),
Some(("network", sub_cli_args)) => todo!(),
Some(("join", sub_cli_args)) => todo!(),
Some(("leave", sub_cli_args)) => todo!(),
Some(("service", _)) => todo!(),
Some(("controller", sub_cli_args)) => todo!(),
Some(("identity", sub_cli_args)) => todo!(),
_ => {
print_help(false);
1
}
};
}
fn main() { fn main() {
let cli_args = Box::new({ let cli_args = Box::new({
let help = make_help(false); let help = make_help(false);
let args = App::new("zerotier") Command::new("zerotier")
.arg(Arg::with_name("json").short("j")) .arg(Arg::new("json").short('j'))
.arg(Arg::with_name("path").short("p").takes_value(true)) .arg(Arg::new("path").short('p').takes_value(true))
.arg(Arg::with_name("token_path").short("t").takes_value(true)) .arg(Arg::new("token_path").short('t').takes_value(true))
.arg(Arg::with_name("token").short("T").takes_value(true)) .arg(Arg::new("token").short('T').takes_value(true))
.subcommand(App::new("help")) .subcommand(Command::new("help"))
.subcommand(App::new("version")) .subcommand(Command::new("version"))
.subcommand(App::new("status")) .subcommand(Command::new("status"))
.subcommand( .subcommand(
App::new("set") Command::new("set")
.subcommand(App::new("port").arg(Arg::with_name("port#").index(1).validator(utils::is_valid_port))) .subcommand(Command::new("port").arg(Arg::new("port#").index(1).validator(utils::is_valid_port)))
.subcommand(App::new("secondaryport").arg(Arg::with_name("port#").index(1).validator(utils::is_valid_port))) .subcommand(Command::new("secondaryport").arg(Arg::new("port#").index(1).validator(utils::is_valid_port)))
.subcommand( .subcommand(
App::new("blacklist") Command::new("blacklist")
.subcommand(App::new("cidr").arg(Arg::with_name("ip_bits").index(1)).arg(Arg::with_name("boolean").index(2).validator(utils::is_valid_bool))) .subcommand(Command::new("cidr").arg(Arg::new("ip_bits").index(1)).arg(Arg::new("boolean").index(2).validator(utils::is_valid_bool)))
.subcommand(App::new("if").arg(Arg::with_name("prefix").index(1)).arg(Arg::with_name("boolean").index(2).validator(utils::is_valid_bool))), .subcommand(Command::new("if").arg(Arg::new("prefix").index(1)).arg(Arg::new("boolean").index(2).validator(utils::is_valid_bool))),
) )
.subcommand(App::new("portmap").arg(Arg::with_name("boolean").index(1).validator(utils::is_valid_bool))), .subcommand(Command::new("portmap").arg(Arg::new("boolean").index(1).validator(utils::is_valid_bool))),
) )
.subcommand(App::new("peer").subcommand(App::new("show").arg(Arg::with_name("address").index(1).required(true))).subcommand(App::new("list")).subcommand(App::new("listroots")).subcommand(App::new("try"))) .subcommand(Command::new("peer").subcommand(Command::new("show").arg(Arg::new("address").index(1).required(true))).subcommand(Command::new("list")).subcommand(Command::new("listroots")).subcommand(Command::new("try")))
.subcommand( .subcommand(
App::new("network") Command::new("network")
.subcommand(App::new("show").arg(Arg::with_name("nwid").index(1).required(true))) .subcommand(Command::new("show").arg(Arg::new("nwid").index(1).required(true)))
.subcommand(App::new("list")) .subcommand(Command::new("list"))
.subcommand(App::new("set").arg(Arg::with_name("nwid").index(1).required(true)).arg(Arg::with_name("setting").index(2).required(false)).arg(Arg::with_name("value").index(3).required(false))), .subcommand(Command::new("set").arg(Arg::new("nwid").index(1).required(true)).arg(Arg::new("setting").index(2).required(false)).arg(Arg::new("value").index(3).required(false))),
) )
.subcommand(App::new("join").arg(Arg::with_name("nwid").index(1).required(true))) .subcommand(Command::new("join").arg(Arg::new("nwid").index(1).required(true)))
.subcommand(App::new("leave").arg(Arg::with_name("nwid").index(1).required(true))) .subcommand(Command::new("leave").arg(Arg::new("nwid").index(1).required(true)))
.subcommand(App::new("service")) .subcommand(Command::new("service"))
.subcommand( .subcommand(
App::new("controller") Command::new("controller")
.subcommand(App::new("list")) .subcommand(Command::new("list"))
.subcommand(App::new("new")) .subcommand(Command::new("new"))
.subcommand(App::new("set").arg(Arg::with_name("id").index(1).required(true)).arg(Arg::with_name("setting").index(2)).arg(Arg::with_name("value").index(3))) .subcommand(Command::new("set").arg(Arg::new("id").index(1).required(true)).arg(Arg::new("setting").index(2)).arg(Arg::new("value").index(3)))
.subcommand(App::new("show").arg(Arg::with_name("id").index(1).required(true)).arg(Arg::with_name("member").index(2))) .subcommand(Command::new("show").arg(Arg::new("id").index(1).required(true)).arg(Arg::new("member").index(2)))
.subcommand(App::new("auth").arg(Arg::with_name("member").index(1).required(true))) .subcommand(Command::new("auth").arg(Arg::new("member").index(1).required(true)))
.subcommand(App::new("deauth").arg(Arg::with_name("member").index(1).required(true))), .subcommand(Command::new("deauth").arg(Arg::new("member").index(1).required(true))),
) )
.subcommand( .subcommand(
App::new("identity") Command::new("identity")
.subcommand(App::new("new").arg(Arg::with_name("type").possible_value("p384").possible_value("c25519").default_value("c25519").index(1))) .subcommand(Command::new("new").arg(Arg::new("type").possible_value("p384").possible_value("c25519").default_value("c25519").index(1)))
.subcommand(App::new("getpublic").arg(Arg::with_name("identity").index(1).required(true))) .subcommand(Command::new("getpublic").arg(Arg::new("identity").index(1).required(true)))
.subcommand(App::new("fingerprint").arg(Arg::with_name("identity").index(1).required(true))) .subcommand(Command::new("fingerprint").arg(Arg::new("identity").index(1).required(true)))
.subcommand(App::new("validate").arg(Arg::with_name("identity").index(1).required(true))) .subcommand(Command::new("validate").arg(Arg::new("identity").index(1).required(true)))
.subcommand(App::new("sign").arg(Arg::with_name("identity").index(1).required(true)).arg(Arg::with_name("path").index(2).required(true))) .subcommand(Command::new("sign").arg(Arg::new("identity").index(1).required(true)).arg(Arg::new("path").index(2).required(true)))
.subcommand(App::new("verify").arg(Arg::with_name("identity").index(1).required(true)).arg(Arg::with_name("path").index(2).required(true)).arg(Arg::with_name("signature").index(3).required(true))), .subcommand(Command::new("verify").arg(Arg::new("identity").index(1).required(true)).arg(Arg::new("path").index(2).required(true)).arg(Arg::new("signature").index(3).required(true))),
) )
.help(help.as_str()) .override_help(help.as_str())
.get_matches_from_safe(std::env::args()); .override_usage(help.as_str())
if args.is_err() { .disable_help_flag(true)
let e = args.err().unwrap(); .get_matches_from(std::env::args())
if e.kind != ErrorKind::HelpDisplayed {
print_help(false);
}
std::process::exit(1);
}
let args = args.unwrap();
if args.subcommand_name().is_none() {
print_help(false);
std::process::exit(1);
}
args
}); });
let global_cli_flags = GlobalCommandLineFlags { std::process::exit(tokio::runtime::Builder::new_multi_thread().enable_all().build().unwrap().block_on(async_main(cli_args)));
json_output: cli_args.is_present("json"),
base_path: cli_args.value_of("path").map_or_else(|| platform_default_home_path(), |p| p.into_string()),
auth_token_path_override: cli_args.value_of("token_path").map(|p| p.into_string()),
auth_token_override: cli_args.value_of("token").map(|t| t.into_string()),
};
std::process::exit({
match cli_args.subcommand() {
("help", None) => {
print_help(false);
0
}
("longhelp", None) => {
print_help(true);
0
}
("oldhelp", None) => todo!(),
("version", None) => {
println!("{}.{}.{}", VERSION_MAJOR, VERSION_MINOR, VERSION_REVISION);
0
}
("status", None) => todo!(),
("set", Some(sub_cli_args)) => todo!(),
("peer", Some(sub_cli_args)) => todo!(),
("network", Some(sub_cli_args)) => todo!(),
("join", Some(sub_cli_args)) => todo!(),
("leave", Some(sub_cli_args)) => todo!(),
("service", None) => {
drop(cli_args); // free no longer needed memory before entering service
service::run(&global_cli_flags)
}
("controller", Some(sub_cli_args)) => todo!(),
("identity", Some(sub_cli_args)) => todo!(),
_ => {
print_help(false);
1
}
}
});
} }

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/ * https://www.zerotier.com/
*/ */
use std::borrow::Borrow;
use std::fs::File;
use std::io::Read;
use std::path::Path;
use std::str::FromStr; use std::str::FromStr;
use std::time::UNIX_EPOCH; use std::time::{Instant, SystemTime, UNIX_EPOCH};
use serde::de::DeserializeOwned; use serde::de::DeserializeOwned;
use serde::Serialize; use serde::Serialize;
use zerotier_core_crypto::hex; use lazy_static::lazy_static;
use tokio::fs::File;
use tokio::io::{AsyncRead, AsyncReadExt};
use zerotier_network_hypervisor::vl1::Identity; use zerotier_network_hypervisor::vl1::Identity;
//use crate::osdep; lazy_static! {
static ref STARTUP_INSTANT: Instant = Instant::now();
pub fn ms_since_epoch() -> i64 {
std::time::SystemTime::now().duration_since(UNIX_EPOCH).unwrap().as_millis() as i64
} }
pub fn ms_monotonic() -> i64 {} /// Get milliseconds since unix epoch.
pub fn ms_since_epoch() -> i64 {
SystemTime::now().duration_since(UNIX_EPOCH).unwrap().as_millis() as i64
}
/// Get milliseconds since an arbitrary time in the past, guaranteed to monotonically increase.
pub fn ms_monotonic() -> i64 {
Instant::now().duration_since(*STARTUP_INSTANT).as_millis() as i64
}
/// Returns true if the string starts with [yY1tT] or false for [nN0fF].
pub fn parse_bool(v: &str) -> Result<bool, String> { pub fn parse_bool(v: &str) -> Result<bool, String> {
if !v.is_empty() { if !v.is_empty() {
match v.chars().next().unwrap() { match v.trim().chars().next().unwrap() {
'y' | 'Y' | '1' | 't' | 'T' => { 'y' | 'Y' | '1' | 't' | 'T' => {
return Ok(true); return Ok(true);
} }
@ -42,78 +49,20 @@ pub fn parse_bool(v: &str) -> Result<bool, String> {
Err(format!("invalid boolean value: '{}'", v)) Err(format!("invalid boolean value: '{}'", v))
} }
pub fn is_valid_bool(v: String) -> Result<(), String> { /// Returns a non-error if a string is a valid boolean.
parse_bool(v.as_str()).map(|_| ()) pub fn is_valid_bool(v: &str) -> Result<(), String> {
parse_bool(v).map(|_| ())
} }
pub fn is_valid_port(v: String) -> Result<(), String> { /// Returns a non-error if the string is a valid port number.
let i = u16::from_str(v.as_str()).unwrap_or(0); pub fn is_valid_port(v: &str) -> Result<(), String> {
if i >= 1 { let i = isize::from_str(v).unwrap_or(0);
if i >= 0x0001 && i <= 0xffff {
return Ok(()); return Ok(());
} }
Err(format!("invalid TCP/IP port number: {}", v)) Err(format!("invalid TCP/IP port number: {}", v))
} }
/// Convenience function to read up to limit bytes from a file.
/// If the file is larger than limit, the excess is not read.
pub fn read_limit<P: AsRef<Path>>(path: P, limit: usize) -> std::io::Result<Vec<u8>> {
let mut v: Vec<u8> = Vec::new();
let _ = File::open(path)?.take(limit as u64).read_to_end(&mut v)?;
Ok(v)
}
/// Read an identity as either a literal or from a file.
pub fn parse_cli_identity(input: &str, validate: bool) -> Result<Identity, String> {
let parse_func = |s: &str| {
Identity::new_from_string(s).map_or_else(
|e| Err(format!("invalid identity: {}", e.to_str())),
|id| {
if !validate || id.validate() {
Ok(id)
} else {
Err(String::from("invalid identity: local validation failed"))
}
},
)
};
if Path::new(input).exists() {
read_limit(input, 16384).map_or_else(|e| Err(e.to_string()), |v| String::from_utf8(v).map_or_else(|e| Err(e.to_string()), |s| parse_func(s.as_str())))
} else {
parse_func(input)
}
}
/// Create a new HTTP authorization nonce by encrypting the current time.
/// The key used to encrypt the current time is random and is re-created for
/// each execution of the process. By decrypting this nonce when it is returned,
/// the client and server may check the age of a digest auth exchange.
pub fn create_http_auth_nonce(timestamp: i64) -> String {
let mut nonce_plaintext: [u64; 2] = [timestamp as u64, timestamp as u64];
unsafe {
//osdep::encryptHttpAuthNonce(nonce_plaintext.as_mut_ptr().cast());
hex::to_string(&nonce_plaintext.as_ptr().cast::<[u8]>())
}
}
/// Decrypt HTTP auth nonce encrypted by this process and return the timestamp.
/// This returns zero if the input was not valid.
pub fn decrypt_http_auth_nonce(nonce: &str) -> i64 {
let nonce = hex::from_string(nonce.trim());
if !nonce.is_err() {
let mut nonce = nonce.unwrap();
if nonce.len() == 16 {
unsafe {
//osdep::decryptHttpAuthNonce(nonce.as_mut_ptr().cast());
let nonce = *nonce.as_ptr().cast::<[u64; 2]>();
if nonce[0] == nonce[1] {
return nonce[0] as i64;
}
}
}
}
return 0;
}
/// Shortcut to use serde_json to serialize an object, returns "null" on error. /// Shortcut to use serde_json to serialize an object, returns "null" on error.
pub fn to_json<O: serde::Serialize>(o: &O) -> String { pub fn to_json<O: serde::Serialize>(o: &O) -> String {
serde_json::to_string(o).unwrap_or("null".into()) serde_json::to_string(o).unwrap_or("null".into())
@ -125,6 +74,7 @@ pub fn to_json_pretty<O: serde::Serialize>(o: &O) -> String {
} }
/// Recursively patch a JSON object. /// Recursively patch a JSON object.
///
/// This is slightly different from a usual JSON merge. For objects in the target their fields /// This is slightly different from a usual JSON merge. For objects in the target their fields
/// are updated by recursively calling json_patch if the same field is present in the source. /// are updated by recursively calling json_patch if the same field is present in the source.
/// If the source tries to set an object to something other than another object, this is ignored. /// If the source tries to set an object to something other than another object, this is ignored.
@ -133,7 +83,7 @@ pub fn to_json_pretty<O: serde::Serialize>(o: &O) -> String {
pub fn json_patch(target: &mut serde_json::value::Value, source: &serde_json::value::Value, depth_limit: usize) { pub fn json_patch(target: &mut serde_json::value::Value, source: &serde_json::value::Value, depth_limit: usize) {
if target.is_object() { if target.is_object() {
if source.is_object() { if source.is_object() {
let mut target = target.as_object_mut().unwrap(); let target = target.as_object_mut().unwrap();
let source = source.as_object().unwrap(); let source = source.as_object().unwrap();
for kv in target.iter_mut() { for kv in target.iter_mut() {
let _ = source.get(kv.0).map(|new_value| { let _ = source.get(kv.0).map(|new_value| {
@ -154,13 +104,14 @@ pub fn json_patch(target: &mut serde_json::value::Value, source: &serde_json::va
} }
/// Patch a serializable object with the fields present in a JSON object. /// Patch a serializable object with the fields present in a JSON object.
///
/// If there are no changes, None is returned. The depth limit is passed through to json_patch and /// If there are no changes, None is returned. The depth limit is passed through to json_patch and
/// should be set to a sanity check value to prevent overflows. /// should be set to a sanity check value to prevent overflows.
pub fn json_patch_object<O: Serialize + DeserializeOwned + Eq>(obj: O, patch: &str, depth_limit: usize) -> Result<Option<O>, serde_json::Error> { pub fn json_patch_object<O: Serialize + DeserializeOwned + Eq>(obj: O, patch: &str, depth_limit: usize) -> Result<Option<O>, serde_json::Error> {
serde_json::from_str::<serde_json::value::Value>(patch).map_or_else( serde_json::from_str::<serde_json::value::Value>(patch).map_or_else(
|e| Err(e), |e| Err(e),
|patch| { |patch| {
serde_json::value::to_value(obj.borrow()).map_or_else( serde_json::value::to_value(&obj).map_or_else(
|e| Err(e), |e| Err(e),
|mut obj_value| { |mut obj_value| {
json_patch(&mut obj_value, &patch, depth_limit); json_patch(&mut obj_value, &patch, depth_limit);
@ -180,6 +131,44 @@ pub fn json_patch_object<O: Serialize + DeserializeOwned + Eq>(obj: O, patch: &s
) )
} }
/// Convenience function to read up to limit bytes from a file.
///
/// If the file is larger than limit, the excess is not read.
pub async fn read_limit(path: &str, limit: usize) -> std::io::Result<Vec<u8>> {
let mut f = File::open(path).await?;
let bytes = f.metadata().await?.len().min(limit as u64) as usize;
let mut v: Vec<u8> = Vec::with_capacity(bytes);
v.resize(bytes, 0);
f.read_exact(v.as_mut_slice()).await?;
Ok(v)
}
/// Returns true if the file exists and is a regular file (or a link to one).
pub async fn file_exists(path: &str) -> bool {
tokio::fs::metadata(path).await.is_ok()
}
/// Read an identity as either a literal or from a file.
pub async fn parse_cli_identity(input: &str, validate: bool) -> Result<Identity, String> {
let parse_func = |s: &str| {
Identity::from_str(s).map_or_else(
|e| Err(format!("invalid identity: {}", e.to_string())),
|id| {
if !validate || id.validate_identity() {
Ok(id)
} else {
Err(String::from("invalid identity: local validation failed"))
}
},
)
};
if file_exists(input).await {
read_limit(input, 16384).await.map_or_else(|e| Err(e.to_string()), |v| String::from_utf8(v).map_or_else(|e| Err(e.to_string()), |s| parse_func(s.as_str())))
} else {
parse_func(input)
}
}
#[cfg(test)] #[cfg(test)]
mod tests { mod tests {
use crate::utils::ms_monotonic; use crate::utils::ms_monotonic;

View file

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