From f2aa066626eda648241b79d69f462d3c907b258f Mon Sep 17 00:00:00 2001 From: Christien Rioux Date: Mon, 11 Nov 2024 22:11:20 -0500 Subject: [PATCH] some virtual-router config work --- Cargo.lock | 6 + veilid-tools/Cargo.toml | 14 +- .../main.rs} | 47 +++++ veilid-tools/src/virtual_network/commands.rs | 4 +- .../src/virtual_network/router_client.rs | 4 +- .../virtual_network/router_server/config.rs | 168 ++++++++++++++++++ .../router_server/default_config.yml | 43 +++++ .../src/virtual_network/router_server/mod.rs | 108 ++++++++++- 8 files changed, 388 insertions(+), 6 deletions(-) rename veilid-tools/src/bin/{virtual_router.rs => virtual_router/main.rs} (51%) create mode 100644 veilid-tools/src/virtual_network/router_server/config.rs create mode 100644 veilid-tools/src/virtual_network/router_server/default_config.yml diff --git a/Cargo.lock b/Cargo.lock index f579aade..9ff45c74 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2874,6 +2874,9 @@ name = "ipnet" version = "2.10.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ddc24109865250148c2e0f3d25d4f0f479571723792d3802153c60922a4fb708" +dependencies = [ + "serde", +] [[package]] name = "ipnetwork" @@ -6555,6 +6558,8 @@ dependencies = [ "backtrace", "cfg-if 1.0.0", "chrono", + "clap 4.5.20", + "config 0.13.4", "console_error_panic_hook", "ctrlc", "eyre", @@ -6564,6 +6569,7 @@ dependencies = [ "futures_codec", "getrandom", "ifstructs", + "ipnet", "jni", "jni-sys", "js-sys", diff --git a/veilid-tools/Cargo.toml b/veilid-tools/Cargo.toml index 012ee07e..b2e31000 100644 --- a/veilid-tools/Cargo.toml +++ b/veilid-tools/Cargo.toml @@ -44,7 +44,13 @@ tracing = ["dep:tracing", "dep:tracing-subscriber", "tokio/tracing"] debug-locks = [] virtual-network = [] -virtual-network-server = ["dep:ws_stream_tungstenite", "dep:async-tungstenite"] +virtual-network-server = [ + "dep:ws_stream_tungstenite", + "dep:async-tungstenite", + "dep:clap", + "dep:config", + "dep:ipnet", +] [dependencies] tracing = { version = "0.1.40", features = [ @@ -95,8 +101,14 @@ socket2 = { version = "0.5.7", features = ["all"] } tokio = { version = "1.38.1", features = ["full"], optional = true } tokio-util = { version = "0.7.11", features = ["compat"], optional = true } tokio-stream = { version = "0.1.15", features = ["net"], optional = true } + ws_stream_tungstenite = { version = "0.14.0", optional = true } async-tungstenite = { version = "0.28.0", optional = true } +clap = { version = "4", features = ["derive"], optional = true } +config = { version = "^0", default-features = false, features = [ + "yaml", +], optional = true } +ipnet = { version = "2", features = ["serde"], optional = true } # Dependencies for WASM builds only [target.'cfg(all(target_arch = "wasm32", target_os = "unknown"))'.dependencies] diff --git a/veilid-tools/src/bin/virtual_router.rs b/veilid-tools/src/bin/virtual_router/main.rs similarity index 51% rename from veilid-tools/src/bin/virtual_router.rs rename to veilid-tools/src/bin/virtual_router/main.rs index 0d4ccf73..5e5d9aa8 100644 --- a/veilid-tools/src/bin/virtual_router.rs +++ b/veilid-tools/src/bin/virtual_router/main.rs @@ -1,7 +1,9 @@ #![cfg(not(all(target_arch = "wasm32", target_os = "unknown")))] use cfg_if::*; +use clap::Parser; use parking_lot::*; +use std::path::PathBuf; use stop_token::StopSource; use veilid_tools::*; @@ -23,12 +25,33 @@ cfg_if! { } } +#[derive(Parser, Debug)] +#[command(author, version, about = "Veilid VirtualRouter")] +struct CmdlineArgs { + /// TCP address to listen on + #[arg(short('t'), long)] + tcp_addr: Option, + /// Turn off TCP listener + #[arg(long)] + no_tcp: bool, + /// WS address to listen on + #[arg(short('w'), long)] + ws_addr: Option, + /// Turn off WS listener + #[arg(long)] + no_ws: bool, + /// Specify a configuration file to use + #[arg(short = 'c', long, value_name = "FILE")] + config_file: Option, +} + fn main() -> Result<(), String> { let stop_source = StopSource::new(); let stop_token = stop_source.token(); let stop_mutex = Mutex::new(Some(stop_source)); ctrlc::set_handler(move || { + println!("Exiting..."); *(stop_mutex.lock()) = None; }) .expect("Error setting Ctrl-C handler"); @@ -36,7 +59,31 @@ fn main() -> Result<(), String> { block_on(async { println!("Veilid VirtualRouter v{}", VERSION); + let args = CmdlineArgs::parse(); + let router_server = virtual_network::RouterServer::new(); + let _ss_tcp = if !args.no_tcp { + Some( + router_server + .listen_tcp(args.tcp_addr) + .await + .map_err(|e| e.to_string())?, + ) + } else { + None + }; + + let _ss_ws = if !args.no_ws { + Some( + router_server + .listen_ws(args.ws_addr) + .await + .map_err(|e| e.to_string())?, + ) + } else { + None + }; + router_server .run(stop_token) .await diff --git a/veilid-tools/src/virtual_network/commands.rs b/veilid-tools/src/virtual_network/commands.rs index 4563eaa6..f66b1b05 100644 --- a/veilid-tools/src/virtual_network/commands.rs +++ b/veilid-tools/src/virtual_network/commands.rs @@ -46,7 +46,9 @@ impl fmt::Display for VirtualProtocolType { #[derive(Debug, PartialEq, Eq, Clone, Serialize, Deserialize)] pub enum ServerProcessorRequest { - AllocateMachine, + AllocateMachine { + profile: String, + }, ReleaseMachine { machine_id: MachineId, }, diff --git a/veilid-tools/src/virtual_network/router_client.rs b/veilid-tools/src/virtual_network/router_client.rs index bccd7ff4..45add081 100644 --- a/veilid-tools/src/virtual_network/router_client.rs +++ b/veilid-tools/src/virtual_network/router_client.rs @@ -180,8 +180,8 @@ impl RouterClient { } } - pub async fn allocate_machine(self) -> VirtualNetworkResult { - let request = ServerProcessorRequest::AllocateMachine; + pub async fn allocate_machine(self, profile: String) -> VirtualNetworkResult { + let request = ServerProcessorRequest::AllocateMachine { profile }; let ServerProcessorReplyValue::AllocateMachine { machine_id } = self.perform_request(request).await? else { diff --git a/veilid-tools/src/virtual_network/router_server/config.rs b/veilid-tools/src/virtual_network/router_server/config.rs new file mode 100644 index 00000000..db748730 --- /dev/null +++ b/veilid-tools/src/virtual_network/router_server/config.rs @@ -0,0 +1,168 @@ +use super::*; +use ipnet::*; +use serde::*; +use std::ffi::OsStr; +use std::path::Path; + +pub use ::config::ConfigError; + +fn load_default_config() -> Result<::config::Config, ConfigError> { + let default_config = include_str!("default_config.yml"); + + ::config::Config::builder() + .add_source(::config::File::from_str( + default_config, + ::config::FileFormat::Yaml, + )) + .build() +} + +fn load_config(cfg: ::config::Config, config_file: &Path) -> Result<::config::Config, ConfigError> { + if let Some(config_file_str) = config_file.to_str() { + ::config::Config::builder() + .add_source(cfg) + .add_source(::config::File::new( + config_file_str, + ::config::FileFormat::Yaml, + )) + .build() + } else { + Err(ConfigError::Message( + "config file path is not valid UTF-8".to_owned(), + )) + } +} + +const fn default_weight() -> f32 { + 1.0f32 +} + +#[derive(Debug, Clone, Deserialize)] +pub struct Profile { + machine: One, +} + +#[derive(Debug, Clone, Deserialize)] +pub struct Machine { + network: One, + #[serde(default)] + address4: Option, + #[serde(default)] + address6: Option, + #[serde(default)] + disable_capabilities: Vec, + #[serde(default)] + bootstrap: bool, +} + +#[derive(Debug, Clone, Default, Deserialize)] +pub struct One { + #[serde(flatten)] + pub value: Vec>, +} + +#[derive(Debug, Clone, Default, Deserialize)] +pub struct OneOrNone { + #[serde(flatten)] + pub value: Vec>, + #[serde(default = "default_weight")] + pub none_weight: f32, +} + +#[derive(Debug, Clone, Default, Deserialize)] +pub struct Probability { + #[serde(flatten)] + probability: f32, +} + +#[derive(Debug, Clone, Deserialize)] +pub struct Weighted { + #[serde(flatten)] + pub value: T, + #[serde(default = "default_weight")] + pub weight: f32, +} + +#[derive(Debug, Clone, Deserialize)] +pub struct Network { + #[serde(flatten)] + subnets: Subnets, + #[serde(default)] + distance: Option, + #[serde(flatten)] + performance: Performance, + #[serde(default)] + gateway: Option, +} + +#[derive(Debug, Clone, Deserialize)] +pub struct Subnets { + subnet4: OneOrNone, + subnet6: OneOrNone, +} + +#[derive(Debug, Clone, Deserialize)] +pub struct Gateway { + translation: One, + upnp: Probability, + network: String, +} + +#[derive(Debug, Clone, Deserialize)] +pub struct Distance { + min: f32, + max: f32, +} + +#[derive(Debug, Clone, Deserialize)] +pub struct Distribution { + mean: f32, + sigma: f32, + skew: f32, + min: f32, + max: f32, +} + +#[derive(Debug, Clone, Deserialize)] +pub struct Decay { + lambda: f32, +} + +#[derive(Debug, Clone, Deserialize)] +pub struct Performance { + latency: Distribution, + loss: Probability, +} + +#[derive(Debug, Clone, Deserialize)] +pub enum Translation { + None, + PortRestricted, + AddressRestricted, + Symmetric, +} + +#[derive(Debug, Clone, Deserialize)] +pub struct Config { + seed: Option, + profiles: HashMap, + machines: HashMap, + networks: HashMap, +} + +impl Config { + pub fn new(config_file: Option<&OsStr>) -> Result { + // Load the default config + let mut cfg = load_default_config()?; + + // Merge in the config file if we have one + if let Some(config_file) = config_file { + let config_file_path = Path::new(config_file); + // If the user specifies a config file on the command line then it must exist + cfg = load_config(cfg, config_file_path)?; + } + + // Generate config + cfg.try_deserialize() + } +} diff --git a/veilid-tools/src/virtual_network/router_server/default_config.yml b/veilid-tools/src/virtual_network/router_server/default_config.yml new file mode 100644 index 00000000..cd312876 --- /dev/null +++ b/veilid-tools/src/virtual_network/router_server/default_config.yml @@ -0,0 +1,43 @@ +--- +seed: 0 +profiles: + - default: + - bootstrap: + - server: +machines: + - bootstrap-1.veilid.net: + network: "boot" + address4: "170.64.128.16" + address6: "2a03:b0c0:2:dd::ddd:0010" + disable_capabilities: + ["ROUT", "TUNL", "SGNL", "RLAY", "DIAL", "DHTV", "DHTW", "APPM"] + bootstrap: true + - bootstrap-2.veilid.net: + network: "boot" + address4: "170.64.128.17" + address6: "2a03:b0c0:2:dd::ddd:0011" + disable_capabilities: + ["ROUT", "TUNL", "SGNL", "RLAY", "DIAL", "DHTV", "DHTW", "APPM"] + bootstrap: true +networks: + - internet: + distance: + min: 0.04 + max: 2.0 + latency: + mean: 0.200 + sigma: 0.080 + skew: 0 + min: 0.030 + max: 0.400 + loss: 0.01 + - boot: + subnet4: ["170.64.128.0/24"] + subnet6: ["2a03:b0c0:2::/48"] + latency: + mean: 0.0038 + sigma: 0.001416 + skew: 0.0009 + min: 0.0015 + max: 0.0075 + loss: 0.0 diff --git a/veilid-tools/src/virtual_network/router_server/mod.rs b/veilid-tools/src/virtual_network/router_server/mod.rs index 130a698a..fa846a74 100644 --- a/veilid-tools/src/virtual_network/router_server/mod.rs +++ b/veilid-tools/src/virtual_network/router_server/mod.rs @@ -1,3 +1,5 @@ +mod config; + use super::*; use async_tungstenite::accept_async; use futures_codec::{Bytes, BytesCodec, FramedRead, FramedWrite}; @@ -139,7 +141,7 @@ impl RouterServer { .new_client_sender .send(inbound_receiver_fut) { - // Error register connection processor + // Error registering connection processor error!("{}", e); break; } @@ -201,7 +203,7 @@ impl RouterServer { .new_client_sender .send(inbound_receiver_fut) { - // Error register connection processor + // Error registering connection processor error!("{}", e); break; } @@ -310,10 +312,112 @@ impl RouterServer { self, cmd: ServerProcessorCommand, outbound_sender: flume::Sender, + ) -> RouterServerResult<()> { + match cmd { + ServerProcessorCommand::Message(server_processor_message) => { + self.process_message( + server_processor_message.message_id, + server_processor_message.request, + outbound_sender, + ) + .await + } + ServerProcessorCommand::CloseSocket { + machine_id, + socket_id, + } => { + self.process_close_socket(machine_id, socket_id, outbound_sender) + .await + } + } + } + async fn process_close_socket( + self, + machine_id: MachineId, + socket_id: SocketId, + outbound_sender: flume::Sender, ) -> RouterServerResult<()> { // Ok(()) } + + async fn process_message( + self, + message_id: MessageId, + request: ServerProcessorRequest, + outbound_sender: flume::Sender, + ) -> RouterServerResult<()> { + match request { + ServerProcessorRequest::AllocateMachine { profile } => todo!(), + ServerProcessorRequest::ReleaseMachine { machine_id } => todo!(), + ServerProcessorRequest::GetInterfaces { machine_id } => todo!(), + ServerProcessorRequest::TcpConnect { + machine_id, + local_address, + remote_address, + timeout_ms, + options, + } => todo!(), + ServerProcessorRequest::TcpBind { + machine_id, + local_address, + options, + } => todo!(), + ServerProcessorRequest::TcpAccept { + machine_id, + listen_socket_id, + } => todo!(), + ServerProcessorRequest::TcpShutdown { + machine_id, + socket_id, + } => todo!(), + ServerProcessorRequest::UdpBind { + machine_id, + local_address, + options, + } => todo!(), + ServerProcessorRequest::Send { + machine_id, + socket_id, + data, + } => todo!(), + ServerProcessorRequest::SendTo { + machine_id, + socket_id, + remote_address, + data, + } => todo!(), + ServerProcessorRequest::Recv { + machine_id, + socket_id, + len, + } => todo!(), + ServerProcessorRequest::RecvFrom { + machine_id, + socket_id, + len, + } => todo!(), + ServerProcessorRequest::GetRoutedLocalAddress { + machine_id, + address_type, + } => todo!(), + ServerProcessorRequest::FindGateway { machine_id } => todo!(), + ServerProcessorRequest::GetExternalAddress { gateway_id } => todo!(), + ServerProcessorRequest::AddPort { + gateway_id, + protocol, + external_port, + local_address, + lease_duration_ms, + description, + } => todo!(), + ServerProcessorRequest::RemovePort { + gateway_id, + protocol, + external_port, + } => todo!(), + } + } } impl Default for RouterServer {