From 993d3d10a16f3f3c7181ec456ca4c176f5e85ff7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C4=99drzej=20Stuczy=C5=84ski?= Date: Thu, 27 Aug 2020 16:28:53 +0100 Subject: [PATCH] Feature/socks client config (#316) * Made base client_core config * Updated socks5 config with mandatory provider field --- Cargo.lock | 29 ++- clients/client-core/src/config/mod.rs | 199 +++++------------- .../src/config/persistence/key_pathfinder.rs | 3 +- clients/native/Cargo.toml | 2 +- clients/native/src/client/config/mod.rs | 157 ++++++++++++++ clients/native/src/client/config/template.rs | 102 +++++++++ clients/native/src/client/mod.rs | 42 ++-- clients/native/src/commands/init.rs | 28 +-- clients/native/src/commands/mod.rs | 6 +- clients/native/src/commands/run.rs | 2 +- clients/socks5/Cargo.toml | 5 + clients/socks5/src/client/config/mod.rs | 154 ++++++++++++++ clients/socks5/src/client/config/template.rs | 101 +++++++++ clients/socks5/src/client/mod.rs | 143 +++---------- clients/socks5/src/commands/init.rs | 42 ++-- clients/socks5/src/commands/mod.rs | 10 +- clients/socks5/src/commands/run.rs | 13 +- clients/socks5/src/socks/server.rs | 4 +- 18 files changed, 701 insertions(+), 341 deletions(-) create mode 100644 clients/native/src/client/config/mod.rs create mode 100644 clients/native/src/client/config/template.rs create mode 100644 clients/socks5/src/client/config/mod.rs create mode 100644 clients/socks5/src/client/config/template.rs diff --git a/Cargo.lock b/Cargo.lock index da9bc5ab8b..9dc886193f 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -443,7 +443,7 @@ dependencies = [ "config", "crypto", "directory-client", - "dirs", + "dirs 2.0.2", "futures 0.3.4", "gateway-client", "gateway-requests", @@ -721,12 +721,20 @@ dependencies = [ ] [[package]] -name = "dirs-sys" -version = "0.3.4" +name = "dirs" +version = "3.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "afa0b23de8fd801745c471deffa6e12d248f962c9fd4b4c33787b055599bde7b" +checksum = "142995ed02755914747cc6ca76fc7e4583cd18578746716d0508ea6ed558b9ff" +dependencies = [ + "dirs-sys", +] + +[[package]] +name = "dirs-sys" +version = "0.3.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8e93d7f5705de3e49895a2b5e0b8855a1c27f080192ae9c32a6432d50741a57a" dependencies = [ - "cfg-if", "libc", "redox_users", "winapi 0.3.8", @@ -1639,7 +1647,7 @@ dependencies = [ "config", "crypto", "directory-client", - "dirs", + "dirs 3.0.1", "dotenv", "futures 0.3.4", "gateway-client", @@ -1687,7 +1695,7 @@ dependencies = [ "config", "crypto", "directory-client", - "dirs", + "dirs 2.0.2", "dotenv", "futures 0.3.4", "gateway-requests", @@ -1717,7 +1725,7 @@ dependencies = [ "crypto", "curve25519-dalek", "directory-client", - "dirs", + "dirs 2.0.2", "dotenv", "futures 0.3.4", "log 0.4.8", @@ -1742,7 +1750,7 @@ dependencies = [ "byteorder", "clap", "config", - "dirs", + "dirs 2.0.2", "dotenv", "futures 0.3.4", "iron", @@ -2856,6 +2864,7 @@ dependencies = [ "config", "crypto", "directory-client", + "dirs 3.0.1", "dotenv", "futures 0.3.4", "gateway-client", @@ -2865,8 +2874,10 @@ dependencies = [ "pin-project", "pretty_env_logger", "rand 0.7.3", + "serde", "simple-socks5-requests", "snafu", + "tempfile", "tokio 0.2.22", "topology", "url 2.1.1", diff --git a/clients/client-core/src/config/mod.rs b/clients/client-core/src/config/mod.rs index 3a0a9b2e43..54652d14cf 100644 --- a/clients/client-core/src/config/mod.rs +++ b/clients/client-core/src/config/mod.rs @@ -12,17 +12,15 @@ // See the License for the specific language governing permissions and // limitations under the License. -use crate::config::template::config_template; use config::NymConfig; use serde::{Deserialize, Serialize}; +use std::marker::PhantomData; use std::path::PathBuf; use std::time; pub mod persistence; -mod template; // 'CLIENT' -const DEFAULT_LISTENING_PORT: u16 = 1977; const DEFAULT_DIRECTORY_SERVER: &str = "https://directory.nymtech.net"; // 'DEBUG' // where applicable, the below are defined in milliseconds @@ -40,29 +38,10 @@ const DEFAULT_TOPOLOGY_RESOLUTION_TIMEOUT: u64 = 5_000; // 5s const DEFAULT_GATEWAY_RESPONSE_TIMEOUT: u64 = 1_500; // 1.5s -#[derive(Debug, Deserialize, PartialEq, Serialize, Clone, Copy)] +#[derive(Debug, Deserialize, PartialEq, Serialize)] #[serde(deny_unknown_fields)] -pub enum SocketType { - WebSocket, - None, -} - -impl SocketType { - pub fn from_string>(val: S) -> Self { - let mut upper = val.into(); - upper.make_ascii_uppercase(); - match upper.as_ref() { - "WEBSOCKET" | "WS" => SocketType::WebSocket, - _ => SocketType::None, - } - } -} - -#[derive(Debug, Default, Deserialize, PartialEq, Serialize)] -#[serde(deny_unknown_fields)] -pub struct Config { - client: Client, - socket: Socket, +pub struct Config { + client: Client, #[serde(default)] logging: Logging, @@ -70,58 +49,24 @@ pub struct Config { debug: Debug, } -impl NymConfig for Config { - fn template() -> &'static str { - config_template() - } - - fn config_file_name() -> String { - "config.toml".to_string() - } - - fn default_root_directory() -> PathBuf { - dirs::home_dir() - .expect("Failed to evaluate $HOME value") - .join(".nym") - .join("clients") - } - - fn root_directory(&self) -> PathBuf { - self.client.nym_root_directory.clone() - } - - fn config_directory(&self) -> PathBuf { - self.client - .nym_root_directory - .join(&self.client.id) - .join("config") - } - - fn data_directory(&self) -> PathBuf { - self.client - .nym_root_directory - .join(&self.client.id) - .join("data") - } -} - -impl Config { +impl Config { pub fn new>(id: S) -> Self { - Config::default().with_id(id) + let mut cfg = Config::default(); + cfg.with_id(id); + cfg } - // builder methods - pub fn with_id>(mut self, id: S) -> Self { + pub fn with_id>(&mut self, id: S) { let id = id.into(); // identity key setting if self.client.private_identity_key_file.as_os_str().is_empty() { self.client.private_identity_key_file = - self::Client::default_private_identity_key_file(&id); + self::Client::::default_private_identity_key_file(&id); } if self.client.public_identity_key_file.as_os_str().is_empty() { self.client.public_identity_key_file = - self::Client::default_public_identity_key_file(&id); + self::Client::::default_public_identity_key_file(&id); } // encryption key setting @@ -132,7 +77,7 @@ impl Config { .is_empty() { self.client.private_encryption_key_file = - self::Client::default_private_encryption_key_file(&id); + self::Client::::default_private_encryption_key_file(&id); } if self .client @@ -141,18 +86,18 @@ impl Config { .is_empty() { self.client.public_encryption_key_file = - self::Client::default_public_encryption_key_file(&id); + self::Client::::default_public_encryption_key_file(&id); } // shared gateway key setting if self.client.gateway_shared_key_file.as_os_str().is_empty() { self.client.gateway_shared_key_file = - self::Client::default_gateway_shared_key_file(&id); + self::Client::::default_gateway_shared_key_file(&id); } // ack key setting if self.client.ack_key_file.as_os_str().is_empty() { - self.client.ack_key_file = self::Client::default_ack_key_file(&id); + self.client.ack_key_file = self::Client::::default_ack_key_file(&id); } if self @@ -162,48 +107,36 @@ impl Config { .is_empty() { self.client.reply_encryption_key_store_path = - self::Client::default_reply_encryption_key_store_path(&id); + self::Client::::default_reply_encryption_key_store_path(&id); } self.client.id = id; - self } - pub fn with_gateway_id>(mut self, id: S) -> Self { + pub fn with_gateway_id>(&mut self, id: S) { self.client.gateway_id = id.into(); - self } - pub fn with_gateway_listener>(mut self, gateway_listener: S) -> Self { + pub fn with_gateway_listener>(&mut self, gateway_listener: S) { self.client.gateway_listener = gateway_listener.into(); - self } - pub fn with_custom_directory>(mut self, directory_server: S) -> Self { + pub fn with_custom_directory>(&mut self, directory_server: S) { self.client.directory_server = directory_server.into(); - self } - pub fn with_socket(mut self, socket_type: SocketType) -> Self { - self.socket.socket_type = socket_type; - self - } - - pub fn with_port(mut self, port: u16) -> Self { - self.socket.listening_port = port; - self - } - - pub fn set_high_default_traffic_volume(mut self) -> Self { + pub fn set_high_default_traffic_volume(&mut self) { self.debug.average_packet_delay = 10; self.debug.loop_cover_traffic_average_delay = 20; // 50 cover messages / s self.debug.message_sending_average_delay = 5; // 200 "real" messages / s - self } - // getters - pub fn get_config_file_save_location(&self) -> PathBuf { - self.config_directory().join(Self::config_file_name()) + pub fn get_id(&self) -> String { + self.client.id.clone() + } + + pub fn get_nym_root_directory(&self) -> PathBuf { + self.client.nym_root_directory.clone() } pub fn get_private_identity_key_file(&self) -> PathBuf { @@ -246,14 +179,6 @@ impl Config { self.client.gateway_listener.clone() } - pub fn get_socket_type(&self) -> SocketType { - self.socket.socket_type - } - - pub fn get_listening_port(&self) -> u16 { - self.socket.listening_port - } - // Debug getters pub fn get_average_packet_delay(&self) -> time::Duration { time::Duration::from_millis(self.debug.average_packet_delay) @@ -292,9 +217,19 @@ impl Config { } } +impl Default for Config { + fn default() -> Self { + Config { + client: Client::::default(), + logging: Default::default(), + debug: Default::default(), + } + } +} + #[derive(Debug, Deserialize, PartialEq, Serialize)] #[serde(deny_unknown_fields)] -pub struct Client { +pub struct Client { /// ID specifies the human readable ID of this particular client. id: String, @@ -335,9 +270,12 @@ pub struct Client { /// nym_home_directory specifies absolute path to the home nym Clients directory. /// It is expected to use default value and hence .toml file should not redefine this field. nym_root_directory: PathBuf, + + #[serde(skip)] + super_struct: PhantomData, } -impl Default for Client { +impl Default for Client { fn default() -> Self { // there must be explicit checks for whether id is not empty later Client { @@ -352,54 +290,39 @@ impl Default for Client { reply_encryption_key_store_path: Default::default(), gateway_id: "".to_string(), gateway_listener: "".to_string(), - nym_root_directory: Config::default_root_directory(), + nym_root_directory: T::default_root_directory(), + super_struct: Default::default(), } } } -impl Client { +impl Client { fn default_private_identity_key_file(id: &str) -> PathBuf { - Config::default_data_directory(Some(id)).join("private_identity.pem") + T::default_data_directory(Some(id)).join("private_identity.pem") } fn default_public_identity_key_file(id: &str) -> PathBuf { - Config::default_data_directory(Some(id)).join("public_identity.pem") + T::default_data_directory(Some(id)).join("public_identity.pem") } fn default_private_encryption_key_file(id: &str) -> PathBuf { - Config::default_data_directory(Some(id)).join("private_encryption.pem") + T::default_data_directory(Some(id)).join("private_encryption.pem") } fn default_public_encryption_key_file(id: &str) -> PathBuf { - Config::default_data_directory(Some(id)).join("public_encryption.pem") + T::default_data_directory(Some(id)).join("public_encryption.pem") } fn default_gateway_shared_key_file(id: &str) -> PathBuf { - Config::default_data_directory(Some(id)).join("gateway_shared.pem") + T::default_data_directory(Some(id)).join("gateway_shared.pem") } fn default_ack_key_file(id: &str) -> PathBuf { - Config::default_data_directory(Some(id)).join("ack_key.pem") + T::default_data_directory(Some(id)).join("ack_key.pem") } fn default_reply_encryption_key_store_path(id: &str) -> PathBuf { - Config::default_data_directory(Some(id)).join("reply_key_store") - } -} - -#[derive(Debug, Deserialize, PartialEq, Serialize)] -#[serde(deny_unknown_fields)] -pub struct Socket { - socket_type: SocketType, - listening_port: u16, -} - -impl Default for Socket { - fn default() -> Self { - Socket { - socket_type: SocketType::WebSocket, - listening_port: DEFAULT_LISTENING_PORT, - } + T::default_data_directory(Some(id)).join("reply_key_store") } } @@ -485,23 +408,3 @@ impl Default for Debug { } } } - -#[cfg(test)] -mod client_config { - use super::*; - - #[test] - fn after_saving_default_config_the_loaded_one_is_identical() { - // need to figure out how to do something similar but without touching the disk - // or the file system at all... - let temp_location = tempfile::tempdir().unwrap().path().join("config.toml"); - let default_config = Config::default().with_id("foomp".to_string()); - default_config - .save_to_file(Some(temp_location.clone())) - .unwrap(); - - let loaded_config = Config::load_from_file(Some(temp_location), None).unwrap(); - - assert_eq!(default_config, loaded_config); - } -} diff --git a/clients/client-core/src/config/persistence/key_pathfinder.rs b/clients/client-core/src/config/persistence/key_pathfinder.rs index 3804c38fea..c23c30e205 100644 --- a/clients/client-core/src/config/persistence/key_pathfinder.rs +++ b/clients/client-core/src/config/persistence/key_pathfinder.rs @@ -13,6 +13,7 @@ // limitations under the License. use crate::config::Config; +use config::NymConfig; use std::path::{Path, PathBuf}; #[derive(Debug)] @@ -39,7 +40,7 @@ impl ClientKeyPathfinder { } } - pub fn new_from_config(config: &Config) -> Self { + pub fn new_from_config(config: &Config) -> Self { ClientKeyPathfinder { identity_private_key: config.get_private_identity_key_file(), identity_public_key: config.get_public_identity_key_file(), diff --git a/clients/native/Cargo.toml b/clients/native/Cargo.toml index 1ecf9ec9b2..24ffcc86d0 100644 --- a/clients/native/Cargo.toml +++ b/clients/native/Cargo.toml @@ -20,7 +20,7 @@ futures = "0.3" # bunch of futures stuff, however, now that I think about it, it url = "2.1" # I think we should just replace it with String clap = "2.33.0" # for the command line arguments -dirs = "2.0.2" # for determining default store directories in config +dirs = "3.0" # for determining default store directories in config dotenv = "0.15.0" # for obtaining environmental variables (only used for RUST_LOG for time being) log = "0.4" # self explanatory pretty_env_logger = "0.3" # for formatting log messages diff --git a/clients/native/src/client/config/mod.rs b/clients/native/src/client/config/mod.rs new file mode 100644 index 0000000000..9de9b8ee90 --- /dev/null +++ b/clients/native/src/client/config/mod.rs @@ -0,0 +1,157 @@ +// Copyright 2020 Nym Technologies SA +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +use crate::client::config::template::config_template; +use client_core::config::Config as BaseConfig; +use config::NymConfig; +use serde::{Deserialize, Serialize}; +use std::path::PathBuf; + +mod template; + +const DEFAULT_LISTENING_PORT: u16 = 1977; + +#[derive(Debug, Deserialize, PartialEq, Serialize, Clone, Copy)] +#[serde(deny_unknown_fields)] +pub enum SocketType { + WebSocket, + None, +} + +impl SocketType { + pub fn from_string>(val: S) -> Self { + let mut upper = val.into(); + upper.make_ascii_uppercase(); + match upper.as_ref() { + "WEBSOCKET" | "WS" => SocketType::WebSocket, + _ => SocketType::None, + } + } +} + +#[derive(Debug, Default, Deserialize, PartialEq, Serialize)] +#[serde(deny_unknown_fields)] +pub struct Config { + #[serde(flatten)] + base: BaseConfig, + + socket: Socket, +} + +impl NymConfig for Config { + fn template() -> &'static str { + config_template() + } + + fn config_file_name() -> String { + "config.toml".to_string() + } + + fn default_root_directory() -> PathBuf { + dirs::home_dir() + .expect("Failed to evaluate $HOME value") + .join(".nym") + .join("clients") + } + + fn root_directory(&self) -> PathBuf { + self.base.get_nym_root_directory() + } + + fn config_directory(&self) -> PathBuf { + self.root_directory() + .join(self.base.get_id()) + .join("config") + } + + fn data_directory(&self) -> PathBuf { + self.root_directory().join(self.base.get_id()).join("data") + } +} + +impl Config { + pub fn new>(id: S) -> Self { + Config { + base: BaseConfig::new(id), + socket: Default::default(), + } + } + + pub fn with_socket(mut self, socket_type: SocketType) -> Self { + self.socket.socket_type = socket_type; + self + } + + pub fn with_port(mut self, port: u16) -> Self { + self.socket.listening_port = port; + self + } + + // getters + pub fn get_config_file_save_location(&self) -> PathBuf { + self.config_directory().join(Self::config_file_name()) + } + + pub fn get_base(&self) -> &BaseConfig { + &self.base + } + + pub fn get_base_mut(&mut self) -> &mut BaseConfig { + &mut self.base + } + + pub fn get_socket_type(&self) -> SocketType { + self.socket.socket_type + } + + pub fn get_listening_port(&self) -> u16 { + self.socket.listening_port + } +} + +#[derive(Debug, Deserialize, PartialEq, Serialize)] +#[serde(deny_unknown_fields)] +pub struct Socket { + socket_type: SocketType, + listening_port: u16, +} + +impl Default for Socket { + fn default() -> Self { + Socket { + socket_type: SocketType::WebSocket, + listening_port: DEFAULT_LISTENING_PORT, + } + } +} + +#[cfg(test)] +mod client_config { + use super::*; + + #[test] + fn after_saving_default_config_the_loaded_one_is_identical() { + // need to figure out how to do something similar but without touching the disk + // or the file system at all... + let temp_location = tempfile::tempdir().unwrap().path().join("config.toml"); + let default_config = Config::new("foomp".to_string()); + default_config + .save_to_file(Some(temp_location.clone())) + .unwrap(); + + let loaded_config = Config::load_from_file(Some(temp_location), None).unwrap(); + + assert_eq!(default_config, loaded_config); + } +} diff --git a/clients/native/src/client/config/template.rs b/clients/native/src/client/config/template.rs new file mode 100644 index 0000000000..fbe0f404c2 --- /dev/null +++ b/clients/native/src/client/config/template.rs @@ -0,0 +1,102 @@ +// Copyright 2020 Nym Technologies SA +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +pub(crate) fn config_template() -> &'static str { + // While using normal toml marshalling would have been way simpler with less overhead, + // I think it's useful to have comments attached to the saved config file to explain behaviour of + // particular fields. + // Note: any changes to the template must be reflected in the appropriate structs in mod.rs. + r#" +# This is a TOML config file. +# For more information, see https://github.com/toml-lang/toml + +##### main base client config options ##### + +[client] +# Human readable ID of this particular client. +id = '{{ client.id }}' + +# URL to the directory server. +directory_server = '{{ client.directory_server }}' + +# Path to file containing private identity key. +private_identity_key_file = '{{ client.private_identity_key_file }}' + +# Path to file containing public identity key. +public_identity_key_file = '{{ client.public_identity_key_file }}' + +# Path to file containing private encryption key. +private_encryption_key_file = '{{ client.private_encryption_key_file }}' + +# Path to file containing public encryption key. +public_encryption_key_file = '{{ client.public_encryption_key_file }}' + +# Full path to file containing reply encryption keys of all reply-SURBs we have ever +# sent but not received back. +reply_encryption_key_store_path = '{{ client.reply_encryption_key_store_path }}' + +##### additional client config options ##### + +# ID of the gateway from which the client should be fetching messages. +gateway_id = '{{ client.gateway_id }}' + +# Address of the gateway listener to which all client requests should be sent. +gateway_listener = '{{ client.gateway_listener }}' + +# A gateway specific, optional, base58 stringified shared key used for +# communication with particular gateway. +gateway_shared_key_file = '{{ client.gateway_shared_key_file }}' + +# Path to file containing key used for encrypting and decrypting the content of an +# acknowledgement so that nobody besides the client knows which packet it refers to. +ack_key_file = '{{ client.ack_key_file }}' + +##### advanced configuration options ##### + +# Absolute path to the home Nym Clients directory. +nym_root_directory = '{{ client.nym_root_directory }}' + + +##### socket config options ##### + +[socket] + +# allowed values are 'WebSocket' or 'None' +socket_type = '{{ socket.socket_type }}' + +# if applicable (for the case of 'WebSocket'), the port on which the client +# will be listening for incoming requests +listening_port = {{ socket.listening_port }} + + +##### logging configuration options ##### + +[logging] + +# TODO + + +##### debug configuration options ##### +# The following options should not be modified unless you know EXACTLY what you are doing +# as if set incorrectly, they may impact your anonymity. + +[debug] + +average_packet_delay = {{ debug.average_packet_delay }} +average_ack_delay = {{ debug.average_ack_delay }} +loop_cover_traffic_average_delay = {{ debug.loop_cover_traffic_average_delay }} +message_sending_average_delay = {{ debug.message_sending_average_delay }} + +"# +} diff --git a/clients/native/src/client/mod.rs b/clients/native/src/client/mod.rs index a5be64e596..116dea26ab 100644 --- a/clients/native/src/client/mod.rs +++ b/clients/native/src/client/mod.rs @@ -12,6 +12,7 @@ // See the License for the specific language governing permissions and // limitations under the License. +use crate::client::config::{Config, SocketType}; use crate::websocket; use client_core::client::cover_traffic_stream::LoopCoverTrafficStream; use client_core::client::inbound_messages::{ @@ -32,7 +33,6 @@ use client_core::client::topology_control::{ TopologyAccessor, TopologyRefresher, TopologyRefresherConfig, }; use client_core::config::persistence::key_pathfinder::ClientKeyPathfinder; -use client_core::config::{Config, SocketType}; use crypto::asymmetric::identity; use futures::channel::mpsc; use gateway_client::{ @@ -46,6 +46,8 @@ use nymsphinx::anonymous_replies::ReplySURB; use nymsphinx::receiver::ReconstructedMessage; use tokio::runtime::Runtime; +pub(crate) mod config; + pub struct NymClient { /// Client configuration options, including, among other things, packet sending rates, /// key filepaths, etc. @@ -71,7 +73,7 @@ pub struct NymClient { impl NymClient { pub fn new(config: Config) -> Self { - let pathfinder = ClientKeyPathfinder::new_from_config(&config); + let pathfinder = ClientKeyPathfinder::new_from_config(config.get_base()); let key_manager = KeyManager::load_keys(&pathfinder).expect("failed to load stored keys"); NymClient { @@ -89,7 +91,7 @@ impl NymClient { self.key_manager.encryption_keypair().public_key().clone(), // TODO: below only works under assumption that gateway address == gateway id // (which currently is true) - NodeIdentity::from_base58_string(self.config.get_gateway_id()).unwrap(), + NodeIdentity::from_base58_string(self.config.get_base().get_gateway_id()).unwrap(), ) } @@ -107,9 +109,11 @@ impl NymClient { .enter(|| { LoopCoverTrafficStream::new( self.key_manager.ack_key(), - self.config.get_average_ack_delay(), - self.config.get_average_packet_delay(), - self.config.get_loop_cover_traffic_average_delay(), + self.config.get_base().get_average_ack_delay(), + self.config.get_base().get_average_packet_delay(), + self.config + .get_base() + .get_loop_cover_traffic_average_delay(), mix_tx, self.as_mix_recipient(), topology_accessor, @@ -128,11 +132,11 @@ impl NymClient { ) { let controller_config = real_messages_control::Config::new( self.key_manager.ack_key(), - self.config.get_ack_wait_multiplier(), - self.config.get_ack_wait_addition(), - self.config.get_average_ack_delay(), - self.config.get_message_sending_average_delay(), - self.config.get_average_packet_delay(), + self.config.get_base().get_ack_wait_multiplier(), + self.config.get_base().get_ack_wait_addition(), + self.config.get_base().get_average_ack_delay(), + self.config.get_base().get_message_sending_average_delay(), + self.config.get_base().get_average_packet_delay(), self.as_mix_recipient(), ); @@ -176,11 +180,11 @@ impl NymClient { mixnet_message_sender: MixnetMessageSender, ack_sender: AcknowledgementSender, ) -> GatewayClient<'static, url::Url> { - let gateway_id = self.config.get_gateway_id(); + let gateway_id = self.config.get_base().get_gateway_id(); if gateway_id.is_empty() { panic!("The identity of the gateway is unknown - did you run `nym-client` init?") } - let gateway_address_str = self.config.get_gateway_listener(); + let gateway_address_str = self.config.get_base().get_gateway_listener(); if gateway_address_str.is_empty() { panic!("The address of the gateway is unknown - did you run `nym-client` init?") } @@ -200,7 +204,7 @@ impl NymClient { Some(self.key_manager.gateway_shared_key()), mixnet_message_sender, ack_sender, - self.config.get_gateway_response_timeout(), + self.config.get_base().get_gateway_response_timeout(), ); self.runtime.block_on(async { @@ -217,8 +221,8 @@ impl NymClient { // the current global view of topology fn start_topology_refresher(&mut self, topology_accessor: TopologyAccessor) { let topology_refresher_config = TopologyRefresherConfig::new( - self.config.get_directory_server(), - self.config.get_topology_refresh_rate(), + self.config.get_base().get_directory_server(), + self.config.get_base().get_topology_refresh_rate(), ); let mut topology_refresher = TopologyRefresher::new_directory_client(topology_refresher_config, topology_accessor); @@ -226,7 +230,7 @@ impl NymClient { // components depending on topology would see a non-empty view info!( "Obtaining initial network topology from {}", - self.config.get_directory_server() + self.config.get_base().get_directory_server() ); self.runtime.block_on(topology_refresher.refresh()); @@ -357,7 +361,7 @@ impl NymClient { let shared_topology_accessor = TopologyAccessor::new(); let reply_key_storage = - ReplyKeyStorage::load(self.config.get_reply_encryption_key_store_path()) + ReplyKeyStorage::load(self.config.get_base().get_reply_encryption_key_store_path()) .expect("Failed to load reply key storage!"); // the components are started in very specific order. Unless you know what you are doing, @@ -413,7 +417,7 @@ impl NymClient { ); info!( "Gateway identity public key is: {:?}", - self.config.get_gateway_id() + self.config.get_base().get_gateway_id() ); } } diff --git a/clients/native/src/commands/init.rs b/clients/native/src/commands/init.rs index 4200a9b1ba..4885481266 100644 --- a/clients/native/src/commands/init.rs +++ b/clients/native/src/commands/init.rs @@ -13,6 +13,7 @@ // limitations under the License. use crate::built_info; +use crate::client::config::Config; use crate::commands::override_config; use clap::{App, Arg, ArgMatches}; use client_core::client::key_manager::KeyManager; @@ -149,48 +150,51 @@ pub fn execute(matches: &ArgMatches) { println!("Initialising client..."); let id = matches.value_of("id").unwrap(); // required for now - let mut config = client_core::config::Config::new(id); + let mut config = Config::new(id); let mut rng = OsRng; config = override_config(config, matches); if matches.is_present("fastmode") { - config = config.set_high_default_traffic_volume(); + config.get_base_mut().set_high_default_traffic_volume(); } // create identity, encryption and ack keys. let mut key_manager = KeyManager::new(&mut rng); // if there is no gateway chosen, get a random-ish one from the topology - if config.get_gateway_id().is_empty() { + if config.get_base().get_gateway_id().is_empty() { // TODO: is there perhaps a way to make it work without having to spawn entire runtime? let mut rt = tokio::runtime::Runtime::new().unwrap(); let (gateway_id, gateway_listener, shared_key) = rt.block_on(choose_gateway( - config.get_directory_server(), + config.get_base().get_directory_server(), key_manager.identity_keypair(), )); - config = config - .with_gateway_id(gateway_id) + config.get_base_mut().with_gateway_id(gateway_id); + config + .get_base_mut() .with_gateway_listener(gateway_listener); key_manager.insert_gateway_shared_key(shared_key) } // we specified our gateway but don't know its physical address - if config.get_gateway_listener().is_empty() { + if config.get_base().get_gateway_listener().is_empty() { // TODO: is there perhaps a way to make it work without having to spawn entire runtime? let mut rt = tokio::runtime::Runtime::new().unwrap(); let gateway_listener = rt .block_on(get_gateway_listener( - config.get_directory_server(), - &config.get_gateway_id(), + config.get_base().get_directory_server(), + &config.get_base().get_gateway_id(), )) .expect("No gateway with provided id exists!"); - config = config.with_gateway_listener(gateway_listener); + config + .get_base_mut() + .with_gateway_listener(gateway_listener); } - let pathfinder = ClientKeyPathfinder::new_from_config(&config); + let pathfinder = ClientKeyPathfinder::new_from_config(config.get_base()); key_manager .store_keys(&pathfinder) .expect("Failed to generated keys"); @@ -204,7 +208,7 @@ pub fn execute(matches: &ArgMatches) { println!( "Unless overridden in all `nym-client run` we will be talking to the following gateway: {}...", - config.get_gateway_id(), + config.get_base().get_gateway_id(), ); println!("Client configuration completed.\n\n\n") diff --git a/clients/native/src/commands/mod.rs b/clients/native/src/commands/mod.rs index 4eba736317..ceb9c81aea 100644 --- a/clients/native/src/commands/mod.rs +++ b/clients/native/src/commands/mod.rs @@ -12,19 +12,19 @@ // See the License for the specific language governing permissions and // limitations under the License. +use crate::client::config::{Config, SocketType}; use clap::ArgMatches; -use client_core::config::{Config, SocketType}; pub mod init; pub mod run; pub(crate) fn override_config(mut config: Config, matches: &ArgMatches) -> Config { if let Some(directory) = matches.value_of("directory") { - config = config.with_custom_directory(directory); + config.get_base_mut().with_custom_directory(directory); } if let Some(gateway_id) = matches.value_of("gateway") { - config = config.with_gateway_id(gateway_id); + config.get_base_mut().with_gateway_id(gateway_id); } if matches.is_present("disable-socket") { diff --git a/clients/native/src/commands/run.rs b/clients/native/src/commands/run.rs index 464b3d962c..eb0486f918 100644 --- a/clients/native/src/commands/run.rs +++ b/clients/native/src/commands/run.rs @@ -12,10 +12,10 @@ // See the License for the specific language governing permissions and // limitations under the License. +use crate::client::config::Config; use crate::client::NymClient; use crate::commands::override_config; use clap::{App, Arg, ArgMatches}; -use client_core::config::Config; use config::NymConfig; pub fn command_args<'a, 'b>() -> clap::App<'a, 'b> { diff --git a/clients/socks5/Cargo.toml b/clients/socks5/Cargo.toml index b6679a97d5..0ad2eff9d1 100644 --- a/clients/socks5/Cargo.toml +++ b/clients/socks5/Cargo.toml @@ -11,12 +11,14 @@ path = "src/lib.rs" [dependencies] clap = "2.33.0" +dirs = "3.0" # for determining default store directories in config dotenv = "0.15.0" futures = "0.3" log = "0.4" pin-project = "0.4" pretty_env_logger = "0.3" rand = { version = "0.7.3", features = ["wasm-bindgen"] } +serde = { version = "1.0", features = ["derive"] } # for config serialization/deserialization snafu = "0.4.1" tokio = { version = "0.2", features = ["rt-threaded"] } url = "2.1" @@ -35,3 +37,6 @@ utils = { path = "../../common/utils" } [build-dependencies] built = "0.4.3" + +[dev-dependencies] +tempfile = "3.1.0" \ No newline at end of file diff --git a/clients/socks5/src/client/config/mod.rs b/clients/socks5/src/client/config/mod.rs new file mode 100644 index 0000000000..6779ade230 --- /dev/null +++ b/clients/socks5/src/client/config/mod.rs @@ -0,0 +1,154 @@ +// Copyright 2020 Nym Technologies SA +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +use crate::client::config::template::config_template; +use client_core::config::Config as BaseConfig; +use config::NymConfig; +use nymsphinx::addressing::clients::Recipient; +use serde::{Deserialize, Serialize}; +use std::path::PathBuf; + +mod template; + +const DEFAULT_LISTENING_PORT: u16 = 1080; + +#[derive(Debug, Default, Deserialize, PartialEq, Serialize)] +#[serde(deny_unknown_fields)] +pub struct Config { + #[serde(flatten)] + base: BaseConfig, + + socks5: Socks5, +} + +impl NymConfig for Config { + fn template() -> &'static str { + config_template() + } + + fn config_file_name() -> String { + "config.toml".to_string() + } + + fn default_root_directory() -> PathBuf { + dirs::home_dir() + .expect("Failed to evaluate $HOME value") + .join(".nym") + .join("socks5-clients") + } + + fn root_directory(&self) -> PathBuf { + self.base.get_nym_root_directory() + } + + fn config_directory(&self) -> PathBuf { + self.root_directory() + .join(self.base.get_id()) + .join("config") + } + + fn data_directory(&self) -> PathBuf { + self.root_directory().join(self.base.get_id()).join("data") + } +} + +impl Config { + pub fn new>(id: S, provider_mix_address: S) -> Self { + Config { + base: BaseConfig::new(id), + socks5: Socks5::new(provider_mix_address), + } + } + + pub fn with_port(mut self, port: u16) -> Self { + self.socks5.listening_port = port; + self + } + + pub fn with_provider_mix_address(mut self, address: String) -> Self { + self.socks5.provider_mix_address = address; + self + } + + // getters + pub fn get_config_file_save_location(&self) -> PathBuf { + self.config_directory().join(Self::config_file_name()) + } + + pub fn get_provider_mix_address(&self) -> Recipient { + Recipient::try_from_base58_string(&self.socks5.provider_mix_address) + .expect("malformed provider address") + } + + pub fn get_base(&self) -> &BaseConfig { + &self.base + } + + pub fn get_base_mut(&mut self) -> &mut BaseConfig { + &mut self.base + } + + pub fn get_listening_port(&self) -> u16 { + self.socks5.listening_port + } +} + +#[derive(Debug, Deserialize, PartialEq, Serialize)] +#[serde(deny_unknown_fields)] +pub struct Socks5 { + /// The port on which the client will be listening for incoming requests + listening_port: u16, + + /// The mix address of the provider to which all requests are going to be sent. + provider_mix_address: String, +} + +impl Socks5 { + pub fn new>(provider_mix_address: S) -> Self { + Socks5 { + listening_port: DEFAULT_LISTENING_PORT, + provider_mix_address: provider_mix_address.into(), + } + } +} + +impl Default for Socks5 { + fn default() -> Self { + Socks5 { + listening_port: DEFAULT_LISTENING_PORT, + provider_mix_address: "".into(), + } + } +} + +#[cfg(test)] +mod client_config { + use super::*; + + #[test] + fn after_saving_default_config_the_loaded_one_is_identical() { + // need to figure out how to do something similar but without touching the disk + // or the file system at all... + let temp_location = tempfile::tempdir().unwrap().path().join("config.toml"); + let fake_address = "CytBseW6yFXUMzz4SGAKdNLGR7q3sJLLYxyBGvutNEQV.4QXYyEVc5fUDjmmi8PrHN9tdUFV4PCvSJE1278cHyvoe@FioFa8nMmPpQnYi7JyojoTuwGLeyNS8BF4ChPr29zUML"; + let default_config = Config::new("foomp", fake_address); + default_config + .save_to_file(Some(temp_location.clone())) + .unwrap(); + + let loaded_config = Config::load_from_file(Some(temp_location), None).unwrap(); + + assert_eq!(default_config, loaded_config); + } +} diff --git a/clients/socks5/src/client/config/template.rs b/clients/socks5/src/client/config/template.rs new file mode 100644 index 0000000000..80861a7bc4 --- /dev/null +++ b/clients/socks5/src/client/config/template.rs @@ -0,0 +1,101 @@ +// Copyright 2020 Nym Technologies SA +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +pub(crate) fn config_template() -> &'static str { + // While using normal toml marshalling would have been way simpler with less overhead, + // I think it's useful to have comments attached to the saved config file to explain behaviour of + // particular fields. + // Note: any changes to the template must be reflected in the appropriate structs in mod.rs. + r#" +# This is a TOML config file. +# For more information, see https://github.com/toml-lang/toml + +##### main base client config options ##### + +[client] +# Human readable ID of this particular client. +id = '{{ client.id }}' + +# URL to the directory server. +directory_server = '{{ client.directory_server }}' + +# Path to file containing private identity key. +private_identity_key_file = '{{ client.private_identity_key_file }}' + +# Path to file containing public identity key. +public_identity_key_file = '{{ client.public_identity_key_file }}' + +# Path to file containing private encryption key. +private_encryption_key_file = '{{ client.private_encryption_key_file }}' + +# Path to file containing public encryption key. +public_encryption_key_file = '{{ client.public_encryption_key_file }}' + +# Full path to file containing reply encryption keys of all reply-SURBs we have ever +# sent but not received back. +reply_encryption_key_store_path = '{{ client.reply_encryption_key_store_path }}' + +##### additional client config options ##### + +# ID of the gateway from which the client should be fetching messages. +gateway_id = '{{ client.gateway_id }}' + +# Address of the gateway listener to which all client requests should be sent. +gateway_listener = '{{ client.gateway_listener }}' + +# A gateway specific, optional, base58 stringified shared key used for +# communication with particular gateway. +gateway_shared_key_file = '{{ client.gateway_shared_key_file }}' + +# Path to file containing key used for encrypting and decrypting the content of an +# acknowledgement so that nobody besides the client knows which packet it refers to. +ack_key_file = '{{ client.ack_key_file }}' + +##### advanced configuration options ##### + +# Absolute path to the home Nym Clients directory. +nym_root_directory = '{{ client.nym_root_directory }}' + + +##### socket config options ##### + +[socks5] + +# The mix address of the provider to which all requests are going to be sent. +provider_mix_address = '{{ socks5.provider_mix_address }}' + +# The port on which the client will be listening for incoming requests +listening_port = {{ socks5.listening_port }} + + +##### logging configuration options ##### + +[logging] + +# TODO + + +##### debug configuration options ##### +# The following options should not be modified unless you know EXACTLY what you are doing +# as if set incorrectly, they may impact your anonymity. + +[debug] + +average_packet_delay = {{ debug.average_packet_delay }} +average_ack_delay = {{ debug.average_ack_delay }} +loop_cover_traffic_average_delay = {{ debug.loop_cover_traffic_average_delay }} +message_sending_average_delay = {{ debug.message_sending_average_delay }} + +"# +} diff --git a/clients/socks5/src/client/mod.rs b/clients/socks5/src/client/mod.rs index 1b5d1bedc4..3963bad44f 100644 --- a/clients/socks5/src/client/mod.rs +++ b/clients/socks5/src/client/mod.rs @@ -12,6 +12,7 @@ // See the License for the specific language governing permissions and // limitations under the License. +use crate::client::config::Config; use crate::socks::{ authentication::{AuthenticationMethods, Authenticator, User}, server::SphinxSocksServer, @@ -25,7 +26,6 @@ use client_core::client::mix_traffic::{ MixMessageReceiver, MixMessageSender, MixTrafficController, }; use client_core::client::real_messages_control::RealMessagesController; -use client_core::client::received_buffer::{ReceivedBufferMessage, ReconstructedMessagesReceiver}; use client_core::client::received_buffer::{ ReceivedBufferRequestReceiver, ReceivedBufferRequestSender, ReceivedMessagesBufferController, }; @@ -34,7 +34,6 @@ use client_core::client::topology_control::{ TopologyAccessor, TopologyRefresher, TopologyRefresherConfig, }; use client_core::config::persistence::key_pathfinder::ClientKeyPathfinder; -use client_core::config::{Config, SocketType}; use crypto::asymmetric::identity; use futures::channel::mpsc; use gateway_client::{ @@ -42,12 +41,12 @@ use gateway_client::{ MixnetMessageSender, }; use log::*; -use nymsphinx::addressing::clients::{ClientEncryptionKey, ClientIdentity, Recipient}; +use nymsphinx::addressing::clients::Recipient; use nymsphinx::addressing::nodes::NodeIdentity; -use nymsphinx::anonymous_replies::ReplySURB; -use nymsphinx::receiver::ReconstructedMessage; use tokio::runtime::Runtime; +pub(crate) mod config; + pub struct NymClient { /// Client configuration options, including, among other things, packet sending rates, /// key filepaths, etc. @@ -60,28 +59,17 @@ pub struct NymClient { /// KeyManager object containing smart pointers to all relevant keys used by the client. key_manager: KeyManager, - - /// Channel used for transforming 'raw' messages into sphinx packets and sending them - /// through the mix network. - /// It is only available if the client started with the websocket listener disabled. - input_tx: Option, - - /// Channel used for obtaining reconstructed messages received from the mix network. - /// It is only available if the client started with the websocket listener disabled. - receive_tx: Option, } impl NymClient { pub fn new(config: Config) -> Self { - let pathfinder = ClientKeyPathfinder::new_from_config(&config); + let pathfinder = ClientKeyPathfinder::new_from_config(config.get_base()); let key_manager = KeyManager::load_keys(&pathfinder).expect("failed to load stored keys"); NymClient { runtime: Runtime::new().unwrap(), config, key_manager, - input_tx: None, - receive_tx: None, } } @@ -91,7 +79,7 @@ impl NymClient { self.key_manager.encryption_keypair().public_key().clone(), // TODO: below only works under assumption that gateway address == gateway id // (which currently is true) - NodeIdentity::from_base58_string(self.config.get_gateway_id()).unwrap(), + NodeIdentity::from_base58_string(self.config.get_base().get_gateway_id()).unwrap(), ) } @@ -109,9 +97,11 @@ impl NymClient { .enter(|| { LoopCoverTrafficStream::new( self.key_manager.ack_key(), - self.config.get_average_ack_delay(), - self.config.get_average_packet_delay(), - self.config.get_loop_cover_traffic_average_delay(), + self.config.get_base().get_average_ack_delay(), + self.config.get_base().get_average_packet_delay(), + self.config + .get_base() + .get_loop_cover_traffic_average_delay(), mix_tx, self.as_mix_recipient(), topology_accessor, @@ -130,11 +120,11 @@ impl NymClient { ) { let controller_config = client_core::client::real_messages_control::Config::new( self.key_manager.ack_key(), - self.config.get_ack_wait_multiplier(), - self.config.get_ack_wait_addition(), - self.config.get_average_ack_delay(), - self.config.get_message_sending_average_delay(), - self.config.get_average_packet_delay(), + self.config.get_base().get_ack_wait_multiplier(), + self.config.get_base().get_ack_wait_addition(), + self.config.get_base().get_average_ack_delay(), + self.config.get_base().get_message_sending_average_delay(), + self.config.get_base().get_average_packet_delay(), self.as_mix_recipient(), ); @@ -178,11 +168,11 @@ impl NymClient { mixnet_message_sender: MixnetMessageSender, ack_sender: AcknowledgementSender, ) -> GatewayClient<'static, url::Url> { - let gateway_id = self.config.get_gateway_id(); + let gateway_id = self.config.get_base().get_gateway_id(); if gateway_id.is_empty() { panic!("The identity of the gateway is unknown - did you run `nym-client` init?") } - let gateway_address_str = self.config.get_gateway_listener(); + let gateway_address_str = self.config.get_base().get_gateway_listener(); if gateway_address_str.is_empty() { panic!("The address of the gateway is unknown - did you run `nym-client` init?") } @@ -202,7 +192,7 @@ impl NymClient { Some(self.key_manager.gateway_shared_key()), mixnet_message_sender, ack_sender, - self.config.get_gateway_response_timeout(), + self.config.get_base().get_gateway_response_timeout(), ); self.runtime.block_on(async { @@ -219,8 +209,8 @@ impl NymClient { // the current global view of topology fn start_topology_refresher(&mut self, topology_accessor: TopologyAccessor) { let topology_refresher_config = TopologyRefresherConfig::new( - self.config.get_directory_server(), - self.config.get_topology_refresh_rate(), + self.config.get_base().get_directory_server(), + self.config.get_base().get_topology_refresh_rate(), ); let mut topology_refresher = TopologyRefresher::new_directory_client(topology_refresher_config, topology_accessor); @@ -228,7 +218,7 @@ impl NymClient { // components depending on topology would see a non-empty view info!( "Obtaining initial network topology from {}", - self.config.get_directory_server() + self.config.get_base().get_directory_server() ); self.runtime.block_on(topology_refresher.refresh()); @@ -271,74 +261,15 @@ impl NymClient { let allowed_users: Vec = Vec::new(); let authenticator = Authenticator::new(auth_methods, allowed_users); - let recipient = self.load_socks5_service_provider(); let mut sphinx_socks = SphinxSocksServer::new( - 1080, - "127.0.0.1", + self.config.get_listening_port(), authenticator, - recipient, + self.config.get_provider_mix_address(), self.as_mix_recipient(), ); self.runtime .spawn(async move { sphinx_socks.serve(msg_input, buffer_requester).await }); } - // TODO: make this configurable in the client config file - // TODO: Talk to JS about where I can easily find these. - fn load_socks5_service_provider(&self) -> Recipient { - // load from file here, or better yet, inject it - let identity = "G3WSu7S3Jdm5HHQwZ3bT7NKoEBNSGr8JY2jngbUigqfz"; - let encryption_key = "6KUoz9gexdFziLrrh6M2gXpAW4Fdn3HBHss11WJDYKEL"; - let gateway_key = "5QAR66H9aMqaEo9y9G4hxAsvenJe13wJSRkwQ8qjsF6C"; - - let client_identity = ClientIdentity::from_base58_string(identity).unwrap(); - let client_encryption_key = - ClientEncryptionKey::from_base58_string(encryption_key).unwrap(); - let gateway = NodeIdentity::from_base58_string(gateway_key).unwrap(); - Recipient::new(client_identity, client_encryption_key, gateway) - } - - /// EXPERIMENTAL DIRECT RUST API - /// It's untested and there are absolutely no guarantees about it (but seems to have worked - /// well enough in local tests) - pub fn send_message(&mut self, recipient: Recipient, message: Vec, with_reply_surb: bool) { - let input_msg = InputMessage::new_fresh(recipient, message, with_reply_surb); - - self.input_tx - .as_ref() - .expect("start method was not called before!") - .unbounded_send(input_msg) - .unwrap(); - } - - /// EXPERIMENTAL DIRECT RUST API - /// It's untested and there are absolutely no guarantees about it (but seems to have worked - /// well enough in local tests) - pub fn send_reply(&mut self, reply_surb: ReplySURB, message: Vec) { - let input_msg = InputMessage::new_reply(reply_surb, message); - - self.input_tx - .as_ref() - .expect("start method was not called before!") - .unbounded_send(input_msg) - .unwrap(); - } - - /// EXPERIMENTAL DIRECT RUST API - /// It's untested and there are absolutely no guarantees about it (but seems to have worked - /// well enough in local tests) - /// Note: it waits for the first occurrence of messages being sent to ourselves. If you expect multiple - /// messages, you might have to call this function repeatedly. - // TODO: I guess this should really return something that `impl Stream` - pub async fn wait_for_messages(&mut self) -> Vec { - use futures::StreamExt; - - self.receive_tx - .as_mut() - .expect("start method was not called before!") - .next() - .await - .expect("buffer controller seems to have somehow died!") - } /// blocking version of `start` method. Will run forever (or until SIGINT is sent) pub fn run_forever(&mut self) { @@ -382,7 +313,7 @@ impl NymClient { let shared_topology_accessor = TopologyAccessor::new(); let reply_key_storage = - ReplyKeyStorage::load(self.config.get_reply_encryption_key_store_path()) + ReplyKeyStorage::load(self.config.get_base().get_reply_encryption_key_store_path()) .expect("Failed to load reply key storage!"); // the components are started in very specific order. Unless you know what you are doing, @@ -405,27 +336,7 @@ impl NymClient { sphinx_message_sender.clone(), ); self.start_cover_traffic_stream(shared_topology_accessor, sphinx_message_sender); - - match self.config.get_socket_type() { - SocketType::WebSocket => { - self.start_socks5_listener(received_buffer_request_sender, input_sender) - } - SocketType::None => { - // if we did not start the socket, it means we're running (supposedly) in the native mode - // and hence we should announce 'ourselves' to the buffer - let (reconstructed_sender, reconstructed_receiver) = mpsc::unbounded(); - - // tell the buffer to start sending stuff to us - received_buffer_request_sender - .unbounded_send(ReceivedBufferMessage::ReceiverAnnounce( - reconstructed_sender, - )) - .expect("the buffer request failed!"); - - self.receive_tx = Some(reconstructed_receiver); - self.input_tx = Some(input_sender); - } - } + self.start_socks5_listener(received_buffer_request_sender, input_sender); info!("Client startup finished!"); info!( @@ -438,7 +349,7 @@ impl NymClient { ); info!( "Gateway identity public key is: {:?}", - self.config.get_gateway_id() + self.config.get_base().get_gateway_id() ); } } diff --git a/clients/socks5/src/commands/init.rs b/clients/socks5/src/commands/init.rs index 4200a9b1ba..4d0deed6bd 100644 --- a/clients/socks5/src/commands/init.rs +++ b/clients/socks5/src/commands/init.rs @@ -13,6 +13,7 @@ // limitations under the License. use crate::built_info; +use crate::client::config::Config; use crate::commands::override_config; use clap::{App, Arg, ArgMatches}; use client_core::client::key_manager::KeyManager; @@ -37,6 +38,12 @@ pub fn command_args<'a, 'b>() -> clap::App<'a, 'b> { .takes_value(true) .required(true) ) + .arg(Arg::with_name("provider") + .long("provider") + .help("Address of the socks5 provider to send messages to.") + .takes_value(true) + .required(true) + ) .arg(Arg::with_name("gateway") .long("gateway") .help("Id of the gateway we have preference to connect to. If left empty, a random gateway will be chosen.") @@ -47,14 +54,10 @@ pub fn command_args<'a, 'b>() -> clap::App<'a, 'b> { .help("Address of the directory server the client is getting topology from") .takes_value(true), ) - .arg(Arg::with_name("disable-socket") - .long("disable-socket") - .help("Whether to not start the websocket") - ) .arg(Arg::with_name("port") .short("p") .long("port") - .help("Port for the socket (if applicable) to listen on in all subsequent runs") + .help("Port for the socket to listen on in all subsequent runs") .takes_value(true) ) .arg(Arg::with_name("fastmode") @@ -149,48 +152,53 @@ pub fn execute(matches: &ArgMatches) { println!("Initialising client..."); let id = matches.value_of("id").unwrap(); // required for now - let mut config = client_core::config::Config::new(id); + let provider_address = matches.value_of("provider").unwrap(); + + let mut config = Config::new(id, provider_address); let mut rng = OsRng; config = override_config(config, matches); if matches.is_present("fastmode") { - config = config.set_high_default_traffic_volume(); + config.get_base_mut().set_high_default_traffic_volume(); } // create identity, encryption and ack keys. let mut key_manager = KeyManager::new(&mut rng); // if there is no gateway chosen, get a random-ish one from the topology - if config.get_gateway_id().is_empty() { + if config.get_base().get_gateway_id().is_empty() { // TODO: is there perhaps a way to make it work without having to spawn entire runtime? let mut rt = tokio::runtime::Runtime::new().unwrap(); let (gateway_id, gateway_listener, shared_key) = rt.block_on(choose_gateway( - config.get_directory_server(), + config.get_base().get_directory_server(), key_manager.identity_keypair(), )); - config = config - .with_gateway_id(gateway_id) + config.get_base_mut().with_gateway_id(gateway_id); + config + .get_base_mut() .with_gateway_listener(gateway_listener); key_manager.insert_gateway_shared_key(shared_key) } // we specified our gateway but don't know its physical address - if config.get_gateway_listener().is_empty() { + if config.get_base().get_gateway_listener().is_empty() { // TODO: is there perhaps a way to make it work without having to spawn entire runtime? let mut rt = tokio::runtime::Runtime::new().unwrap(); let gateway_listener = rt .block_on(get_gateway_listener( - config.get_directory_server(), - &config.get_gateway_id(), + config.get_base().get_directory_server(), + &config.get_base().get_gateway_id(), )) .expect("No gateway with provided id exists!"); - config = config.with_gateway_listener(gateway_listener); + config + .get_base_mut() + .with_gateway_listener(gateway_listener); } - let pathfinder = ClientKeyPathfinder::new_from_config(&config); + let pathfinder = ClientKeyPathfinder::new_from_config(config.get_base()); key_manager .store_keys(&pathfinder) .expect("Failed to generated keys"); @@ -204,7 +212,7 @@ pub fn execute(matches: &ArgMatches) { println!( "Unless overridden in all `nym-client run` we will be talking to the following gateway: {}...", - config.get_gateway_id(), + config.get_base().get_gateway_id(), ); println!("Client configuration completed.\n\n\n") diff --git a/clients/socks5/src/commands/mod.rs b/clients/socks5/src/commands/mod.rs index 4eba736317..2906c40d9e 100644 --- a/clients/socks5/src/commands/mod.rs +++ b/clients/socks5/src/commands/mod.rs @@ -12,23 +12,19 @@ // See the License for the specific language governing permissions and // limitations under the License. +use crate::client::config::Config; use clap::ArgMatches; -use client_core::config::{Config, SocketType}; pub mod init; pub mod run; pub(crate) fn override_config(mut config: Config, matches: &ArgMatches) -> Config { if let Some(directory) = matches.value_of("directory") { - config = config.with_custom_directory(directory); + config.get_base_mut().with_custom_directory(directory); } if let Some(gateway_id) = matches.value_of("gateway") { - config = config.with_gateway_id(gateway_id); - } - - if matches.is_present("disable-socket") { - config = config.with_socket(SocketType::None); + config.get_base_mut().with_gateway_id(gateway_id); } if let Some(port) = matches.value_of("port").map(|port| port.parse::()) { diff --git a/clients/socks5/src/commands/run.rs b/clients/socks5/src/commands/run.rs index 464b3d962c..f8c9aea11c 100644 --- a/clients/socks5/src/commands/run.rs +++ b/clients/socks5/src/commands/run.rs @@ -12,10 +12,10 @@ // See the License for the specific language governing permissions and // limitations under the License. +use crate::client::config::Config; use crate::client::NymClient; use crate::commands::override_config; use clap::{App, Arg, ArgMatches}; -use client_core::config::Config; use config::NymConfig; pub fn command_args<'a, 'b>() -> clap::App<'a, 'b> { @@ -33,6 +33,11 @@ pub fn command_args<'a, 'b>() -> clap::App<'a, 'b> { .help("Custom path to the nym-mixnet-client configuration file") .takes_value(true) ) + .arg(Arg::with_name("provider") + .long("provider") + .help("Address of the socks5 provider to send messages to.") + .takes_value(true) + ) .arg(Arg::with_name("directory") .long("directory") .help("Address of the directory server the client is getting topology from") @@ -43,14 +48,10 @@ pub fn command_args<'a, 'b>() -> clap::App<'a, 'b> { .help("Id of the gateway we want to connect to. If overridden, it is user's responsibility to ensure prior registration happened") .takes_value(true) ) - .arg(Arg::with_name("disable-socket") - .long("disable-socket") - .help("Whether to not start the websocket") - ) .arg(Arg::with_name("port") .short("p") .long("port") - .help("Port for the socket (if applicable) to listen on") + .help("Port for the socket to listen on") .takes_value(true) ) } diff --git a/clients/socks5/src/socks/server.rs b/clients/socks5/src/socks/server.rs index 12d6141457..7fa3d897c2 100644 --- a/clients/socks5/src/socks/server.rs +++ b/clients/socks5/src/socks/server.rs @@ -25,11 +25,13 @@ impl SphinxSocksServer { /// Create a new SphinxSocks instance pub(crate) fn new( port: u16, - ip: &str, authenticator: Authenticator, service_provider: Recipient, self_address: Recipient, ) -> Self { + // hardcode ip as we (presumably) ONLY want to listen locally. If we change it, we can + // just modify the config + let ip = "127.0.0.1"; info!("Listening on {}:{}", ip, port); SphinxSocksServer { authenticator,