initial import of main veilid core
This commit is contained in:
316
veilid-server/src/client_api.rs
Normal file
316
veilid-server/src/client_api.rs
Normal file
@@ -0,0 +1,316 @@
|
||||
use crate::veilid_client_capnp::*;
|
||||
use async_std::net::TcpListener;
|
||||
use async_std::prelude::FutureExt;
|
||||
use capnp::capability::Promise;
|
||||
use capnp_rpc::{pry, rpc_twoparty_capnp, twoparty, RpcSystem};
|
||||
use failure::*;
|
||||
use futures::FutureExt as FuturesFutureExt;
|
||||
use futures::{AsyncReadExt, StreamExt};
|
||||
use log::*;
|
||||
use std::cell::RefCell;
|
||||
use std::collections::HashMap;
|
||||
use std::net::SocketAddr;
|
||||
use std::rc::Rc;
|
||||
use veilid_core::xx::Eventual;
|
||||
|
||||
#[derive(Fail, Debug)]
|
||||
#[fail(display = "Client API error: {}", _0)]
|
||||
pub struct ClientAPIError(String);
|
||||
|
||||
// --- interface Registration ---------------------------------
|
||||
|
||||
struct RegistrationHandle {
|
||||
client: veilid_client::Client,
|
||||
requests_in_flight: i32,
|
||||
}
|
||||
|
||||
struct RegistrationMap {
|
||||
registrations: HashMap<u64, RegistrationHandle>,
|
||||
}
|
||||
|
||||
impl RegistrationMap {
|
||||
fn new() -> Self {
|
||||
Self {
|
||||
registrations: HashMap::new(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
struct RegistrationImpl {
|
||||
id: u64,
|
||||
registration_map: Rc<RefCell<RegistrationMap>>,
|
||||
}
|
||||
|
||||
impl RegistrationImpl {
|
||||
fn new(id: u64, registrations: Rc<RefCell<RegistrationMap>>) -> Self {
|
||||
Self {
|
||||
id: id,
|
||||
registration_map: registrations,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Drop for RegistrationImpl {
|
||||
fn drop(&mut self) {
|
||||
debug!("Registration dropped");
|
||||
self.registration_map
|
||||
.borrow_mut()
|
||||
.registrations
|
||||
.remove(&self.id);
|
||||
}
|
||||
}
|
||||
|
||||
impl registration::Server for RegistrationImpl {}
|
||||
|
||||
// --- interface VeilidServer ---------------------------------
|
||||
|
||||
struct VeilidServerImpl {
|
||||
veilid_api: veilid_core::VeilidAPI,
|
||||
next_id: u64,
|
||||
pub registration_map: Rc<RefCell<RegistrationMap>>,
|
||||
}
|
||||
|
||||
impl VeilidServerImpl {
|
||||
pub fn new(veilid_api: veilid_core::VeilidAPI) -> Self {
|
||||
Self {
|
||||
next_id: 0,
|
||||
registration_map: Rc::new(RefCell::new(RegistrationMap::new())),
|
||||
veilid_api: veilid_api,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl veilid_server::Server for VeilidServerImpl {
|
||||
fn register(
|
||||
&mut self,
|
||||
params: veilid_server::RegisterParams,
|
||||
mut results: veilid_server::RegisterResults,
|
||||
) -> Promise<(), ::capnp::Error> {
|
||||
trace!("VeilidServerImpl::register");
|
||||
self.registration_map.borrow_mut().registrations.insert(
|
||||
self.next_id,
|
||||
RegistrationHandle {
|
||||
client: pry!(pry!(params.get()).get_veilid_client()),
|
||||
requests_in_flight: 0,
|
||||
},
|
||||
);
|
||||
|
||||
results
|
||||
.get()
|
||||
.set_registration(capnp_rpc::new_client(RegistrationImpl::new(
|
||||
self.next_id,
|
||||
self.registration_map.clone(),
|
||||
)));
|
||||
|
||||
self.next_id += 1;
|
||||
|
||||
// Send state update
|
||||
let veilid_api = self.veilid_api.clone();
|
||||
Promise::from_future(async move {
|
||||
veilid_api.send_state_update().await;
|
||||
Ok(())
|
||||
})
|
||||
}
|
||||
|
||||
fn attach(
|
||||
&mut self,
|
||||
_params: veilid_server::AttachParams,
|
||||
mut _results: veilid_server::AttachResults,
|
||||
) -> Promise<(), ::capnp::Error> {
|
||||
trace!("VeilidServerImpl::attach");
|
||||
let veilid_api = self.veilid_api.clone();
|
||||
Promise::from_future(async move {
|
||||
veilid_api.attach().await;
|
||||
Ok(())
|
||||
})
|
||||
}
|
||||
fn detach(
|
||||
&mut self,
|
||||
_params: veilid_server::DetachParams,
|
||||
mut _results: veilid_server::DetachResults,
|
||||
) -> Promise<(), ::capnp::Error> {
|
||||
trace!("VeilidServerImpl::detach");
|
||||
let veilid_api = self.veilid_api.clone();
|
||||
Promise::from_future(async move {
|
||||
veilid_api.detach().await;
|
||||
Ok(())
|
||||
})
|
||||
}
|
||||
fn shutdown(
|
||||
&mut self,
|
||||
_params: veilid_server::ShutdownParams,
|
||||
mut _results: veilid_server::ShutdownResults,
|
||||
) -> Promise<(), ::capnp::Error> {
|
||||
trace!("VeilidServerImpl::shutdown");
|
||||
|
||||
cfg_if::cfg_if! {
|
||||
if #[cfg(windows)] {
|
||||
assert!(false, "write me!");
|
||||
}
|
||||
else {
|
||||
crate::unix::shutdown();
|
||||
}
|
||||
}
|
||||
|
||||
Promise::ok(())
|
||||
}
|
||||
}
|
||||
|
||||
// --- Client API Server-Side ---------------------------------
|
||||
|
||||
struct ClientApiInner {
|
||||
veilid_api: veilid_core::VeilidAPI,
|
||||
registration_map: Rc<RefCell<RegistrationMap>>,
|
||||
stop: Eventual,
|
||||
join_handle: Option<
|
||||
async_std::task::JoinHandle<Result<Vec<()>, Box<(dyn std::error::Error + 'static)>>>,
|
||||
>,
|
||||
}
|
||||
|
||||
pub struct ClientApi {
|
||||
inner: RefCell<ClientApiInner>,
|
||||
}
|
||||
|
||||
impl ClientApi {
|
||||
pub fn new(veilid_api: veilid_core::VeilidAPI) -> Rc<Self> {
|
||||
Rc::new(Self {
|
||||
inner: RefCell::new(ClientApiInner {
|
||||
veilid_api: veilid_api,
|
||||
registration_map: Rc::new(RefCell::new(RegistrationMap::new())),
|
||||
stop: Eventual::new(),
|
||||
join_handle: None,
|
||||
}),
|
||||
})
|
||||
}
|
||||
|
||||
pub async fn stop(self: Rc<Self>) {
|
||||
trace!("ClientApi::stop requested");
|
||||
let jh = {
|
||||
let mut inner = self.inner.borrow_mut();
|
||||
if inner.join_handle.is_none() {
|
||||
trace!("ClientApi stop ignored");
|
||||
return;
|
||||
}
|
||||
inner.stop.resolve();
|
||||
inner.join_handle.take().unwrap()
|
||||
};
|
||||
trace!("ClientApi::stop: waiting for stop");
|
||||
let _ = jh.await;
|
||||
trace!("ClientApi::stop: stopped");
|
||||
}
|
||||
|
||||
async fn handle_incoming(
|
||||
self: Rc<Self>,
|
||||
bind_addr: SocketAddr,
|
||||
client: veilid_server::Client,
|
||||
) -> Result<(), Box<dyn std::error::Error>> {
|
||||
let listener = TcpListener::bind(bind_addr).await?;
|
||||
debug!("Client API listening on: {:?}", bind_addr);
|
||||
|
||||
// Get the
|
||||
let mut incoming = listener.incoming();
|
||||
let stop = self.inner.borrow().stop.clone();
|
||||
let incoming_loop = async move {
|
||||
while let Some(stream_result) = stop.instance_none().race(incoming.next()).await {
|
||||
let stream = stream_result?;
|
||||
stream.set_nodelay(true)?;
|
||||
let (reader, writer) = stream.split();
|
||||
let network = twoparty::VatNetwork::new(
|
||||
reader,
|
||||
writer,
|
||||
rpc_twoparty_capnp::Side::Server,
|
||||
Default::default(),
|
||||
);
|
||||
|
||||
let rpc_system = RpcSystem::new(Box::new(network), Some(client.clone().client));
|
||||
|
||||
async_std::task::spawn_local(rpc_system.map(drop));
|
||||
}
|
||||
Ok::<(), Box<dyn std::error::Error>>(())
|
||||
};
|
||||
|
||||
incoming_loop.await
|
||||
}
|
||||
|
||||
fn convert_attachment_state(state: &veilid_core::AttachmentState) -> AttachmentState {
|
||||
match state {
|
||||
veilid_core::AttachmentState::Detached => AttachmentState::Detached,
|
||||
veilid_core::AttachmentState::Attaching => AttachmentState::Attaching,
|
||||
veilid_core::AttachmentState::AttachedWeak => AttachmentState::AttachedWeak,
|
||||
veilid_core::AttachmentState::AttachedGood => AttachmentState::AttachedGood,
|
||||
veilid_core::AttachmentState::AttachedStrong => AttachmentState::AttachedStrong,
|
||||
veilid_core::AttachmentState::FullyAttached => AttachmentState::FullyAttached,
|
||||
veilid_core::AttachmentState::OverAttached => AttachmentState::OverAttached,
|
||||
veilid_core::AttachmentState::Detaching => AttachmentState::Detaching,
|
||||
}
|
||||
}
|
||||
|
||||
fn convert_state_changed(
|
||||
changed: &veilid_core::VeilidStateChange,
|
||||
rpc_changed: crate::veilid_client_capnp::veilid_state_change::Builder,
|
||||
) {
|
||||
match changed {
|
||||
veilid_core::VeilidStateChange::Attachment {
|
||||
old_state,
|
||||
new_state,
|
||||
} => {
|
||||
let mut att = rpc_changed.init_attachment();
|
||||
att.set_old_state(ClientApi::convert_attachment_state(old_state));
|
||||
att.set_new_state(ClientApi::convert_attachment_state(new_state));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn handle_state_change(self: Rc<Self>, changed: veilid_core::VeilidStateChange) {
|
||||
trace!("state changed: {:?}", changed);
|
||||
|
||||
// Send status update to each registered client
|
||||
let registration_map = self.inner.borrow().registration_map.clone();
|
||||
let registration_map1 = registration_map.clone();
|
||||
let regs = &mut registration_map.borrow_mut().registrations;
|
||||
for (&id, mut registration) in regs.iter_mut() {
|
||||
if registration.requests_in_flight > 5 {
|
||||
debug!(
|
||||
"too many requests in flight for status updates: {}",
|
||||
registration.requests_in_flight
|
||||
);
|
||||
}
|
||||
registration.requests_in_flight += 1;
|
||||
// Make a state changed request
|
||||
let mut request = registration.client.state_changed_request();
|
||||
let rpc_changed = request.get().init_changed();
|
||||
ClientApi::convert_state_changed(&changed, rpc_changed);
|
||||
let registration_map2 = registration_map1.clone();
|
||||
async_std::task::spawn_local(request.send().promise.map(move |r| match r {
|
||||
Ok(_) => {
|
||||
registration_map2
|
||||
.borrow_mut()
|
||||
.registrations
|
||||
.get_mut(&id)
|
||||
.map(|ref mut s| {
|
||||
s.requests_in_flight -= 1;
|
||||
});
|
||||
}
|
||||
Err(e) => {
|
||||
debug!("Got error: {:?}. Dropping registation.", e);
|
||||
registration_map2.borrow_mut().registrations.remove(&id);
|
||||
}
|
||||
}));
|
||||
}
|
||||
}
|
||||
|
||||
pub fn run(self: Rc<Self>, bind_addrs: Vec<SocketAddr>) {
|
||||
// Create client api VeilidServer
|
||||
let veilid_server_impl = VeilidServerImpl::new(self.inner.borrow().veilid_api.clone());
|
||||
self.inner.borrow_mut().registration_map = veilid_server_impl.registration_map.clone();
|
||||
|
||||
// Make a client object for the server to send to each rpc client
|
||||
let client: veilid_server::Client = capnp_rpc::new_client(veilid_server_impl);
|
||||
|
||||
let bind_futures = bind_addrs
|
||||
.iter()
|
||||
.map(|addr| self.clone().handle_incoming(addr.clone(), client.clone()));
|
||||
let bind_futures_join = futures::future::try_join_all(bind_futures);
|
||||
self.inner.borrow_mut().join_handle = Some(async_std::task::spawn_local(bind_futures_join));
|
||||
}
|
||||
}
|
27
veilid-server/src/main.rs
Normal file
27
veilid-server/src/main.rs
Normal file
@@ -0,0 +1,27 @@
|
||||
#![forbid(unsafe_code)]
|
||||
|
||||
mod client_api;
|
||||
mod settings;
|
||||
|
||||
pub mod veilid_client_capnp {
|
||||
include!(concat!(env!("OUT_DIR"), "/proto/veilid_client_capnp.rs"));
|
||||
}
|
||||
|
||||
use cfg_if;
|
||||
|
||||
cfg_if::cfg_if! {
|
||||
if #[cfg(windows)] {
|
||||
mod windows;
|
||||
|
||||
fn main() -> windows_service::Result<(), String> {
|
||||
windows::main()
|
||||
}
|
||||
}
|
||||
else {
|
||||
mod unix;
|
||||
|
||||
fn main() -> Result<(), String> {
|
||||
async_std::task::block_on(unix::main())
|
||||
}
|
||||
}
|
||||
}
|
972
veilid-server/src/settings.rs
Normal file
972
veilid-server/src/settings.rs
Normal file
@@ -0,0 +1,972 @@
|
||||
use config;
|
||||
use directories::*;
|
||||
use log::*;
|
||||
use parking_lot::*;
|
||||
use serde;
|
||||
use serde_derive::*;
|
||||
use std::ffi::OsStr;
|
||||
use std::net::{SocketAddr, ToSocketAddrs};
|
||||
use std::path::{Path, PathBuf};
|
||||
use std::str::FromStr;
|
||||
use std::sync::Arc;
|
||||
use url::Url;
|
||||
|
||||
pub fn load_default_config(cfg: &mut config::Config) -> Result<(), config::ConfigError> {
|
||||
let default_config = String::from(
|
||||
r#"---
|
||||
daemon: false
|
||||
client_api:
|
||||
enabled: true
|
||||
listen_address: "localhost:5959"
|
||||
auto_attach: false
|
||||
logging:
|
||||
terminal:
|
||||
enabled: true
|
||||
level: "info"
|
||||
file:
|
||||
enabled: false
|
||||
path: ""
|
||||
append: true
|
||||
level: "info"
|
||||
testing:
|
||||
subnode_index: 0
|
||||
core:
|
||||
tablestore:
|
||||
directory: "%TABLESTORE_DIRECTORY%"
|
||||
network:
|
||||
max_connections: 16
|
||||
connection_initial_timeout: 2000000
|
||||
node_id: ""
|
||||
node_id_secret: ""
|
||||
bootstrap: []
|
||||
rpc:
|
||||
concurrency: 0
|
||||
queue_size: 1024
|
||||
max_timestamp_behind: 10000000
|
||||
max_timestamp_ahead: 10000000
|
||||
timeout: 10000000
|
||||
max_route_hop_count: 7
|
||||
dht:
|
||||
resolve_node_timeout:
|
||||
resolve_node_count: 20
|
||||
resolve_node_fanout: 3
|
||||
max_find_node_count: 20
|
||||
get_value_timeout:
|
||||
get_value_count: 20
|
||||
get_value_fanout: 3
|
||||
set_value_timeout:
|
||||
set_value_count: 20
|
||||
set_value_fanout: 5
|
||||
min_peer_count: 20
|
||||
min_peer_refresh_time: 2000000
|
||||
validate_dial_info_receipt_time: 5000000
|
||||
upnp: false
|
||||
natpmp: false
|
||||
address_filter: true
|
||||
tls:
|
||||
certificate_path: "/etc/veilid/server.crt"
|
||||
private_key_path: "/etc/veilid/private/server.key"
|
||||
connection_initial_timeout: 2000000
|
||||
application:
|
||||
path: "app"
|
||||
https:
|
||||
enabled: true
|
||||
listen_address: "[::]:5150"
|
||||
http:
|
||||
enabled: true
|
||||
listen_address: "[::]:5150"
|
||||
protocol:
|
||||
udp:
|
||||
enabled: true
|
||||
socket_pool_size: 0
|
||||
listen_address: "[::]:5150"
|
||||
# public_address: ""
|
||||
tcp:
|
||||
connect: true
|
||||
listen: true
|
||||
max_connections: 32
|
||||
listen_address: "[::]:5150"
|
||||
# "public_address": ""
|
||||
ws:
|
||||
connect: true
|
||||
listen: true
|
||||
max_connections: 16
|
||||
listen_address: "[::]:5150"
|
||||
path: "/ws"
|
||||
# "public_address": ""
|
||||
wss:
|
||||
connect: true
|
||||
listen: true
|
||||
max_connections: 16
|
||||
listen_address: "[::]:5150"
|
||||
path: "/ws"
|
||||
# "public_address": ""
|
||||
leases:
|
||||
max_server_signal_leases: 256
|
||||
max_server_relay_leases: 8
|
||||
max_client_signal_leases: 2
|
||||
max_client_relay_leases: 2
|
||||
"#,
|
||||
)
|
||||
.replace(
|
||||
"%TABLESTORE_DIRECTORY%",
|
||||
&Settings::get_default_table_store_path().to_string_lossy(),
|
||||
);
|
||||
cfg.merge(config::File::from_str(
|
||||
&default_config,
|
||||
config::FileFormat::Yaml,
|
||||
))
|
||||
.map(drop)
|
||||
}
|
||||
|
||||
pub fn load_config(
|
||||
cfg: &mut config::Config,
|
||||
config_file: &Path,
|
||||
) -> Result<(), config::ConfigError> {
|
||||
if let Some(config_file_str) = config_file.to_str() {
|
||||
cfg.merge(config::File::new(config_file_str, config::FileFormat::Yaml))
|
||||
.map(drop)
|
||||
} else {
|
||||
Err(config::ConfigError::Message(
|
||||
"config file path is not valid UTF-8".to_owned(),
|
||||
))
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Copy, Clone, Debug, PartialEq)]
|
||||
pub enum LogLevel {
|
||||
Error,
|
||||
Warn,
|
||||
Info,
|
||||
Debug,
|
||||
Trace,
|
||||
}
|
||||
impl<'de> serde::Deserialize<'de> for LogLevel {
|
||||
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
|
||||
where
|
||||
D: serde::Deserializer<'de>,
|
||||
{
|
||||
let s = String::deserialize(deserializer)?;
|
||||
match s.to_ascii_lowercase().as_str() {
|
||||
"error" => Ok(LogLevel::Error),
|
||||
"warn" => Ok(LogLevel::Warn),
|
||||
"info" => Ok(LogLevel::Info),
|
||||
"debug" => Ok(LogLevel::Debug),
|
||||
"trace" => Ok(LogLevel::Trace),
|
||||
_ => Err(serde::de::Error::custom(format!(
|
||||
"Invalid log level: {}",
|
||||
s
|
||||
))),
|
||||
}
|
||||
}
|
||||
}
|
||||
pub fn convert_loglevel(log_level: LogLevel) -> LevelFilter {
|
||||
match log_level {
|
||||
LogLevel::Error => LevelFilter::Error,
|
||||
LogLevel::Warn => LevelFilter::Warn,
|
||||
LogLevel::Info => LevelFilter::Info,
|
||||
LogLevel::Debug => LevelFilter::Debug,
|
||||
LogLevel::Trace => LevelFilter::Trace,
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq)]
|
||||
pub struct ParsedURL {
|
||||
pub urlstring: String,
|
||||
pub url: Url,
|
||||
}
|
||||
|
||||
impl FromStr for ParsedURL {
|
||||
type Err = url::ParseError;
|
||||
fn from_str(s: &str) -> Result<ParsedURL, url::ParseError> {
|
||||
let url = Url::parse(s)?;
|
||||
|
||||
Ok(Self {
|
||||
urlstring: s.to_string(),
|
||||
url: url,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
impl<'de> serde::Deserialize<'de> for ParsedURL {
|
||||
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
|
||||
where
|
||||
D: serde::Deserializer<'de>,
|
||||
{
|
||||
let s = String::deserialize(deserializer)?;
|
||||
Ok(ParsedURL::from_str(s.as_str()).map_err(|x| serde::de::Error::custom(x))?)
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, PartialEq)]
|
||||
pub struct NamedSocketAddrs {
|
||||
pub name: String,
|
||||
pub addrs: Vec<SocketAddr>,
|
||||
}
|
||||
|
||||
impl FromStr for NamedSocketAddrs {
|
||||
type Err = std::io::Error;
|
||||
fn from_str(s: &str) -> Result<NamedSocketAddrs, std::io::Error> {
|
||||
let addr_iter = s.to_socket_addrs()?;
|
||||
Ok(NamedSocketAddrs {
|
||||
name: s.to_owned(),
|
||||
addrs: addr_iter.collect(),
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
impl<'de> serde::Deserialize<'de> for NamedSocketAddrs {
|
||||
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
|
||||
where
|
||||
D: serde::Deserializer<'de>,
|
||||
{
|
||||
let s = String::deserialize(deserializer)?;
|
||||
Ok(NamedSocketAddrs::from_str(s.as_str()).map_err(|x| serde::de::Error::custom(x))?)
|
||||
}
|
||||
}
|
||||
|
||||
impl NamedSocketAddrs {
|
||||
pub fn offset_port(&mut self, offset: u16) -> Result<(), ()> {
|
||||
// Bump port on name
|
||||
if let Some(split) = self.name.rfind(':') {
|
||||
let hoststr = &self.name[0..split];
|
||||
let portstr = &self.name[split + 1..];
|
||||
let port: u16 = portstr.parse::<u16>().map_err(drop)? + offset;
|
||||
|
||||
self.name = format!("{}:{}", hoststr, port.to_string());
|
||||
} else {
|
||||
return Err(());
|
||||
}
|
||||
|
||||
// Bump port on addresses
|
||||
for addr in self.addrs.iter_mut() {
|
||||
addr.set_port(addr.port() + offset);
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Deserialize)]
|
||||
pub struct Terminal {
|
||||
pub enabled: bool,
|
||||
pub level: LogLevel,
|
||||
}
|
||||
|
||||
#[derive(Debug, Deserialize)]
|
||||
pub struct File {
|
||||
pub enabled: bool,
|
||||
pub path: String,
|
||||
pub append: bool,
|
||||
pub level: LogLevel,
|
||||
}
|
||||
|
||||
#[derive(Debug, Deserialize)]
|
||||
pub struct ClientApi {
|
||||
pub enabled: bool,
|
||||
pub listen_address: NamedSocketAddrs,
|
||||
}
|
||||
|
||||
#[derive(Debug, Deserialize)]
|
||||
pub struct Logging {
|
||||
pub terminal: Terminal,
|
||||
pub file: File,
|
||||
}
|
||||
|
||||
#[derive(Debug, Deserialize)]
|
||||
pub struct HTTPS {
|
||||
pub enabled: bool,
|
||||
pub listen_address: NamedSocketAddrs,
|
||||
}
|
||||
|
||||
#[derive(Debug, Deserialize)]
|
||||
pub struct HTTP {
|
||||
pub enabled: bool,
|
||||
pub listen_address: NamedSocketAddrs,
|
||||
}
|
||||
|
||||
#[derive(Debug, Deserialize)]
|
||||
pub struct Application {
|
||||
pub path: PathBuf,
|
||||
pub https: HTTPS,
|
||||
pub http: HTTP,
|
||||
}
|
||||
|
||||
#[derive(Debug, Deserialize)]
|
||||
pub struct UDP {
|
||||
pub enabled: bool,
|
||||
pub socket_pool_size: u32,
|
||||
pub listen_address: NamedSocketAddrs,
|
||||
pub public_address: Option<NamedSocketAddrs>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Deserialize)]
|
||||
pub struct TCP {
|
||||
pub connect: bool,
|
||||
pub listen: bool,
|
||||
pub max_connections: u32,
|
||||
pub listen_address: NamedSocketAddrs,
|
||||
pub public_address: Option<NamedSocketAddrs>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Deserialize)]
|
||||
pub struct WS {
|
||||
pub connect: bool,
|
||||
pub listen: bool,
|
||||
pub max_connections: u32,
|
||||
pub listen_address: NamedSocketAddrs,
|
||||
pub path: String,
|
||||
pub public_address: Option<NamedSocketAddrs>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Deserialize)]
|
||||
pub struct WSS {
|
||||
pub connect: bool,
|
||||
pub listen: bool,
|
||||
pub max_connections: u32,
|
||||
pub listen_address: NamedSocketAddrs,
|
||||
pub path: String,
|
||||
pub public_address: Option<NamedSocketAddrs>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Deserialize)]
|
||||
pub struct Protocol {
|
||||
pub udp: UDP,
|
||||
pub tcp: TCP,
|
||||
pub ws: WS,
|
||||
pub wss: WSS,
|
||||
}
|
||||
|
||||
#[derive(Debug, Deserialize)]
|
||||
pub struct TLS {
|
||||
pub certificate_path: PathBuf,
|
||||
pub private_key_path: PathBuf,
|
||||
pub connection_initial_timeout: u64,
|
||||
}
|
||||
|
||||
#[derive(Debug, Deserialize)]
|
||||
pub struct RPC {
|
||||
pub concurrency: u32,
|
||||
pub queue_size: u32,
|
||||
pub max_timestamp_behind: Option<u64>,
|
||||
pub max_timestamp_ahead: Option<u64>,
|
||||
pub timeout: u64,
|
||||
pub max_route_hop_count: u8,
|
||||
}
|
||||
|
||||
#[derive(Debug, Deserialize)]
|
||||
pub struct DHT {
|
||||
pub resolve_node_timeout: Option<u64>,
|
||||
pub resolve_node_count: u32,
|
||||
pub resolve_node_fanout: u32,
|
||||
pub max_find_node_count: u32,
|
||||
pub get_value_timeout: Option<u64>,
|
||||
pub get_value_count: u32,
|
||||
pub get_value_fanout: u32,
|
||||
pub set_value_timeout: Option<u64>,
|
||||
pub set_value_count: u32,
|
||||
pub set_value_fanout: u32,
|
||||
pub min_peer_count: u32,
|
||||
pub min_peer_refresh_time: u64,
|
||||
pub validate_dial_info_receipt_time: u64,
|
||||
}
|
||||
|
||||
#[derive(Debug, Deserialize)]
|
||||
pub struct Leases {
|
||||
pub max_server_signal_leases: u32,
|
||||
pub max_server_relay_leases: u32,
|
||||
pub max_client_signal_leases: u32,
|
||||
pub max_client_relay_leases: u32,
|
||||
}
|
||||
|
||||
#[derive(Debug, Deserialize)]
|
||||
pub struct Network {
|
||||
pub max_connections: u32,
|
||||
pub connection_initial_timeout: u64,
|
||||
pub node_id: veilid_core::DHTKey,
|
||||
pub node_id_secret: veilid_core::DHTKeySecret,
|
||||
pub bootstrap: Vec<ParsedURL>,
|
||||
pub rpc: RPC,
|
||||
pub dht: DHT,
|
||||
pub upnp: bool,
|
||||
pub natpmp: bool,
|
||||
pub address_filter: bool,
|
||||
pub tls: TLS,
|
||||
pub application: Application,
|
||||
pub protocol: Protocol,
|
||||
pub leases: Leases,
|
||||
}
|
||||
|
||||
#[derive(Debug, Deserialize)]
|
||||
pub struct Testing {
|
||||
pub subnode_index: u16,
|
||||
}
|
||||
|
||||
#[derive(Debug, Deserialize)]
|
||||
pub struct TableStore {
|
||||
pub directory: PathBuf,
|
||||
}
|
||||
|
||||
#[derive(Debug, Deserialize)]
|
||||
pub struct Core {
|
||||
pub tablestore: TableStore,
|
||||
pub network: Network,
|
||||
}
|
||||
|
||||
#[derive(Debug, Deserialize)]
|
||||
pub struct SettingsInner {
|
||||
pub daemon: bool,
|
||||
pub client_api: ClientApi,
|
||||
pub auto_attach: bool,
|
||||
pub logging: Logging,
|
||||
pub testing: Testing,
|
||||
pub core: Core,
|
||||
}
|
||||
|
||||
type Handle<T> = Arc<RwLock<T>>;
|
||||
|
||||
pub struct Settings {
|
||||
inner: Handle<SettingsInner>,
|
||||
}
|
||||
|
||||
impl Settings {
|
||||
pub fn new(
|
||||
config_file_is_default: bool,
|
||||
config_file: &OsStr,
|
||||
) -> Result<Self, config::ConfigError> {
|
||||
// Create a config
|
||||
let mut cfg = config::Config::default();
|
||||
|
||||
// Load the default config
|
||||
load_default_config(&mut cfg)?;
|
||||
|
||||
// Merge in the config file if we have one
|
||||
let config_file_path = Path::new(config_file);
|
||||
if !config_file_is_default || config_file_path.exists() {
|
||||
// If the user specifies a config file on the command line then it must exist
|
||||
load_config(&mut cfg, config_file_path)?;
|
||||
}
|
||||
|
||||
Ok(Self {
|
||||
inner: Arc::new(RwLock::new(cfg.try_into()?)),
|
||||
})
|
||||
}
|
||||
pub fn read(&self) -> RwLockReadGuard<SettingsInner> {
|
||||
self.inner.read()
|
||||
}
|
||||
pub fn write(&self) -> RwLockWriteGuard<SettingsInner> {
|
||||
self.inner.write()
|
||||
}
|
||||
|
||||
pub fn apply_subnode_index(&self) -> Result<(), ()> {
|
||||
let mut settingsrw = self.write();
|
||||
let idx = settingsrw.testing.subnode_index;
|
||||
if idx == 0 {
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
// bump client api port
|
||||
(*settingsrw).client_api.listen_address.offset_port(idx)?;
|
||||
|
||||
// bump protocol ports
|
||||
(*settingsrw)
|
||||
.core
|
||||
.network
|
||||
.protocol
|
||||
.udp
|
||||
.listen_address
|
||||
.offset_port(idx)?;
|
||||
(*settingsrw)
|
||||
.core
|
||||
.network
|
||||
.protocol
|
||||
.tcp
|
||||
.listen_address
|
||||
.offset_port(idx)?;
|
||||
(*settingsrw)
|
||||
.core
|
||||
.network
|
||||
.protocol
|
||||
.ws
|
||||
.listen_address
|
||||
.offset_port(idx)?;
|
||||
(*settingsrw)
|
||||
.core
|
||||
.network
|
||||
.protocol
|
||||
.wss
|
||||
.listen_address
|
||||
.offset_port(idx)?;
|
||||
// bump application ports
|
||||
(*settingsrw)
|
||||
.core
|
||||
.network
|
||||
.application
|
||||
.http
|
||||
.listen_address
|
||||
.offset_port(idx)?;
|
||||
(*settingsrw)
|
||||
.core
|
||||
.network
|
||||
.application
|
||||
.https
|
||||
.listen_address
|
||||
.offset_port(idx)?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn get_default_config_path() -> PathBuf {
|
||||
// Get default configuration file location
|
||||
let mut default_config_path;
|
||||
|
||||
if let Some(my_proj_dirs) = ProjectDirs::from("org", "Veilid", "Veilid") {
|
||||
default_config_path = PathBuf::from(my_proj_dirs.config_dir());
|
||||
} else {
|
||||
default_config_path = PathBuf::from("./");
|
||||
}
|
||||
default_config_path.push("veilid-server.conf");
|
||||
|
||||
default_config_path
|
||||
}
|
||||
|
||||
pub fn get_default_table_store_path() -> PathBuf {
|
||||
// Get default configuration file location
|
||||
let mut default_config_path;
|
||||
|
||||
if let Some(my_proj_dirs) = ProjectDirs::from("org", "Veilid", "Veilid") {
|
||||
default_config_path = PathBuf::from(my_proj_dirs.data_local_dir());
|
||||
} else {
|
||||
default_config_path = PathBuf::from("./");
|
||||
}
|
||||
default_config_path.push("tablestore");
|
||||
|
||||
default_config_path
|
||||
}
|
||||
|
||||
pub fn get_core_config_callback(&self) -> veilid_core::ConfigCallback {
|
||||
let inner = self.inner.clone();
|
||||
|
||||
Arc::new(move |key: String| {
|
||||
let inner = inner.read();
|
||||
let out: Result<Box<dyn core::any::Any>, String> = match key.as_str() {
|
||||
"namespace" => Ok(Box::new(if inner.testing.subnode_index == 0 {
|
||||
"".to_owned()
|
||||
} else {
|
||||
format!("subnode{}", inner.testing.subnode_index)
|
||||
})),
|
||||
"capabilities.protocol_udp" => Ok(Box::new(true)),
|
||||
"capabilities.protocol_connect_tcp" => Ok(Box::new(true)),
|
||||
"capabilities.protocol_accept_tcp" => Ok(Box::new(true)),
|
||||
"capabilities.protocol_connect_ws" => Ok(Box::new(true)),
|
||||
"capabilities.protocol_accept_ws" => Ok(Box::new(true)),
|
||||
"capabilities.protocol_connect_wss" => Ok(Box::new(true)),
|
||||
"capabilities.protocol_accept_wss" => Ok(Box::new(true)),
|
||||
"tablestore.directory" => Ok(Box::new(
|
||||
inner
|
||||
.core
|
||||
.tablestore
|
||||
.directory
|
||||
.to_string_lossy()
|
||||
.to_string(),
|
||||
)),
|
||||
"network.max_connections" => Ok(Box::new(inner.core.network.max_connections)),
|
||||
"network.connection_initial_timeout" => {
|
||||
Ok(Box::new(inner.core.network.connection_initial_timeout))
|
||||
}
|
||||
"network.node_id" => Ok(Box::new(inner.core.network.node_id)),
|
||||
"network.node_id_secret" => Ok(Box::new(inner.core.network.node_id_secret)),
|
||||
"network.bootstrap" => Ok(Box::new(
|
||||
inner
|
||||
.core
|
||||
.network
|
||||
.bootstrap
|
||||
.clone()
|
||||
.into_iter()
|
||||
.map(|e| e.urlstring)
|
||||
.collect::<Vec<String>>(),
|
||||
)),
|
||||
"network.rpc.concurrency" => Ok(Box::new(inner.core.network.rpc.concurrency)),
|
||||
"network.rpc.queue_size" => Ok(Box::new(inner.core.network.rpc.queue_size)),
|
||||
"network.rpc.max_timestamp_behind" => {
|
||||
Ok(Box::new(inner.core.network.rpc.max_timestamp_behind))
|
||||
}
|
||||
"network.rpc.max_timestamp_ahead" => {
|
||||
Ok(Box::new(inner.core.network.rpc.max_timestamp_ahead))
|
||||
}
|
||||
"network.rpc.timeout" => Ok(Box::new(inner.core.network.rpc.timeout)),
|
||||
"network.rpc.max_route_hop_count" => {
|
||||
Ok(Box::new(inner.core.network.rpc.max_route_hop_count))
|
||||
}
|
||||
"network.dht.resolve_node_timeout" => {
|
||||
Ok(Box::new(inner.core.network.dht.resolve_node_timeout))
|
||||
}
|
||||
"network.dht.resolve_node_count" => {
|
||||
Ok(Box::new(inner.core.network.dht.resolve_node_count))
|
||||
}
|
||||
"network.dht.resolve_node_fanout" => {
|
||||
Ok(Box::new(inner.core.network.dht.resolve_node_fanout))
|
||||
}
|
||||
"network.dht.max_find_node_count" => {
|
||||
Ok(Box::new(inner.core.network.dht.max_find_node_count))
|
||||
}
|
||||
"network.dht.get_value_timeout" => {
|
||||
Ok(Box::new(inner.core.network.dht.get_value_timeout))
|
||||
}
|
||||
"network.dht.get_value_count" => {
|
||||
Ok(Box::new(inner.core.network.dht.get_value_count))
|
||||
}
|
||||
"network.dht.get_value_fanout" => {
|
||||
Ok(Box::new(inner.core.network.dht.get_value_fanout))
|
||||
}
|
||||
"network.dht.set_value_timeout" => {
|
||||
Ok(Box::new(inner.core.network.dht.set_value_timeout))
|
||||
}
|
||||
"network.dht.set_value_count" => {
|
||||
Ok(Box::new(inner.core.network.dht.set_value_count))
|
||||
}
|
||||
"network.dht.set_value_fanout" => {
|
||||
Ok(Box::new(inner.core.network.dht.set_value_fanout))
|
||||
}
|
||||
"network.dht.min_peer_count" => Ok(Box::new(inner.core.network.dht.min_peer_count)),
|
||||
"network.dht.min_peer_refresh_time" => {
|
||||
Ok(Box::new(inner.core.network.dht.min_peer_refresh_time))
|
||||
}
|
||||
"network.dht.validate_dial_info_receipt_time" => Ok(Box::new(
|
||||
inner.core.network.dht.validate_dial_info_receipt_time,
|
||||
)),
|
||||
"network.upnp" => Ok(Box::new(inner.core.network.upnp)),
|
||||
"network.natpmp" => Ok(Box::new(inner.core.network.natpmp)),
|
||||
"network.address_filter" => Ok(Box::new(inner.core.network.address_filter)),
|
||||
"network.tls.certificate_path" => Ok(Box::new(
|
||||
inner
|
||||
.core
|
||||
.network
|
||||
.tls
|
||||
.certificate_path
|
||||
.to_string_lossy()
|
||||
.to_string(),
|
||||
)),
|
||||
"network.tls.private_key_path" => Ok(Box::new(
|
||||
inner
|
||||
.core
|
||||
.network
|
||||
.tls
|
||||
.private_key_path
|
||||
.to_string_lossy()
|
||||
.to_string(),
|
||||
)),
|
||||
"network.tls.connection_initial_timeout" => {
|
||||
Ok(Box::new(inner.core.network.tls.connection_initial_timeout))
|
||||
}
|
||||
"network.application.path" => Ok(Box::new(
|
||||
inner
|
||||
.core
|
||||
.network
|
||||
.application
|
||||
.path
|
||||
.to_string_lossy()
|
||||
.to_string(),
|
||||
)),
|
||||
"network.application.https.enabled" => {
|
||||
Ok(Box::new(inner.core.network.application.https.enabled))
|
||||
}
|
||||
"network.application.https.listen_address" => Ok(Box::new(
|
||||
inner
|
||||
.core
|
||||
.network
|
||||
.application
|
||||
.https
|
||||
.listen_address
|
||||
.name
|
||||
.clone(),
|
||||
)),
|
||||
"network.application.http.enabled" => {
|
||||
Ok(Box::new(inner.core.network.application.http.enabled))
|
||||
}
|
||||
"network.application.http.listen_address" => Ok(Box::new(
|
||||
inner
|
||||
.core
|
||||
.network
|
||||
.application
|
||||
.http
|
||||
.listen_address
|
||||
.name
|
||||
.clone(),
|
||||
)),
|
||||
"network.protocol.udp.enabled" => {
|
||||
Ok(Box::new(inner.core.network.protocol.udp.enabled))
|
||||
}
|
||||
"network.protocol.udp.socket_pool_size" => {
|
||||
Ok(Box::new(inner.core.network.protocol.udp.socket_pool_size))
|
||||
}
|
||||
"network.protocol.udp.listen_address" => Ok(Box::new(
|
||||
inner.core.network.protocol.udp.listen_address.name.clone(),
|
||||
)),
|
||||
"network.protocol.udp.public_address" => Ok(Box::new(
|
||||
if let Some(a) = &inner.core.network.protocol.udp.public_address {
|
||||
Some(a.name.clone())
|
||||
} else {
|
||||
None
|
||||
},
|
||||
)),
|
||||
"network.protocol.tcp.connect" => {
|
||||
Ok(Box::new(inner.core.network.protocol.tcp.connect))
|
||||
}
|
||||
"network.protocol.tcp.listen" => {
|
||||
Ok(Box::new(inner.core.network.protocol.tcp.listen))
|
||||
}
|
||||
"network.protocol.tcp.max_connections" => {
|
||||
Ok(Box::new(inner.core.network.protocol.tcp.max_connections))
|
||||
}
|
||||
"network.protocol.tcp.listen_address" => Ok(Box::new(
|
||||
inner.core.network.protocol.tcp.listen_address.name.clone(),
|
||||
)),
|
||||
"network.protocol.tcp.public_address" => Ok(Box::new(
|
||||
if let Some(a) = &inner.core.network.protocol.tcp.public_address {
|
||||
Some(a.name.clone())
|
||||
} else {
|
||||
None
|
||||
},
|
||||
)),
|
||||
"network.protocol.ws.connect" => {
|
||||
Ok(Box::new(inner.core.network.protocol.ws.connect))
|
||||
}
|
||||
"network.protocol.ws.listen" => Ok(Box::new(inner.core.network.protocol.ws.listen)),
|
||||
"network.protocol.ws.max_connections" => {
|
||||
Ok(Box::new(inner.core.network.protocol.ws.max_connections))
|
||||
}
|
||||
"network.protocol.ws.listen_address" => Ok(Box::new(
|
||||
inner.core.network.protocol.ws.listen_address.name.clone(),
|
||||
)),
|
||||
"network.protocol.ws.path" => {
|
||||
Ok(Box::new(inner.core.network.protocol.ws.path.clone()))
|
||||
}
|
||||
"network.protocol.ws.public_address" => Ok(Box::new(
|
||||
if let Some(a) = &inner.core.network.protocol.ws.public_address {
|
||||
Some(a.name.clone())
|
||||
} else {
|
||||
None
|
||||
},
|
||||
)),
|
||||
"network.protocol.wss.connect" => {
|
||||
Ok(Box::new(inner.core.network.protocol.wss.connect))
|
||||
}
|
||||
"network.protocol.wss.listen" => {
|
||||
Ok(Box::new(inner.core.network.protocol.wss.listen))
|
||||
}
|
||||
"network.protocol.wss.max_connections" => {
|
||||
Ok(Box::new(inner.core.network.protocol.wss.max_connections))
|
||||
}
|
||||
"network.protocol.wss.listen_address" => Ok(Box::new(
|
||||
inner.core.network.protocol.wss.listen_address.name.clone(),
|
||||
)),
|
||||
"network.protocol.wss.path" => {
|
||||
Ok(Box::new(inner.core.network.protocol.wss.path.clone()))
|
||||
}
|
||||
"network.protocol.wss.public_address" => Ok(Box::new(
|
||||
if let Some(a) = &inner.core.network.protocol.wss.public_address {
|
||||
Some(a.name.clone())
|
||||
} else {
|
||||
None
|
||||
},
|
||||
)),
|
||||
"network.leases.max_server_signal_leases" => {
|
||||
Ok(Box::new(inner.core.network.leases.max_server_signal_leases))
|
||||
}
|
||||
"network.leases.max_server_relay_leases" => {
|
||||
Ok(Box::new(inner.core.network.leases.max_server_relay_leases))
|
||||
}
|
||||
"network.leases.max_client_signal_leases" => {
|
||||
Ok(Box::new(inner.core.network.leases.max_client_signal_leases))
|
||||
}
|
||||
"network.leases.max_client_relay_leases" => {
|
||||
Ok(Box::new(inner.core.network.leases.max_client_relay_leases))
|
||||
}
|
||||
_ => Err(format!("config key '{}' doesn't exist", key)),
|
||||
};
|
||||
out
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use serial_test::serial;
|
||||
|
||||
#[test]
|
||||
#[serial]
|
||||
fn test_default_config() {
|
||||
let mut cfg = config::Config::default();
|
||||
load_default_config(&mut cfg).unwrap();
|
||||
let inner = cfg.try_into::<SettingsInner>().unwrap();
|
||||
println!("default settings: {:?}", inner);
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[serial]
|
||||
fn test_default_config_settings() {
|
||||
let settings = Settings::new(true, OsStr::new("!!!")).unwrap();
|
||||
|
||||
let s = settings.read();
|
||||
assert_eq!(s.daemon, false);
|
||||
assert_eq!(s.client_api.enabled, true);
|
||||
assert_eq!(s.client_api.listen_address.name, "localhost:5959");
|
||||
assert_eq!(
|
||||
s.client_api.listen_address.addrs,
|
||||
"localhost:5959"
|
||||
.to_socket_addrs()
|
||||
.unwrap()
|
||||
.collect::<Vec<SocketAddr>>()
|
||||
);
|
||||
assert_eq!(s.auto_attach, false);
|
||||
assert_eq!(s.logging.terminal.enabled, true);
|
||||
assert_eq!(s.logging.terminal.level, LogLevel::Info);
|
||||
assert_eq!(s.logging.file.enabled, false);
|
||||
assert_eq!(s.logging.file.path, "");
|
||||
assert_eq!(s.logging.file.append, true);
|
||||
assert_eq!(s.logging.file.level, LogLevel::Info);
|
||||
assert_eq!(s.testing.subnode_index, 0);
|
||||
assert_eq!(
|
||||
s.core.tablestore.directory,
|
||||
Settings::get_default_table_store_path()
|
||||
);
|
||||
assert_eq!(s.core.network.max_connections, 16);
|
||||
assert_eq!(s.core.network.connection_initial_timeout, 2_000_000u64);
|
||||
assert_eq!(s.core.network.node_id, veilid_core::DHTKey::default());
|
||||
assert_eq!(
|
||||
s.core.network.node_id_secret,
|
||||
veilid_core::DHTKeySecret::default()
|
||||
);
|
||||
//
|
||||
assert!(s.core.network.bootstrap.len() == 0);
|
||||
//
|
||||
assert_eq!(s.core.network.rpc.concurrency, 0);
|
||||
assert_eq!(s.core.network.rpc.queue_size, 1024);
|
||||
assert_eq!(s.core.network.rpc.max_timestamp_behind, Some(10_000_000u64));
|
||||
assert_eq!(s.core.network.rpc.max_timestamp_ahead, Some(10_000_000u64));
|
||||
assert_eq!(s.core.network.rpc.timeout, 10000000);
|
||||
assert_eq!(s.core.network.rpc.max_route_hop_count, 7);
|
||||
//
|
||||
assert_eq!(s.core.network.dht.resolve_node_timeout, None);
|
||||
assert_eq!(s.core.network.dht.resolve_node_count, 20u32);
|
||||
assert_eq!(s.core.network.dht.resolve_node_fanout, 3u32);
|
||||
assert_eq!(s.core.network.dht.max_find_node_count, 20u32);
|
||||
assert_eq!(s.core.network.dht.get_value_timeout, None);
|
||||
assert_eq!(s.core.network.dht.get_value_count, 20u32);
|
||||
assert_eq!(s.core.network.dht.get_value_fanout, 3u32);
|
||||
assert_eq!(s.core.network.dht.set_value_timeout, None);
|
||||
assert_eq!(s.core.network.dht.set_value_count, 20u32);
|
||||
assert_eq!(s.core.network.dht.set_value_fanout, 5u32);
|
||||
assert_eq!(s.core.network.dht.min_peer_count, 20u32);
|
||||
assert_eq!(s.core.network.dht.min_peer_refresh_time, 2000000u64);
|
||||
assert_eq!(
|
||||
s.core.network.dht.validate_dial_info_receipt_time,
|
||||
5000000u64
|
||||
);
|
||||
//
|
||||
assert_eq!(s.core.network.upnp, false);
|
||||
assert_eq!(s.core.network.natpmp, false);
|
||||
assert_eq!(s.core.network.address_filter, true);
|
||||
//
|
||||
assert_eq!(
|
||||
s.core.network.tls.certificate_path,
|
||||
std::path::PathBuf::from("/etc/veilid/server.crt")
|
||||
);
|
||||
assert_eq!(
|
||||
s.core.network.tls.private_key_path,
|
||||
std::path::PathBuf::from("/etc/veilid/private/server.key")
|
||||
);
|
||||
assert_eq!(s.core.network.tls.connection_initial_timeout, 2_000_000u64);
|
||||
//
|
||||
assert_eq!(
|
||||
s.core.network.application.path,
|
||||
std::path::PathBuf::from("app")
|
||||
);
|
||||
assert_eq!(s.core.network.application.https.enabled, true);
|
||||
assert_eq!(
|
||||
s.core.network.application.https.listen_address.name,
|
||||
"[::]:5150"
|
||||
);
|
||||
assert_eq!(
|
||||
s.core.network.application.https.listen_address.addrs,
|
||||
"[::]:5150"
|
||||
.to_socket_addrs()
|
||||
.unwrap()
|
||||
.collect::<Vec<SocketAddr>>()
|
||||
);
|
||||
assert_eq!(s.core.network.application.http.enabled, true);
|
||||
assert_eq!(
|
||||
s.core.network.application.http.listen_address.name,
|
||||
"[::]:5150"
|
||||
);
|
||||
assert_eq!(
|
||||
s.core.network.application.http.listen_address.addrs,
|
||||
"[::]:5150"
|
||||
.to_socket_addrs()
|
||||
.unwrap()
|
||||
.collect::<Vec<SocketAddr>>()
|
||||
);
|
||||
//
|
||||
assert_eq!(s.core.network.protocol.udp.enabled, true);
|
||||
assert_eq!(s.core.network.protocol.udp.socket_pool_size, 0);
|
||||
assert_eq!(s.core.network.protocol.udp.listen_address.name, "[::]:5150");
|
||||
assert_eq!(
|
||||
s.core.network.protocol.udp.listen_address.addrs,
|
||||
"[::]:5150"
|
||||
.to_socket_addrs()
|
||||
.unwrap()
|
||||
.collect::<Vec<SocketAddr>>()
|
||||
);
|
||||
assert_eq!(s.core.network.protocol.udp.public_address, None);
|
||||
|
||||
//
|
||||
assert_eq!(s.core.network.protocol.tcp.connect, true);
|
||||
assert_eq!(s.core.network.protocol.tcp.listen, true);
|
||||
assert_eq!(s.core.network.protocol.tcp.max_connections, 32);
|
||||
assert_eq!(s.core.network.protocol.tcp.listen_address.name, "[::]:5150");
|
||||
assert_eq!(
|
||||
s.core.network.protocol.tcp.listen_address.addrs,
|
||||
"[::]:5150"
|
||||
.to_socket_addrs()
|
||||
.unwrap()
|
||||
.collect::<Vec<SocketAddr>>()
|
||||
);
|
||||
assert_eq!(s.core.network.protocol.tcp.public_address, None);
|
||||
|
||||
//
|
||||
assert_eq!(s.core.network.protocol.ws.connect, true);
|
||||
assert_eq!(s.core.network.protocol.ws.listen, true);
|
||||
assert_eq!(s.core.network.protocol.ws.max_connections, 16);
|
||||
assert_eq!(s.core.network.protocol.ws.listen_address.name, "[::]:5150");
|
||||
assert_eq!(
|
||||
s.core.network.protocol.ws.listen_address.addrs,
|
||||
"[::]:5150"
|
||||
.to_socket_addrs()
|
||||
.unwrap()
|
||||
.collect::<Vec<SocketAddr>>()
|
||||
);
|
||||
assert_eq!(s.core.network.protocol.ws.path, "/ws");
|
||||
assert_eq!(s.core.network.protocol.ws.public_address, None);
|
||||
//
|
||||
assert_eq!(s.core.network.protocol.wss.connect, true);
|
||||
assert_eq!(s.core.network.protocol.wss.listen, true);
|
||||
assert_eq!(s.core.network.protocol.wss.max_connections, 16);
|
||||
assert_eq!(s.core.network.protocol.wss.listen_address.name, "[::]:5150");
|
||||
assert_eq!(
|
||||
s.core.network.protocol.wss.listen_address.addrs,
|
||||
"[::]:5150"
|
||||
.to_socket_addrs()
|
||||
.unwrap()
|
||||
.collect::<Vec<SocketAddr>>()
|
||||
);
|
||||
assert_eq!(s.core.network.protocol.wss.path, "/ws");
|
||||
assert_eq!(s.core.network.protocol.wss.public_address, None);
|
||||
|
||||
assert_eq!(s.core.network.leases.max_server_signal_leases, 256);
|
||||
assert_eq!(s.core.network.leases.max_server_relay_leases, 8);
|
||||
assert_eq!(s.core.network.leases.max_client_signal_leases, 2);
|
||||
assert_eq!(s.core.network.leases.max_client_relay_leases, 2);
|
||||
}
|
||||
}
|
336
veilid-server/src/unix.rs
Normal file
336
veilid-server/src/unix.rs
Normal file
@@ -0,0 +1,336 @@
|
||||
#[cfg(unix)]
|
||||
use crate::client_api;
|
||||
use crate::settings;
|
||||
use async_std::channel::{bounded, Receiver, Sender};
|
||||
use clap::{App, Arg};
|
||||
use lazy_static::*;
|
||||
use log::*;
|
||||
use parking_lot::Mutex;
|
||||
use simplelog::*;
|
||||
use std::cell::RefCell;
|
||||
use std::ffi::OsStr;
|
||||
use std::fs::OpenOptions;
|
||||
use std::path::Path;
|
||||
use std::rc::Rc;
|
||||
use std::str::FromStr;
|
||||
use std::sync::Arc;
|
||||
use veilid_core::xx::SingleShotEventual;
|
||||
|
||||
fn parse_command_line<'a>(
|
||||
default_config_path: &'a OsStr,
|
||||
) -> Result<clap::ArgMatches<'a>, clap::Error> {
|
||||
let matches = App::new("veilid-server")
|
||||
.version("0.1")
|
||||
.about("Veilid Server")
|
||||
.arg(
|
||||
Arg::with_name("daemon")
|
||||
.long("daemon")
|
||||
.short("d")
|
||||
.help("Run in daemon mode in the background"),
|
||||
)
|
||||
.arg(
|
||||
Arg::with_name("subnode_index")
|
||||
.long("subnode_index")
|
||||
.takes_value(true)
|
||||
.help("Run as an extra daemon on the same machine for testing purposes, specify a number greater than zero to offset the listening ports"),
|
||||
)
|
||||
.arg(
|
||||
Arg::with_name("debug")
|
||||
.long("debug")
|
||||
.help("Turn on debug logging"),
|
||||
)
|
||||
.arg(
|
||||
Arg::with_name("trace")
|
||||
.long("trace")
|
||||
.conflicts_with("debug")
|
||||
.help("Turn on trace logging"),
|
||||
)
|
||||
.arg(
|
||||
Arg::with_name("generate-id")
|
||||
.long("generate-id")
|
||||
.help("Only generate a new node id and print it"),
|
||||
)
|
||||
.arg(
|
||||
Arg::with_name("config-file")
|
||||
.short("c")
|
||||
.long("config-file")
|
||||
.takes_value(true)
|
||||
.value_name("FILE")
|
||||
.default_value_os(default_config_path)
|
||||
.help("Specify a configuration file to use"),
|
||||
)
|
||||
.arg(
|
||||
Arg::with_name("bootstrap")
|
||||
.long("bootstrap")
|
||||
.takes_value(true)
|
||||
.value_name("BOOTSTRAP_LIST")
|
||||
.help("Specify a list of bootstrap servers to use"),
|
||||
)
|
||||
.arg(
|
||||
Arg::with_name("attach")
|
||||
.long("attach")
|
||||
.takes_value(true)
|
||||
.value_name("BOOL")
|
||||
.possible_values(&["false", "true"])
|
||||
.help("Automatically attach the server to the Veilid network"),
|
||||
)
|
||||
.arg(
|
||||
Arg::with_name("wait-for-debug")
|
||||
.long("wait-for-debug")
|
||||
.help("Wait for debugger to attach"),
|
||||
)
|
||||
|
||||
.get_matches();
|
||||
|
||||
Ok(matches)
|
||||
}
|
||||
|
||||
lazy_static! {
|
||||
static ref SHUTDOWN_SWITCH: Mutex<Option<SingleShotEventual<()>>> =
|
||||
Mutex::new(Some(SingleShotEventual::new(())));
|
||||
}
|
||||
|
||||
pub fn shutdown() {
|
||||
let mut shutdown_switch_locked = SHUTDOWN_SWITCH.lock();
|
||||
if let Some(shutdown_switch) = shutdown_switch_locked.take() {
|
||||
shutdown_switch.resolve(());
|
||||
}
|
||||
}
|
||||
|
||||
pub async fn main() -> Result<(), String> {
|
||||
// Wait until signal
|
||||
ctrlc::set_handler(move || {
|
||||
shutdown();
|
||||
})
|
||||
.expect("Error setting Ctrl-C handler");
|
||||
|
||||
// Get command line options
|
||||
let default_config_path = settings::Settings::get_default_config_path();
|
||||
let matches = parse_command_line(default_config_path.as_os_str())
|
||||
.map_err(|e| format!("failed to parse command line: {}", e))?;
|
||||
|
||||
// Check for one-off commands
|
||||
if matches.occurrences_of("wait-for-debug") != 0 {
|
||||
use bugsalot::debugger;
|
||||
debugger::wait_until_attached(None).expect("state() not implemented on this platform");
|
||||
}
|
||||
if matches.occurrences_of("generate-id") != 0 {
|
||||
let (key, secret) = veilid_core::generate_secret();
|
||||
println!("Public: {}\nSecret: {}", key.encode(), secret.encode());
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
// Attempt to load configuration
|
||||
let settings = settings::Settings::new(
|
||||
matches.occurrences_of("config-file") == 0,
|
||||
matches.value_of_os("config-file").unwrap(),
|
||||
)
|
||||
.map_err(|e| format!("configuration is invalid: {}", e))?;
|
||||
|
||||
// write lock the settings
|
||||
let mut settingsrw = settings.write();
|
||||
|
||||
// Set config from command line
|
||||
if matches.occurrences_of("daemon") != 0 {
|
||||
settingsrw.daemon = true;
|
||||
settingsrw.logging.terminal.enabled = false;
|
||||
}
|
||||
if matches.occurrences_of("subnode_index") != 0 {
|
||||
let subnode_index = match matches.value_of("subnode_index") {
|
||||
Some(x) => x
|
||||
.parse()
|
||||
.map_err(|e| format!("couldn't parse subnode index: {}", e))?,
|
||||
None => {
|
||||
return Err("value not specified for subnode_index".to_owned());
|
||||
}
|
||||
};
|
||||
if subnode_index == 0 {
|
||||
return Err("value of subnode_index should be between 1 and 65535".to_owned());
|
||||
}
|
||||
settingsrw.testing.subnode_index = subnode_index;
|
||||
}
|
||||
if matches.occurrences_of("debug") != 0 {
|
||||
settingsrw.logging.terminal.level = settings::LogLevel::Debug;
|
||||
settingsrw.logging.file.level = settings::LogLevel::Debug;
|
||||
}
|
||||
if matches.occurrences_of("trace") != 0 {
|
||||
settingsrw.logging.terminal.level = settings::LogLevel::Trace;
|
||||
settingsrw.logging.file.level = settings::LogLevel::Trace;
|
||||
}
|
||||
if matches.is_present("attach") {
|
||||
settingsrw.auto_attach = match matches.value_of("attach") {
|
||||
Some("false") => false,
|
||||
_ => true,
|
||||
};
|
||||
}
|
||||
if matches.occurrences_of("bootstrap") != 0 {
|
||||
let bootstrap = match matches.value_of("bootstrap") {
|
||||
Some(x) => {
|
||||
println!("Overriding bootstrap with: ");
|
||||
let mut out: Vec<settings::ParsedURL> = Vec::new();
|
||||
for x in x.split(",") {
|
||||
println!(" {}", x);
|
||||
out.push(
|
||||
settings::ParsedURL::from_str(x)
|
||||
.map_err(|e| format!("unable to parse url in bootstrap list: {}", e))?,
|
||||
);
|
||||
}
|
||||
out
|
||||
}
|
||||
None => {
|
||||
return Err("value not specified for bootstrap".to_owned());
|
||||
}
|
||||
};
|
||||
settingsrw.core.network.bootstrap = bootstrap;
|
||||
}
|
||||
|
||||
// Apply subnode index if we're testing
|
||||
drop(settingsrw);
|
||||
settings
|
||||
.apply_subnode_index()
|
||||
.map_err(|_| "failed to apply subnode index".to_owned())?;
|
||||
let settingsr = settings.read();
|
||||
|
||||
// Set up loggers
|
||||
let mut logs: Vec<Box<dyn SharedLogger>> = Vec::new();
|
||||
|
||||
let mut cb = ConfigBuilder::new();
|
||||
cb.add_filter_ignore_str("async_std");
|
||||
cb.add_filter_ignore_str("async_io");
|
||||
cb.add_filter_ignore_str("polling");
|
||||
cb.add_filter_ignore_str("rustls");
|
||||
cb.add_filter_ignore_str("async_tungstenite");
|
||||
cb.add_filter_ignore_str("tungstenite");
|
||||
|
||||
if settingsr.logging.terminal.enabled {
|
||||
logs.push(TermLogger::new(
|
||||
settings::convert_loglevel(settingsr.logging.terminal.level),
|
||||
cb.build(),
|
||||
TerminalMode::Mixed,
|
||||
ColorChoice::Auto,
|
||||
))
|
||||
}
|
||||
if settingsr.logging.file.enabled {
|
||||
let log_path = Path::new(&settingsr.logging.file.path);
|
||||
|
||||
let logfile;
|
||||
if settingsr.logging.file.append {
|
||||
logfile = OpenOptions::new()
|
||||
.create(true)
|
||||
.append(true)
|
||||
.open(log_path)
|
||||
.map_err(|e| format!("failed to open log file: {}", e))?
|
||||
} else {
|
||||
logfile = OpenOptions::new()
|
||||
.create(true)
|
||||
.truncate(true)
|
||||
.write(true)
|
||||
.open(log_path)
|
||||
.map_err(|e| format!("failed to open log file: {}", e))?
|
||||
}
|
||||
logs.push(WriteLogger::new(
|
||||
settings::convert_loglevel(settingsr.logging.file.level),
|
||||
cb.build(),
|
||||
logfile,
|
||||
))
|
||||
}
|
||||
CombinedLogger::init(logs).map_err(|e| format!("failed to init logs: {}", e))?;
|
||||
|
||||
// Create Veilid Core
|
||||
let veilid_core = veilid_core::VeilidCore::new();
|
||||
|
||||
// Create client api state change pipe
|
||||
let (sender, receiver): (
|
||||
Sender<veilid_core::VeilidStateChange>,
|
||||
Receiver<veilid_core::VeilidStateChange>,
|
||||
) = bounded(1);
|
||||
|
||||
// Create VeilidCore setup
|
||||
let vcs = veilid_core::VeilidCoreSetup {
|
||||
state_change_callback: Arc::new(
|
||||
move |change: veilid_core::VeilidStateChange| -> veilid_core::SystemPinBoxFuture<()> {
|
||||
let sender = sender.clone();
|
||||
Box::pin(async move {
|
||||
if let Err(_) = sender.send(change).await {
|
||||
error!("error sending state change callback");
|
||||
}
|
||||
})
|
||||
},
|
||||
),
|
||||
config_callback: settings.get_core_config_callback(),
|
||||
};
|
||||
|
||||
// Start Veilid Core and get API
|
||||
let veilid_api = veilid_core
|
||||
.startup(vcs)
|
||||
.await
|
||||
.map_err(|e| format!("VeilidCore startup failed: {}", e))?;
|
||||
|
||||
// Start client api if one is requested
|
||||
let capi = Rc::new(RefCell::new(if settingsr.client_api.enabled {
|
||||
let some_capi = client_api::ClientApi::new(veilid_api.clone());
|
||||
some_capi
|
||||
.clone()
|
||||
.run(settingsr.client_api.listen_address.addrs.clone());
|
||||
Some(some_capi)
|
||||
} else {
|
||||
None
|
||||
}));
|
||||
|
||||
// Drop rwlock on settings
|
||||
let auto_attach = settingsr.auto_attach;
|
||||
drop(settingsr);
|
||||
|
||||
// Handle state changes on main thread for capnproto rpc
|
||||
let capi2 = capi.clone();
|
||||
let capi_jh = async_std::task::spawn_local(async move {
|
||||
loop {
|
||||
let change = match receiver.recv().await {
|
||||
Ok(change) => change,
|
||||
Err(_) => {
|
||||
break;
|
||||
}
|
||||
};
|
||||
let c = match capi2.borrow_mut().as_mut() {
|
||||
Some(some_capi) => some_capi.clone(),
|
||||
None => continue,
|
||||
};
|
||||
c.handle_state_change(change);
|
||||
}
|
||||
});
|
||||
|
||||
// Auto-attach if desired
|
||||
if auto_attach {
|
||||
info!("Auto-attach to the Veilid network");
|
||||
veilid_api.attach().await;
|
||||
}
|
||||
|
||||
// Idle while waiting to exit
|
||||
let shutdown_switch = {
|
||||
let shutdown_switch_locked = SHUTDOWN_SWITCH.lock();
|
||||
if let Some(ss) = &*shutdown_switch_locked {
|
||||
Some(ss.instance())
|
||||
} else {
|
||||
None
|
||||
}
|
||||
};
|
||||
if let Some(shutdown_switch) = shutdown_switch {
|
||||
shutdown_switch.await;
|
||||
}
|
||||
|
||||
// Stop the client api if we have one
|
||||
match capi.borrow_mut().as_mut().cloned() {
|
||||
Some(some_capi) => {
|
||||
some_capi.stop().await;
|
||||
}
|
||||
None => (),
|
||||
}
|
||||
|
||||
// Shut down Veilid API
|
||||
veilid_api.shutdown().await;
|
||||
|
||||
// Wait for statechanged handler to exit
|
||||
capi_jh.await;
|
||||
|
||||
Ok(())
|
||||
}
|
1
veilid-server/src/windows.rs
Normal file
1
veilid-server/src/windows.rs
Normal file
@@ -0,0 +1 @@
|
||||
|
Reference in New Issue
Block a user