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;
|
||||
}
|
||||
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);
|
||||
s0 = splitmix64(s0.wrapping_add(s1));
|
||||
|
|
|
@ -10,7 +10,7 @@ use std::num::NonZeroI64;
|
|||
use std::time::Duration;
|
||||
|
||||
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::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)]
|
||||
pub fn get_packet_buffer(&self) -> PacketBuffer {
|
||||
self.vl1.get_packet_buffer()
|
||||
|
@ -47,12 +44,18 @@ impl NetworkHypervisor {
|
|||
&self.vl1.identity
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
pub fn do_background_tasks<I: Interface>(&self, ii: &I) -> Duration {
|
||||
self.vl1.do_background_tasks(ii)
|
||||
}
|
||||
|
||||
#[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)
|
||||
}
|
||||
|
||||
#[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)
|
||||
}
|
||||
|
||||
/// 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 {
|
||||
let mut roots = self.roots.lock();
|
||||
let entry = roots.sets.get_mut(&rs.name);
|
||||
|
@ -422,16 +432,17 @@ impl Node {
|
|||
if rs.should_replace(old_rs) {
|
||||
*old_rs = rs;
|
||||
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 {
|
||||
if rs.verify() {
|
||||
roots.sets.insert(rs.name.clone(), rs);
|
||||
roots.sets_modified = true;
|
||||
return true;
|
||||
}
|
||||
false
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/// 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 network: BTreeMap<NetworkId, NetworkSettings>,
|
||||
pub settings: GlobalSettings,
|
||||
|
||||
#[serde(skip_serializing, skip_deserializing)]
|
||||
file_content_hash: [u8; 48],
|
||||
}
|
||||
|
||||
impl Default for Config {
|
||||
|
@ -148,48 +145,6 @@ impl Default for Config {
|
|||
virtual_: BTreeMap::new(),
|
||||
network: BTreeMap::new(),
|
||||
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};
|
||||
|
||||
pub mod cli;
|
||||
pub mod datadir;
|
||||
pub mod exitcode;
|
||||
pub mod getifaddrs;
|
||||
pub mod jsonformatter;
|
||||
|
|
|
@ -6,6 +6,7 @@
|
|||
* https://www.zerotier.com/
|
||||
*/
|
||||
|
||||
use std::path::Path;
|
||||
use std::str::FromStr;
|
||||
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.
|
||||
///
|
||||
/// 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 bytes = f.metadata().await?.len().min(limit as u64) as usize;
|
||||
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)
|
||||
}
|
||||
|
||||
/// 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()
|
||||
/// Set permissions on a file or directory to be most restrictive (visible only to the service's user).
|
||||
#[cfg(unix)]
|
||||
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.
|
||||
|
@ -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 {
|
||||
parse_func(input)
|
||||
}
|
||||
|
|
Loading…
Add table
Reference in a new issue