From 83d691d591613d69a926e4f882d12d52c601e2f6 Mon Sep 17 00:00:00 2001 From: Niko Date: Tue, 20 Jun 2023 04:06:07 +0300 Subject: [PATCH] ngd config local core public private dynamic save-config key save-key base --- Cargo.lock | 13 +- ngd/Cargo.toml | 3 + ngd/src/cli.rs | 55 ++++- ngd/src/main.rs | 507 +++++++++++++++++++++++++++++++++++++++- ngd/src/types.rs | 4 +- p2p-broker/src/types.rs | 61 ++++- 6 files changed, 619 insertions(+), 24 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 578dfb0..aade76a 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2757,13 +2757,16 @@ dependencies = [ "clap", "default-net", "env_logger", + "lazy_static", "log", "p2p-broker", "p2p-net", "p2p-repo", + "regex", "serde", "serde_bare", "serde_bytes", + "serde_json", "slice_as_array", ] @@ -3618,13 +3621,13 @@ dependencies = [ [[package]] name = "regex" -version = "1.8.1" +version = "1.8.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "af83e617f331cc6ae2da5443c602dfa5af81e517212d9d611a5b3ba1777b5370" +checksum = "d0ab3ca65655bb1e41f2a8c8cd662eb4fb035e67c3f78da1d61dffe89d07300f" dependencies = [ "aho-corasick", "memchr", - "regex-syntax 0.7.1", + "regex-syntax 0.7.2", ] [[package]] @@ -3644,9 +3647,9 @@ checksum = "f162c6dd7b008981e4d40210aca20b4bd0f9b60ca9271061b07f78537722f2e1" [[package]] name = "regex-syntax" -version = "0.7.1" +version = "0.7.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a5996294f19bd3aae0453a862ad728f60e6600695733dd5df01da90c54363a3c" +checksum = "436b050e76ed2903236f032a59761c1eb99e1b0aead2c257922771dab1fc8c78" [[package]] name = "reqwest" diff --git a/ngd/Cargo.toml b/ngd/Cargo.toml index 74ea3aa..88a4b8e 100644 --- a/ngd/Cargo.toml +++ b/ngd/Cargo.toml @@ -21,3 +21,6 @@ env_logger = "0.10" clap = { version = "4.3.4", features = ["derive","env"] } base64-url = "2.0.0" slice_as_array = "1.1.0" +serde_json = "1.0" +regex = "1.8.4" +lazy_static = "1.4.0" \ No newline at end of file diff --git a/ngd/src/cli.rs b/ngd/src/cli.rs index bad8eb3..51d20c3 100644 --- a/ngd/src/cli.rs +++ b/ngd/src/cli.rs @@ -9,6 +9,8 @@ use clap::Parser; +use p2p_net::WS_PORT; + #[derive(Parser, Debug)] #[command(author, version, about, long_about = None)] pub(crate) struct Cli { @@ -21,7 +23,7 @@ pub(crate) struct Cli { pub verbose: u8, /// Base path for server home folder containing all persistent files - #[arg(short, long, default_value = ".ng")] + #[arg(short, long, default_value = ".ng", value_name("PATH"))] pub base: String, /// Master key of the server. Should be a base64-url encoded serde serialization of a [u8; 32]. if not provided, a new key will be generated for you @@ -31,4 +33,55 @@ pub(crate) struct Cli { /// Saves to disk the provided or automatically generated key. Only used if file storage is secure. Alternatives are passing the key at every start with --key or NG_SERVER_KEY env var. #[arg(long)] pub save_key: bool, + + /// Quick config to listen for clients on localhost port PORT. Defaults to port 80 + #[arg(short, long, value_name("PORT"), default_missing_value("80"), num_args(0..=1))] + pub local: Option, + + /// Quick config to listen for core brokers on public INTERFACE (and optional :PORT). Defaults to first public interface on the host, port 80 + #[arg(short, long, value_name("INTERFACE:PORT"), default_missing_value("default"), num_args(0..=1))] + pub core: Option, + + /// Quick config to forward all requests to another BROKER. format is "DOMAIN/IP[:PORT]@PEERID" + #[arg( + short, + long, + value_name("BROKER"), + conflicts_with("core"), + conflicts_with("public"), + conflicts_with("dynamic") + )] + pub forward: Option, + + /// Quick config to listen for clients on private INTERFACE (and optional :PORT). Defaults to first private interface on the host, port 80 + #[arg(short, long, value_name("INTERFACE:PORT"), default_missing_value("default"), num_args(0..=1))] + pub private: Option, + + /// Quick config to listen for clients and core brokers on PRIVATE_INTERFACE, behind a DMZ or port forwarding of a public static IP. PORTs defaults to 80 + #[arg( + short('g'), + long, + value_name("PRIVATE_INTERFACE:PORT,[PUBLIC_IPV6,]PUBLIC_IPV4:PORT") + )] + pub public: Option, + + /// Quick config to listen for clients and core brokers on PRIVATE_INTERFACE, behind a DMZ or port forwarding of a public dynamic IP. PORTs defaults to 80 + #[arg(short('n'), long, value_name("PRIVATE_INTERFACE:PORT,PORT"), default_missing_value("default"), num_args(0..=1))] + pub dynamic: Option, + + /// Quick config to listen for clients on localhost port PORT, behind a reverse proxy that sends X-Forwarded-For for a TLS terminated DOMAIN name + #[arg(short, long, value_name("DOMAIN:PORT"))] + pub domain: Option, + + /// Option for --domain if this host is part of a pool of load-balanced servers behind a reverse proxy, and the same PeerId should be shared among them all + #[arg(short('e'), long, value_name("PEER_KEY"))] + pub domain_peer: Option, + + /// Option for quick config: does not listen on any IPv6 interfaces + #[arg(long)] + pub no_ipv6: bool, + + /// Saves the quick config into a file on disk, that can then be modified for advanced configs + #[arg(long)] + pub save_config: bool, } diff --git a/ngd/src/main.rs b/ngd/src/main.rs index 214c3ba..d7aa854 100644 --- a/ngd/src/main.rs +++ b/ngd/src/main.rs @@ -14,9 +14,12 @@ pub mod types; mod cli; use crate::cli::*; +use crate::types::*; use clap::Parser; use p2p_broker::server_ws::run_server; +use p2p_broker::types::*; use p2p_broker::utils::*; +use p2p_net::types::*; use p2p_net::utils::{gen_keys, keys_from_bytes, Dual25519Keys, Sensitive, U8Array}; use p2p_net::WS_PORT; use p2p_repo::log::*; @@ -24,10 +27,12 @@ use p2p_repo::{ types::{PrivKey, PubKey}, utils::{generate_keypair, keypair_from_ed, sign, verify}, }; +use serde_json::{from_str, json, to_string_pretty}; use std::fs::{read_to_string, write}; use std::io::Read; use std::io::Write; use std::io::{BufReader, ErrorKind}; +use std::net::{IpAddr, Ipv4Addr, Ipv6Addr}; use std::path::{Path, PathBuf}; #[derive(Clone, Debug, PartialEq, Eq)] @@ -46,6 +51,7 @@ pub fn print_ipv6(ip: &default_net::ip::Ipv6Net) -> String { format!("{}/{}", ip.addr, ip.prefix_len) } +#[derive(Clone, Debug)] pub struct Interface { pub if_type: InterfaceType, pub name: String, @@ -56,6 +62,28 @@ pub struct Interface { pub ipv6: Vec, } +fn find_first(list: &Vec, iftype: InterfaceType) -> Option { + for inf in list { + if inf.if_type == iftype { + return Some(inf.clone()); + } + } + None +} + +fn find_first_or_name( + list: &Vec, + iftype: InterfaceType, + name: &String, +) -> Option { + for inf in list { + if (name == "default" || *name == inf.name) && inf.if_type == iftype { + return Some(inf.clone()); + } + } + None +} + pub fn get_interface() -> Vec { let mut res: Vec = vec![]; let interfaces = default_net::get_interfaces(); @@ -127,6 +155,172 @@ fn decode_key(key_string: String) -> Result<[u8; 32], ()> { .map_err(|_| log_err!("key has invalid content array"))?) } +use lazy_static::lazy_static; +use regex::Regex; + +//For windows: {846EE342-7039-11DE-9D20-806E6F6E6963} +//For the other OSes: en0 lo ... +#[cfg(not(target_os = "windows"))] +lazy_static! { + static ref RE_INTERFACE: Regex = Regex::new(r"^([0-9a-z]{2,16})(\:\d{1,5})?$").unwrap(); +} +#[cfg(target_os = "windows")] +lazy_static! { + static ref RE_INTERFACE: Regex = Regex::new( + r"^(\{[0-9a-fA-F]{8}\b-[0-9a-fA-F]{4}\b-[0-9a-fA-F]{4}\b-[0-9a-fA-F]{4}\b-[0-9a-fA-F]{12}\})(\:\d{1,5})?$" + ) + .unwrap(); +} + +lazy_static! { + static ref RE_IPV6_WITH_PORT: Regex = + Regex::new(r"^\[([0-9a-fA-F:]{3,39})\](\:\d{1,5})?$").unwrap(); +} + +pub static DEFAULT_PORT: u16 = 80; + +fn parse_interface_and_port_for(string: String, for_option: &str) -> Result<(String, u16), ()> { + let c = RE_INTERFACE.captures(&string); + + if c.is_some() && c.as_ref().unwrap().get(1).is_some() { + let cap = c.unwrap(); + let interface = cap.get(1).unwrap().as_str(); + let port = match cap.get(2) { + None => DEFAULT_PORT, + Some(p) => { + let mut chars = p.as_str().chars(); + chars.next(); + match from_str::(chars.as_str()) { + Err(_) => DEFAULT_PORT, + Ok(p) => p, + } + } + }; + Ok((interface.to_string(), port)) + } else { + log_err!( + "The value submitted for the {} option is invalid. It should be the name of an interface found with --list-interfaces, with an optional port suffix of the form :123. Stopping here", + for_option + ); + Err(()) + } +} + +fn parse_ipv6_for(string: String, for_option: &str) -> Result { + string.parse::().map_err(|_| ()) +} + +fn parse_ipv4_and_port_for(string: String, for_option: &str) -> Result<(Ipv4Addr, u16), ()> { + let parts: Vec<&str> = string.split(":").collect(); + let ipv4_res = parts[0].parse::(); + + if ipv4_res.is_err() { + log_err!( + "The value submitted for the {} option is invalid. Stopping here", + for_option + ); + return Err(()); + } + let port; + let ipv4 = ipv4_res.unwrap(); + if parts.len() > 1 { + port = match from_str::(parts[1]) { + Err(_) => DEFAULT_PORT, + Ok(p) => p, + }; + } else { + port = DEFAULT_PORT; + } + return Ok((ipv4, port)); +} + +fn parse_ip_and_port_for(string: String, for_option: &str) -> Result<(IpAddr, u16), ()> { + let c = RE_IPV6_WITH_PORT.captures(&string); + let ipv6; + let port; + if c.is_some() && c.as_ref().unwrap().get(1).is_some() { + let cap = c.unwrap(); + let ipv6_str = cap.get(1).unwrap().as_str(); + port = match cap.get(2) { + None => DEFAULT_PORT, + Some(p) => { + let mut chars = p.as_str().chars(); + chars.next(); + match from_str::(chars.as_str()) { + Err(_) => DEFAULT_PORT, + Ok(p) => p, + } + } + }; + let ipv6_res = ipv6_str.parse::(); + if ipv6_res.is_err() { + log_err!( + "The <[IPv6]:PORT> value submitted for the {} option is invalid. Stopping here", + for_option + ); + return Err(()); + } + ipv6 = ipv6_res.unwrap(); + return Ok((IpAddr::V6(ipv6), port)); + } else { + // we try just an IPV6 without port + let ipv6_res = string.parse::(); + if ipv6_res.is_err() { + // let's try IPv4 + + return parse_ipv4_and_port_for(string, for_option) + .map(|ipv4| (IpAddr::V4(ipv4.0), ipv4.1)); + } else { + ipv6 = ipv6_res.unwrap(); + port = DEFAULT_PORT; + return Ok((IpAddr::V6(ipv6), port)); + } + } +} + +fn parse_triple_interface_and_port_for( + string: String, + for_option: &str, +) -> Result<((String, u16), (Option, (Ipv4Addr, u16))), ()> { + let parts: Vec<&str> = string.split(',').collect(); + if parts.len() < 2 { + log_err!( + "The value submitted for the {} option is invalid. It should be composed of at least 2 parts separated by a comma. Stopping here", + for_option + ); + return Err(()); + } + let first_part = parse_interface_and_port_for( + parts[0].to_string(), + &format!("private interface+PORT (left) part of the {}", for_option), + ); + if first_part.is_err() { + return Err(()); + } + + let mut middle_part = None; + if parts.len() == 3 { + let middle_part_res = parse_ipv6_for( + parts[1].to_string(), + &format!("public IPv6 (middle) part of the {}", for_option), + ); + if middle_part_res.is_err() { + return Err(()); + } + middle_part = middle_part_res.ok(); + } + + let last_part = parse_ipv4_and_port_for( + parts[parts.len() - 1].to_string(), + &format!("public IPv4+PORT (right) part of the {}", for_option), + ); + if last_part.is_err() { + return Err(()); + } + + Ok((first_part.unwrap(), (middle_part, last_part.unwrap()))) +} + #[async_std::main] async fn main() -> std::io::Result<()> { let args = Cli::parse(); @@ -188,10 +382,7 @@ async fn main() -> std::io::Result<()> { ); return Err(ErrorKind::InvalidInput.into()); } - key_from_file = match res { - Err(_) => None, - Ok(k) => Some(k), - }; + key_from_file = res.ok(); let keys: [[u8; 32]; 4] = match args.key { Some(key_string) => { @@ -228,7 +419,7 @@ async fn main() -> std::io::Result<()> { } log_info!("The key has been saved to {}", key_path.to_str().unwrap()); } else { - // on purpose we don't log the key, just print it out stdout, as it should be saved in logger's files + // on purpose we don't log the key, just print it out to stdout, as it should not be saved in logger's files println!("YOUR GENERATED KEY IS: {}", master_key); log_err!("At your request, the key wasn't saved."); log_err!("provide it again to the next start of ngd with --key option or NG_SERVER_KEY env variable"); @@ -240,6 +431,312 @@ async fn main() -> std::io::Result<()> { println!("{:?}", keys); + // DEALING WITH CONFIG + + // reading config from file, if any + let mut config_path = path.clone(); + config_path.push("config.json"); + let mut config: Option; + let res = |config_path| -> Result { + let file = read_to_string(config_path).map_err(|_| "".to_string())?; + from_str(&file).map_err(|e| e.to_string()) + }(&config_path); + + if res.is_err() && res.as_ref().unwrap_err().len() > 0 { + log_err!( + "provided config file is incorrect. {}. aborting start", + res.unwrap_err() + ); + return Err(ErrorKind::InvalidInput.into()); + } + config = res.ok(); + + println!("CONFIG {:?}", config); + + if config.is_some() && args.save_config { + log_err!("A config file is present. We cannot override it with Quick config options."); + return Err(ErrorKind::InvalidInput.into()); + } + + if args.local.is_some() + || args.forward.is_some() + || args.core.is_some() + || args.private.is_some() + || args.public.is_some() + || args.dynamic.is_some() + || args.domain.is_some() + { + // QUICK CONFIG + + if config.is_some() { + log_err!( + "A config file is present. You can use the Quick config options of the command-line. In order to use them, delete your config file first." + ); + return Err(ErrorKind::InvalidInput.into()); + } + + if args.domain_peer.is_some() && args.domain.is_none() { + log_err!( + "The --domain-peer option can only be set when the --domain option is also present on the command line" + ); + return Err(ErrorKind::InvalidInput.into()); + } + + let mut listeners: Vec = vec![]; + let mut overlays_config: BrokerOverlayConfigV0 = BrokerOverlayConfigV0::new(); + + let interfaces = get_interface(); + + //// --local + + if args.local.is_some() { + match find_first(&interfaces, InterfaceType::Loopback) { + None => { + log_err!( + "That's pretty unusual, but no loopback interface could be found on your host" + ); + return Err(ErrorKind::InvalidInput.into()); + } + Some(loopback) => { + overlays_config.server = BrokerOverlayPermission::AllRegisteredUser; + listeners.push(ListenerV0::new_direct( + loopback.name, + !args.no_ipv6, + args.local.unwrap(), + )); + } + } + } + + //// --core + + if args.core.is_some() { + let arg_value = parse_interface_and_port_for(args.core.unwrap(), "--core"); + if arg_value.is_err() { + return Err(ErrorKind::InvalidInput.into()); + } + + let if_name = &arg_value.as_ref().unwrap().0; + match find_first_or_name(&interfaces, InterfaceType::Public, &if_name) { + None => { + log_err!( + "{}", + if if_name == "default" { + "We could not find a public IP interface on your host. If you are setting up a server behind a reverse proxy, enter the config manually in the config file".to_string() + } else { + format!( + "We could not find a public IP interface named {} on your host. use --list-interfaces to find the available interfaces on your host", + if_name + ) + } + ); + return Err(ErrorKind::InvalidInput.into()); + } + Some(public) => { + overlays_config.core = BrokerOverlayPermission::AllRegisteredUser; + overlays_config.server = BrokerOverlayPermission::AllRegisteredUser; + listeners.push(ListenerV0::new_direct( + public.name, + !args.no_ipv6, + arg_value.unwrap().1, + )); + } + } + } + + //// --public + + if args.public.is_some() { + let arg_value = parse_triple_interface_and_port_for(args.public.unwrap(), "--public"); + if arg_value.is_err() { + return Err(ErrorKind::InvalidInput.into()); + } + let public_part = &arg_value.as_ref().unwrap().1; + let private_part = &arg_value.as_ref().unwrap().0; + let private_interface; + let if_name = &private_part.0; + match find_first_or_name(&interfaces, InterfaceType::Private, &if_name) { + None => { + log_err!( "We could not find a private IP interface named {} on your host. use --list-interfaces to find the available interfaces on your host", + if_name + ); + return Err(ErrorKind::InvalidInput.into()); + } + Some(inter) => { + private_interface = inter; + } + } + + if args.no_ipv6 && public_part.0.is_some() { + log_err!("The public IP is IPv6 but you selected the --no-ipv6 option"); + return Err(ErrorKind::InvalidInput.into()); + } + + overlays_config.core = BrokerOverlayPermission::AllRegisteredUser; + overlays_config.server = BrokerOverlayPermission::AllRegisteredUser; + + let ipv6 = public_part.0.map(|ipv6| BindAddress { + port: public_part.1 .1, + ip: (&IpAddr::V6(ipv6)).into(), + }); + + listeners.push(ListenerV0 { + interface_name: private_interface.name, + ipv6: public_part.0.is_some(), + interface_refresh: 0, + port: private_part.1, + discoverable: false, + accept_direct: false, + accept_forward_for: AcceptForwardForV0::PublicStatic(( + BindAddress { + port: public_part.1 .1, + ip: (&IpAddr::V4(public_part.1 .0)).into(), + }, + ipv6, + "".to_string(), + )), + }); + } + + //// --private + + if args.private.is_some() { + let arg_value = parse_interface_and_port_for(args.private.unwrap(), "--private"); + if arg_value.is_err() { + return Err(ErrorKind::InvalidInput.into()); + } + + let if_name = &arg_value.as_ref().unwrap().0; + match find_first_or_name(&interfaces, InterfaceType::Private, &if_name) { + None => { + log_err!( + "{}", + if if_name == "default" { + "We could not find a private IP interface on your host.".to_string() + } else { + format!( + "We could not find a private IP interface named {} on your host. use --list-interfaces to find the available interfaces on your host", + if_name + ) + } + ); + return Err(ErrorKind::InvalidInput.into()); + } + Some(inter) => { + overlays_config.server = BrokerOverlayPermission::AllRegisteredUser; + + if listeners.last().is_some() + && listeners.last().unwrap().interface_name == inter.name + && listeners.last().unwrap().port == arg_value.as_ref().unwrap().1 + { + let r = listeners.last_mut().unwrap(); + r.accept_direct = true; + r.ipv6 = !args.no_ipv6; + } else { + listeners.push(ListenerV0::new_direct( + inter.name, + !args.no_ipv6, + arg_value.unwrap().1, + )); + } + } + } + } + + //// --dynamic + + if args.dynamic.is_some() { + let dynamic_string = args.dynamic.unwrap(); + let parts: Vec<&str> = dynamic_string.split(',').collect(); + + let arg_value = parse_interface_and_port_for(parts[0].to_string(), "--dynamic"); + if arg_value.is_err() { + return Err(ErrorKind::InvalidInput.into()); + } + + let public_port = if parts.len() == 2 { + match from_str::(parts[1]) { + Err(_) => DEFAULT_PORT, + Ok(p) => p, + } + } else { + DEFAULT_PORT + }; + + let if_name = &arg_value.as_ref().unwrap().0; + + match find_first_or_name(&interfaces, InterfaceType::Private, if_name) { + None => { + log_err!( + "{}", + if if_name == "default" { + "We could not find a private IP interface on your host.".to_string() + } else { + format!( + "We could not find a private IP interface named {} on your host. use --list-interfaces to find the available interfaces on your host", + if_name + ) + } + ); + return Err(ErrorKind::InvalidInput.into()); + } + Some(inter) => { + overlays_config.core = BrokerOverlayPermission::AllRegisteredUser; + overlays_config.server = BrokerOverlayPermission::AllRegisteredUser; + + if listeners.last().is_some() + && listeners.last().unwrap().interface_name == inter.name + && listeners.last().unwrap().port == arg_value.as_ref().unwrap().1 + { + let r = listeners.last_mut().unwrap(); + r.ipv6 = !args.no_ipv6; + if r.accept_forward_for != AcceptForwardForV0::No { + log_err!("The same private interface is already forwarding with a different setting, probably because of a --public option. Aborting"); + return Err(ErrorKind::InvalidInput.into()); + } + r.accept_forward_for = + AcceptForwardForV0::PublicDyn((public_port, 60, "".to_string())); + } else { + let mut listener = + ListenerV0::new_direct(inter.name, !args.no_ipv6, arg_value.unwrap().1); + listener.accept_direct = false; + listener.accept_forward_for = + AcceptForwardForV0::PublicDyn((public_port, 60, "".to_string())); + listeners.push(listener); + } + } + } + } + + config = Some(DaemonConfig::V0(DaemonConfigV0 { + listeners, + overlays_config, + })); + + if args.save_config { + // saves the config to file + let json_string = to_string_pretty(config.as_ref().unwrap()).unwrap(); + if let Err(e) = write(config_path.clone(), json_string) { + log_err!("cannot save config to file. aborting start"); + return Err(e); + } + log_info!( + "The config file has been saved to {}", + config_path.to_str().unwrap() + ); + log_info!( + "You cannot use Quick config options anymore on the command line in your next start of the server. But you can go to modify the config file directly, or delete it.", + ); + } + } else { + if config.is_none() { + log_err!( + "No Quick config option passed, neither is a config file present. We cannot start the server. Choose at least one Quick config option. see --help for details" + ); + return Err(ErrorKind::InvalidInput.into()); + } + } + // let keys = gen_keys(); // let pub_key = PubKey::Ed25519PubKey(keys.1); // let (ed_priv_key, ed_pub_key) = generate_keypair(); diff --git a/ngd/src/types.rs b/ngd/src/types.rs index 57ca3a7..9a03884 100644 --- a/ngd/src/types.rs +++ b/ngd/src/types.rs @@ -6,7 +6,7 @@ // at your option. All files in the project carrying such // notice may not be copied, modified, or distributed except // according to those terms. -use p2p_broker::types::BrokerOverlayConfig; +use p2p_broker::types::BrokerOverlayConfigV0; use p2p_broker::types::ListenerV0; use p2p_repo::types::PrivKey; use serde::{Deserialize, Serialize}; @@ -17,7 +17,7 @@ pub struct DaemonConfigV0 { /// List of listeners for TCP (HTTP) incoming connections pub listeners: Vec, - pub overlays_config: BrokerOverlayConfig, + pub overlays_config: BrokerOverlayConfigV0, } /// Daemon config diff --git a/p2p-broker/src/types.rs b/p2p-broker/src/types.rs index 73fdb5f..44353c7 100644 --- a/p2p-broker/src/types.rs +++ b/p2p-broker/src/types.rs @@ -18,18 +18,28 @@ use serde::{Deserialize, Serialize}; pub enum AcceptForwardForV0 { /// X-Forwarded-For not allowed No, - /// X-Forwarded-For accepted only for clients with private LAN addresses. First param is the bind address of the proxy server - Private((BindAddress, String)), + + /// X-Forwarded-For accepted only for clients with private LAN addresses. First param is the domain of the proxy server + Private((String, String)), + /// X-Forwarded-For accepted only for clients with public addresses. First param is the domain of the proxy server - /// domain can take an option port with a trailing `:port` + /// domain can take an option port (trailing `:port`) PublicDomain((String, String)), + /// X-Forwarded-For accepted only for clients with public addresses. First param is the domain of the proxy server - /// domain can take an option port with a trailing `:port` + /// domain can take an option port (trailing `:port`) /// second param is the privKey of the PeerId of the proxy server, useful when the proxy server is load balancing to several daemons /// that should all use the same PeerId to answer requests PublicDomainPeer((String, PrivKey, String)), - PublicDyn((u16, u32, String)), // first param is the port, second param in tuple is the interval for periodic probe of the external IP - PublicStatic((BindAddress, String)), + + /// accepts only clients with public addresses that arrive on a LAN address binding. This is used for DMZ and port forwarding configs + /// first param is the port, second param in tuple is the interval for periodic probe of the external IP + PublicDyn((u16, u32, String)), + + /// accepts only clients with public addresses that arrive on a LAN address binding. This is used for DMZ and port forwarding configs + /// First param is the IPv4 bind address of the reverse NAT server (DMZ, port forwarding) + /// Second param is ab optional IPv6 bind address of the reverse NAT server (DMZ, port forwarding) + PublicStatic((BindAddress, Option, String)), } /// DaemonConfig Listener Version 0 @@ -37,10 +47,12 @@ pub enum AcceptForwardForV0 { pub struct ListenerV0 { /// local interface name to bind to /// names of interfaces can be retrieved with the --list-interfaces option - /// the string can take an optional trailing option of the form `:3600` for number of seconds - /// for an interval periodic refresh of the actual IP(s) of the interface. Used for dynamic IP interfaces. pub interface_name: String, + /// optional number of seconds for an interval of periodic refresh + /// of the actual IP(s) of the interface. Used for dynamic IP interfaces (DHCP) + pub interface_refresh: u32, + // if to bind to the ipv6 address of the interface pub ipv6: bool, @@ -63,6 +75,20 @@ pub struct ListenerV0 { // an interface with no accept_forward_for and no accept_direct, is de facto, disabled } +impl ListenerV0 { + pub fn new_direct(name: String, ipv6: bool, port: u16) -> Self { + Self { + interface_name: name, + interface_refresh: 0, + ipv6, + port, + discoverable: false, + accept_direct: true, + accept_forward_for: AcceptForwardForV0::No, + } + } +} + /// Broker Overlay Permission #[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq)] pub enum BrokerOverlayPermission { @@ -74,10 +100,10 @@ pub enum BrokerOverlayPermission { /// Broker Overlay Config #[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq)] -pub struct BrokerOverlayConfig { +pub struct BrokerOverlayConfigV0 { // list of overlays this config applies to. empty array means applying to all pub overlays: Vec, - // Who can ask to join an overlay on the core protocol + // Who can ask to join an overlay on the core pub core: BrokerOverlayPermission, // Who can connect as a client to this server pub server: BrokerOverlayPermission, @@ -87,6 +113,19 @@ pub struct BrokerOverlayConfig { pub allow_read: bool, /// an empty list means to forward to the peer known for each overlay. - /// forward becomes the default when core is disabled + /// forward and core are mutually exclusive. forward becomes the default when core is disabled (set to Nobody). + /// core always takes precedence. pub forward: Vec, } + +impl BrokerOverlayConfigV0 { + pub fn new() -> Self { + BrokerOverlayConfigV0 { + overlays: vec![], + core: BrokerOverlayPermission::Nobody, + server: BrokerOverlayPermission::Nobody, + allow_read: false, + forward: vec![], + } + } +}