mirror of
https://github.com/zerotier/ZeroTierOne.git
synced 2025-06-06 20:43:44 +02:00
Wire up logging, service, etc.
This commit is contained in:
parent
f9649217fb
commit
8d6d457dd6
12 changed files with 505 additions and 368 deletions
|
@ -10,6 +10,7 @@ struct prf_ra {
|
||||||
#include "../core/Constants.hpp"
|
#include "../core/Constants.hpp"
|
||||||
#include "../core/Mutex.hpp"
|
#include "../core/Mutex.hpp"
|
||||||
#include "../core/Containers.hpp"
|
#include "../core/Containers.hpp"
|
||||||
|
#include "../core/SHA512.hpp"
|
||||||
#include "OSUtils.hpp"
|
#include "OSUtils.hpp"
|
||||||
|
|
||||||
#include "rust-osdep.h"
|
#include "rust-osdep.h"
|
||||||
|
@ -116,4 +117,13 @@ int64_t msSinceEpoch()
|
||||||
void lockDownFile(const char *path, int isDir)
|
void lockDownFile(const char *path, int isDir)
|
||||||
{ ZeroTier::OSUtils::lockDownFile(path, isDir != 0); }
|
{ ZeroTier::OSUtils::lockDownFile(path, isDir != 0); }
|
||||||
|
|
||||||
|
void getSecureRandom(void *buf, unsigned int len)
|
||||||
|
{ ZeroTier::Utils::getSecureRandom(buf, len); }
|
||||||
|
|
||||||
|
void sha384(const void *in, unsigned int len, void *out)
|
||||||
|
{ ZeroTier::SHA384(out, in, len); }
|
||||||
|
|
||||||
|
void sha512(const void *in, unsigned int len, void *out)
|
||||||
|
{ ZeroTier::SHA512(out, in, len); }
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -58,6 +58,9 @@ extern "C" {
|
||||||
extern const char *platformDefaultHomePath();
|
extern const char *platformDefaultHomePath();
|
||||||
extern int64_t msSinceEpoch();
|
extern int64_t msSinceEpoch();
|
||||||
extern void lockDownFile(const char *path, int isDir);
|
extern void lockDownFile(const char *path, int isDir);
|
||||||
|
extern void getSecureRandom(void *buf, unsigned int len);
|
||||||
|
extern void sha384(const void *in, unsigned int len, void *out);
|
||||||
|
extern void sha512(const void *in, unsigned int len, void *out);
|
||||||
|
|
||||||
#ifdef __cplusplus
|
#ifdef __cplusplus
|
||||||
}
|
}
|
||||||
|
|
|
@ -165,6 +165,23 @@ pub enum ResultCode {
|
||||||
ErrorInternalNonFatal = ztcore::ZT_ResultCode_ZT_RESULT_ERROR_INTERNAL as isize,
|
ErrorInternalNonFatal = ztcore::ZT_ResultCode_ZT_RESULT_ERROR_INTERNAL as isize,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl ToString for ResultCode {
|
||||||
|
fn to_string(&self) -> String {
|
||||||
|
match *self {
|
||||||
|
ResultCode::Ok => "Ok",
|
||||||
|
ResultCode::FatalErrorOutOfMemory => "FatalErrorOutOfMemory",
|
||||||
|
ResultCode::FatalErrorDataStoreFailed => "FatalErrorDataStoreFailed",
|
||||||
|
ResultCode::FatalErrorInternal => "FatalErrorInternal",
|
||||||
|
ResultCode::ErrorNetworkNotFound => "ErrorNetworkNotFound",
|
||||||
|
ResultCode::ErrorUnsupportedOperation => "ErrorUnsupportedOperation",
|
||||||
|
ResultCode::ErrorBadParameter => "ErrorBadParameter",
|
||||||
|
ResultCode::ErrorInvalidCredential => "ErrorInvalidCredential",
|
||||||
|
ResultCode::ErrorCollidingObject => "ErrorCollidingObject",
|
||||||
|
ResultCode::ErrorInternalNonFatal => "ErrorInternalNonFatal",
|
||||||
|
}.to_string()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// Returns a tuple of major, minor, revision, and build version numbers from the ZeroTier core.
|
/// Returns a tuple of major, minor, revision, and build version numbers from the ZeroTier core.
|
||||||
pub fn version() -> (i32, i32, i32, i32) {
|
pub fn version() -> (i32, i32, i32, i32) {
|
||||||
let mut major: c_int = 0;
|
let mut major: c_int = 0;
|
||||||
|
|
|
@ -11,15 +11,12 @@
|
||||||
*/
|
*/
|
||||||
/****/
|
/****/
|
||||||
|
|
||||||
use std::cell::Cell;
|
|
||||||
use std::collections::hash_map::HashMap;
|
use std::collections::hash_map::HashMap;
|
||||||
use std::intrinsics::copy_nonoverlapping;
|
use std::intrinsics::copy_nonoverlapping;
|
||||||
use std::mem::{MaybeUninit, transmute};
|
use std::mem::{MaybeUninit, transmute};
|
||||||
use std::os::raw::{c_int, c_uint, c_ulong, c_void};
|
use std::os::raw::{c_int, c_uint, c_ulong, c_void};
|
||||||
use std::ptr::{null_mut, slice_from_raw_parts};
|
use std::ptr::{null_mut, slice_from_raw_parts};
|
||||||
use std::sync::*;
|
use std::sync::*;
|
||||||
use std::sync::atomic::*;
|
|
||||||
use std::time::Duration;
|
|
||||||
|
|
||||||
use num_traits::FromPrimitive;
|
use num_traits::FromPrimitive;
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
|
|
|
@ -11,7 +11,6 @@
|
||||||
*/
|
*/
|
||||||
/****/
|
/****/
|
||||||
|
|
||||||
pub(crate) mod service;
|
|
||||||
pub(crate) mod status;
|
pub(crate) mod status;
|
||||||
pub(crate) mod set;
|
pub(crate) mod set;
|
||||||
pub(crate) mod peer;
|
pub(crate) mod peer;
|
||||||
|
|
|
@ -1,284 +0,0 @@
|
||||||
/*
|
|
||||||
* Copyright (c)2013-2020 ZeroTier, Inc.
|
|
||||||
*
|
|
||||||
* Use of this software is governed by the Business Source License included
|
|
||||||
* in the LICENSE.TXT file in the project's root directory.
|
|
||||||
*
|
|
||||||
* Change Date: 2025-01-01
|
|
||||||
*
|
|
||||||
* On the date above, in accordance with the Business Source License, use
|
|
||||||
* of this software will be governed by version 2.0 of the Apache License.
|
|
||||||
*/
|
|
||||||
/****/
|
|
||||||
|
|
||||||
use std::cell::Cell;
|
|
||||||
use std::collections::BTreeMap;
|
|
||||||
use std::net::IpAddr;
|
|
||||||
use std::rc::Rc;
|
|
||||||
use std::str::FromStr;
|
|
||||||
use std::sync::{Arc, Mutex};
|
|
||||||
use std::sync::atomic::{AtomicBool, Ordering};
|
|
||||||
use std::time::Duration;
|
|
||||||
|
|
||||||
use futures::stream::{self, StreamExt};
|
|
||||||
use warp::{Filter, Rejection, Reply};
|
|
||||||
use warp::http::{HeaderMap, Method, StatusCode};
|
|
||||||
use warp::hyper::body::Bytes;
|
|
||||||
|
|
||||||
use zerotier_core::{Buffer, Address, IpScope, Node, NodeEventHandler, NetworkId, VirtualNetworkConfigOperation, VirtualNetworkConfig, StateObjectType, MAC, Event, InetAddress, InetAddressFamily, Identity};
|
|
||||||
|
|
||||||
use crate::fastudpsocket::*;
|
|
||||||
use crate::getifaddrs;
|
|
||||||
use crate::localconfig::*;
|
|
||||||
use crate::log::Log;
|
|
||||||
use crate::network::Network;
|
|
||||||
use crate::store::Store;
|
|
||||||
|
|
||||||
// Check local addresses and bindings every (this) milliseconds.
|
|
||||||
const BINDING_CHECK_INTERVAL: i64 = 5000;
|
|
||||||
|
|
||||||
struct Service {
|
|
||||||
local_config: Mutex<Arc<LocalConfig>>,
|
|
||||||
run: Arc<AtomicBool>,
|
|
||||||
store: Arc<Store>,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Clone for Service {
|
|
||||||
fn clone(&self) -> Self {
|
|
||||||
Service {
|
|
||||||
local_config: Mutex::new(self.local_config.lock().unwrap().clone()),
|
|
||||||
run: self.run.clone(),
|
|
||||||
store: self.store.clone(),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl NodeEventHandler<Network> for Service {
|
|
||||||
fn virtual_network_config(&self, network_id: NetworkId, network_obj: &Arc<Network>, config_op: VirtualNetworkConfigOperation, config: Option<&VirtualNetworkConfig>) {
|
|
||||||
}
|
|
||||||
|
|
||||||
#[inline(always)]
|
|
||||||
fn virtual_network_frame(&self, network_id: NetworkId, network_obj: &Arc<Network>, source_mac: MAC, dest_mac: MAC, ethertype: u16, vlan_id: u16, data: &[u8]) {
|
|
||||||
}
|
|
||||||
|
|
||||||
fn event(&self, event: Event, event_data: &[u8]) {
|
|
||||||
match event {
|
|
||||||
Event::Up => {},
|
|
||||||
Event::Down => {},
|
|
||||||
Event::Online => {},
|
|
||||||
Event::Offline => {},
|
|
||||||
Event::Trace => {},
|
|
||||||
Event::UserMessage => {},
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[inline(always)]
|
|
||||||
fn state_put(&self, obj_type: StateObjectType, obj_id: &[u64], obj_data: &[u8]) -> std::io::Result<()> {
|
|
||||||
self.store.store_object(&obj_type, obj_id, obj_data)
|
|
||||||
}
|
|
||||||
|
|
||||||
#[inline(always)]
|
|
||||||
fn state_get(&self, obj_type: StateObjectType, obj_id: &[u64]) -> std::io::Result<Vec<u8>> {
|
|
||||||
self.store.load_object(&obj_type, obj_id)
|
|
||||||
}
|
|
||||||
|
|
||||||
#[inline(always)]
|
|
||||||
fn wire_packet_send(&self, local_socket: i64, sock_addr: &InetAddress, data: &[u8], packet_ttl: u32) -> i32 {
|
|
||||||
0
|
|
||||||
}
|
|
||||||
|
|
||||||
fn path_check(&self, address: Address, id: &Identity, local_socket: i64, sock_addr: &InetAddress) -> bool {
|
|
||||||
true
|
|
||||||
}
|
|
||||||
|
|
||||||
fn path_lookup(&self, address: Address, id: &Identity, desired_family: InetAddressFamily) -> Option<InetAddress> {
|
|
||||||
let lc = self.get_local_config();
|
|
||||||
let vc = lc.virtual_.get(&address);
|
|
||||||
vc.map_or(None, |c: &LocalConfigVirtualConfig| {
|
|
||||||
if c.try_.is_empty() {
|
|
||||||
None
|
|
||||||
} else {
|
|
||||||
let t = c.try_.get((zerotier_core::random() as usize) % c.try_.len());
|
|
||||||
t.map_or(None, |v: &InetAddress| {
|
|
||||||
Some(v.clone())
|
|
||||||
})
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Service {
|
|
||||||
#[inline(always)]
|
|
||||||
fn web_api_status(&self, method: Method, headers: HeaderMap, post_data: Bytes) -> Box<dyn Reply> {
|
|
||||||
Box::new(warp::http::StatusCode::BAD_REQUEST)
|
|
||||||
}
|
|
||||||
|
|
||||||
#[inline(always)]
|
|
||||||
fn web_api_network(&self, network_str: String, method: Method, headers: HeaderMap, post_data: Bytes) -> Box<dyn Reply> {
|
|
||||||
Box::new(warp::http::StatusCode::BAD_REQUEST)
|
|
||||||
}
|
|
||||||
|
|
||||||
#[inline(always)]
|
|
||||||
fn web_api_peer(&self, peer_str: String, method: Method, headers: HeaderMap, post_data: Bytes) -> Box<dyn Reply> {
|
|
||||||
Box::new(warp::http::StatusCode::BAD_REQUEST)
|
|
||||||
}
|
|
||||||
|
|
||||||
#[inline(always)]
|
|
||||||
fn get_local_config(&self) -> Arc<LocalConfig> {
|
|
||||||
self.local_config.lock().unwrap().clone()
|
|
||||||
}
|
|
||||||
|
|
||||||
#[inline(always)]
|
|
||||||
fn set_local_config(&self, new_lc: &Arc<LocalConfig>) {
|
|
||||||
let mut lc = self.local_config.lock().unwrap();
|
|
||||||
*lc = new_lc.clone();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub(crate) fn run(store: &Arc<Store>) -> i32 {
|
|
||||||
let mut process_exit_value: i32 = 0;
|
|
||||||
|
|
||||||
let tokio_rt = tokio::runtime::Builder::new_current_thread().build().unwrap();
|
|
||||||
tokio_rt.block_on(async {
|
|
||||||
let mut udp_sockets: BTreeMap<InetAddress, FastUDPSocket> = BTreeMap::new();
|
|
||||||
let (mut interrupt_tx, mut interrupt_rx) = futures::channel::mpsc::channel::<u8>(2);
|
|
||||||
|
|
||||||
let service = Service {
|
|
||||||
local_config: Mutex::new(Arc::new(LocalConfig::default())),
|
|
||||||
run: Arc::new(AtomicBool::new(true)),
|
|
||||||
store: store.clone(),
|
|
||||||
};
|
|
||||||
|
|
||||||
let node = Node::new(service.clone());
|
|
||||||
if node.is_err() {
|
|
||||||
// TODO: log and handle error
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
let node = Arc::new(node.ok().unwrap());
|
|
||||||
|
|
||||||
let mut primary_port_bind_failure = false;
|
|
||||||
let mut last_checked_bindings: i64 = 0;
|
|
||||||
let mut loop_delay = zerotier_core::NODE_BACKGROUND_TASKS_MAX_INTERVAL;
|
|
||||||
loop {
|
|
||||||
let mut current_local_config = service.get_local_config();
|
|
||||||
|
|
||||||
let (mut shutdown_tx, mut shutdown_rx) = futures::channel::oneshot::channel();
|
|
||||||
let warp_server;
|
|
||||||
{
|
|
||||||
let s0 = service.clone();
|
|
||||||
let s1 = service.clone();
|
|
||||||
let s2 = service.clone();
|
|
||||||
warp_server = warp::serve(warp::any().and(warp::path::end().map(|| { warp::reply::with_status("404", warp::hyper::StatusCode::NOT_FOUND) })
|
|
||||||
.or(warp::path("status").and(warp::method()).and(warp::header::headers_cloned()).and(warp::body::bytes())
|
|
||||||
.map(move |method: Method, headers: HeaderMap, post_data: Bytes| { s0.web_api_status(method, headers, post_data) }))
|
|
||||||
.or(warp::path!("network" / String).and(warp::method()).and(warp::header::headers_cloned()).and(warp::body::bytes())
|
|
||||||
.map(move |network_str: String, method: Method, headers: HeaderMap, post_data: Bytes| { s1.web_api_network(network_str, method, headers, post_data) }))
|
|
||||||
.or(warp::path!("peer" / String).and(warp::method()).and(warp::header::headers_cloned()).and(warp::body::bytes())
|
|
||||||
.map(move |peer_str: String, method: Method, headers: HeaderMap, post_data: Bytes| { s2.web_api_peer(peer_str, method, headers, post_data) }))
|
|
||||||
)).try_bind_with_graceful_shutdown((IpAddr::from([127_u8, 0_u8, 0_u8, 1_u8]), current_local_config.settings.primary_port), async { let _ = shutdown_rx.await; });
|
|
||||||
}
|
|
||||||
if warp_server.is_err() {
|
|
||||||
primary_port_bind_failure = true;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
let warp_server = tokio_rt.spawn(warp_server.unwrap().1);
|
|
||||||
|
|
||||||
loop {
|
|
||||||
tokio::select! {
|
|
||||||
_ = tokio::time::sleep(Duration::from_millis(loop_delay)) => {},
|
|
||||||
_ = interrupt_rx.next() => {},
|
|
||||||
_ = tokio::signal::ctrl_c() => {
|
|
||||||
// TODO: log CTRL+C received
|
|
||||||
service.run.store(false, Ordering::Relaxed);
|
|
||||||
let _ = shutdown_tx.send(());
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
loop_delay = node.process_background_tasks();
|
|
||||||
|
|
||||||
let now = zerotier_core::now();
|
|
||||||
if (now - last_checked_bindings) >= BINDING_CHECK_INTERVAL {
|
|
||||||
last_checked_bindings = now;
|
|
||||||
|
|
||||||
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 !current_local_config.settings.is_interface_blacklisted(dev) {
|
|
||||||
let mut a = addr.clone();
|
|
||||||
a.set_port(current_local_config.settings.primary_port);
|
|
||||||
system_addrs.insert(a, String::from(dev));
|
|
||||||
if current_local_config.settings.secondary_port.is_some() {
|
|
||||||
let mut a = addr.clone();
|
|
||||||
a.set_port(current_local_config.settings.secondary_port.unwrap());
|
|
||||||
system_addrs.insert(a, String::from(dev));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
_ => {}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
let mut udp_sockets_to_close: Vec<InetAddress> = Vec::new();
|
|
||||||
for sock in udp_sockets.iter() {
|
|
||||||
if !system_addrs.contains_key(sock.0) {
|
|
||||||
udp_sockets_to_close.push(sock.0.clone());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
for k in udp_sockets_to_close.iter() {
|
|
||||||
udp_sockets.remove(k);
|
|
||||||
}
|
|
||||||
|
|
||||||
for addr in system_addrs.iter() {
|
|
||||||
if !udp_sockets.contains_key(addr.0) {
|
|
||||||
let s = FastUDPSocket::new(addr.1.as_str(), addr.0, |raw_socket: &FastUDPRawOsSocket, from_address: &InetAddress, data: Buffer| {
|
|
||||||
// TODO: incoming packet handler
|
|
||||||
});
|
|
||||||
if s.is_ok() {
|
|
||||||
udp_sockets.insert(addr.0.clone(), s.unwrap());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
primary_port_bind_failure = true;
|
|
||||||
for s in udp_sockets.iter() {
|
|
||||||
if s.0.port() == current_local_config.settings.primary_port {
|
|
||||||
primary_port_bind_failure = false;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if primary_port_bind_failure {
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
let next_local_config = service.get_local_config();
|
|
||||||
if !service.run.load(Ordering::Relaxed) || current_local_config.settings.primary_port != next_local_config.settings.primary_port {
|
|
||||||
let _ = shutdown_tx.send(());
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
current_local_config = next_local_config;
|
|
||||||
}
|
|
||||||
|
|
||||||
let _ = warp_server.await;
|
|
||||||
|
|
||||||
if !service.run.load(Ordering::Relaxed) {
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
tokio::time::sleep(Duration::from_millis(250)).await;
|
|
||||||
if !service.run.load(Ordering::Relaxed) {
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
if primary_port_bind_failure {
|
|
||||||
let local_config = service.get_local_config();
|
|
||||||
if local_config.settings.auto_port_search {
|
|
||||||
// TODO: port hunting if enabled
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
process_exit_value
|
|
||||||
}
|
|
|
@ -33,6 +33,21 @@ pub(crate) type FastUDPRawOsSocket = winsock2::SOCKET;
|
||||||
#[cfg(unix)]
|
#[cfg(unix)]
|
||||||
pub(crate) type FastUDPRawOsSocket = c_int;
|
pub(crate) type FastUDPRawOsSocket = c_int;
|
||||||
|
|
||||||
|
/// Test bind UDP to a port at 0.0.0.0 and ::0, returning whether IPv4 and/or IPv6 succeeded (respectively).
|
||||||
|
pub(crate) fn test_bind_udp(port: u16) -> (bool, bool) {
|
||||||
|
let v4 = InetAddress::new_ipv4_any(port);
|
||||||
|
let v6 = InetAddress::new_ipv6_any(port);
|
||||||
|
let v4b = bind_udp_socket("", &v4);
|
||||||
|
if v4b.is_ok() {
|
||||||
|
fast_udp_socket_close(v4b.as_ref().unwrap());
|
||||||
|
}
|
||||||
|
let v6b = bind_udp_socket("", &v6);
|
||||||
|
if v6b.is_ok() {
|
||||||
|
fast_udp_socket_close(v6b.as_ref().unwrap());
|
||||||
|
}
|
||||||
|
(v4b.is_ok(), v6b.is_ok())
|
||||||
|
}
|
||||||
|
|
||||||
//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||||
// bind_udp_socket() implementations for each platform
|
// bind_udp_socket() implementations for each platform
|
||||||
|
|
||||||
|
@ -122,21 +137,6 @@ fn bind_udp_socket(_: &str, address: &InetAddress) -> Result<FastUDPRawOsSocket,
|
||||||
|
|
||||||
//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||||
|
|
||||||
/// Test bind UDP to a port at 0.0.0.0 and ::0, returning whether IPv4 and/or IPv6 succeeded (respectively).
|
|
||||||
pub(crate) fn test_bind_udp(port: u16) -> (bool, bool) {
|
|
||||||
let v4 = InetAddress::new_ipv4_any(port);
|
|
||||||
let v6 = InetAddress::new_ipv6_any(port);
|
|
||||||
let v4b = bind_udp_socket("", &v4);
|
|
||||||
if v4b.is_ok() {
|
|
||||||
fast_udp_socket_close(v4b.as_ref().unwrap());
|
|
||||||
}
|
|
||||||
let v6b = bind_udp_socket("", &v6);
|
|
||||||
if v6b.is_ok() {
|
|
||||||
fast_udp_socket_close(v6b.as_ref().unwrap());
|
|
||||||
}
|
|
||||||
(v4b.is_ok(), v6b.is_ok())
|
|
||||||
}
|
|
||||||
|
|
||||||
/// A multi-threaded (or otherwise fast) UDP socket that binds to both IPv4 and IPv6 addresses.
|
/// A multi-threaded (or otherwise fast) UDP socket that binds to both IPv4 and IPv6 addresses.
|
||||||
pub(crate) struct FastUDPSocket {
|
pub(crate) struct FastUDPSocket {
|
||||||
threads: Vec<std::thread::JoinHandle<()>>,
|
threads: Vec<std::thread::JoinHandle<()>>,
|
||||||
|
|
|
@ -160,7 +160,7 @@ impl Default for LocalConfigNetworkSettings {
|
||||||
|
|
||||||
impl LocalConfigSettings {
|
impl LocalConfigSettings {
|
||||||
#[cfg(target_os = "macos")]
|
#[cfg(target_os = "macos")]
|
||||||
const DEFAULT_PREFIX_BLACKLIST: [&'static str; 7] = ["lo", "utun", "gif", "stf", "iptap", "pktap", "feth"];
|
const DEFAULT_PREFIX_BLACKLIST: [&'static str; 8] = ["lo", "utun", "gif", "stf", "iptap", "pktap", "feth", "zt"];
|
||||||
|
|
||||||
#[cfg(target_os = "linux")]
|
#[cfg(target_os = "linux")]
|
||||||
const DEFAULT_PREFIX_BLACKLIST: [&'static str; 5] = ["lo", "tun", "tap", "ipsec", "zt"];
|
const DEFAULT_PREFIX_BLACKLIST: [&'static str; 5] = ["lo", "tun", "tap", "ipsec", "zt"];
|
||||||
|
|
|
@ -15,24 +15,20 @@ use std::cell::Cell;
|
||||||
use std::fmt::Display;
|
use std::fmt::Display;
|
||||||
use std::fs::{File, OpenOptions};
|
use std::fs::{File, OpenOptions};
|
||||||
use std::io::{Seek, SeekFrom, Write};
|
use std::io::{Seek, SeekFrom, Write};
|
||||||
use std::sync::atomic::{AtomicUsize, Ordering, AtomicBool};
|
|
||||||
use std::sync::Mutex;
|
use std::sync::Mutex;
|
||||||
|
|
||||||
use chrono::Datelike;
|
use chrono::Datelike;
|
||||||
|
|
||||||
use zerotier_core::PortableAtomicI64;
|
|
||||||
|
|
||||||
struct LogIntl {
|
struct LogIntl {
|
||||||
file: Option<File>,
|
file: Option<File>,
|
||||||
cur_size: u64,
|
cur_size: u64,
|
||||||
max_size: usize,
|
max_size: usize,
|
||||||
enabled: bool
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(crate) struct Log {
|
pub(crate) struct Log {
|
||||||
prefix: String,
|
prefix: String,
|
||||||
path: String,
|
path: String,
|
||||||
intl: Mutex<LogIntl>
|
intl: Mutex<LogIntl>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Log {
|
impl Log {
|
||||||
|
@ -50,7 +46,6 @@ impl Log {
|
||||||
file: None,
|
file: None,
|
||||||
cur_size: 0,
|
cur_size: 0,
|
||||||
max_size: if max_size < Log::MIN_MAX_SIZE { Log::MIN_MAX_SIZE } else { max_size },
|
max_size: if max_size < Log::MIN_MAX_SIZE { Log::MIN_MAX_SIZE } else { max_size },
|
||||||
enabled: true,
|
|
||||||
}),
|
}),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -59,13 +54,13 @@ impl Log {
|
||||||
self.intl.lock().unwrap().max_size = if new_max_size < Log::MIN_MAX_SIZE { Log::MIN_MAX_SIZE } else { new_max_size };
|
self.intl.lock().unwrap().max_size = if new_max_size < Log::MIN_MAX_SIZE { Log::MIN_MAX_SIZE } else { new_max_size };
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn set_enabled(&self, enabled: bool) {
|
pub fn log<S: AsRef<str>>(&self, s: S) {
|
||||||
self.intl.lock().unwrap().enabled = enabled;
|
let ss: &str = s.as_ref();
|
||||||
|
if ss.starts_with("FATAL") {
|
||||||
|
eprintln!("{}", ss);
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn log<S: AsRef<str>>(&self, s: S) {
|
|
||||||
let mut l = self.intl.lock().unwrap();
|
let mut l = self.intl.lock().unwrap();
|
||||||
if l.enabled {
|
|
||||||
if l.file.is_none() {
|
if l.file.is_none() {
|
||||||
let mut f = OpenOptions::new().read(true).write(true).create(true).open(self.path.as_str());
|
let mut f = OpenOptions::new().read(true).write(true).create(true).open(self.path.as_str());
|
||||||
if f.is_err() {
|
if f.is_err() {
|
||||||
|
@ -99,15 +94,20 @@ impl Log {
|
||||||
|
|
||||||
let f = l.file.as_mut().unwrap();
|
let f = l.file.as_mut().unwrap();
|
||||||
let now_str = chrono::Local::now().format("%Y-%m-%d %H:%M:%S").to_string();
|
let now_str = chrono::Local::now().format("%Y-%m-%d %H:%M:%S").to_string();
|
||||||
let ss: &str = s.as_ref();
|
|
||||||
let log_line = format!("{}[{}] {}\n", self.prefix.as_str(), now_str.as_str(), ss);
|
let log_line = format!("{}[{}] {}\n", self.prefix.as_str(), now_str.as_str(), ss);
|
||||||
let _ = f.write_all(log_line.as_bytes());
|
let _ = f.write_all(log_line.as_bytes());
|
||||||
let _ = f.flush();
|
let _ = f.flush();
|
||||||
l.cur_size += log_line.len() as u64;
|
l.cur_size += log_line.len() as u64;
|
||||||
}
|
}
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[macro_export]
|
||||||
|
macro_rules! l(
|
||||||
|
($logger:ident, $($arg:tt)*) => {
|
||||||
|
$logger.log(format!($($arg)*))
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
unsafe impl Sync for Log {}
|
unsafe impl Sync for Log {}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
|
|
|
@ -17,10 +17,11 @@ mod commands;
|
||||||
mod fastudpsocket;
|
mod fastudpsocket;
|
||||||
mod localconfig;
|
mod localconfig;
|
||||||
mod getifaddrs;
|
mod getifaddrs;
|
||||||
mod log;
|
#[macro_use] mod log;
|
||||||
mod store;
|
mod store;
|
||||||
mod network;
|
mod network;
|
||||||
mod vnic;
|
mod vnic;
|
||||||
|
mod service;
|
||||||
|
|
||||||
#[allow(non_snake_case,non_upper_case_globals,non_camel_case_types,dead_code,improper_ctypes)]
|
#[allow(non_snake_case,non_upper_case_globals,non_camel_case_types,dead_code,improper_ctypes)]
|
||||||
mod osdep; // bindgen generated
|
mod osdep; // bindgen generated
|
||||||
|
@ -28,18 +29,19 @@ mod osdep; // bindgen generated
|
||||||
use std::boxed::Box;
|
use std::boxed::Box;
|
||||||
use std::ffi::CStr;
|
use std::ffi::CStr;
|
||||||
use std::path::Path;
|
use std::path::Path;
|
||||||
|
use std::sync::Arc;
|
||||||
|
|
||||||
use crate::store::Store;
|
use crate::store::Store;
|
||||||
use std::sync::Arc;
|
|
||||||
|
|
||||||
fn main() {
|
fn main() {
|
||||||
let mut process_exit_value: i32 = 0;
|
let mut process_exit_value: i32 = 0;
|
||||||
|
|
||||||
let mut cli_args = Some(Box::new(cli::parse_cli_args()));
|
let mut cli_args = Some(Box::new(cli::parse_cli_args()));
|
||||||
let mut zerotier_path = unsafe { zerotier_core::cstr_to_string(osdep::platformDefaultHomePath(), 256) };
|
let mut zerotier_path = unsafe { zerotier_core::cstr_to_string(osdep::platformDefaultHomePath(), -1) };
|
||||||
let json_output;
|
|
||||||
let mut token: Option<String> = None;
|
let json_output: bool;
|
||||||
let mut token_path = Path::new(&zerotier_path).join("authtoken.secret");
|
let mut auth_token: Option<String> = None;
|
||||||
|
let mut auth_token_path: Option<String> = None;
|
||||||
{
|
{
|
||||||
let a = cli_args.as_ref().unwrap();
|
let a = cli_args.as_ref().unwrap();
|
||||||
json_output = a.is_present("json");
|
json_output = a.is_present("json");
|
||||||
|
@ -49,21 +51,35 @@ fn main() {
|
||||||
}
|
}
|
||||||
let v = a.value_of("token");
|
let v = a.value_of("token");
|
||||||
if v.is_some() {
|
if v.is_some() {
|
||||||
token = Some(String::from(v.unwrap().trim()));
|
auth_token = Some(v.unwrap().trim().to_string());
|
||||||
}
|
}
|
||||||
let v = a.value_of("token_path");
|
let v = a.value_of("token_path");
|
||||||
if v.is_some() {
|
if v.is_some() {
|
||||||
token_path = Path::new(v.unwrap().trim()).to_path_buf();
|
auth_token_path = Some(v.unwrap().to_string());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
let store = Store::new(zerotier_path.as_str());
|
let store = Store::new(zerotier_path.as_str());
|
||||||
if store.is_err() {
|
if store.is_err() {
|
||||||
println!("FATAL: error accessing directory '{}': {}", zerotier_path, store.err().unwrap().to_string());
|
eprintln!("FATAL: error accessing directory '{}': {}", zerotier_path, store.err().unwrap().to_string());
|
||||||
std::process::exit(1);
|
std::process::exit(1);
|
||||||
}
|
}
|
||||||
let store = Arc::new(store.unwrap());
|
let store = Arc::new(store.unwrap());
|
||||||
|
|
||||||
|
if auth_token.is_none() {
|
||||||
|
let t;
|
||||||
|
if auth_token_path.is_some() {
|
||||||
|
t = store.read_file_str(auth_token_path.unwrap().trim());
|
||||||
|
} else {
|
||||||
|
t = store.read_authtoken_secret();
|
||||||
|
}
|
||||||
|
if t.is_ok() {
|
||||||
|
auth_token = Some(t.unwrap().trim().to_string());
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
auth_token = Some(auth_token.unwrap().trim().to_string());
|
||||||
|
}
|
||||||
|
|
||||||
match cli_args.as_ref().unwrap().subcommand_name().unwrap() {
|
match cli_args.as_ref().unwrap().subcommand_name().unwrap() {
|
||||||
"version" => {
|
"version" => {
|
||||||
let ver = zerotier_core::version();
|
let ver = zerotier_core::version();
|
||||||
|
@ -71,7 +87,7 @@ fn main() {
|
||||||
},
|
},
|
||||||
"service" => {
|
"service" => {
|
||||||
cli_args = None; // free any memory we can when launching service
|
cli_args = None; // free any memory we can when launching service
|
||||||
process_exit_value = commands::service::run(&store);
|
process_exit_value = service::run(&store, auth_token);
|
||||||
},
|
},
|
||||||
_ => cli::print_help(), // includes "help"
|
_ => cli::print_help(), // includes "help"
|
||||||
}
|
}
|
||||||
|
|
363
rust-zerotier-service/src/service.rs
Normal file
363
rust-zerotier-service/src/service.rs
Normal file
|
@ -0,0 +1,363 @@
|
||||||
|
/*
|
||||||
|
* Copyright (c)2013-2020 ZeroTier, Inc.
|
||||||
|
*
|
||||||
|
* Use of this software is governed by the Business Source License included
|
||||||
|
* in the LICENSE.TXT file in the project's root directory.
|
||||||
|
*
|
||||||
|
* Change Date: 2025-01-01
|
||||||
|
*
|
||||||
|
* On the date above, in accordance with the Business Source License, use
|
||||||
|
* of this software will be governed by version 2.0 of the Apache License.
|
||||||
|
*/
|
||||||
|
/****/
|
||||||
|
|
||||||
|
use std::collections::BTreeMap;
|
||||||
|
use std::net::IpAddr;
|
||||||
|
use std::str::FromStr;
|
||||||
|
use std::sync::{Arc, Mutex, Weak};
|
||||||
|
use std::sync::atomic::{AtomicBool, Ordering};
|
||||||
|
use std::time::Duration;
|
||||||
|
|
||||||
|
use futures::stream::StreamExt;
|
||||||
|
use warp::{Filter, Reply};
|
||||||
|
use warp::http::{HeaderMap, Method, StatusCode};
|
||||||
|
use warp::hyper::body::Bytes;
|
||||||
|
|
||||||
|
use zerotier_core::{Buffer, Address, IpScope, Node, NodeEventHandler, NetworkId, VirtualNetworkConfigOperation, VirtualNetworkConfig, StateObjectType, MAC, Event, InetAddress, InetAddressFamily, Identity};
|
||||||
|
|
||||||
|
use crate::fastudpsocket::*;
|
||||||
|
use crate::getifaddrs;
|
||||||
|
use crate::localconfig::*;
|
||||||
|
use crate::log::Log;
|
||||||
|
use crate::network::Network;
|
||||||
|
use crate::store::Store;
|
||||||
|
|
||||||
|
const CONFIG_CHECK_INTERVAL: i64 = 5000;
|
||||||
|
|
||||||
|
#[derive(Clone)]
|
||||||
|
struct Service {
|
||||||
|
auth_token: Arc<String>,
|
||||||
|
log: Arc<Log>,
|
||||||
|
_local_config: Arc<Mutex<Arc<LocalConfig>>>,
|
||||||
|
run: Arc<AtomicBool>,
|
||||||
|
store: Arc<Store>,
|
||||||
|
node: Weak<Node<Service, Network>>, // weak since Node can hold a reference to this
|
||||||
|
}
|
||||||
|
|
||||||
|
impl NodeEventHandler<Network> for Service {
|
||||||
|
fn virtual_network_config(&self, network_id: NetworkId, network_obj: &Arc<Network>, config_op: VirtualNetworkConfigOperation, config: Option<&VirtualNetworkConfig>) {}
|
||||||
|
|
||||||
|
#[inline(always)]
|
||||||
|
fn virtual_network_frame(&self, network_id: NetworkId, network_obj: &Arc<Network>, source_mac: MAC, dest_mac: MAC, ethertype: u16, vlan_id: u16, data: &[u8]) {}
|
||||||
|
|
||||||
|
fn event(&self, event: Event, event_data: &[u8]) {
|
||||||
|
match event {
|
||||||
|
Event::Up => {}
|
||||||
|
Event::Down => {}
|
||||||
|
Event::Online => {}
|
||||||
|
Event::Offline => {}
|
||||||
|
Event::Trace => {}
|
||||||
|
Event::UserMessage => {}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[inline(always)]
|
||||||
|
fn state_put(&self, obj_type: StateObjectType, obj_id: &[u64], obj_data: &[u8]) -> std::io::Result<()> {
|
||||||
|
self.store.store_object(&obj_type, obj_id, obj_data)
|
||||||
|
}
|
||||||
|
|
||||||
|
#[inline(always)]
|
||||||
|
fn state_get(&self, obj_type: StateObjectType, obj_id: &[u64]) -> std::io::Result<Vec<u8>> {
|
||||||
|
self.store.load_object(&obj_type, obj_id)
|
||||||
|
}
|
||||||
|
|
||||||
|
#[inline(always)]
|
||||||
|
fn wire_packet_send(&self, local_socket: i64, sock_addr: &InetAddress, data: &[u8], packet_ttl: u32) -> i32 {
|
||||||
|
0
|
||||||
|
}
|
||||||
|
|
||||||
|
fn path_check(&self, address: Address, id: &Identity, local_socket: i64, sock_addr: &InetAddress) -> bool {
|
||||||
|
true
|
||||||
|
}
|
||||||
|
|
||||||
|
fn path_lookup(&self, address: Address, id: &Identity, desired_family: InetAddressFamily) -> Option<InetAddress> {
|
||||||
|
let lc = self.local_config();
|
||||||
|
let vc = lc.virtual_.get(&address);
|
||||||
|
vc.map_or(None, |c: &LocalConfigVirtualConfig| {
|
||||||
|
if c.try_.is_empty() {
|
||||||
|
None
|
||||||
|
} else {
|
||||||
|
let t = c.try_.get((zerotier_core::random() as usize) % c.try_.len());
|
||||||
|
t.map_or(None, |v: &InetAddress| {
|
||||||
|
Some(v.clone())
|
||||||
|
})
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Service {
|
||||||
|
#[inline(always)]
|
||||||
|
fn web_api_status(&self, method: Method, headers: HeaderMap, post_data: Bytes) -> Box<dyn Reply> {
|
||||||
|
Box::new(StatusCode::BAD_REQUEST)
|
||||||
|
}
|
||||||
|
|
||||||
|
#[inline(always)]
|
||||||
|
fn web_api_network(&self, network_str: String, method: Method, headers: HeaderMap, post_data: Bytes) -> Box<dyn Reply> {
|
||||||
|
Box::new(StatusCode::BAD_REQUEST)
|
||||||
|
}
|
||||||
|
|
||||||
|
#[inline(always)]
|
||||||
|
fn web_api_peer(&self, peer_str: String, method: Method, headers: HeaderMap, post_data: Bytes) -> Box<dyn Reply> {
|
||||||
|
Box::new(StatusCode::BAD_REQUEST)
|
||||||
|
}
|
||||||
|
|
||||||
|
#[inline(always)]
|
||||||
|
fn local_config(&self) -> Arc<LocalConfig> {
|
||||||
|
self._local_config.lock().unwrap().clone()
|
||||||
|
}
|
||||||
|
|
||||||
|
#[inline(always)]
|
||||||
|
fn set_local_config(&self, new_lc: LocalConfig) {
|
||||||
|
*(self._local_config.lock().unwrap()) = Arc::new(new_lc);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(crate) fn run(store: &Arc<Store>, auth_token: Option<String>) -> i32 {
|
||||||
|
let mut process_exit_value: i32 = 0;
|
||||||
|
|
||||||
|
let init_local_config = Arc::new(store.read_local_conf(false).unwrap_or(LocalConfig::default()));
|
||||||
|
|
||||||
|
// Open log in store.
|
||||||
|
let log = Arc::new(Log::new(
|
||||||
|
if init_local_config.settings.log_path.as_ref().is_some() { init_local_config.settings.log_path.as_ref().unwrap().as_str() } else { store.default_log_path.to_str().unwrap() },
|
||||||
|
init_local_config.settings.log_size_max,
|
||||||
|
"",
|
||||||
|
));
|
||||||
|
|
||||||
|
// Generate authtoken.secret from secure random bytes if not already set.
|
||||||
|
let auth_token = auth_token.unwrap_or_else(|| {
|
||||||
|
let mut rb = [0_u8; 64];
|
||||||
|
unsafe {
|
||||||
|
crate::osdep::getSecureRandom(rb.as_mut_ptr().cast(), 64);
|
||||||
|
}
|
||||||
|
let mut t = String::new();
|
||||||
|
t.reserve(64);
|
||||||
|
for b in rb.iter() {
|
||||||
|
if *b > 127_u8 {
|
||||||
|
t.push((65 + (*b % 26)) as char); // A..Z
|
||||||
|
} else {
|
||||||
|
t.push((97 + (*b % 26)) as char); // a..z
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if store.write_authtoken_secret(t.as_str()).is_err() {
|
||||||
|
t.clear();
|
||||||
|
}
|
||||||
|
t
|
||||||
|
});
|
||||||
|
if auth_token.is_empty() {
|
||||||
|
l!(log, "FATAL: unable to write authtoken.secret to '{}'", store.base_path.to_str().unwrap());
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
let auth_token = Arc::new(auth_token);
|
||||||
|
|
||||||
|
let tokio_rt = tokio::runtime::Builder::new_current_thread().build().unwrap();
|
||||||
|
tokio_rt.block_on(async {
|
||||||
|
let mut udp_sockets: BTreeMap<InetAddress, FastUDPSocket> = BTreeMap::new();
|
||||||
|
let (mut interrupt_tx, mut interrupt_rx) = futures::channel::mpsc::channel::<()>(1);
|
||||||
|
|
||||||
|
// Create clonable implementation of NodeEventHandler and local web API endpoints.
|
||||||
|
let mut service = Service {
|
||||||
|
auth_token: auth_token.clone(),
|
||||||
|
log: log.clone(),
|
||||||
|
_local_config: Arc::new(Mutex::new(init_local_config)),
|
||||||
|
run: Arc::new(AtomicBool::new(true)),
|
||||||
|
store: store.clone(),
|
||||||
|
node: Weak::new(),
|
||||||
|
};
|
||||||
|
|
||||||
|
// Create instance of Node which will call Service on events.
|
||||||
|
let node = Node::new(service.clone());
|
||||||
|
if node.is_err() {
|
||||||
|
process_exit_value = 1;
|
||||||
|
l!(log, "FATAL: error initializing node: {}", node.err().unwrap().to_string());
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
let node = Arc::new(node.ok().unwrap());
|
||||||
|
|
||||||
|
service.node = Arc::downgrade(&node);
|
||||||
|
let service = service; // make immutable after setting node
|
||||||
|
|
||||||
|
let mut last_checked_config: i64 = 0;
|
||||||
|
let mut loop_delay = zerotier_core::NODE_BACKGROUND_TASKS_MAX_INTERVAL;
|
||||||
|
loop {
|
||||||
|
let mut local_config = service.local_config();
|
||||||
|
|
||||||
|
let (mut shutdown_tx, mut shutdown_rx) = futures::channel::oneshot::channel();
|
||||||
|
let warp_server;
|
||||||
|
{
|
||||||
|
let s0 = service.clone();
|
||||||
|
let s1 = service.clone();
|
||||||
|
let s2 = service.clone();
|
||||||
|
warp_server = warp::serve(
|
||||||
|
warp::any().and(warp::path::end().map(|| {
|
||||||
|
warp::reply::with_status("404", StatusCode::NOT_FOUND)
|
||||||
|
})
|
||||||
|
.or(warp::path("status").and(warp::method()).and(warp::header::headers_cloned()).and(warp::body::bytes())
|
||||||
|
.map(move |method: Method, headers: HeaderMap, post_data: Bytes| {
|
||||||
|
s0.web_api_status(method, headers, post_data)
|
||||||
|
}))
|
||||||
|
.or(warp::path!("network" / String).and(warp::method()).and(warp::header::headers_cloned()).and(warp::body::bytes())
|
||||||
|
.map(move |network_str: String, method: Method, headers: HeaderMap, post_data: Bytes| {
|
||||||
|
s1.web_api_network(network_str, method, headers, post_data)
|
||||||
|
}))
|
||||||
|
.or(warp::path!("peer" / String).and(warp::method()).and(warp::header::headers_cloned()).and(warp::body::bytes())
|
||||||
|
.map(move |peer_str: String, method: Method, headers: HeaderMap, post_data: Bytes| {
|
||||||
|
s2.web_api_peer(peer_str, method, headers, post_data)
|
||||||
|
}))
|
||||||
|
)).try_bind_with_graceful_shutdown(
|
||||||
|
(IpAddr::from([127_u8, 0_u8, 0_u8, 1_u8]), local_config.settings.primary_port),
|
||||||
|
async { let _ = shutdown_rx.await; },
|
||||||
|
);
|
||||||
|
}
|
||||||
|
if warp_server.is_err() {
|
||||||
|
l!(log, "ERROR: local API http server failed to bind to port {}: {}", local_config.settings.primary_port, warp_server.err().unwrap().to_string());
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
let warp_server = tokio_rt.spawn(warp_server.unwrap().1);
|
||||||
|
|
||||||
|
loop {
|
||||||
|
// Wait for (1) loop delay elapsed, (2) a signal to interrupt delay now, or
|
||||||
|
// (3) an external signal to exit.
|
||||||
|
tokio::select! {
|
||||||
|
_ = tokio::time::sleep(Duration::from_millis(loop_delay)) => {},
|
||||||
|
_ = interrupt_rx.next() => {},
|
||||||
|
_ = tokio::signal::ctrl_c() => {
|
||||||
|
l!(log, "exit signal received, shutting down...");
|
||||||
|
service.run.store(false, Ordering::Relaxed);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check every CONFIG_CHECK_INTERVAL for changes to either the system configuration
|
||||||
|
// or the node's local configuration and take actions as needed.
|
||||||
|
let now = zerotier_core::now();
|
||||||
|
if (now - last_checked_config) >= CONFIG_CHECK_INTERVAL {
|
||||||
|
last_checked_config = now;
|
||||||
|
|
||||||
|
// Check for changes to local.conf.
|
||||||
|
let new_config = store.read_local_conf(true);
|
||||||
|
if new_config.is_ok() {
|
||||||
|
service.set_local_config(new_config.unwrap());
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check for configuration changes that require a reboot of the inner loop
|
||||||
|
// or other actions to be taken.
|
||||||
|
let next_local_config = service.local_config();
|
||||||
|
if local_config.settings.primary_port != next_local_config.settings.primary_port {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
local_config = next_local_config;
|
||||||
|
|
||||||
|
// Enumerate all useful addresses bound to interfaces on the system.
|
||||||
|
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));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
_ => {}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// Drop bound sockets that are no longer valid or are now blacklisted.
|
||||||
|
let mut udp_sockets_to_close: Vec<InetAddress> = Vec::new();
|
||||||
|
for sock in udp_sockets.iter() {
|
||||||
|
if !system_addrs.contains_key(sock.0) {
|
||||||
|
udp_sockets_to_close.push(sock.0.clone());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
for k in udp_sockets_to_close.iter() {
|
||||||
|
udp_sockets.remove(k);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create sockets for unbound addresses.
|
||||||
|
for addr in system_addrs.iter() {
|
||||||
|
if !udp_sockets.contains_key(addr.0) {
|
||||||
|
let s = FastUDPSocket::new(addr.1.as_str(), addr.0, |raw_socket: &FastUDPRawOsSocket, from_address: &InetAddress, data: Buffer| {
|
||||||
|
// TODO: incoming packet handler
|
||||||
|
});
|
||||||
|
if s.is_ok() {
|
||||||
|
udp_sockets.insert(addr.0.clone(), s.unwrap());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Determine if primary and secondary port (if secondary enabled) failed to
|
||||||
|
// bind to any interface.
|
||||||
|
let mut primary_port_bind_failure = true;
|
||||||
|
let mut 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 {
|
||||||
|
primary_port_bind_failure = false;
|
||||||
|
if !secondary_port_bind_failure {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if s.0.port() == local_config.settings.secondary_port.unwrap() {
|
||||||
|
secondary_port_bind_failure = false;
|
||||||
|
if !primary_port_bind_failure {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if primary_port_bind_failure {
|
||||||
|
if local_config.settings.auto_port_search {
|
||||||
|
// TODO: port hunting if enabled
|
||||||
|
} else {
|
||||||
|
l!(log, "primary port {} failed to bind, waiting and trying again...", local_config.settings.primary_port);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if secondary_port_bind_failure {
|
||||||
|
l!(log, "secondary port {} failed to bind (non-fatal, will try again)", local_config.settings.secondary_port.unwrap_or(0));
|
||||||
|
// hunt for a secondary port.
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check to make sure nothing outside this code turned off the run flag.
|
||||||
|
if !service.run.load(Ordering::Relaxed) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Run background task handler in ZeroTier core.
|
||||||
|
loop_delay = node.process_background_tasks();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Gracefully shut down the local web server.
|
||||||
|
let _ = shutdown_tx.send(());
|
||||||
|
let _ = warp_server.await;
|
||||||
|
|
||||||
|
// Sleep for a brief period of time to prevent thrashing if some invalid
|
||||||
|
// state is hit that causes the inner loop to keep breaking.
|
||||||
|
if !service.run.load(Ordering::Relaxed) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
tokio::time::sleep(Duration::from_secs(1)).await;
|
||||||
|
if !service.run.load(Ordering::Relaxed) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
process_exit_value
|
||||||
|
}
|
|
@ -14,6 +14,7 @@
|
||||||
use std::error::Error;
|
use std::error::Error;
|
||||||
use std::io::{Read, Write};
|
use std::io::{Read, Write};
|
||||||
use std::path::{Path, PathBuf};
|
use std::path::{Path, PathBuf};
|
||||||
|
use std::sync::Mutex;
|
||||||
use std::ffi::CString;
|
use std::ffi::CString;
|
||||||
|
|
||||||
use zerotier_core::{StateObjectType, NetworkId};
|
use zerotier_core::{StateObjectType, NetworkId};
|
||||||
|
@ -23,6 +24,7 @@ use crate::localconfig::LocalConfig;
|
||||||
pub(crate) struct Store {
|
pub(crate) struct Store {
|
||||||
pub base_path: Box<Path>,
|
pub base_path: Box<Path>,
|
||||||
pub default_log_path: Box<Path>,
|
pub default_log_path: Box<Path>,
|
||||||
|
prev_local_config: Mutex<String>,
|
||||||
peers_path: Box<Path>,
|
peers_path: Box<Path>,
|
||||||
controller_path: Box<Path>,
|
controller_path: Box<Path>,
|
||||||
networks_path: Box<Path>,
|
networks_path: Box<Path>,
|
||||||
|
@ -53,6 +55,7 @@ impl Store {
|
||||||
let s = Store {
|
let s = Store {
|
||||||
base_path: bp.to_path_buf().into_boxed_path(),
|
base_path: bp.to_path_buf().into_boxed_path(),
|
||||||
default_log_path: bp.join("service.log").into_boxed_path(),
|
default_log_path: bp.join("service.log").into_boxed_path(),
|
||||||
|
prev_local_config: Mutex::new(String::new()),
|
||||||
peers_path: bp.join("peers.d").into_boxed_path(),
|
peers_path: bp.join("peers.d").into_boxed_path(),
|
||||||
controller_path: bp.join("controller.d").into_boxed_path(),
|
controller_path: bp.join("controller.d").into_boxed_path(),
|
||||||
networks_path: bp.join("networks.d").into_boxed_path(),
|
networks_path: bp.join("networks.d").into_boxed_path(),
|
||||||
|
@ -161,13 +164,24 @@ impl Store {
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Write a file to the base ZeroTier home directory.
|
/// Write a file to the base ZeroTier home directory.
|
||||||
|
/// Error code std::io::ErrorKind::Other is returned if skip_if_unchanged is true
|
||||||
|
/// and there has been no change from the last read.
|
||||||
pub fn write_file(&self, fname: &str, data: &[u8]) -> std::io::Result<()> {
|
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)
|
std::fs::OpenOptions::new().write(true).truncate(true).create(true).open(self.base_path.join(fname))?.write_all(data)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Reads local.conf and deserializes into a LocalConfig object.
|
/// Reads local.conf and deserializes into a LocalConfig object.
|
||||||
pub fn read_local_conf(&self) -> std::io::Result<LocalConfig> {
|
pub fn read_local_conf(&self, skip_if_unchanged: bool) -> std::io::Result<LocalConfig> {
|
||||||
let data = self.read_file_str("local.conf")?;
|
let data = self.read_file_str("local.conf")?;
|
||||||
|
if skip_if_unchanged {
|
||||||
|
let mut prev = self.prev_local_config.lock().unwrap();
|
||||||
|
if prev.eq(&data) {
|
||||||
|
return Err(std::io::Error::new(std::io::ErrorKind::Other, "unchangd"));
|
||||||
|
}
|
||||||
|
*prev = data.clone();
|
||||||
|
} else {
|
||||||
|
*(self.prev_local_config.lock().unwrap()) = data.clone();
|
||||||
|
}
|
||||||
let lc = LocalConfig::new_from_json(data.as_str());
|
let lc = LocalConfig::new_from_json(data.as_str());
|
||||||
if lc.is_err() {
|
if lc.is_err() {
|
||||||
return Err(std::io::Error::new(std::io::ErrorKind::InvalidData, lc.err().unwrap()));
|
return Err(std::io::Error::new(std::io::ErrorKind::InvalidData, lc.err().unwrap()));
|
||||||
|
@ -182,9 +196,9 @@ impl Store {
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Reads the authtoken.secret file in the home directory.
|
/// Reads the authtoken.secret file in the home directory.
|
||||||
|
#[inline(always)]
|
||||||
pub fn read_authtoken_secret(&self) -> std::io::Result<String> {
|
pub fn read_authtoken_secret(&self) -> std::io::Result<String> {
|
||||||
let data = self.read_file_str("authtoken.secret")?;
|
Ok(self.read_file_str("authtoken.secret")?)
|
||||||
Ok(data.trim().to_string())
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Write authtoken.secret and lock down file permissions.
|
/// Write authtoken.secret and lock down file permissions.
|
||||||
|
@ -219,12 +233,14 @@ impl Store {
|
||||||
if obj_path.is_some() {
|
if obj_path.is_some() {
|
||||||
let obj_path = obj_path.unwrap();
|
let obj_path = obj_path.unwrap();
|
||||||
std::fs::OpenOptions::new().write(true).truncate(true).create(true).open(&obj_path)?.write_all(obj_data)?;
|
std::fs::OpenOptions::new().write(true).truncate(true).create(true).open(&obj_path)?.write_all(obj_data)?;
|
||||||
|
|
||||||
if obj_type.eq(&StateObjectType::IdentitySecret) || obj_type.eq(&StateObjectType::TrustStore) {
|
if obj_type.eq(&StateObjectType::IdentitySecret) || obj_type.eq(&StateObjectType::TrustStore) {
|
||||||
lock_down_file(obj_path.to_str().unwrap());
|
lock_down_file(obj_path.to_str().unwrap());
|
||||||
}
|
}
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
} else {
|
} else {
|
||||||
Err(std::io::Error::new(std::io::ErrorKind::InvalidInput, "object ID not valid"))
|
Err(std::io::Error::new(std::io::ErrorKind::InvalidInput, "object type or ID not valid"))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
Loading…
Add table
Reference in a new issue