mirror of
https://github.com/zerotier/ZeroTierOne.git
synced 2025-04-26 17:03:43 +02:00
Data directory abstraction, local config, various other things.
This commit is contained in:
parent
2d3b96725b
commit
f14efdcd3d
7 changed files with 163 additions and 64 deletions
|
@ -72,7 +72,7 @@ pub fn random() -> u64 {
|
||||||
s0 = SystemTime::now().duration_since(SystemTime::UNIX_EPOCH).unwrap().as_nanos() as u64;
|
s0 = SystemTime::now().duration_since(SystemTime::UNIX_EPOCH).unwrap().as_nanos() as u64;
|
||||||
}
|
}
|
||||||
if s1 == 0 {
|
if s1 == 0 {
|
||||||
s1 = splitmix64((std::process::id() as u64).wrapping_add((unsafe { &RANDOM_STATE_0 } as *const u64) as u64));
|
s1 = splitmix64(std::process::id() as u64);
|
||||||
}
|
}
|
||||||
let s1_new = xorshift64(s1);
|
let s1_new = xorshift64(s1);
|
||||||
s0 = splitmix64(s0.wrapping_add(s1));
|
s0 = splitmix64(s0.wrapping_add(s1));
|
||||||
|
|
|
@ -10,7 +10,7 @@ use std::num::NonZeroI64;
|
||||||
use std::time::Duration;
|
use std::time::Duration;
|
||||||
|
|
||||||
use crate::error::InvalidParameterError;
|
use crate::error::InvalidParameterError;
|
||||||
use crate::vl1::{Address, Endpoint, Identity, Node, SystemInterface};
|
use crate::vl1::{Address, Endpoint, Identity, Node, RootSet, SystemInterface};
|
||||||
use crate::vl2::{Switch, SwitchInterface};
|
use crate::vl2::{Switch, SwitchInterface};
|
||||||
use crate::PacketBuffer;
|
use crate::PacketBuffer;
|
||||||
|
|
||||||
|
@ -29,9 +29,6 @@ impl NetworkHypervisor {
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Obtain a new packet buffer from the buffer pool.
|
|
||||||
///
|
|
||||||
/// The returned object is a Pooled<Buffer<>> instance. The buffer is returned to the pool when the container is destroyed.
|
|
||||||
#[inline(always)]
|
#[inline(always)]
|
||||||
pub fn get_packet_buffer(&self) -> PacketBuffer {
|
pub fn get_packet_buffer(&self) -> PacketBuffer {
|
||||||
self.vl1.get_packet_buffer()
|
self.vl1.get_packet_buffer()
|
||||||
|
@ -47,12 +44,18 @@ impl NetworkHypervisor {
|
||||||
&self.vl1.identity
|
&self.vl1.identity
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[inline(always)]
|
||||||
pub fn do_background_tasks<I: Interface>(&self, ii: &I) -> Duration {
|
pub fn do_background_tasks<I: Interface>(&self, ii: &I) -> Duration {
|
||||||
self.vl1.do_background_tasks(ii)
|
self.vl1.do_background_tasks(ii)
|
||||||
}
|
}
|
||||||
|
|
||||||
#[inline(always)]
|
#[inline(always)]
|
||||||
pub fn wire_receive<I: SystemInterface>(&self, ii: &I, source_endpoint: &Endpoint, source_local_socket: Option<NonZeroI64>, source_local_interface: Option<NonZeroI64>, mut data: PacketBuffer) {
|
pub fn wire_receive<I: Interface>(&self, ii: &I, source_endpoint: &Endpoint, source_local_socket: Option<NonZeroI64>, source_local_interface: Option<NonZeroI64>, data: PacketBuffer) {
|
||||||
self.vl1.wire_receive(ii, &self.vl2, source_endpoint, source_local_socket, source_local_interface, data)
|
self.vl1.wire_receive(ii, &self.vl2, source_endpoint, source_local_socket, source_local_interface, data)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[inline(always)]
|
||||||
|
pub fn add_update_root_set(&self, rs: RootSet) -> bool {
|
||||||
|
self.vl1.add_update_root_set(rs)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -414,6 +414,16 @@ impl Node {
|
||||||
self.roots.lock().roots.contains_key(peer)
|
self.roots.lock().roots.contains_key(peer)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Add or update a root set.
|
||||||
|
///
|
||||||
|
/// If no root set exists by this name, a new root set is added. If one already
|
||||||
|
/// exists it's checked against the new one and updated if the new set is valid
|
||||||
|
/// and should supersede it.
|
||||||
|
///
|
||||||
|
/// Changes will take effect within a few seconds when root sets are next
|
||||||
|
/// examined and synchronized with peer and root list state.
|
||||||
|
///
|
||||||
|
/// This returns true if the new root set was accepted and false otherwise.
|
||||||
pub fn add_update_root_set(&self, rs: RootSet) -> bool {
|
pub fn add_update_root_set(&self, rs: RootSet) -> bool {
|
||||||
let mut roots = self.roots.lock();
|
let mut roots = self.roots.lock();
|
||||||
let entry = roots.sets.get_mut(&rs.name);
|
let entry = roots.sets.get_mut(&rs.name);
|
||||||
|
@ -422,16 +432,17 @@ impl Node {
|
||||||
if rs.should_replace(old_rs) {
|
if rs.should_replace(old_rs) {
|
||||||
*old_rs = rs;
|
*old_rs = rs;
|
||||||
roots.sets_modified = true;
|
roots.sets_modified = true;
|
||||||
return true;
|
true
|
||||||
|
} else {
|
||||||
|
false
|
||||||
}
|
}
|
||||||
|
} else if rs.verify() {
|
||||||
|
roots.sets.insert(rs.name.clone(), rs);
|
||||||
|
roots.sets_modified = true;
|
||||||
|
true
|
||||||
} else {
|
} else {
|
||||||
if rs.verify() {
|
false
|
||||||
roots.sets.insert(rs.name.clone(), rs);
|
|
||||||
roots.sets_modified = true;
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
return false;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Get the canonical Path object for a given endpoint and local socket information.
|
/// Get the canonical Path object for a given endpoint and local socket information.
|
||||||
|
|
122
zerotier-system-service/src/datadir.rs
Normal file
122
zerotier-system-service/src/datadir.rs
Normal file
|
@ -0,0 +1,122 @@
|
||||||
|
/* 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::path::{Path, PathBuf};
|
||||||
|
use std::str::FromStr;
|
||||||
|
|
||||||
|
use crate::localconfig;
|
||||||
|
use crate::utils::{read_limit, DEFAULT_FILE_IO_READ_LIMIT};
|
||||||
|
|
||||||
|
use tokio::sync::{Mutex, RwLock, RwLockReadGuard};
|
||||||
|
|
||||||
|
use zerotier_core_crypto::random::next_u32_secure;
|
||||||
|
use zerotier_network_hypervisor::vl1::identity::{Identity, IDENTITY_ALGORITHM_ALL};
|
||||||
|
|
||||||
|
const AUTH_TOKEN_DEFAULT_LENGTH: usize = 64;
|
||||||
|
const AUTH_TOKEN_POSSIBLE_CHARS: &'static str = "0123456789abcdefghijklmnopqrstuvwxyz";
|
||||||
|
const AUTH_TOKEN_FILENAME: &'static str = "authtoken.secret";
|
||||||
|
const IDENTITY_PUBLIC_FILENAME: &'static str = "identity.public";
|
||||||
|
const IDENTITY_SECRET_FILENAME: &'static str = "identity.secret";
|
||||||
|
const CONFIG_FILENAME: &'static str = "local.conf";
|
||||||
|
|
||||||
|
/// Abstraction around ZeroTier's home data directory.
|
||||||
|
pub struct DataDir {
|
||||||
|
pub base_path: PathBuf,
|
||||||
|
config: RwLock<localconfig::Config>,
|
||||||
|
authtoken: Mutex<String>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl DataDir {
|
||||||
|
pub async fn open<P: AsRef<Path>>(path: P) -> std::io::Result<Self> {
|
||||||
|
let base_path = path.as_ref().to_path_buf();
|
||||||
|
if !base_path.is_dir() {
|
||||||
|
let _ = std::fs::create_dir_all(&base_path);
|
||||||
|
if !base_path.is_dir() {
|
||||||
|
return Err(std::io::Error::new(std::io::ErrorKind::NotFound, "base path not found and cannot be created"));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let config_path = base_path.join(CONFIG_FILENAME);
|
||||||
|
let config_data = read_limit(&config_path, DEFAULT_FILE_IO_READ_LIMIT).await;
|
||||||
|
let config = RwLock::new(if config_data.is_ok() {
|
||||||
|
let c = serde_json::from_slice::<localconfig::Config>(config_data.unwrap().as_slice());
|
||||||
|
if c.is_err() {
|
||||||
|
return Err(std::io::Error::new(std::io::ErrorKind::InvalidData, c.err().unwrap()));
|
||||||
|
}
|
||||||
|
c.unwrap()
|
||||||
|
} else {
|
||||||
|
if config_path.is_file() {
|
||||||
|
return Err(std::io::Error::new(std::io::ErrorKind::PermissionDenied, "local.conf not readable"));
|
||||||
|
} else {
|
||||||
|
localconfig::Config::default()
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
return Ok(Self { base_path, config, authtoken: Mutex::new(String::new()) });
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Load identity.secret from data directory.
|
||||||
|
pub async fn load_identity(&self) -> std::io::Result<Identity> {
|
||||||
|
let id_data = Identity::from_str(String::from_utf8_lossy(read_limit(self.base_path.join(IDENTITY_PUBLIC_FILENAME), 4096).await?.as_slice()).as_ref());
|
||||||
|
if id_data.is_err() {
|
||||||
|
return Err(std::io::Error::new(std::io::ErrorKind::InvalidData, id_data.err().unwrap()));
|
||||||
|
}
|
||||||
|
Ok(id_data.unwrap())
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Save identity.secret and identity.public to data directory.
|
||||||
|
pub async fn save_identity(&self, id: &Identity) -> std::io::Result<()> {
|
||||||
|
assert!(id.secret.is_some());
|
||||||
|
let id_secret_str = id.to_string_with_options(IDENTITY_ALGORITHM_ALL, true);
|
||||||
|
let id_public_str = id.to_string_with_options(IDENTITY_ALGORITHM_ALL, false);
|
||||||
|
let secret_path = self.base_path.join(IDENTITY_SECRET_FILENAME);
|
||||||
|
tokio::fs::write(&secret_path, id_secret_str.as_bytes()).await?;
|
||||||
|
assert!(crate::utils::fs_restrict_permissions(&secret_path));
|
||||||
|
tokio::fs::write(self.base_path.join(IDENTITY_PUBLIC_FILENAME), id_public_str.as_bytes()).await
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Get authorization token for local API, creating and saving if it does not exist.
|
||||||
|
pub async fn authtoken(&self) -> std::io::Result<String> {
|
||||||
|
let mut authtoken = self.authtoken.lock().await;
|
||||||
|
if authtoken.is_empty() {
|
||||||
|
let authtoken_path = self.base_path.join(AUTH_TOKEN_FILENAME);
|
||||||
|
let authtoken_bytes = read_limit(&authtoken_path, 4096).await;
|
||||||
|
if authtoken_bytes.is_err() {
|
||||||
|
let mut tmp = String::with_capacity(AUTH_TOKEN_DEFAULT_LENGTH);
|
||||||
|
for _ in 0..AUTH_TOKEN_DEFAULT_LENGTH {
|
||||||
|
tmp.push(AUTH_TOKEN_POSSIBLE_CHARS.as_bytes()[(next_u32_secure() as usize) % AUTH_TOKEN_POSSIBLE_CHARS.len()] as char);
|
||||||
|
}
|
||||||
|
tokio::fs::write(&authtoken_path, tmp.as_bytes()).await?;
|
||||||
|
*authtoken = tmp;
|
||||||
|
} else {
|
||||||
|
*authtoken = String::from_utf8_lossy(authtoken_bytes.unwrap().as_slice()).into();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Ok(authtoken.clone())
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Get a readable locked reference to this node's configuration.
|
||||||
|
///
|
||||||
|
/// Use clone() to get a copy of the configuration if you want to modify it. Then use
|
||||||
|
/// save_config() to save the modified configuration and update the internal copy in
|
||||||
|
/// this structure.
|
||||||
|
pub async fn config(&self) -> RwLockReadGuard<'_, localconfig::Config> {
|
||||||
|
self.config.read().await
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Save a modified copy of the configuration and replace the internal copy in this structure (if it's actually changed).
|
||||||
|
pub async fn save_config(&self, modified_config: localconfig::Config) -> std::io::Result<()> {
|
||||||
|
let mut config = self.config.write().await;
|
||||||
|
if !config.eq(&modified_config) {
|
||||||
|
let config_data = crate::utils::to_json_pretty(&modified_config);
|
||||||
|
tokio::fs::write(self.base_path.join(CONFIG_FILENAME), config_data.as_bytes()).await?;
|
||||||
|
*config = modified_config;
|
||||||
|
}
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
}
|
|
@ -136,9 +136,6 @@ pub struct Config {
|
||||||
pub virtual_: BTreeMap<Address, VirtualNetworkSettings>,
|
pub virtual_: BTreeMap<Address, VirtualNetworkSettings>,
|
||||||
pub network: BTreeMap<NetworkId, NetworkSettings>,
|
pub network: BTreeMap<NetworkId, NetworkSettings>,
|
||||||
pub settings: GlobalSettings,
|
pub settings: GlobalSettings,
|
||||||
|
|
||||||
#[serde(skip_serializing, skip_deserializing)]
|
|
||||||
file_content_hash: [u8; 48],
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Default for Config {
|
impl Default for Config {
|
||||||
|
@ -148,48 +145,6 @@ impl Default for Config {
|
||||||
virtual_: BTreeMap::new(),
|
virtual_: BTreeMap::new(),
|
||||||
network: BTreeMap::new(),
|
network: BTreeMap::new(),
|
||||||
settings: GlobalSettings::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<Self> {
|
|
||||||
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<bool> {
|
|
||||||
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
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -14,6 +14,7 @@ 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};
|
||||||
|
|
||||||
pub mod cli;
|
pub mod cli;
|
||||||
|
pub mod datadir;
|
||||||
pub mod exitcode;
|
pub mod exitcode;
|
||||||
pub mod getifaddrs;
|
pub mod getifaddrs;
|
||||||
pub mod jsonformatter;
|
pub mod jsonformatter;
|
||||||
|
|
|
@ -6,6 +6,7 @@
|
||||||
* https://www.zerotier.com/
|
* https://www.zerotier.com/
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
use std::path::Path;
|
||||||
use std::str::FromStr;
|
use std::str::FromStr;
|
||||||
use std::time::{Instant, SystemTime, UNIX_EPOCH};
|
use std::time::{Instant, SystemTime, UNIX_EPOCH};
|
||||||
|
|
||||||
|
@ -145,7 +146,7 @@ pub fn to_json_pretty<O: serde::Serialize>(o: &O) -> String {
|
||||||
/// Convenience function to read up to limit bytes from a file.
|
/// Convenience function to read up to limit bytes from a file.
|
||||||
///
|
///
|
||||||
/// If the file is larger than limit, the excess is not read.
|
/// 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>> {
|
pub async fn read_limit<P: AsRef<Path>>(path: P, limit: usize) -> std::io::Result<Vec<u8>> {
|
||||||
let mut f = File::open(path).await?;
|
let mut f = File::open(path).await?;
|
||||||
let bytes = f.metadata().await?.len().min(limit as u64) as usize;
|
let bytes = f.metadata().await?.len().min(limit as u64) as usize;
|
||||||
let mut v: Vec<u8> = Vec::with_capacity(bytes);
|
let mut v: Vec<u8> = Vec::with_capacity(bytes);
|
||||||
|
@ -154,9 +155,13 @@ pub async fn read_limit(path: &str, limit: usize) -> std::io::Result<Vec<u8>> {
|
||||||
Ok(v)
|
Ok(v)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Returns true if the file exists and is a regular file (or a link to one).
|
/// Set permissions on a file or directory to be most restrictive (visible only to the service's user).
|
||||||
pub async fn file_exists(path: &str) -> bool {
|
#[cfg(unix)]
|
||||||
tokio::fs::metadata(path).await.is_ok()
|
pub fn fs_restrict_permissions<P: AsRef<Path>>(path: P) -> bool {
|
||||||
|
unsafe {
|
||||||
|
let c_path = std::ffi::CString::new(path.as_ref().to_str().unwrap()).unwrap();
|
||||||
|
libc::chmod(c_path.as_ptr(), if path.as_ref().is_dir() { 0o700 } else { 0o600 }) == 0
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Read an identity as either a literal or from a file.
|
/// Read an identity as either a literal or from a file.
|
||||||
|
@ -173,8 +178,10 @@ pub async fn parse_cli_identity(input: &str, validate: bool) -> Result<Identity,
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
};
|
};
|
||||||
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())))
|
let input_p = Path::new(input);
|
||||||
|
if input_p.is_file() {
|
||||||
|
read_limit(input_p, 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 {
|
} else {
|
||||||
parse_func(input)
|
parse_func(input)
|
||||||
}
|
}
|
||||||
|
|
Loading…
Add table
Reference in a new issue