Data directory abstraction, local config, various other things.

This commit is contained in:
Adam Ierymenko 2022-05-11 13:25:51 -04:00
parent 2d3b96725b
commit f14efdcd3d
No known key found for this signature in database
GPG key ID: C8877CF2D7A5D7F3
7 changed files with 163 additions and 64 deletions

View file

@ -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));

View file

@ -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)
}
}

View file

@ -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,17 +432,18 @@ impl Node {
if rs.should_replace(old_rs) {
*old_rs = rs;
roots.sets_modified = true;
return true;
}
true
} else {
if rs.verify() {
false
}
} else if rs.verify() {
roots.sets.insert(rs.name.clone(), rs);
roots.sets_modified = true;
return true;
true
} else {
false
}
}
return false;
}
/// Get the canonical Path object for a given endpoint and local socket information.
///

View 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(())
}
}

View file

@ -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
}
}
}

View file

@ -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;

View file

@ -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)
}