From a491f7543a3fb97a908336ba9ba0956e80c8ba6f Mon Sep 17 00:00:00 2001 From: Jedrzej Stuczynski Date: Mon, 3 Feb 2020 17:20:29 +0000 Subject: [PATCH] More builder methods, incl. announce host/port parsers --- Cargo.lock | 2 + mixnode/Cargo.toml | 2 + mixnode/src/commands/init.rs | 90 ++++++++++++++++++++++++++--- mixnode/src/config/mod.rs | 106 ++++++++++++++++++++++++++++++++++- 4 files changed, 188 insertions(+), 12 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 9ec38d5c16..e21e731b3f 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1449,12 +1449,14 @@ dependencies = [ "built", "clap", "config", + "crypto", "curve25519-dalek", "directory-client", "dirs", "dotenv", "futures 0.3.1", "log", + "pemstore", "pretty_env_logger", "serde", "sphinx", diff --git a/mixnode/Cargo.toml b/mixnode/Cargo.toml index 26ccefad3d..f583498830 100644 --- a/mixnode/Cargo.toml +++ b/mixnode/Cargo.toml @@ -22,7 +22,9 @@ tokio = { version = "0.2", features = ["full"] } ## internal addressing = {path = "../common/addressing" } config = {path = "../common/config"} +crypto = {path = "../common/crypto"} directory-client = { path = "../common/clients/directory-client" } +pemstore = {path = "../common/pemstore"} ## will be moved to proper dependencies once released sphinx = { git = "https://github.com/nymtech/sphinx", rev="5862939c52e4dd76f836856a01787faec32152b4" } diff --git a/mixnode/src/commands/init.rs b/mixnode/src/commands/init.rs index 6b42e22025..ccbd1e0251 100644 --- a/mixnode/src/commands/init.rs +++ b/mixnode/src/commands/init.rs @@ -2,23 +2,95 @@ use clap::{App, Arg, ArgMatches}; use config::NymConfig; pub fn command_args<'a, 'b>() -> clap::App<'a, 'b> { - App::new("init").about("Initialise the mixnode").arg( - Arg::with_name("id") - .long("id") - .help("Id of the nym-mixnode we want to create config for.") - .takes_value(true) - .required(true), - ) + App::new("init") + .about("Initialise the mixnode") + .arg( + Arg::with_name("id") + .long("id") + .help("Id of the nym-mixnode we want to create config for.") + .takes_value(true) + .required(true), + ) + .arg( + Arg::with_name("layer") + .long("layer") + .help("The mixnet layer of this particular node") + .takes_value(true) + .required(true), + ) + .arg( + Arg::with_name("host") + .long("host") + .help("The custom host on which the mixnode will be running") + .takes_value(true) + .required(true), + ) + .arg( + Arg::with_name("port") + .long("port") + .help("The port on which the mixnode will be listening") + .takes_value(true), + ) + .arg( + Arg::with_name("announce-host") + .long("announce-host") + .help("The host that will be reported to the directory server") + .takes_value(true), + ) + .arg( + Arg::with_name("announce-port") + .long("announce-port") + .help("The port that will be reported to the directory server") + .takes_value(true), + ) + .arg( + Arg::with_name("directory") + .long("directory") + .help("Address of the directory server the node is sending presence and metrics to") + .takes_value(true), + ) } pub fn execute(matches: &ArgMatches) { println!("Initialising mixnode..."); - let id = matches.value_of("id").unwrap(); // required for now - let mut config = crate::config::Config::new(id); + let id = matches.value_of("id").unwrap(); + let layer = matches.value_of("layer").unwrap().parse().unwrap(); + let mut config = crate::config::Config::new(id, layer); // overriding config defaults here + if let Some(host) = matches.value_of("host") { + config = config.with_listening_host(host); + } + + if let Some(port) = matches.value_of("port").map(|port| port.parse::()) { + if let Err(err) = port { + // if port was overridden, it must be parsable + panic!("Invalid port value provided - {:?}", err); + } + config = config.with_listening_port(port.unwrap()); + } + + if let Some(directory) = matches.value_of("directory") { + config = config.with_custom_directory(directory); + } + + if let Some(announce_host) = matches.value_of("announce-host") { + config = config.with_announce_host(announce_host); + } + + if let Some(announce_port) = matches + .value_of("announce-port") + .map(|port| port.parse::()) + { + if let Err(err) = announce_port { + // if port was overridden, it must be parsable + panic!("Invalid port value provided - {:?}", err); + } + config = config.with_announce_port(announce_port.unwrap()); + } + let config_save_location = config.get_config_file_save_location(); config .save_to_file(None) diff --git a/mixnode/src/config/mod.rs b/mixnode/src/config/mod.rs index 21d5234ce2..4c5b760e30 100644 --- a/mixnode/src/config/mod.rs +++ b/mixnode/src/config/mod.rs @@ -1,7 +1,10 @@ use crate::config::template::config_template; use config::NymConfig; -use serde::{Deserialize, Deserializer, Serialize}; +use log::*; +use serde::{Deserialize, Serialize}; +use std::net::{IpAddr, SocketAddr}; use std::path::PathBuf; +use std::str::FromStr; mod template; @@ -52,8 +55,8 @@ impl NymConfig for Config { } impl Config { - pub fn new>(id: S) -> Self { - Config::default().with_id(id) + pub fn new>(id: S, layer: usize) -> Self { + Config::default().with_id(id).with_layer(layer) } // builder methods @@ -76,6 +79,86 @@ impl Config { self } + pub fn with_layer(mut self, layer: usize) -> Self { + self.mixnode.layer = layer; + self + } + + pub fn with_custom_directory>(mut self, directory_server: S) -> Self { + self.mixnode.directory_server = directory_server.into(); + self + } + + pub fn with_listening_host>(mut self, host: S) -> Self { + // see if the provided `host` is just an ip address or ip:port + let host = host.into(); + + // is it ip:port? + match SocketAddr::from_str(host.as_ref()) { + Ok(socket_addr) => { + self.mixnode.listening_address = socket_addr; + self + } + // try just for ip + Err(_) => match IpAddr::from_str(host.as_ref()) { + Ok(ip_addr) => { + self.mixnode.listening_address.set_ip(ip_addr); + self + } + Err(_) => { + error!( + "failed to make any changes to config - invalid host {}", + host + ); + self + } + }, + } + } + + pub fn with_listening_port(mut self, port: u16) -> Self { + self.mixnode.listening_address.set_port(port); + self + } + + pub fn with_announce_host>(mut self, host: S) -> Self { + // this is slightly more complicated as we store announce information as String, + // since it might not necessarily be a valid SocketAddr (say `nymtech.net:8080` is a valid + // announce address, yet invalid SocketAddr` + + // first lets see if we received host:port or just host part of an address + let host = host.into(); + let split_host: Vec<_> = host.split(':').collect(); + match split_host.len() { + 1 => { + // we provided only 'host' part so we are going to reuse existing port + self.mixnode.announce_address = + format!("{}:{}", host, self.mixnode.listening_address.port()); + self + } + 2 => { + // we provided 'host:port' so just put the whole thing there + self.mixnode.announce_address = host; + self + } + _ => { + // we provided something completely invalid, so don't try to parse it + error!( + "failed to make any changes to config - invalid announce host {}", + host + ); + self + } + } + } + + pub fn with_announce_port(mut self, port: u16) -> Self { + let current_host: Vec<_> = self.mixnode.announce_address.split(':').collect(); + debug_assert_eq!(current_host.len(), 2); + self.mixnode.announce_address = format!("{}:{}", current_host[0], port); + self + } + // getters pub fn get_config_file_save_location(&self) -> PathBuf { self.config_directory().join(Self::config_file_name()) @@ -103,6 +186,20 @@ pub struct MixNode { /// URL to the directory server. directory_server: String, + /// Layer of this particular mixnode determining its position in the network. + layer: usize, + + /// Socket address to which this mixnode will bind to and will be listening for packets. + listening_address: SocketAddr, + + /// Optional address announced to the directory server for the clients to connect to. + /// It is useful, say, in NAT scenarios or wanting to more easily update actual IP address + /// later on by using name resolvable with a DNS query, such as `nymtech.net:8080`. + /// Additionally a custom port can be provided, so both `nymtech.net:8080` and `nymtech.net` + /// are valid announce addresses, while the later will default to whatever port is used for + /// `listening_address`. + announce_address: String, + /// Path to file containing private identity key. private_identity_key_file: PathBuf, @@ -138,6 +235,9 @@ impl Default for MixNode { MixNode { id: "".to_string(), directory_server: Self::default_directory_server(), + layer: 0, + listening_address: "0.0.0.0:1789".parse().unwrap(), + announce_address: "127.0.0.1789".to_string(), private_identity_key_file: Default::default(), public_identity_key_file: Default::default(), nym_root_directory: Config::default_root_directory(),