diff --git a/zerotier-network-hypervisor/default-rootset/make-root-set.sh b/zerotier-network-hypervisor/default-rootset/make-root-set.sh new file mode 100755 index 000000000..723a45b94 --- /dev/null +++ b/zerotier-network-hypervisor/default-rootset/make-root-set.sh @@ -0,0 +1,14 @@ +#!/bin/bash + +# This is for internal use by ZeroTier, but obviously users can repurpose it to make their own. + +# Usage: make-root-set.sh [<...>] + +for i in $*; do + echo $i + ../../zerotier-system-service/target/debug/zerotier-system-service rootset sign root.zerotier.com.json $i >tmp.json + mv -f tmp.json root.zerotier.com.json + ../../zerotier-system-service/target/debug/zerotier-system-service rootset marshal root.zerotier.com.json >root.zerotier.com.bin +done + +cat root.zerotier.com.json diff --git a/zerotier-network-hypervisor/default-rootset/root.zerotier.com.bin b/zerotier-network-hypervisor/default-rootset/root.zerotier.com.bin new file mode 100644 index 000000000..43311beef Binary files /dev/null and b/zerotier-network-hypervisor/default-rootset/root.zerotier.com.bin differ diff --git a/zerotier-network-hypervisor/default-rootset/root.zerotier.com.json b/zerotier-network-hypervisor/default-rootset/root.zerotier.com.json new file mode 100644 index 000000000..3ac195f93 --- /dev/null +++ b/zerotier-network-hypervisor/default-rootset/root.zerotier.com.json @@ -0,0 +1,25 @@ +{ + "name": "root.zerotier.com", + "revision": 1, + "members": [ { + "identity": "62f865ae71:0:e2076c57de870e6288d7d5e7404408b1545efca37d67f77b87e9e54168c25d3ef1a9abf2905ea5e785c01dff23887ad4232d95c7a8fd2c27111a72bd159322dc", + "endpoints": [ "udp:50.7.252.138/9993", "udp:2001:49f0:d0db:2::2/9993" ], + "signature": [ 1, 250, 55, 178, 250, 221, 13, 136, 125, 246, 209, 222, 43, 154, 3, 87, 224, 202, 86, 154, 217, 132, 141, 77, 246, 233, 118, 35, 112, 37, 58, 226, 104, 232, 33, 180, 29, 159, 20, 100, 217, 129, 242, 16, 247, 253, 70, 245, 59, 22, 126, 148, 52, 7, 231, 5, 230, 252, 35, 204, 22, 12, 85, 122, 10 ], + "priority": 0 + }, { + "identity": "778cde7190:0:3f6681a99e5ad1895e9fba33e6212d4454e168bcec7112101bf000956ed8e92e42892cb6f2ec410881a84ab19da50e1287ba3d926c3a1f755cccf299a1207055", + "endpoints": [ "udp:103.195.103.66/9993", "udp:2605:9880:400:c3:254:f2bc:a1f7:19/9993" ], + "signature": [ 1, 115, 251, 30, 185, 137, 187, 219, 80, 35, 19, 117, 38, 241, 200, 137, 205, 208, 73, 54, 30, 158, 150, 64, 232, 214, 248, 54, 26, 180, 29, 68, 87, 34, 102, 251, 199, 158, 215, 199, 77, 8, 128, 93, 166, 199, 39, 139, 143, 20, 180, 29, 145, 232, 90, 181, 75, 237, 175, 238, 2, 124, 18, 124, 4 ], + "priority": 0 + }, { + "identity": "cafe04eba9:0:6c6a9d1dea55c1616bfe2a2b8f0ff9a8cacaf70374fb1f39e3bef81cbfebef17b7228268a0a2a29d3488c752565c6c965cbd6506ec24397cc8a5d9d15285a87f", + "endpoints": [ "udp:84.17.53.155/9993", "udp:2a02:6ea0:d405::9993/9993" ], + "signature": [ 1, 51, 245, 92, 49, 30, 240, 161, 49, 14, 233, 231, 237, 169, 55, 1, 171, 91, 121, 3, 157, 139, 135, 177, 212, 199, 26, 188, 98, 130, 138, 39, 193, 45, 190, 243, 146, 15, 234, 220, 203, 154, 39, 230, 88, 152, 164, 74, 44, 136, 125, 207, 23, 31, 112, 52, 16, 116, 179, 99, 93, 133, 133, 189, 6 ], + "priority": 0 + }, { + "identity": "cafe9efeb9:0:ccdef76bc7b97ded904eabc5df09886d9c1514a610036cb9139cc214001a2958978efcec15712dd3948c6e6b3a8e893df01ff493d1f8d9806a860c5420571bf0", + "endpoints": [ "udp:104.194.8.134/9993", "udp:2605:9880:200:1200:30:571:e34:51/9993" ], + "signature": [ 1, 237, 145, 250, 221, 80, 44, 48, 158, 74, 198, 149, 192, 96, 220, 223, 232, 141, 163, 254, 173, 190, 7, 16, 67, 234, 182, 183, 16, 36, 154, 40, 141, 98, 18, 253, 57, 186, 222, 71, 223, 247, 43, 131, 203, 38, 79, 36, 43, 52, 130, 80, 218, 188, 3, 175, 221, 108, 218, 139, 248, 37, 228, 112, 5 ], + "priority": 0 + } ] +} diff --git a/zerotier-network-hypervisor/src/util/buffer.rs b/zerotier-network-hypervisor/src/util/buffer.rs index d170233ce..a19a84eea 100644 --- a/zerotier-network-hypervisor/src/util/buffer.rs +++ b/zerotier-network-hypervisor/src/util/buffer.rs @@ -497,6 +497,20 @@ impl AsMut<[u8]> for Buffer { } } +impl From<[u8; L]> for Buffer { + #[inline(always)] + fn from(a: [u8; L]) -> Self { + Self(L, a) + } +} + +impl From<&[u8; L]> for Buffer { + #[inline(always)] + fn from(a: &[u8; L]) -> Self { + Self(L, a.clone()) + } +} + pub struct PooledBufferFactory; impl PooledBufferFactory { diff --git a/zerotier-network-hypervisor/src/vl1/rootset.rs b/zerotier-network-hypervisor/src/vl1/rootset.rs index 9d977cb36..65fb28551 100644 --- a/zerotier-network-hypervisor/src/vl1/rootset.rs +++ b/zerotier-network-hypervisor/src/vl1/rootset.rs @@ -18,34 +18,44 @@ use crate::vl1::Endpoint; use serde::{Deserialize, Serialize}; /// Description of a member of a root cluster. +/// +/// Natural sort order is in order of identity address. #[derive(Clone, PartialEq, Eq, Serialize, Deserialize)] pub struct Root { - /// Full identity of this node. + /// Identity of this node (not including secret). pub identity: Identity, - /// Endpoints for this root or None if this is a former member attesting to an update that removes it. + /// Endpoints for this root or None if this is a disabled entry. + /// + /// Disabled entries typically exist when a former member is needed to sign a new revision to + /// achieve N-1 quorum and issue an update. pub endpoints: Option>, /// Signature of entire root set by this identity. + /// + /// This is populated by the sign() method when the completed root set is signed by each member. + /// All member roots must sign. #[serde(default)] pub signature: Vec, - /// Flags field (currently unused). + /// Priority (higher number is lower priority, 0 is default). + /// + /// Lower priority roots are only used if NO roots of a higher priority can be reached (in any root set). #[serde(default)] - pub flags: u64, + pub priority: u8, } impl PartialOrd for Root { #[inline(always)] fn partial_cmp(&self, other: &Self) -> Option { - self.identity.partial_cmp(&other.identity) + self.identity.address.partial_cmp(&other.identity.address) } } impl Ord for Root { #[inline(always)] fn cmp(&self, other: &Self) -> std::cmp::Ordering { - self.identity.cmp(&other.identity) + self.identity.address.cmp(&other.identity.address) } } @@ -67,7 +77,8 @@ pub struct RootSet { pub revision: u64, /// A set of Root nodes that are current or immediately former members of this cluster. - /// This will always be sorted by member identity. + /// + /// This will always be sorted by member identity address. Duplicate addresses are not allowed. pub members: Vec, } @@ -76,6 +87,12 @@ impl RootSet { Self { name, revision, members: Vec::new() } } + /// Get the ZeroTier default root set, which contains roots run by ZeroTier Inc. + pub fn zerotier_default() -> Self { + let mut cursor = 0; + Self::unmarshal(&Buffer::from(include_bytes!("../../default-rootset/root.zerotier.com.json")), &mut cursor).unwrap() + } + fn marshal_internal(&self, buf: &mut Buffer, include_signatures: bool) -> std::io::Result<()> { buf.append_u8(0)?; // version byte for future use buf.append_varint(self.name.as_bytes().len() as u64)?; @@ -97,7 +114,8 @@ impl RootSet { buf.append_varint(m.signature.len() as u64)?; buf.append_bytes(m.signature.as_slice())?; } - buf.append_varint(m.flags)?; + buf.append_varint(0)?; // flags, currently always 0 + buf.append_u8(m.priority)?; buf.append_varint(0)?; // size of additional fields for future use } buf.append_varint(0)?; // size of additional fields for future use @@ -127,9 +145,9 @@ impl RootSet { return true; } - /// Add a member to this definition, replacing any current entry for this identity. - pub fn add<'a, I: Iterator>(&mut self, member_identity: &Identity, endpoints: Option) { - self.members.retain(|m| !m.identity.eq(member_identity)); + /// Add a member to this definition, replacing any current entry with this address. + pub fn add<'a, I: Iterator>(&mut self, member_identity: &Identity, endpoints: Option, priority: u8) { + self.members.retain(|m| m.identity.address != member_identity.address); let _ = self.members.push(Root { identity: member_identity.clone_without_secret(), endpoints: endpoints.map(|endpoints| { @@ -140,7 +158,7 @@ impl RootSet { tmp }), signature: Vec::new(), - flags: 0, + priority, }); self.members.sort(); } @@ -162,7 +180,7 @@ impl RootSet { identity: unsigned_entry.identity, endpoints: unsigned_entry.endpoints, signature: signature.unwrap(), - flags: unsigned_entry.flags, + priority: unsigned_entry.priority, }); self.members.sort(); return true; @@ -178,7 +196,7 @@ impl RootSet { /// root nodes can replace one member if three cluster members sign the update, but to /// remove two at a time one of the exiting members would have to sign. This is done by /// adding it with None as its address list, making it disabled. Disabled members function - /// only as signers (witnesses). + /// only as signers (witnesses) and only if they were enabled previously. /// /// There is one edge case though. If a cluster definition has only one member, that one /// member must sign the next update. N-1 is not permitted to be less than one. If that was @@ -237,7 +255,7 @@ impl Marshalable for RootSet { identity: Identity::unmarshal(buf, cursor)?, endpoints: None, signature: Vec::new(), - flags: 0, + priority: 0, }; let endpoint_count = buf.read_varint(cursor)?; @@ -252,7 +270,8 @@ impl Marshalable for RootSet { let signature_size = buf.read_varint(cursor)?; let _ = m.signature.write_all(buf.read_bytes(signature_size as usize, cursor)?); - m.flags = buf.read_varint(cursor)?; + let _ = buf.read_varint(cursor)?; // flags, currently unused + m.priority = buf.read_u8(cursor)?; *cursor += buf.read_varint(cursor)? as usize; diff --git a/zerotier-system-service/rootsets/root.zerotier.com.bin b/zerotier-system-service/rootsets/root.zerotier.com.bin deleted file mode 100644 index d82a0b509..000000000 Binary files a/zerotier-system-service/rootsets/root.zerotier.com.bin and /dev/null differ diff --git a/zerotier-system-service/rootsets/root.zerotier.com.json b/zerotier-system-service/rootsets/root.zerotier.com.json deleted file mode 100644 index 5a7598e12..000000000 --- a/zerotier-system-service/rootsets/root.zerotier.com.json +++ /dev/null @@ -1,25 +0,0 @@ -{ - "name": "root.zerotier.com", - "revision": 1, - "members": [ { - "identity": "62f865ae71:0:e2076c57de870e6288d7d5e7404408b1545efca37d67f77b87e9e54168c25d3ef1a9abf2905ea5e785c01dff23887ad4232d95c7a8fd2c27111a72bd159322dc", - "endpoints": [ "udp:50.7.252.138/9993", "udp:2001:49f0:d0db:2::2/9993" ], - "signature": [ 1, 45, 14, 211, 108, 240, 151, 85, 43, 241, 113, 18, 24, 45, 198, 197, 67, 254, 96, 138, 194, 77, 170, 156, 168, 31, 240, 55, 168, 108, 69, 135, 253, 198, 153, 36, 166, 200, 222, 157, 122, 50, 149, 149, 40, 35, 125, 93, 78, 228, 51, 245, 53, 238, 133, 84, 188, 190, 98, 145, 177, 19, 54, 154, 0 ], - "flags": 0 - }, { - "identity": "778cde7190:0:3f6681a99e5ad1895e9fba33e6212d4454e168bcec7112101bf000956ed8e92e42892cb6f2ec410881a84ab19da50e1287ba3d926c3a1f755cccf299a1207055", - "endpoints": [ "udp:103.195.103.66/9993", "udp:2605:9880:400:c3:254:f2bc:a1f7:19/9993" ], - "signature": [ 1, 202, 181, 145, 69, 58, 169, 42, 149, 210, 160, 77, 220, 56, 246, 54, 210, 161, 144, 158, 103, 70, 104, 236, 58, 66, 127, 100, 117, 242, 208, 70, 68, 87, 142, 163, 222, 231, 146, 60, 205, 180, 202, 18, 181, 137, 216, 204, 109, 118, 224, 86, 220, 26, 142, 61, 18, 50, 174, 173, 44, 167, 231, 249, 0 ], - "flags": 0 - }, { - "identity": "cafe04eba9:0:6c6a9d1dea55c1616bfe2a2b8f0ff9a8cacaf70374fb1f39e3bef81cbfebef17b7228268a0a2a29d3488c752565c6c965cbd6506ec24397cc8a5d9d15285a87f", - "endpoints": [ "udp:84.17.53.155/9993", "udp:2a02:6ea0:d405::9993/9993" ], - "signature": [ 1, 129, 31, 37, 249, 242, 179, 153, 184, 117, 15, 192, 41, 69, 112, 196, 189, 18, 57, 96, 33, 82, 31, 142, 57, 251, 151, 118, 86, 71, 11, 170, 197, 11, 20, 55, 74, 66, 10, 248, 133, 216, 88, 212, 34, 139, 128, 179, 246, 241, 8, 126, 105, 195, 126, 235, 140, 219, 66, 92, 166, 203, 111, 132, 0 ], - "flags": 0 - }, { - "identity": "cafe9efeb9:0:ccdef76bc7b97ded904eabc5df09886d9c1514a610036cb9139cc214001a2958978efcec15712dd3948c6e6b3a8e893df01ff493d1f8d9806a860c5420571bf0", - "endpoints": [ "udp:104.194.8.134/9993", "udp:2605:9880:200:1200:30:571:e34:51/9993" ], - "signature": [ 1, 254, 236, 249, 244, 29, 229, 55, 85, 171, 15, 42, 222, 51, 237, 237, 47, 54, 158, 123, 96, 24, 101, 207, 63, 82, 113, 254, 154, 225, 188, 147, 75, 115, 243, 200, 253, 221, 198, 234, 74, 168, 126, 13, 137, 143, 13, 56, 73, 206, 242, 29, 97, 8, 221, 31, 236, 187, 86, 190, 15, 65, 184, 253, 13 ], - "flags": 0 - } ] -} diff --git a/zerotier-system-service/src/cli/rootset.rs b/zerotier-system-service/src/cli/rootset.rs index 4be20b6a1..647d6865a 100644 --- a/zerotier-system-service/src/cli/rootset.rs +++ b/zerotier-system-service/src/cli/rootset.rs @@ -30,7 +30,7 @@ pub async fn cmd(flags: Flags, cmd_args: &ArgMatches) -> i32 { let path = path.unwrap(); let secret_arg = secret_arg.unwrap(); let secret = crate::utils::parse_cli_identity(secret_arg, true).await; - let json_data = crate::utils::read_limit(path, 1048576).await; + let json_data = crate::utils::read_limit(path, crate::utils::DEFAULT_FILE_IO_READ_LIMIT).await; if secret.is_err() { eprintln!("ERROR: unable to parse '{}' or read as a file.", secret_arg); return exitcode::ERR_IOERR; @@ -66,7 +66,7 @@ pub async fn cmd(flags: Flags, cmd_args: &ArgMatches) -> i32 { let path = sc_args.value_of("path"); if path.is_some() { let path = path.unwrap(); - let json_data = crate::utils::read_limit(path, 1048576).await; + let json_data = crate::utils::read_limit(path, crate::utils::DEFAULT_FILE_IO_READ_LIMIT).await; if json_data.is_err() { eprintln!("ERROR: unable to read '{}'.", path); return exitcode::ERR_IOERR; diff --git a/zerotier-system-service/src/jsonformatter.rs b/zerotier-system-service/src/jsonformatter.rs index 667986664..765ca7489 100644 --- a/zerotier-system-service/src/jsonformatter.rs +++ b/zerotier-system-service/src/jsonformatter.rs @@ -7,7 +7,6 @@ use serde_json::ser::Formatter; -#[derive(Clone, Debug)] pub struct JsonFormatter<'a> { current_indent: usize, has_value: bool, diff --git a/zerotier-system-service/src/localconfig.rs b/zerotier-system-service/src/localconfig.rs index 3defcdbc0..63a4c9f96 100644 --- a/zerotier-system-service/src/localconfig.rs +++ b/zerotier-system-service/src/localconfig.rs @@ -13,6 +13,7 @@ use serde::{Deserialize, Serialize}; use zerotier_network_hypervisor::vl1::{Address, InetAddress}; use zerotier_network_hypervisor::vl2::NetworkId; +/// A list of unassigned or obsolete ports under 1024 that could possibly be squatted. 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, @@ -22,36 +23,37 @@ pub const UNASSIGNED_PRIVILEGED_PORTS: [u16; 299] = [ 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, ]; +/// Default primary ZeroTier port. pub const DEFAULT_PORT: u16 = 9993; #[derive(Serialize, Deserialize, Clone, PartialEq, Eq)] #[serde(default)] -pub struct LocalConfigPhysicalPathConfig { +pub struct PhysicalPathSettings { pub blacklist: bool, } -impl Default for LocalConfigPhysicalPathConfig { +impl Default for PhysicalPathSettings { fn default() -> Self { - LocalConfigPhysicalPathConfig { blacklist: false } + Self { blacklist: false } } } #[derive(Serialize, Deserialize, Clone, PartialEq, Eq)] #[serde(default)] -pub struct LocalConfigVirtualConfig { +pub struct VirtualNetworkSettings { #[serde(rename = "try")] pub try_: Vec, } -impl Default for LocalConfigVirtualConfig { +impl Default for VirtualNetworkSettings { fn default() -> Self { - LocalConfigVirtualConfig { try_: Vec::new() } + Self { try_: Vec::new() } } } #[derive(Serialize, Deserialize, Clone, PartialEq, Eq)] #[serde(default)] -pub struct LocalConfigNetworkSettings { +pub struct NetworkSettings { #[serde(rename = "allowManagedIPs")] pub allow_managed_ips: bool, #[serde(rename = "allowGlobalIPs")] @@ -64,9 +66,9 @@ pub struct LocalConfigNetworkSettings { pub allow_default_route_override: bool, } -impl Default for LocalConfigNetworkSettings { +impl Default for NetworkSettings { fn default() -> Self { - LocalConfigNetworkSettings { + Self { allow_managed_ips: true, allow_global_ips: false, allow_managed_routes: true, @@ -78,78 +80,43 @@ impl Default for LocalConfigNetworkSettings { #[derive(Serialize, Deserialize, Clone, PartialEq, Eq)] #[serde(default)] -pub struct LocalConfigLogSettings { - pub path: Option, - #[serde(rename = "maxSize")] - pub max_size: usize, - pub vl1: bool, - pub vl2: bool, - #[serde(rename = "vl2TraceRules")] - pub vl2_trace_rules: bool, - #[serde(rename = "vl2TraceMulticast")] - pub vl2_trace_multicast: bool, - pub debug: bool, - pub stderr: bool, -} - -impl Default for LocalConfigLogSettings { - fn default() -> Self { - // TODO: change before release to saner defaults - LocalConfigLogSettings { - path: None, - max_size: 131072, - vl1: true, - vl2: true, - vl2_trace_rules: true, - vl2_trace_multicast: true, - debug: true, - stderr: true, - } - } -} - -#[derive(Serialize, Deserialize, Clone, PartialEq, Eq)] -#[serde(default)] -pub struct LocalConfigSettings { +pub struct GlobalSettings { #[serde(rename = "primaryPort")] pub primary_port: u16, #[serde(rename = "portMapping")] pub port_mapping: bool, - #[serde(rename = "log")] - pub log: LocalConfigLogSettings, #[serde(rename = "interfacePrefixBlacklist")] pub interface_prefix_blacklist: Vec, #[serde(rename = "explicitAddresses")] pub explicit_addresses: Vec, } -impl Default for LocalConfigSettings { +impl Default for GlobalSettings { fn default() -> Self { let mut bl: Vec = Vec::new(); - bl.reserve(LocalConfigSettings::DEFAULT_PREFIX_BLACKLIST.len()); - for n in LocalConfigSettings::DEFAULT_PREFIX_BLACKLIST.iter() { + bl.reserve(Self::DEFAULT_PREFIX_BLACKLIST.len()); + for n in Self::DEFAULT_PREFIX_BLACKLIST.iter() { bl.push(String::from(*n)); } - LocalConfigSettings { + Self { primary_port: DEFAULT_PORT, port_mapping: true, - log: LocalConfigLogSettings::default(), interface_prefix_blacklist: bl, explicit_addresses: Vec::new(), } } } -impl LocalConfigSettings { +impl GlobalSettings { #[cfg(target_os = "macos")] - const DEFAULT_PREFIX_BLACKLIST: [&'static str; 9] = ["lo", "utun", "gif", "stf", "iptap", "pktap", "feth", "zt", "llw"]; + pub const DEFAULT_PREFIX_BLACKLIST: [&'static str; 9] = ["lo", "utun", "gif", "stf", "iptap", "pktap", "feth", "zt", "llw"]; #[cfg(target_os = "linux")] - const DEFAULT_PREFIX_BLACKLIST: [&'static str; 5] = ["lo", "tun", "tap", "ipsec", "zt"]; + pub const DEFAULT_PREFIX_BLACKLIST: [&'static str; 5] = ["lo", "tun", "tap", "ipsec", "zt", "tailscale"]; #[cfg(windows)] - const DEFAULT_PREFIX_BLACKLIST: [&'static str; 0] = []; + pub const DEFAULT_PREFIX_BLACKLIST: [&'static str; 0] = []; pub fn is_interface_blacklisted(&self, ifname: &str) -> bool { for p in self.interface_prefix_blacklist.iter() { @@ -163,21 +130,66 @@ impl LocalConfigSettings { #[derive(Serialize, Deserialize, Clone, PartialEq, Eq)] #[serde(default)] -pub struct LocalConfig { - pub physical: BTreeMap, +pub struct Config { + pub physical: BTreeMap, #[serde(rename = "virtual")] - pub virtual_: BTreeMap, - pub network: BTreeMap, - pub settings: LocalConfigSettings, + pub virtual_: BTreeMap, + pub network: BTreeMap, + pub settings: GlobalSettings, + + #[serde(skip_serializing, skip_deserializing)] + file_content_hash: [u8; 48], } -impl Default for LocalConfig { +impl Default for Config { fn default() -> Self { - LocalConfig { + Self { physical: BTreeMap::new(), virtual_: BTreeMap::new(), network: BTreeMap::new(), - settings: LocalConfigSettings::default(), + settings: GlobalSettings::default(), + file_content_hash: [0_u8; 48], + } + } +} + +impl Config { + /// Load this configuration from disk. + pub async fn load(path: &str) -> std::io::Result { + let mut t = Self::default(); + t.reload(path).await.map(|_| t) + } + + /// Load this configuration from disk if its contents have changed. + /// + /// This returns true if a new configuration was loaded, false if the file hasn't changed, or an error. + pub async fn reload(&mut self, path: &str) -> std::io::Result { + let config_data = crate::utils::read_limit(path, crate::utils::DEFAULT_FILE_IO_READ_LIMIT).await?; + let hash = zerotier_core_crypto::hash::SHA384::hash(config_data.as_slice()); + Ok(if hash != self.file_content_hash { + let config = serde_json::from_slice(config_data.as_slice()); + if config.is_err() { + return Err(std::io::Error::new(std::io::ErrorKind::InvalidData, config.err().unwrap().to_string())); + } + *self = config.unwrap(); + self.file_content_hash = hash; + true + } else { + false + }) + } + + /// Write this configuration to a file (with human-friendly formatting). + /// + /// This is mutable because it updates an external state if the write is successful. + pub async fn save(&mut self, path: &str) -> std::io::Result<()> { + let config_data = crate::utils::to_json_pretty(self); + let result = tokio::fs::write(path, &config_data).await; + if result.is_ok() { + self.file_content_hash = zerotier_core_crypto::hash::SHA384::hash(config_data.as_bytes()); + Ok(()) + } else { + result } } } diff --git a/zerotier-system-service/src/main.rs b/zerotier-system-service/src/main.rs index 27bb02814..79e98eb04 100644 --- a/zerotier-system-service/src/main.rs +++ b/zerotier-system-service/src/main.rs @@ -86,7 +86,6 @@ Advanced Operations: ยท list List root sets in use sign Sign a root set with an identity verify Load and verify a root set - marshal Dump root set as binary to stdout service Start local service (usually not invoked manually) @@ -105,12 +104,12 @@ pub fn print_help() { let _ = std::io::stdout().write_all(h.as_bytes()); } -#[cfg(any(target_os = "macos"))] +#[cfg(target_os = "macos")] pub fn platform_default_home_path() -> String { "/Library/Application Support/ZeroTier".into() } -#[cfg(any(target_os = "linux"))] +#[cfg(target_os = "linux")] pub fn platform_default_home_path() -> String { "/var/lib/zerotier".into() } diff --git a/zerotier-system-service/src/utils.rs b/zerotier-system-service/src/utils.rs index 5fd1cce3b..eddb09309 100644 --- a/zerotier-system-service/src/utils.rs +++ b/zerotier-system-service/src/utils.rs @@ -6,22 +6,24 @@ * https://www.zerotier.com/ */ -use std::error::Error; use std::str::FromStr; use std::time::{Instant, SystemTime, UNIX_EPOCH}; use serde::de::DeserializeOwned; -use serde::{Serialize, Serializer}; +use serde::Serialize; use lazy_static::lazy_static; use tokio::fs::File; -use tokio::io::{AsyncRead, AsyncReadExt}; +use tokio::io::AsyncReadExt; use crate::jsonformatter::JsonFormatter; use zerotier_network_hypervisor::vl1::Identity; +/// Default sanity limit parameter for read_limit() used throughout the service. +pub const DEFAULT_FILE_IO_READ_LIMIT: usize = 1048576; + lazy_static! { static ref STARTUP_INSTANT: Instant = Instant::now(); }