diff --git a/automap/Cargo.lock b/automap/Cargo.lock index 4c474a63d..0ac7f92e4 100644 --- a/automap/Cargo.lock +++ b/automap/Cargo.lock @@ -125,7 +125,7 @@ checksum = "cdb031dd78e28731d87d56cc8ffef4a8f36ca26c38fe2de700543e627f8a464a" [[package]] name = "automap" -version = "0.7.0" +version = "0.7.2" dependencies = [ "crossbeam-channel 0.5.1", "flexi_logger", @@ -908,7 +908,7 @@ dependencies = [ [[package]] name = "masq_lib" -version = "0.7.1" +version = "0.7.2" dependencies = [ "actix", "clap", diff --git a/automap/Cargo.toml b/automap/Cargo.toml index e3c98ef7f..f29ec687c 100644 --- a/automap/Cargo.toml +++ b/automap/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "automap" -version = "0.7.0" +version = "0.7.2" authors = ["Dan Wiebe ", "MASQ"] license = "GPL-3.0-only" copyright = "Copyright (c) 2019-2021, MASQ (https://masq.ai) and/or its affiliates. All rights reserved." diff --git a/ci/all.sh b/ci/all.sh index 2eee8d215..29492b9f4 100755 --- a/ci/all.sh +++ b/ci/all.sh @@ -44,4 +44,5 @@ echo "*** AUTOMAP HEAD cd "$CI_DIR/../automap" ci/all.sh "$PARENT_DIR" echo "*** AUTOMAP TAIL ***" -echo "*********************************************************************************************************" \ No newline at end of file +echo "*********************************************************************************************************" + diff --git a/dns_utility/Cargo.lock b/dns_utility/Cargo.lock index 850141b1f..588d01413 100644 --- a/dns_utility/Cargo.lock +++ b/dns_utility/Cargo.lock @@ -407,7 +407,7 @@ dependencies = [ [[package]] name = "dns_utility" -version = "0.7.1" +version = "0.7.2" dependencies = [ "core-foundation", "ipconfig 0.2.2", @@ -811,7 +811,7 @@ dependencies = [ [[package]] name = "masq_lib" -version = "0.7.1" +version = "0.7.2" dependencies = [ "actix", "clap", diff --git a/dns_utility/Cargo.toml b/dns_utility/Cargo.toml index 21cbebca3..5b1a93b60 100644 --- a/dns_utility/Cargo.toml +++ b/dns_utility/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "dns_utility" -version = "0.7.1" +version = "0.7.2" license = "GPL-3.0-only" authors = ["Dan Wiebe ", "MASQ"] copyright = "Copyright (c) 2019, MASQ (https://masq.ai) and/or its affiliates. All rights reserved." diff --git a/masq/Cargo.toml b/masq/Cargo.toml index c796f6b7d..7582f21b3 100644 --- a/masq/Cargo.toml +++ b/masq/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "masq" -version = "0.7.1" +version = "0.7.2" authors = ["Dan Wiebe ", "MASQ"] license = "GPL-3.0-only" copyright = "Copyright (c) 2019, MASQ (https://masq.ai) and/or its affiliates. All rights reserved." diff --git a/masq_lib/Cargo.toml b/masq_lib/Cargo.toml index 04eccbb2f..2460d570f 100644 --- a/masq_lib/Cargo.toml +++ b/masq_lib/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "masq_lib" -version = "0.7.1" +version = "0.7.2" authors = ["Dan Wiebe ", "MASQ"] license = "GPL-3.0-only" copyright = "Copyright (c) 2019, MASQ (https://masq.ai) and/or its affiliates. All rights reserved." diff --git a/multinode_integration_tests/Cargo.toml b/multinode_integration_tests/Cargo.toml index b31f772e5..d230c904c 100644 --- a/multinode_integration_tests/Cargo.toml +++ b/multinode_integration_tests/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "multinode_integration_tests" -version = "0.7.1" +version = "0.7.2" authors = ["Dan Wiebe ", "MASQ"] license = "GPL-3.0-only" copyright = "Copyright (c) 2019, MASQ (https://masq.ai) and/or its affiliates. All rights reserved." diff --git a/multinode_integration_tests/docker_dump.sh b/multinode_integration_tests/docker_dump.sh index 17295fc53..96e8bfe42 100755 --- a/multinode_integration_tests/docker_dump.sh +++ b/multinode_integration_tests/docker_dump.sh @@ -2,7 +2,7 @@ OUTPUT_DIR="$1" mkdir -p "$OUTPUT_DIR" -for container in $(docker ps -a | tail -n +2 | cut -c 128- | grep test_node_) +for container in $(docker ps -a | tail -n +2 | cut -c 177- | grep test_node_) do ./docker_logs.sh "$container" > "$OUTPUT_DIR"/"$container".log done diff --git a/multinode_integration_tests/src/masq_mock_node.rs b/multinode_integration_tests/src/masq_mock_node.rs index 1b73a95bf..dfccbad08 100644 --- a/multinode_integration_tests/src/masq_mock_node.rs +++ b/multinode_integration_tests/src/masq_mock_node.rs @@ -12,9 +12,10 @@ use node_lib::hopper::live_cores_package::LiveCoresPackage; use node_lib::json_masquerader::JsonMasquerader; use node_lib::masquerader::{MasqueradeError, Masquerader}; use node_lib::neighborhood::gossip::Gossip_0v1; +use node_lib::neighborhood::node_record::NodeRecord; +use node_lib::sub_lib::cryptde::CryptData; use node_lib::sub_lib::cryptde::PublicKey; use node_lib::sub_lib::cryptde::{encodex, CryptDE}; -use node_lib::sub_lib::cryptde::{CodexError, CryptData, CryptdecError}; use node_lib::sub_lib::cryptde_null::CryptDENull; use node_lib::sub_lib::cryptde_real::CryptDEReal; use node_lib::sub_lib::framer::Framer; @@ -29,7 +30,6 @@ use node_lib::test_utils::data_hunk_framer::DataHunkFramer; use node_lib::test_utils::{make_paying_wallet, make_wallet}; use std::cell::RefCell; use std::convert::TryFrom; -use std::io; use std::io::{Error, ErrorKind, Read, Write}; use std::net::Ipv4Addr; use std::net::SocketAddr; @@ -42,6 +42,10 @@ use std::time::{Duration, Instant}; pub struct MASQMockNode { control_stream: RefCell, + // retain this Rc pointer because as long as there is at least one reference we won't drop + // the actual structure, instead, only the reference count will be affected; unlike to situation + // with this structure creating its clones directly, then the whole Docker container would + // immediately halt for this Node...because that's how its Drop implementation works... guts: Rc, } @@ -98,6 +102,10 @@ impl MASQNode for MASQMockNode { self.signing_cryptde().unwrap().public_key() } + fn alias_public_key(&self) -> &PublicKey { + self.alias_cryptde_null().unwrap().public_key() + } + fn ip_address(&self) -> IpAddr { self.guts.node_addr.ip_addr() } @@ -139,63 +147,90 @@ impl MASQNode for MASQMockNode { } } -impl MASQMockNode { - pub fn start_with_public_key( +pub struct MutableMASQMockNode { + pub control_stream: RefCell, + configurable_guts: MASQMockNodeGuts, +} + +impl MutableMASQMockNode { + pub fn absorb_configuration(&mut self, node_record: &NodeRecord) { + // Copy attributes from the NodeRecord into the MASQNode. + self.configurable_guts.earning_wallet = node_record.earning_wallet(); + self.configurable_guts.rate_pack = *node_record.rate_pack(); + } +} + +impl From for MASQMockNode { + fn from(mutable_handle: MutableMASQMockNode) -> Self { + MASQMockNode { + control_stream: mutable_handle.control_stream, + guts: Rc::new(mutable_handle.configurable_guts), + } + } +} + +pub trait MASQMockNodeStarter { + fn start( + &self, ports: Vec, index: usize, host_node_parent_dir: Option, - public_key: &PublicKey, + public_key_opt: Option<&PublicKey>, chain: Chain, - ) -> MASQMockNode { - let main_cryptde = CryptDENull::from(public_key, chain); - let mut key = public_key.as_slice().to_vec(); - key.reverse(); - let alias_cryptde = CryptDENull::from(&PublicKey::new(&key), chain); - let cryptde_enum = CryptDEEnum::Fake((main_cryptde, alias_cryptde)); - Self::start_with_cryptde_enum(ports, index, host_node_parent_dir, cryptde_enum) - } + ) -> T; +} + +pub struct ImmutableMASQMockNodeStarter {} - pub fn start( +impl MASQMockNodeStarter for ImmutableMASQMockNodeStarter { + fn start( + &self, ports: Vec, index: usize, host_node_parent_dir: Option, + public_key_opt: Option<&PublicKey>, chain: Chain, ) -> MASQMockNode { - let cryptde_enum = CryptDEEnum::Real(CryptDEReal::new(chain)); - Self::start_with_cryptde_enum(ports, index, host_node_parent_dir, cryptde_enum) + let (control_stream, mock_node_guts) = MASQMockNode::start_masq_mock_node_with_bare_guts( + ports, + index, + host_node_parent_dir, + public_key_opt, + chain, + ); + MASQMockNode { + control_stream, + guts: Rc::new(mock_node_guts), + } } +} - fn start_with_cryptde_enum( +pub struct MutableMASQMockNodeStarter {} + +impl MASQMockNodeStarter for MutableMASQMockNodeStarter { + fn start( + &self, ports: Vec, index: usize, host_node_parent_dir: Option, - cryptde_enum: CryptDEEnum, - ) -> MASQMockNode { - let name = format!("mock_node_{}", index); - let node_addr = NodeAddr::new(&IpAddr::V4(Ipv4Addr::new(172, 18, 1, index as u8)), &ports); - let earning_wallet = make_wallet(format!("{}_earning", name).as_str()); - let consuming_wallet = Some(make_paying_wallet(format!("{}_consuming", name).as_bytes())); - MASQNodeUtils::clean_up_existing_container(&name[..]); - Self::do_docker_run(&node_addr, host_node_parent_dir, &name); - let wait_addr = SocketAddr::new(node_addr.ip_addr(), CONTROL_STREAM_PORT); - let control_stream = RefCell::new(Self::wait_for_startup(wait_addr, &name)); - let framer = RefCell::new(DataHunkFramer::new()); - let guts = Rc::new(MASQMockNodeGuts { - name, - node_addr, - earning_wallet, - consuming_wallet, - rate_pack: DEFAULT_RATE_PACK, - cryptde_enum, - framer, - chain: TEST_DEFAULT_MULTINODE_CHAIN, - }); - MASQMockNode { + public_key_opt: Option<&PublicKey>, + chain: Chain, + ) -> MutableMASQMockNode { + let (control_stream, mock_node_guts) = MASQMockNode::start_masq_mock_node_with_bare_guts( + ports, + index, + host_node_parent_dir, + public_key_opt, + chain, + ); + MutableMASQMockNode { control_stream, - guts, + configurable_guts: mock_node_guts, } } +} +impl MASQMockNode { pub fn cryptde_real(&self) -> Option<&CryptDEReal> { match &self.guts.cryptde_enum { CryptDEEnum::Fake(_) => None, @@ -203,7 +238,7 @@ impl MASQMockNode { } } - pub fn transmit_data(&self, data_hunk: DataHunk) -> Result<(), io::Error> { + pub fn transmit_data(&self, data_hunk: DataHunk) -> Result<(), Error> { let to_transmit: Vec = data_hunk.into(); match self.control_stream.borrow_mut().write(&to_transmit[..]) { Ok(_) => Ok(()), @@ -218,7 +253,7 @@ impl MASQMockNode { masquerader: &dyn Masquerader, target_key: &PublicKey, target_addr: SocketAddr, - ) -> Result<(), io::Error> { + ) -> Result<(), Error> { let (lcp, _) = LiveCoresPackage::from_incipient(package, self.signing_cryptde().unwrap()).unwrap(); let encrypted_data = encodex(self.signing_cryptde().unwrap(), target_key, &lcp).unwrap(); @@ -237,7 +272,7 @@ impl MASQMockNode { gossip: Gossip_0v1, target_key: &PublicKey, target_addr: SocketAddr, - ) -> Result<(), io::Error> { + ) -> Result<(), Error> { let masquerader = JsonMasquerader::new(); let route = Route::single_hop(target_key, self.signing_cryptde().unwrap()).unwrap(); let package = IncipientCoresPackage::new( @@ -256,31 +291,34 @@ impl MASQMockNode { ) } - pub fn transmit_debut(&self, receiver: &dyn MASQNode) -> Result<(), io::Error> { - self.transmit_multinode_gossip(receiver, &SingleNode::new(self)) + pub fn transmit_debut(&self, receiver: &dyn MASQNode) -> Result<(), Error> { + let gossip = SingleNode::new(self); + self.transmit_multinode_gossip(receiver, &gossip) } pub fn transmit_pass( &self, receiver: &dyn MASQNode, target: &dyn MASQNode, - ) -> Result<(), io::Error> { - self.transmit_multinode_gossip(receiver, &SingleNode::new(target)) + ) -> Result<(), Error> { + let gossip = SingleNode::new(target); + self.transmit_multinode_gossip(receiver, &gossip) } pub fn transmit_introduction( &self, receiver: &dyn MASQNode, introducee: &dyn MASQNode, - ) -> Result<(), io::Error> { - self.transmit_multinode_gossip(receiver, &Introduction::new(self, introducee)) + ) -> Result<(), Error> { + let gossip = Introduction::new(self, introducee); + self.transmit_multinode_gossip(receiver, &gossip) } pub fn transmit_multinode_gossip( &self, receiver: &dyn MASQNode, multinode_gossip: &dyn MultinodeGossip, - ) -> Result<(), io::Error> { + ) -> Result<(), Error> { let gossip = multinode_gossip.render(); self.transmit_gossip( receiver.port_list()[0], @@ -290,7 +328,7 @@ impl MASQMockNode { ) } - pub fn wait_for_data(&self, timeout: Duration) -> Result { + pub fn wait_for_data(&self, timeout: Duration) -> Result { let mut buf = [0u8; 16384]; let mut framer = self.guts.framer.borrow_mut(); let mut control_stream = self.control_stream.borrow_mut(); @@ -320,15 +358,10 @@ impl MASQMockNode { &self, masquerader: &dyn Masquerader, timeout: Duration, - ) -> Result<(SocketAddr, SocketAddr, LiveCoresPackage), io::Error> { + ) -> Result<(SocketAddr, SocketAddr, LiveCoresPackage), Error> { let stop_at = Instant::now().add(timeout); let mut accumulated_data: Vec = vec![]; - // dunno why these are a problem; they _are_ used on the last line of the function. - #[allow(unused_assignments)] - let mut from_opt: Option = None; - #[allow(unused_assignments)] - let mut to_opt: Option = None; - let unmasked_chunk: Vec = loop { + let (unmasked_chunk, socket_from, socket_to) = loop { match self.wait_for_data(Duration::from_millis(100)) { Err(ref e) if e.kind() == ErrorKind::WouldBlock => { if Instant::now() > stop_at { @@ -339,14 +372,14 @@ impl MASQMockNode { Err(e) => return Err(e), Ok(data_hunk) => { accumulated_data.extend(data_hunk.data); - from_opt = Some(data_hunk.from); - to_opt = Some(data_hunk.to); match masquerader.try_unmask(&accumulated_data) { Err(MasqueradeError::NotThisMasquerader) => { panic!("Wrong Masquerader supplied to wait_for_package") } Err(_) => continue, - Ok(unmasked_chunk) => break unmasked_chunk.chunk, + Ok(unmasked_chunk) => { + break (unmasked_chunk.chunk, data_hunk.from, data_hunk.to) + } } } } @@ -358,7 +391,7 @@ impl MASQMockNode { .unwrap(); let live_cores_package = serde_cbor::de::from_slice::(decrypted_data.as_slice()).unwrap(); - Ok((from_opt.unwrap(), to_opt.unwrap(), live_cores_package)) + Ok((socket_from, socket_to, live_cores_package)) } pub fn wait_for_gossip(&self, timeout: Duration) -> Option<(Gossip_0v1, IpAddr)> { @@ -368,18 +401,9 @@ impl MASQMockNode { let incoming_cores_package = match package.to_expired( from, self.main_cryptde_null().unwrap(), - self.alias_cryptde_null().unwrap(), + self.main_cryptde_null().unwrap(), ) { Ok(icp) => icp, - Err(CodexError::DecryptionError(CryptdecError::OpeningFailed)) => match package - .to_expired( - from, - self.main_cryptde_null().unwrap(), - self.main_cryptde_null().unwrap(), - ) { - Ok(icp) => icp, - Err(e) => panic!("Couldn't expire LiveCoresPackage: {:?}", e), - }, Err(e) => panic!("Couldn't expire LiveCoresPackage: {:?}", e), }; match incoming_cores_package.payload { @@ -427,6 +451,58 @@ impl MASQMockNode { stream.shutdown(Shutdown::Both).unwrap(); } + fn start_masq_mock_node_with_bare_guts( + ports: Vec, + index: usize, + host_node_parent_dir: Option, + public_key_opt: Option<&PublicKey>, + chain: Chain, + ) -> (RefCell, MASQMockNodeGuts) { + let cryptde_enum = Self::initiate_cryptde_enum(public_key_opt, chain); + Self::start_with_cryptde_enum(ports, index, host_node_parent_dir, cryptde_enum) + } + + fn initiate_cryptde_enum(public_key_opt: Option<&PublicKey>, chain: Chain) -> CryptDEEnum { + match public_key_opt { + Some(public_key) => { + let main_cryptde = CryptDENull::from(public_key, chain); + let mut key = public_key.as_slice().to_vec(); + key.reverse(); + let alias_cryptde = CryptDENull::from(&PublicKey::new(&key), chain); + CryptDEEnum::Fake((main_cryptde, alias_cryptde)) + } + None => CryptDEEnum::Real(CryptDEReal::new(chain)), + } + } + + fn start_with_cryptde_enum( + ports: Vec, + index: usize, + host_node_parent_dir: Option, + cryptde_enum: CryptDEEnum, + ) -> (RefCell, MASQMockNodeGuts) { + let name = format!("mock_node_{}", index); + let node_addr = NodeAddr::new(&IpAddr::V4(Ipv4Addr::new(172, 18, 1, index as u8)), &ports); + let earning_wallet = make_wallet(format!("{}_earning", name).as_str()); + let consuming_wallet = Some(make_paying_wallet(format!("{}_consuming", name).as_bytes())); + MASQNodeUtils::clean_up_existing_container(&name[..]); + MASQMockNode::do_docker_run(&node_addr, host_node_parent_dir, &name); + let wait_addr = SocketAddr::new(node_addr.ip_addr(), CONTROL_STREAM_PORT); + let control_stream = RefCell::new(MASQMockNode::wait_for_startup(wait_addr, &name)); + let framer = RefCell::new(DataHunkFramer::new()); + let guts = MASQMockNodeGuts { + name, + node_addr, + earning_wallet, + consuming_wallet, + rate_pack: DEFAULT_RATE_PACK, + cryptde_enum, + framer, + chain: TEST_DEFAULT_MULTINODE_CHAIN, + }; + (control_stream, guts) + } + fn do_docker_run(node_addr: &NodeAddr, host_node_parent_dir: Option, name: &str) { let root = match host_node_parent_dir { Some(dir) => dir, diff --git a/multinode_integration_tests/src/masq_node.rs b/multinode_integration_tests/src/masq_node.rs index 7964e6262..02e6d7cea 100644 --- a/multinode_integration_tests/src/masq_node.rs +++ b/multinode_integration_tests/src/masq_node.rs @@ -179,6 +179,7 @@ pub enum PortSelector { } pub trait MASQNode: Any { + // This is the name of the Docker container on which this MASQNode will run. fn name(&self) -> &str; // This is the NodeReference stated by the Node in the console. Its IP address won't be accurate if it's a zero-hop Node. fn node_reference(&self) -> NodeReference; @@ -190,6 +191,8 @@ pub trait MASQNode: Any { fn signing_cryptde(&self) -> Option<&dyn CryptDE>; // A reference to this MASQNode's main public key. fn main_public_key(&self) -> &PublicKey; + // A reference to this MASQNode's alias public key. + fn alias_public_key(&self) -> &PublicKey; // This is the IP address of the container in which the Node is running. fn ip_address(&self) -> IpAddr; fn port_list(&self) -> Vec; diff --git a/multinode_integration_tests/src/masq_node_cluster.rs b/multinode_integration_tests/src/masq_node_cluster.rs index 5ffe597a5..86a94af54 100644 --- a/multinode_integration_tests/src/masq_node_cluster.rs +++ b/multinode_integration_tests/src/masq_node_cluster.rs @@ -1,6 +1,9 @@ // Copyright (c) 2019, MASQ (https://masq.ai) and/or its affiliates. All rights reserved. use crate::command::Command; -use crate::masq_mock_node::MASQMockNode; +use crate::masq_mock_node::{ + ImmutableMASQMockNodeStarter, MASQMockNode, MASQMockNodeStarter, MutableMASQMockNode, + MutableMASQMockNodeStarter, +}; use crate::masq_node::{MASQNode, MASQNodeUtils}; use crate::masq_real_node::MASQRealNode; use crate::masq_real_node::NodeStartupConfig; @@ -77,7 +80,7 @@ impl MASQNodeCluster { } pub fn start_mock_node_with_real_cryptde(&mut self, ports: Vec) -> MASQMockNode { - self.start_mock_node(ports, None) + self.start_mock_node_added_to_cluster(ports, None) } pub fn start_mock_node_with_public_key( @@ -85,30 +88,50 @@ impl MASQNodeCluster { ports: Vec, public_key: &PublicKey, ) -> MASQMockNode { - self.start_mock_node(ports, Some(public_key)) + self.start_mock_node_added_to_cluster(ports, Some(public_key)) } - fn start_mock_node( + pub fn start_mutable_mock_node_with_public_key( + &mut self, + ports: Vec, + public_key: &PublicKey, + ) -> MutableMASQMockNode { + self.start_mock_node(&MutableMASQMockNodeStarter {}, ports, Some(public_key)) + } + + fn start_mock_node_added_to_cluster( &mut self, ports: Vec, public_key_opt: Option<&PublicKey>, ) -> MASQMockNode { + let mock_node = + self.start_mock_node(&ImmutableMASQMockNodeStarter {}, ports, public_key_opt); + let name = mock_node.name().to_string(); + self.mock_nodes.insert(name.clone(), mock_node); + self.mock_nodes.get(&name).unwrap().clone() + } + + fn start_mock_node( + &mut self, + mock_node_starter: &dyn MASQMockNodeStarter, + ports: Vec, + public_key_opt: Option<&PublicKey>, + ) -> T { let index = self.next_index; self.next_index += 1; - let node = match public_key_opt { - Some(public_key) => MASQMockNode::start_with_public_key( - ports, - index, - self.host_node_parent_dir.clone(), - public_key, - self.chain, - ), - None => { - MASQMockNode::start(ports, index, self.host_node_parent_dir.clone(), self.chain) - } - }; - let name = node.name().to_string(); - self.mock_nodes.insert(name.clone(), node); + mock_node_starter.start( + ports, + index, + self.host_node_parent_dir.clone(), + public_key_opt, + self.chain, + ) + } + + pub fn finalize_and_add(&mut self, mutable_mock_node: MutableMASQMockNode) -> MASQMockNode { + let mock_node = MASQMockNode::from(mutable_mock_node); + let name = mock_node.name().to_string(); + self.mock_nodes.insert(name.clone(), mock_node); self.mock_nodes.get(&name).unwrap().clone() } diff --git a/multinode_integration_tests/src/masq_real_node.rs b/multinode_integration_tests/src/masq_real_node.rs index de1fdce2f..0bdeb75bd 100644 --- a/multinode_integration_tests/src/masq_real_node.rs +++ b/multinode_integration_tests/src/masq_real_node.rs @@ -696,6 +696,12 @@ impl MASQNode for MASQRealNode { &self.guts.node_reference.public_key } + fn alias_public_key(&self) -> &PublicKey { + self.alias_cryptde_null() + .expect("Alias Cryptde Null not found") + .public_key() + } + fn ip_address(&self) -> IpAddr { self.guts.container_ip } diff --git a/multinode_integration_tests/src/neighborhood_constructor.rs b/multinode_integration_tests/src/neighborhood_constructor.rs index ecf1f84bf..3847528bc 100644 --- a/multinode_integration_tests/src/neighborhood_constructor.rs +++ b/multinode_integration_tests/src/neighborhood_constructor.rs @@ -12,7 +12,6 @@ use node_lib::neighborhood::neighborhood_database::NeighborhoodDatabase; use node_lib::neighborhood::node_record::{NodeRecord, NodeRecordMetadata}; use node_lib::neighborhood::AccessibleGossipRecord; use node_lib::sub_lib::cryptde::PublicKey; -use node_lib::sub_lib::neighborhood::DEFAULT_RATE_PACK; use node_lib::sub_lib::utils::time_t_timestamp; use node_lib::test_utils::neighborhood_test_utils::db_from_node; use std::collections::{BTreeSet, HashMap}; @@ -67,6 +66,7 @@ pub fn construct_neighborhood( .consuming_wallet_info(make_consuming_wallet_info( model_db.root().public_key().to_string().as_str(), )) + .rate_pack(model_db.root().inner.rate_pack) .chain(cluster.chain) .build(), ); @@ -187,7 +187,10 @@ fn form_mock_node_skeleton( .full_neighbor_keys(model_db) .into_iter() .map(|model_node_key| { - let node = cluster.start_mock_node_with_public_key(vec![10000], model_node_key); + let mut configurable_node = + cluster.start_mutable_mock_node_with_public_key(vec![10000], model_node_key); + configurable_node.absorb_configuration(model_db.node_by_key(model_node_key).unwrap()); + let node = cluster.finalize_and_add(configurable_node); node.transmit_debut(real_node).unwrap(); node.wait_for_gossip(Duration::from_secs(2)).unwrap(); let standard_gossip = StandardBuilder::new() @@ -213,7 +216,6 @@ fn modify_node( None => model_node.node_addr_opt(), }; gossip_node.metadata.node_addr_opt = node_addr_opt; - gossip_node.inner.rate_pack = DEFAULT_RATE_PACK; gossip_node.inner.version = 2; gossip_node.inner.neighbors = model_node .half_neighbor_keys() @@ -247,9 +249,9 @@ fn from_masq_node_to_node_record(masq_node: &dyn MASQNode) -> NodeRecord { NodeRecord { inner: agr.inner.clone(), metadata: NodeRecordMetadata { - desirable_for_exit: true, last_update: time_t_timestamp(), node_addr_opt: agr.node_addr_opt.clone(), + unreachable_hosts: Default::default(), }, signed_gossip: agr.signed_gossip.clone(), signature: agr.signature, diff --git a/multinode_integration_tests/tests/bookkeeping_test.rs b/multinode_integration_tests/tests/bookkeeping_test.rs index 98b4d3630..05b2d6b7e 100644 --- a/multinode_integration_tests/tests/bookkeeping_test.rs +++ b/multinode_integration_tests/tests/bookkeeping_test.rs @@ -26,6 +26,7 @@ fn provided_and_consumed_services_are_recorded_in_databases() { thread::sleep(Duration::from_secs(10)); let mut client = originating_node.make_client(8080); + client.set_timeout(Duration::from_secs(10)); let request = "GET / HTTP/1.1\r\nHost: example.com\r\n\r\n".as_bytes(); client.send_chunk(request); @@ -40,7 +41,7 @@ fn provided_and_consumed_services_are_recorded_in_databases() { let payables = non_pending_payables(&originating_node); // Waiting until the serving nodes have finished accruing their receivables - thread::sleep(Duration::from_secs(2)); + thread::sleep(Duration::from_secs(3)); // get all receivables from all other nodes let receivable_balances = non_originating_nodes diff --git a/multinode_integration_tests/tests/communication_failure_test.rs b/multinode_integration_tests/tests/communication_failure_test.rs index 063b07c69..b051367ad 100644 --- a/multinode_integration_tests/tests/communication_failure_test.rs +++ b/multinode_integration_tests/tests/communication_failure_test.rs @@ -1,12 +1,28 @@ // Copyright (c) 2019, MASQ (https://masq.ai) and/or its affiliates. All rights reserved. +use masq_lib::test_utils::utils::TEST_DEFAULT_MULTINODE_CHAIN; use masq_lib::utils::find_free_port; -use multinode_integration_tests_lib::masq_node::MASQNode; +use multinode_integration_tests_lib::masq_node::{MASQNode, PortSelector}; +use multinode_integration_tests_lib::masq_node_client::MASQNodeClient; use multinode_integration_tests_lib::masq_node_cluster::MASQNodeCluster; use multinode_integration_tests_lib::masq_real_node::NodeStartupConfigBuilder; +use multinode_integration_tests_lib::neighborhood_constructor::construct_neighborhood; +use node_lib::json_masquerader::JsonMasquerader; +use node_lib::neighborhood::neighborhood_database::NeighborhoodDatabase; +use node_lib::neighborhood::node_record::NodeRecord; use node_lib::neighborhood::AccessibleGossipRecord; -use node_lib::sub_lib::cryptde::PublicKey; +use node_lib::sub_lib::cryptde::{CryptDE, PublicKey}; +use node_lib::sub_lib::cryptde_null::CryptDENull; +use node_lib::sub_lib::hopper::{ExpiredCoresPackage, IncipientCoresPackage, MessageType}; +use node_lib::sub_lib::neighborhood::RatePack; +use node_lib::sub_lib::proxy_client::DnsResolveFailure_0v1; +use node_lib::sub_lib::route::Route; +use node_lib::sub_lib::versioned_data::VersionedData; +use node_lib::test_utils::assert_string_contains; +use node_lib::test_utils::neighborhood_test_utils::{db_from_node, make_node_record}; use std::convert::TryInto; +use std::net::SocketAddr; +use std::str::FromStr; use std::time::Duration; #[test] @@ -107,3 +123,155 @@ fn neighborhood_notified_of_newly_missing_node() { dot_graph ); } + +#[test] +fn dns_resolution_failure_no_longer_blacklists_exit_node_for_all_hosts() { + let mut cluster = MASQNodeCluster::start().unwrap(); + // Make network: + // originating_node --> relay1 --> relay2 --> cheap_exit + // | + // +--> normal_exit + let (originating_node, relay1_mock, cheap_exit_key, normal_exit_key) = { + let originating_node: NodeRecord = make_node_record(1234, true); + let mut db: NeighborhoodDatabase = db_from_node(&originating_node); + let relay1_key = db.add_node(make_node_record(2345, true)).unwrap(); + let relay2_key = db.add_node(make_node_record(3456, false)).unwrap(); + let mut cheap_exit_node = make_node_record(4567, false); + let normal_exit_node = make_node_record(5678, false); + cheap_exit_node.inner.rate_pack = cheaper_rate_pack(normal_exit_node.rate_pack(), 1); + let cheap_exit_key = db.add_node(cheap_exit_node).unwrap(); + let normal_exit_key = db.add_node(normal_exit_node).unwrap(); + db.add_arbitrary_full_neighbor(originating_node.public_key(), &relay1_key); + db.add_arbitrary_full_neighbor(&relay1_key, &relay2_key); + db.add_arbitrary_full_neighbor(&relay2_key, &cheap_exit_key); + db.add_arbitrary_full_neighbor(&relay2_key, &normal_exit_key); + let (_, originating_node, mut node_map) = construct_neighborhood(&mut cluster, db, vec![]); + let relay1_mock = node_map.remove(&relay1_key).unwrap(); + ( + originating_node, + relay1_mock, + cheap_exit_key, + normal_exit_key, + ) + }; + let mut client: MASQNodeClient = originating_node.make_client(8080); + let masquerader = JsonMasquerader::new(); + let originating_node_alias_cryptde = CryptDENull::from( + &originating_node.alias_public_key(), + TEST_DEFAULT_MULTINODE_CHAIN, + ); + let relay1_cryptde = + CryptDENull::from(&relay1_mock.main_public_key(), TEST_DEFAULT_MULTINODE_CHAIN); + let cheap_exit_cryptde = CryptDENull::from(&cheap_exit_key, TEST_DEFAULT_MULTINODE_CHAIN); + let key_length = normal_exit_key.len(); + + // This request should be routed through cheap_exit because it's cheaper + client.send_chunk("GET / HTTP/1.1\r\nHost: nonexistent.com\r\n\r\n".as_bytes()); + let (_, _, live_cores_package) = relay1_mock + .wait_for_package(&masquerader, Duration::from_secs(2)) + .unwrap(); + let (_, intended_exit_public_key) = + CryptDENull::extract_key_pair(key_length, &live_cores_package.payload); + assert_eq!(intended_exit_public_key, cheap_exit_key); + let expired_cores_package: ExpiredCoresPackage = live_cores_package + .to_expired( + SocketAddr::from_str("1.2.3.4:5678").unwrap(), + &relay1_cryptde, + &cheap_exit_cryptde, + ) + .unwrap(); + + // Respond with a DNS failure to put nonexistent.com on the unreachable-host list + let dns_fail_pkg = + make_dns_fail_package(expired_cores_package, &originating_node_alias_cryptde); + relay1_mock + .transmit_package( + relay1_mock.port_list()[0], + dns_fail_pkg, + &masquerader, + originating_node.main_public_key(), + originating_node.socket_addr(PortSelector::First), + ) + .unwrap(); + let dns_error_response: Vec = client.wait_for_chunk(); + let dns_error_response_str = String::from_utf8(dns_error_response).unwrap(); + assert_string_contains(&dns_error_response_str, "

Error 503

"); + assert_string_contains( + &dns_error_response_str, + "

Title: DNS Resolution Problem

", + ); + assert_string_contains( + &dns_error_response_str, + "

Subtitle: Exit Node couldn't resolve \"nonexistent.com\"

", + ); + assert_string_contains( + &dns_error_response_str, + &format!( + "

We chose the exit Node {cheap_exit_key} for your request to nonexistent.com; \ + but when it asked its DNS server to look up the IP address for nonexistent.com, \ + it wasn't found. If nonexistent.com exists, it will need to be looked up by a \ + different exit Node. We've deprioritized this exit Node. Reload the page, \ + and we'll try to find another.

" + ), + ); + + // This request should be routed through normal_exit because it's unreachable through cheap_exit + client.send_chunk("GET / HTTP/1.1\r\nHost: nonexistent.com\r\n\r\n".as_bytes()); + let (_, _, live_cores_package) = relay1_mock + .wait_for_package(&masquerader, Duration::from_secs(2)) + .unwrap(); + let (_, intended_exit_public_key) = + CryptDENull::extract_key_pair(key_length, &live_cores_package.payload); + assert_eq!(intended_exit_public_key, normal_exit_key); + + // TODO GH-674: Once the new exit node is picked, the Node will continue using that exit node + // for all its route unless the current exit node faces an extreme penalty. Maybe it's not the + // most economical solution. For example, in the assertion below, we may prefer to use + // cheaper_exit_node instead. + client.send_chunk("GET / HTTP/1.1\r\nHost: example.com\r\n\r\n".as_bytes()); + let (_, _, live_cores_package) = relay1_mock + .wait_for_package(&masquerader, Duration::from_secs(2)) + .unwrap(); + let (_, intended_exit_public_key) = + CryptDENull::extract_key_pair(key_length, &live_cores_package.payload); + assert_eq!(intended_exit_public_key, normal_exit_key); +} + +fn cheaper_rate_pack(base_rate_pack: &RatePack, decrement: u64) -> RatePack { + let mut result = *base_rate_pack; + result.exit_byte_rate -= decrement; + result.exit_service_rate -= decrement; + result +} + +fn make_dns_fail_package( + expired_cores_package: ExpiredCoresPackage, + destination_alias_cryptde: &dyn CryptDE, +) -> IncipientCoresPackage { + let route = Route { + hops: vec![ + expired_cores_package.remaining_route.hops[4].clone(), + expired_cores_package.remaining_route.hops[5].clone(), + expired_cores_package.remaining_route.hops[6].clone(), + ], + }; + let client_request_vdata = match expired_cores_package.payload { + MessageType::ClientRequest(vdata) => vdata, + x => panic!("Expected ClientRequest, got {:?}", x), + }; + let stream_key = client_request_vdata + .extract(&node_lib::sub_lib::migrations::client_request_payload::MIGRATIONS) + .unwrap() + .stream_key; + let dns_fail_vdata = VersionedData::new( + &node_lib::sub_lib::migrations::dns_resolve_failure::MIGRATIONS, + &DnsResolveFailure_0v1 { stream_key }, + ); + IncipientCoresPackage::new( + destination_alias_cryptde, + route, + MessageType::DnsResolveFailed(dns_fail_vdata), + destination_alias_cryptde.public_key(), + ) + .unwrap() +} diff --git a/multinode_integration_tests/tests/connection_termination_test.rs b/multinode_integration_tests/tests/connection_termination_test.rs index 41ba0a88c..5cb6f940c 100644 --- a/multinode_integration_tests/tests/connection_termination_test.rs +++ b/multinode_integration_tests/tests/connection_termination_test.rs @@ -338,7 +338,7 @@ fn create_request_icp( target_hostname: Some(format!("{}", server.local_addr().ip())), target_port: server.local_addr().port(), protocol: ProxyProtocol::HTTP, - originator_public_key: originating_node.main_public_key().clone(), + originator_alias_public_key: originating_node.main_public_key().clone(), }, )), exit_node.main_public_key(), @@ -383,7 +383,7 @@ fn create_meaningless_icp( target_hostname: Some(format!("nowhere.com")), target_port: socket_addr.port(), protocol: ProxyProtocol::HTTP, - originator_public_key: originating_node.main_public_key().clone(), + originator_alias_public_key: originating_node.main_public_key().clone(), }, )), exit_node.main_public_key(), @@ -433,7 +433,7 @@ fn create_server_drop_report( exit_node.main_cryptde_null().unwrap(), route, payload, - originating_node.main_public_key(), + originating_node.alias_public_key(), ) .unwrap() } @@ -473,7 +473,7 @@ fn create_client_drop_report( target_hostname: Some(String::from("doesnt.matter.com")), target_port: 80, protocol: ProxyProtocol::HTTP, - originator_public_key: originating_node.main_public_key().clone(), + originator_alias_public_key: originating_node.main_public_key().clone(), }, )); diff --git a/multinode_integration_tests/tests/data_routing_test.rs b/multinode_integration_tests/tests/data_routing_test.rs index 6e397a101..6be340d4e 100644 --- a/multinode_integration_tests/tests/data_routing_test.rs +++ b/multinode_integration_tests/tests/data_routing_test.rs @@ -316,7 +316,7 @@ fn multiple_stream_zero_hop_test() { let mut another_client = zero_hop_node.make_client(8080); one_client.send_chunk(b"GET / HTTP/1.1\r\nHost: www.example.com\r\n\r\n"); - another_client.send_chunk(b"GET / HTTP/1.1\r\nHost: www.fallingfalling.com\r\n\r\n"); + another_client.send_chunk(b"GET /online/ HTTP/1.1\r\nHost: whatever.neverssl.com\r\n\r\n"); let one_response = one_client.wait_for_chunk(); let another_response = another_client.wait_for_chunk(); @@ -330,7 +330,7 @@ fn multiple_stream_zero_hop_test() { assert_eq!( index_of( &another_response, - &b"FALLING FALLING .COM BY RAFAEL ROZENDAAL"[..], + &b"neverssl.com will never use SSL (also known as TLS)"[..], ) .is_some(), true, diff --git a/node/Cargo.lock b/node/Cargo.lock index 7d685bc6a..dac811f03 100644 --- a/node/Cargo.lock +++ b/node/Cargo.lock @@ -182,7 +182,7 @@ checksum = "cdb031dd78e28731d87d56cc8ffef4a8f36ca26c38fe2de700543e627f8a464a" [[package]] name = "automap" -version = "0.7.0" +version = "0.7.2" dependencies = [ "crossbeam-channel 0.5.1", "flexi_logger 0.17.1", @@ -1827,7 +1827,7 @@ dependencies = [ [[package]] name = "masq" -version = "0.7.1" +version = "0.7.2" dependencies = [ "atty", "clap", @@ -1847,7 +1847,7 @@ dependencies = [ [[package]] name = "masq_lib" -version = "0.7.1" +version = "0.7.2" dependencies = [ "actix", "clap", @@ -2023,7 +2023,7 @@ dependencies = [ [[package]] name = "multinode_integration_tests" -version = "0.7.1" +version = "0.7.2" dependencies = [ "base64 0.13.0", "crossbeam-channel 0.5.1", @@ -2116,7 +2116,7 @@ dependencies = [ [[package]] name = "node" -version = "0.7.1" +version = "0.7.2" dependencies = [ "actix", "automap", diff --git a/node/Cargo.toml b/node/Cargo.toml index 3f8d369f0..d92699af1 100644 --- a/node/Cargo.toml +++ b/node/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "node" -version = "0.7.1" +version = "0.7.2" license = "GPL-3.0-only" authors = ["Dan Wiebe ", "MASQ"] copyright = "Copyright (c) 2019, MASQ (https://masq.ai) and/or its affiliates. All rights reserved." diff --git a/node/src/actor_system_factory.rs b/node/src/actor_system_factory.rs index b7f848998..f03f57df2 100644 --- a/node/src/actor_system_factory.rs +++ b/node/src/actor_system_factory.rs @@ -132,6 +132,7 @@ impl ActorSystemFactoryTools for ActorSystemFactoryToolsReal { .rate_pack() .exit_service_rate, exit_byte_rate: config.neighborhood_config.mode.rate_pack().exit_byte_rate, + is_decentralized: config.neighborhood_config.mode.is_decentralized(), crashable: is_crashable(&config), }), ) @@ -1189,13 +1190,14 @@ mod tests { check_start_message(&recordings.neighborhood, 2); let hopper_config = Parameters::get(parameters.hopper_params); check_cryptde(hopper_config.cryptdes.main); - assert_eq!(hopper_config.per_routing_service, 102); + assert_eq!(hopper_config.per_routing_service, 300); assert_eq!(hopper_config.per_routing_byte, 101); let proxy_client_config = Parameters::get(parameters.proxy_client_params); check_cryptde(proxy_client_config.cryptde); - assert_eq!(proxy_client_config.exit_service_rate, 104); + assert_eq!(proxy_client_config.exit_service_rate, 500); assert_eq!(proxy_client_config.exit_byte_rate, 103); assert_eq!(proxy_client_config.dns_servers, config.dns_servers); + assert_eq!(proxy_client_config.is_decentralized, true); let (actual_cryptde_pair, bootstrapper_config) = Parameters::get(parameters.proxy_server_params); check_cryptde(actual_cryptde_pair.main); @@ -1668,6 +1670,7 @@ mod tests { SocketAddrV4::from_str("1.1.1.1:45").unwrap(), )], exit_service_rate: 50, + is_decentralized: true, crashable: true, exit_byte_rate: 50, }; diff --git a/node/src/bootstrapper.rs b/node/src/bootstrapper.rs index f424812b4..2103cdf04 100644 --- a/node/src/bootstrapper.rs +++ b/node/src/bootstrapper.rs @@ -90,7 +90,12 @@ impl Clone for CryptDEPair { #[derive(Copy)] pub struct CryptDEPair { + // This has the public key by which this Node is known to other Nodes on the network pub main: &'static dyn CryptDE, + // This has the public key with which this Node instructs exit Nodes to encrypt responses. + // In production, it is unrelated to the main public key to prevent the exit Node from + // identifying the originating Node. In tests using --fake-public-key, the alias public key + // is the main public key reversed. pub alias: &'static dyn CryptDE, } diff --git a/node/src/hopper/live_cores_package.rs b/node/src/hopper/live_cores_package.rs index 82637094e..9ec210678 100644 --- a/node/src/hopper/live_cores_package.rs +++ b/node/src/hopper/live_cores_package.rs @@ -72,11 +72,11 @@ impl LiveCoresPackage { pub fn to_expired( &self, immediate_neighbor_addr: SocketAddr, - main_cryptde: &dyn CryptDE, // Must be the main CryptDE of the Node for which the payload is intended. + main_cryptde: &dyn CryptDE, // Must be the main CryptDE of the Node for which the package (not necessarily the payload) is intended. payload_cryptde: &dyn CryptDE, // Must be the main or alias CryptDE of the Node for which the payload is intended. ) -> Result, CodexError> { let top_hop = self.route.next_hop(main_cryptde)?; - match decodex::(payload_cryptde, &self.payload).map(|decoded_payload| { + decodex::(payload_cryptde, &self.payload).map(|decoded_payload| { ExpiredCoresPackage::new( immediate_neighbor_addr, top_hop.payer.map(|p| p.wallet), @@ -84,10 +84,7 @@ impl LiveCoresPackage { decoded_payload, self.payload.len(), ) - }) { - Ok(ecp) => Ok(ecp), - Err(e) => Err(e), - } + }) } } diff --git a/node/src/hopper/routing_service.rs b/node/src/hopper/routing_service.rs index d30280a7e..81f14263a 100644 --- a/node/src/hopper/routing_service.rs +++ b/node/src/hopper/routing_service.rs @@ -4,7 +4,7 @@ use crate::blockchain::payer::Payer; use crate::bootstrapper::CryptDEPair; use crate::neighborhood::gossip::Gossip_0v1; use crate::sub_lib::accountant::ReportRoutingServiceProvidedMessage; -use crate::sub_lib::cryptde::{decodex, encodex, CodexError, CryptData, CryptdecError}; +use crate::sub_lib::cryptde::{decodex, encodex, CryptData, CryptdecError}; use crate::sub_lib::dispatcher::{Component, Endpoint, InboundClientData}; use crate::sub_lib::hop::LiveHop; use crate::sub_lib::hopper::{ExpiredCoresPackage, HopperSubs, MessageType}; @@ -207,7 +207,7 @@ impl RoutingService { payer_owns_secret_key: bool, ) { let expired_package = - match self.extract_expired_package(immediate_neighbor_addr, live_package) { + match self.extract_expired_package(immediate_neighbor_addr, live_package, component) { None => return, Some(p) => p, }; @@ -219,49 +219,30 @@ impl RoutingService { self.route_expired_package(component, expired_package, payer_owns_secret_key) } - // TODO: Rather than trying both alias and main cryptdes, this method should accept the Component - // that is to receive the package. If that Component is Neighborhood, it should use the main_cryptde - // to expire it; if the Component is anything else, it should use the alias_cryptde. fn extract_expired_package( &self, immediate_neighbor_addr: SocketAddr, live_package: LiveCoresPackage, + component: Component, ) -> Option> { let data_len = live_package.payload.len(); + let (payload_cryptde, cryptde_name) = match component { + Component::ProxyServer => (self.cryptdes.alias, "alias"), + _ => (self.cryptdes.main, "main"), + }; let expired_package = match live_package.to_expired( immediate_neighbor_addr, self.cryptdes.main, - self.cryptdes.alias, + payload_cryptde, ) { Ok(pkg) => pkg, - Err(CodexError::DecryptionError(CryptdecError::OpeningFailed)) => { - match live_package.to_expired( - immediate_neighbor_addr, - self.cryptdes.main, - self.cryptdes.main, - ) { - Ok(pkg) => pkg, - Err(CodexError::DecryptionError(CryptdecError::OpeningFailed)) => { - error!( - self.logger, - "Neither alias key nor main key can expire CORES package with {}-byte payload", data_len - ); - return None; - } - Err(e2) => { - error!( - self.logger, - "Couldn't expire CORES package with {}-byte payload using main key: {:?}", data_len, e2 - ); - return None; - } - } - } Err(e) => { error!( self.logger, - "Couldn't expire CORES package with {}-byte payload using alias key: {:?}", + "Couldn't expire CORES package with {}-byte payload to {:?} using {} key: {:?}", data_len, + component, + cryptde_name, e ); return None; @@ -520,16 +501,21 @@ impl RoutingService { mod tests { use super::*; use crate::banned_dao::BAN_CACHE; + use crate::bootstrapper::Bootstrapper; use crate::neighborhood::gossip::{GossipBuilder, Gossip_0v1}; use crate::node_test_utils::check_timestamp; use crate::sub_lib::accountant::ReportRoutingServiceProvidedMessage; use crate::sub_lib::cryptde::{encodex, CryptDE, PlainData, PublicKey}; use crate::sub_lib::cryptde_null::CryptDENull; + use crate::sub_lib::cryptde_real::CryptDEReal; use crate::sub_lib::hopper::{IncipientCoresPackage, MessageType, MessageType::ClientRequest}; use crate::sub_lib::neighborhood::GossipFailure_0v1; + use crate::sub_lib::peer_actors::PeerActors; use crate::sub_lib::proxy_client::{ClientResponsePayload_0v1, DnsResolveFailure_0v1}; - use crate::sub_lib::proxy_server::ClientRequestPayload_0v1; + use crate::sub_lib::proxy_server::{ClientRequestPayload_0v1, ProxyProtocol}; use crate::sub_lib::route::{Route, RouteSegment}; + use crate::sub_lib::sequence_buffer::SequencedPacket; + use crate::sub_lib::stream_key::StreamKey; use crate::sub_lib::versioned_data::VersionedData; use crate::sub_lib::wallet::Wallet; use crate::test_utils::recorder::{make_recorder, peer_actors_builder}; @@ -641,23 +627,26 @@ mod tests { subject.route(inbound_client_data); - TestLogHandler::new().exists_log_matching( - "Couldn't expire CORES package with \\d+-byte payload using main key: DeserializationError.*", + TestLogHandler::new().exists_log_containing( + "ERROR: RoutingService: Couldn't expire CORES package with 35-byte payload to ProxyClient using main key", ); } #[test] fn logs_and_ignores_message_that_cannot_be_decrypted() { init_test_logging(); - let main_cryptde = main_cryptde(); - let alias_cryptde = alias_cryptde(); - let rogue_cryptde = CryptDENull::new(TEST_DEFAULT_CHAIN); - let route = route_from_proxy_client(&main_cryptde.public_key(), main_cryptde); + let (main_cryptde, alias_cryptde) = { + //initialization to real CryptDEs + let pair = Bootstrapper::pub_initialize_cryptdes_for_testing(&None, &None); + (pair.main, pair.alias) + }; + let rogue_cryptde = CryptDEReal::new(TEST_DEFAULT_CHAIN); + let route = route_from_proxy_client(main_cryptde.public_key(), main_cryptde); let lcp = LiveCoresPackage::new( route, encodex(&rogue_cryptde, rogue_cryptde.public_key(), &[42u8]).unwrap(), ); - let data_enc = encodex(main_cryptde, &main_cryptde.public_key(), &lcp).unwrap(); + let data_enc = encodex(main_cryptde, main_cryptde.public_key(), &lcp).unwrap(); let inbound_client_data = InboundClientData { timestamp: SystemTime::now(), peer_addr: SocketAddr::from_str("1.2.3.4:5678").unwrap(), @@ -688,8 +677,8 @@ mod tests { subject.route(inbound_client_data); - TestLogHandler::new().exists_log_matching( - "Neither alias key nor main key can expire CORES package with \\d+-byte payload", + TestLogHandler::new().exists_log_containing( + "ERROR: RoutingService: Couldn't expire CORES package with 51-byte payload to ProxyClient using main key: DecryptionError(OpeningFailed)", ); } @@ -882,9 +871,9 @@ mod tests { BAN_CACHE.clear(); let main_cryptde = main_cryptde(); let alias_cryptde = alias_cryptde(); - let (component, _, component_recording_arc) = make_recorder(); + let (proxy_server, _, proxy_server_recording_arc) = make_recorder(); let route = route_to_proxy_server(&main_cryptde.public_key(), main_cryptde); - let payload = make_response_payload(0, main_cryptde); + let payload = make_response_payload(0, alias_cryptde); let lcp = LiveCoresPackage::new( route, encodex::( @@ -907,7 +896,7 @@ mod tests { }; let system = System::new("converts_live_message_to_expired_for_proxy_server"); - let peer_actors = peer_actors_builder().proxy_server(component).build(); + let peer_actors = peer_actors_builder().proxy_server(proxy_server).build(); let subject = RoutingService::new( CryptDEPair { main: main_cryptde, @@ -930,9 +919,9 @@ mod tests { System::current().stop(); system.run(); - let component_recording = component_recording_arc.lock().unwrap(); + let proxy_server_recording = proxy_server_recording_arc.lock().unwrap(); let record = - component_recording.get_record::>(0); + proxy_server_recording.get_record::>(0); let expected_ecp = lcp_a .to_expired( SocketAddr::from_str("1.3.2.4:5678").unwrap(), @@ -1402,7 +1391,6 @@ mod tests { Component::ProxyClient, ), ]; - let hops = live_hops .iter() .map(|hop| match hop.encode(&hop.public_key, main_cryptde) { @@ -1410,10 +1398,8 @@ mod tests { Err(e) => panic!("Couldn't encode hop: {:?}", e), }) .collect(); - let route = Route { hops }; - - let icp = IncipientCoresPackage::new(main_cryptde, route, payload, &public_key).unwrap(); + let icp = IncipientCoresPackage::new(main_cryptde, route, payload, public_key).unwrap(); let (lcp, _) = LiveCoresPackage::from_incipient(icp, main_cryptde).unwrap(); let data_ser = PlainData::new(&serde_cbor::ser::to_vec(&lcp).unwrap()[..]); let data_enc = main_cryptde @@ -1869,6 +1855,142 @@ mod tests { ); } + fn make_routing_service_subs(peer_actors: PeerActors) -> RoutingServiceSubs { + RoutingServiceSubs { + proxy_client_subs_opt: peer_actors.proxy_client_opt, + proxy_server_subs: peer_actors.proxy_server, + neighborhood_subs: peer_actors.neighborhood, + hopper_subs: peer_actors.hopper, + to_dispatcher: peer_actors.dispatcher.from_dispatcher_client, + to_accountant_routing: peer_actors.accountant.report_routing_service_provided, + } + } + + fn route_data_to_peripheral_component_uses_proper_key_on_payload_for_component( + payload_factory: F, + target_component: Component, + ) where + F: FnOnce(&CryptDEPair) -> CryptData, + { + let peer_actors = peer_actors_builder().build(); + let subject = RoutingService::new( + make_cryptde_pair(), + make_routing_service_subs(peer_actors), + 100, + 200, + true, + ); + let route = Route::single_hop(&PublicKey::new(b"1234"), subject.cryptdes.main).unwrap(); + let payload = payload_factory(&subject.cryptdes); + let live_package = LiveCoresPackage::new(route, payload); + + subject.route_data_to_peripheral_component( + target_component, + SocketAddr::from_str("1.2.3.4:1234").unwrap(), + live_package, + true, + ); + + // CryptDENull panics when you try decrypting with the wrong key; no panic means test passes + } + + #[test] + fn route_data_to_peripheral_component_uses_main_key_on_payload_for_proxy_client() { + let payload_factory = |cryptdes: &CryptDEPair| { + encodex( + cryptdes.main, + cryptdes.main.public_key(), + &MessageType::ClientRequest(VersionedData::new( + &crate::sub_lib::migrations::client_request_payload::MIGRATIONS, + &ClientRequestPayload_0v1 { + stream_key: StreamKey::new( + PublicKey::new(b"1234"), + SocketAddr::from_str("1.2.3.4:1234").unwrap(), + ), + sequenced_packet: SequencedPacket::new(vec![1, 2, 3, 4], 1234, false), + target_hostname: Some("hostname".to_string()), + target_port: 1234, + protocol: ProxyProtocol::TLS, + originator_alias_public_key: PublicKey::new(b"1234"), + }, + )), + ) + .unwrap() + }; + route_data_to_peripheral_component_uses_proper_key_on_payload_for_component( + payload_factory, + Component::ProxyClient, + ); + } + + #[test] + fn route_data_to_peripheral_component_uses_alias_key_on_payload_for_proxy_server() { + let payload_factory = |cryptdes: &CryptDEPair| { + encodex( + cryptdes.alias, + cryptdes.alias.public_key(), + &MessageType::DnsResolveFailed(VersionedData::new( + &crate::sub_lib::migrations::dns_resolve_failure::MIGRATIONS, + &DnsResolveFailure_0v1 { + stream_key: StreamKey::new( + PublicKey::new(b"1234"), + SocketAddr::from_str("1.2.3.4:1234").unwrap(), + ), + }, + )), + ) + .unwrap() + }; + route_data_to_peripheral_component_uses_proper_key_on_payload_for_component( + payload_factory, + Component::ProxyServer, + ); + } + + #[test] + fn route_data_to_peripheral_component_uses_main_key_on_payload_for_neighborhood() { + let payload_factory = |cryptdes: &CryptDEPair| { + encodex( + cryptdes.main, + cryptdes.main.public_key(), + &MessageType::GossipFailure(VersionedData::new( + &crate::sub_lib::migrations::gossip_failure::MIGRATIONS, + &GossipFailure_0v1::Unknown, + )), + ) + .unwrap() + }; + route_data_to_peripheral_component_uses_proper_key_on_payload_for_component( + payload_factory, + Component::Neighborhood, + ); + } + + #[test] + fn route_data_to_peripheral_component_uses_main_key_on_payload_for_hopper() { + let payload_factory = |cryptdes: &CryptDEPair| { + encodex( + cryptdes.main, + cryptdes.main.public_key(), + &MessageType::ClientResponse(VersionedData::new( + &crate::sub_lib::migrations::client_request_payload::MIGRATIONS, + &ClientResponsePayload_0v1 { + stream_key: StreamKey::new( + PublicKey::new(b"1234"), + SocketAddr::from_str("1.2.3.4:1234").unwrap(), + ), + sequenced_packet: SequencedPacket::new(vec![1, 2, 3, 4], 1234, false), + }, + )), + ) + .unwrap() + }; + route_data_to_peripheral_component_uses_proper_key_on_payload_for_component( + payload_factory, + Component::Hopper, + ); + } + #[test] fn route_expired_package_handles_unmigratable_gossip() { init_test_logging(); diff --git a/node/src/neighborhood/gossip.rs b/node/src/neighborhood/gossip.rs index a25f24a8c..cdda92fdc 100644 --- a/node/src/neighborhood/gossip.rs +++ b/node/src/neighborhood/gossip.rs @@ -632,7 +632,7 @@ mod tests { let result = format!("{:?}", gossip); let expected = format!( "\nGossipNodeRecord {{{}{}{}{}\n}}", - "\n\tinner: NodeRecordInner_0v1 {\n\t\tpublic_key: AQIDBA,\n\t\tnode_addr_opt: Some(1.2.3.4:[1234]),\n\t\tearning_wallet: Wallet { kind: Address(0x546900db8d6e0937497133d1ae6fdf5f4b75bcd0) },\n\t\trate_pack: RatePack { routing_byte_rate: 1235, routing_service_rate: 1236, exit_byte_rate: 1237, exit_service_rate: 1238 },\n\t\tneighbors: [],\n\t\tversion: 2,\n\t},", + "\n\tinner: NodeRecordInner_0v1 {\n\t\tpublic_key: 0x01020304,\n\t\tnode_addr_opt: Some(1.2.3.4:[1234]),\n\t\tearning_wallet: Wallet { kind: Address(0x546900db8d6e0937497133d1ae6fdf5f4b75bcd0) },\n\t\trate_pack: RatePack { routing_byte_rate: 1235, routing_service_rate: 1434, exit_byte_rate: 1237, exit_service_rate: 1634 },\n\t\tneighbors: [],\n\t\tversion: 2,\n\t},", "\n\tnode_addr_opt: Some(1.2.3.4:[1234]),", "\n\tsigned_data: Length: 229 (0xe5) bytes @@ -644,17 +644,17 @@ Length: 229 (0xe5) bytes 0050: 69 72 61 74 65 5f 70 61 63 6b a4 71 72 6f 75 74 irate_pack.qrout 0060: 69 6e 67 5f 62 79 74 65 5f 72 61 74 65 19 04 d3 ing_byte_rate... 0070: 74 72 6f 75 74 69 6e 67 5f 73 65 72 76 69 63 65 trouting_service -0080: 5f 72 61 74 65 19 04 d4 6e 65 78 69 74 5f 62 79 _rate...nexit_by +0080: 5f 72 61 74 65 19 05 9a 6e 65 78 69 74 5f 62 79 _rate...nexit_by 0090: 74 65 5f 72 61 74 65 19 04 d5 71 65 78 69 74 5f te_rate...qexit_ -00a0: 73 65 72 76 69 63 65 5f 72 61 74 65 19 04 d6 69 service_rate...i +00a0: 73 65 72 76 69 63 65 5f 72 61 74 65 19 06 62 69 service_rate..bi 00b0: 6e 65 69 67 68 62 6f 72 73 80 73 61 63 63 65 70 neighbors.saccep 00c0: 74 73 5f 63 6f 6e 6e 65 63 74 69 6f 6e 73 f5 6b ts_connections.k 00d0: 72 6f 75 74 65 73 5f 64 61 74 61 f5 67 76 65 72 routes_data.gver 00e0: 73 69 6f 6e 02 sion.", "\n\tsignature: Length: 24 (0x18) bytes -0000: 01 02 03 04 8a 93 2b df 1d 8d f3 e3 e5 8c 70 b6 ......+.......p. -0010: f9 31 f0 97 5c c1 ce d8 .1..\\..." +0000: 01 02 03 04 8a 7e b2 0e c4 ea fe d8 ac 3c 89 2d .....~.......<.- +0010: 2d f1 e9 76 d1 76 db 67 -..v.v.g" ); assert_eq!(result, expected); diff --git a/node/src/neighborhood/gossip_acceptor.rs b/node/src/neighborhood/gossip_acceptor.rs index d3f756f9f..ca44b8b6a 100644 --- a/node/src/neighborhood/gossip_acceptor.rs +++ b/node/src/neighborhood/gossip_acceptor.rs @@ -940,12 +940,16 @@ impl GossipHandler for StandardGossipHandler { StandardGossipHandler::check_full_neighbor(database, gossip_source.ip()); let patch = self.compute_patch(&agrs, database.root()); - let agrs = self.filter_agrs_from_patch(agrs, patch); + let filtered_agrs = self.filter_agrs_by_patch(agrs, patch); - let mut db_changed = - self.identify_and_add_non_introductory_new_nodes(database, &agrs, gossip_source); - db_changed = self.identify_and_update_obsolete_nodes(database, agrs) || db_changed; - db_changed = self.handle_root_node(cryptde, database, gossip_source) || db_changed; + let mut db_changed = self.identify_and_add_non_introductory_new_nodes( + database, + &filtered_agrs, + gossip_source, + ); + db_changed = self.identify_and_update_obsolete_nodes(database, filtered_agrs) || db_changed; + db_changed = + self.add_src_node_as_half_neighbor(cryptde, database, gossip_source) || db_changed; let final_neighborship_status = StandardGossipHandler::check_full_neighbor(database, gossip_source.ip()); // If no Nodes need updating, return ::Ignored and don't change the database. @@ -1003,26 +1007,26 @@ impl StandardGossipHandler { fn compute_patch_recursive( &self, patch: &mut HashSet, - node: &PublicKey, + current_node_key: &PublicKey, agrs: &HashMap<&PublicKey, &AccessibleGossipRecord>, hops_remaining: usize, root_node: &NodeRecord, ) { - patch.insert(node.clone()); + patch.insert(current_node_key.clone()); if hops_remaining == 0 { return; } - let neighbors = if node == root_node.public_key() { + let neighbors = if current_node_key == root_node.public_key() { &root_node.inner.neighbors } else { - match agrs.get(node) { + match agrs.get(current_node_key) { Some(agr) => &agr.inner.neighbors, None => { - patch.remove(node); + patch.remove(current_node_key); trace!( self.logger, "While computing patch no AGR record found for public key {:?}", - node + current_node_key ); return; } @@ -1036,7 +1040,7 @@ impl StandardGossipHandler { } } - fn filter_agrs_from_patch( + fn filter_agrs_by_patch( &self, agrs: Vec, patch: HashSet, @@ -1068,8 +1072,15 @@ impl StandardGossipHandler { } }) .for_each(|agr| { + let node_record = NodeRecord::from(agr); + trace!( + self.logger, + "Discovered new Node {:?}: {:?}", + node_record.public_key(), + node_record.full_neighbor_keys(database) + ); database - .add_node(NodeRecord::from(agr)) + .add_node(node_record) .expect("List of new Nodes contained existing Nodes"); }); database.keys().len() != all_keys.len() @@ -1083,6 +1094,13 @@ impl StandardGossipHandler { agrs.into_iter().fold(false, |b, agr| { match database.node_by_key(&agr.inner.public_key) { Some(existing_node) if agr.inner.version > existing_node.version() => { + trace!( + self.logger, + "Updating Node {:?} from v{} to v{}", + existing_node.public_key(), + existing_node.version(), + agr.inner.version + ); self.update_database_record(database, agr) || b } _ => b, @@ -1090,7 +1108,7 @@ impl StandardGossipHandler { }) } - fn handle_root_node( + fn add_src_node_as_half_neighbor( &self, cryptde: &dyn CryptDE, database: &mut NeighborhoodDatabase, @@ -2426,7 +2444,6 @@ mod tests { node_b_db.add_arbitrary_full_neighbor(node_b.public_key(), node_y.public_key()); node_b_db.add_arbitrary_full_neighbor(node_b.public_key(), node_c.public_key()); node_b_db.add_arbitrary_full_neighbor(node_c.public_key(), node_d.public_key()); - let gossip = GossipBuilder::new(&node_b_db) .node(node_b.public_key(), true) .node(node_c.public_key(), false) @@ -3574,6 +3591,29 @@ mod tests { #[test] fn standard_gossip_containing_unfamiliar_node_addrs_leads_to_them_being_ignored() { + /* + + <---- Databases before the gossip ----> + + Destination Database (Root) ==> + + Root + / | + B---A + | + D + + Source Database (A) ==> + + Root + / | + B---A---E + | | + C F + + <-------------------------------------> + */ + let root_node = make_node_record(1234, true); let mut dest_db = db_from_node(&root_node); let node_a = make_node_record(2345, true); diff --git a/node/src/neighborhood/mod.rs b/node/src/neighborhood/mod.rs index 73dcb0dd0..82b4f7a31 100644 --- a/node/src/neighborhood/mod.rs +++ b/node/src/neighborhood/mod.rs @@ -46,7 +46,6 @@ use crate::sub_lib::cryptde::{CryptDE, CryptData, PlainData}; use crate::sub_lib::dispatcher::{Component, StreamShutdownMsg}; use crate::sub_lib::hopper::{ExpiredCoresPackage, NoLookupIncipientCoresPackage}; use crate::sub_lib::hopper::{IncipientCoresPackage, MessageType}; -use crate::sub_lib::neighborhood::NodeQueryMessage; use crate::sub_lib::neighborhood::NodeQueryResponseMetadata; use crate::sub_lib::neighborhood::NodeRecordMetadataMessage; use crate::sub_lib::neighborhood::RemoveNeighborMessage; @@ -56,6 +55,7 @@ use crate::sub_lib::neighborhood::{AskAboutDebutGossipMessage, NodeDescriptor}; use crate::sub_lib::neighborhood::{ConnectionProgressEvent, ExpectedServices}; use crate::sub_lib::neighborhood::{ConnectionProgressMessage, ExpectedService}; use crate::sub_lib::neighborhood::{DispatcherNodeQueryMessage, GossipFailure_0v1}; +use crate::sub_lib::neighborhood::{NRMetadataChange, NodeQueryMessage}; use crate::sub_lib::neighborhood::{NeighborhoodSubs, NeighborhoodTools}; use crate::sub_lib::node_addr::NodeAddr; use crate::sub_lib::peer_actors::{BindMessage, NewPublicIp, StartMessage}; @@ -78,7 +78,8 @@ use neighborhood_database::NeighborhoodDatabase; use node_record::NodeRecord; pub const CRASH_KEY: &str = "NEIGHBORHOOD"; -pub const UNDESIRABLE_FOR_EXIT_PENALTY: i64 = 100_000_000; +pub const UNREACHABLE_HOST_PENALTY: i64 = 100_000_000; +pub const RESPONSE_UNDESIRABILITY_FACTOR: usize = 1_000; // assumed response length is request * this pub struct Neighborhood { cryptde: &'static dyn CryptDE, @@ -340,17 +341,23 @@ impl Handler for Neighborhood { type Result = (); fn handle(&mut self, msg: NodeRecordMetadataMessage, _ctx: &mut Self::Context) -> Self::Result { - match msg { - NodeRecordMetadataMessage::Desirable(public_key, desirable) => { - if let Some(node_record) = self.neighborhood_database.node_by_key_mut(&public_key) { - debug!( - self.logger, - "About to set desirable '{}' for '{:?}'", desirable, public_key - ); - node_record.set_desirable_for_exit(desirable); - }; + match msg.metadata_change { + NRMetadataChange::AddUnreachableHost { hostname } => { + let public_key = msg.public_key; + let node_record = self + .neighborhood_database + .node_by_key_mut(&public_key) + .unwrap_or_else(|| { + panic!("No Node Record found for public_key: {:?}", public_key) + }); + debug!( + self.logger, + "Marking host {hostname} unreachable for the Node with public key {:?}", + public_key + ); + node_record.metadata.unreachable_hosts.insert(hostname); } - }; + } } } @@ -525,7 +532,7 @@ impl Neighborhood { } fn handle_route_query_message(&mut self, msg: RouteQueryMessage) -> Option { - let msg_str = format!("{:?}", msg); + let debug_msg_opt = self.logger.debug_enabled().then(|| format!("{:?}", msg)); let route_result = if msg.minimum_hop_count == 0 { Ok(self.zero_hop_route_response()) } else { @@ -533,6 +540,7 @@ impl Neighborhood { }; match route_result { Ok(response) => { + let msg_str = debug_msg_opt.expect("Debug Message wasn't built but expected."); debug!( self.logger, "Processed {} into {}-hop response", @@ -818,6 +826,7 @@ impl Neighborhood { minimum_hop_count: DEFAULT_MINIMUM_HOP_COUNT, return_component_opt: Some(Component::ProxyServer), payload_size: 10000, + hostname_opt: None, }; if self.handle_route_query_message(msg).is_some() { debug!( @@ -944,24 +953,34 @@ impl Neighborhood { fn make_round_trip_route( &mut self, - msg: RouteQueryMessage, + request_msg: RouteQueryMessage, ) -> Result { + let hostname_opt = request_msg.hostname_opt.as_deref(); let over = self.make_route_segment( self.cryptde.public_key(), - msg.target_key_opt.as_ref(), - msg.minimum_hop_count, - msg.target_component, - msg.payload_size, + request_msg.target_key_opt.as_ref(), + request_msg.minimum_hop_count, + request_msg.target_component, + request_msg.payload_size, RouteDirection::Over, + hostname_opt, )?; debug!(self.logger, "Route over: {:?}", over); + // Estimate for routing-undesirability calculations. + // We don't know what the size of response will be. + // So, we estimate the value by multiplying the payload_size of request with a constant value. + let anticipated_response_payload_len = + request_msg.payload_size * RESPONSE_UNDESIRABILITY_FACTOR; let back = self.make_route_segment( over.keys.last().expect("Empty segment"), Some(self.cryptde.public_key()), - msg.minimum_hop_count, - msg.return_component_opt.expect("No return component"), - msg.payload_size, + request_msg.minimum_hop_count, + request_msg + .return_component_opt + .expect("No return component"), + anticipated_response_payload_len, RouteDirection::Back, + hostname_opt, )?; debug!(self.logger, "Route back: {:?}", back); self.compose_route_query_response(over, back) @@ -1012,6 +1031,7 @@ impl Neighborhood { }) } + #[allow(clippy::too_many_arguments)] fn make_route_segment( &self, origin: &PublicKey, @@ -1020,6 +1040,7 @@ impl Neighborhood { target_component: Component, payload_size: usize, direction: RouteDirection, + hostname_opt: Option<&str>, ) -> Result { let route_opt = self.find_best_route_segment( origin, @@ -1027,6 +1048,7 @@ impl Neighborhood { minimum_hop_count, payload_size, direction, + hostname_opt, ); match route_opt { None => { @@ -1125,30 +1147,37 @@ impl Neighborhood { } fn compute_undesirability( - &self, node_record: &NodeRecord, - payload_size: usize, - link_type: LinkType, + payload_size: u64, + undesirability_type: UndesirabilityType, + logger: &Logger, ) -> i64 { - let (per_byte, per_cores) = match link_type { - LinkType::Relay => ( - node_record.inner.rate_pack.routing_byte_rate, - node_record.inner.rate_pack.routing_service_rate, - ), - LinkType::Exit => ( - node_record.inner.rate_pack.exit_byte_rate, - node_record.inner.rate_pack.exit_service_rate, - ), - LinkType::Origin => (0, 0), - }; - let rate_undesirability = per_cores as i64 + (per_byte as i64 * payload_size as i64); - let desirable_undesirability = - if !node_record.metadata.desirable_for_exit && (link_type == LinkType::Exit) { - UNDESIRABLE_FOR_EXIT_PENALTY - } else { - 0 - }; - rate_undesirability + desirable_undesirability + let mut rate_undesirability = match undesirability_type { + UndesirabilityType::Relay => node_record.inner.rate_pack.routing_charge(payload_size), + UndesirabilityType::ExitRequest(_) => { + node_record.inner.rate_pack.exit_charge(payload_size) + } + UndesirabilityType::ExitAndRouteResponse => { + node_record.inner.rate_pack.exit_charge(payload_size) + + node_record.inner.rate_pack.routing_charge(payload_size) + } + } as i64; + if let UndesirabilityType::ExitRequest(Some(hostname)) = undesirability_type { + if node_record.metadata.unreachable_hosts.contains(hostname) { + trace!( + logger, + "Node with PubKey {:?} failed to reach host {:?} during ExitRequest; Undesirability: {} + {} = {}", + node_record.public_key(), + hostname, + rate_undesirability, + UNREACHABLE_HOST_PENALTY, + rate_undesirability + UNREACHABLE_HOST_PENALTY + ); + rate_undesirability += UNREACHABLE_HOST_PENALTY; + } + } + + rate_undesirability } fn is_orig_node_on_back_leg( @@ -1171,8 +1200,8 @@ impl Neighborhood { return_route_id } - // Interface to main routing engine. Supply source key, target key, if any, in target_opt, - // minimum hop count in hops_remaining, size of payload in bytes, and the route direction. + // Interface to main routing engine. Supply source key, target key--if any--in target_opt, + // minimum hops, size of payload in bytes, the route direction, and the hostname if you know it. // // Return value is the least undesirable route that will either go from the origin to the // target in hops_remaining or more hops with no cycles, or from the origin hops_remaining hops @@ -1185,21 +1214,30 @@ impl Neighborhood { minimum_hops: usize, payload_size: usize, direction: RouteDirection, + hostname_opt: Option<&str>, ) -> Option> { let mut minimum_undesirability = i64::MAX; - self.routing_engine( - vec![source], - 0, - target_opt, - minimum_hops, - payload_size, - direction, - &mut minimum_undesirability, - ) - .into_iter() - .filter(|cr| cr.undesirability <= minimum_undesirability) - .map(|cr| cr.nodes) - .next() + let initial_undesirability = + self.compute_initial_undesirability(source, payload_size as u64, direction); + let result = self + .routing_engine( + vec![source], + initial_undesirability, + target_opt, + minimum_hops, + payload_size, + direction, + &mut minimum_undesirability, + hostname_opt, + ) + .into_iter() + .filter_map(|cr| match cr.undesirability <= minimum_undesirability { + true => Some(cr.nodes), + false => None, + }) + .next(); + + result } #[allow(clippy::too_many_arguments)] @@ -1212,6 +1250,7 @@ impl Neighborhood { payload_size: usize, direction: RouteDirection, minimum_undesirability: &mut i64, + hostname_opt: Option<&str>, ) -> Vec> { if undesirability > *minimum_undesirability { return vec![]; @@ -1261,9 +1300,10 @@ impl Neighborhood { node_record, undesirability, target_opt, - hops_remaining, - payload_size, + new_hops_remaining, + payload_size as u64, direction, + hostname_opt, ); self.routing_engine( @@ -1274,6 +1314,7 @@ impl Neighborhood { payload_size, direction, minimum_undesirability, + hostname_opt, ) }) .collect() @@ -1299,24 +1340,52 @@ impl Neighborhood { ); } + fn compute_initial_undesirability( + &self, + public_key: &PublicKey, + payload_size: u64, + direction: RouteDirection, + ) -> i64 { + if direction == RouteDirection::Over { + return 0; + } + let node_record = self + .neighborhood_database + .node_by_key(public_key) + .expect("Exit node disappeared"); + Self::compute_undesirability( + node_record, + payload_size, + UndesirabilityType::ExitAndRouteResponse, + &self.logger, + ) + } + + #[allow(clippy::too_many_arguments)] fn compute_new_undesirability( &self, node_record: &NodeRecord, undesirability: i64, target_opt: Option<&PublicKey>, hops_remaining: usize, - payload_size: usize, + payload_size: u64, direction: RouteDirection, + hostname_opt: Option<&str>, ) -> i64 { - let link_type = match (direction, target_opt) { - (RouteDirection::Over, None) if hops_remaining == 0 => LinkType::Exit, - (RouteDirection::Over, _) => LinkType::Relay, - (RouteDirection::Back, Some(target)) if &node_record.inner.public_key == target => { - LinkType::Origin + let undesirability_type = match (direction, target_opt) { + (RouteDirection::Over, None) if hops_remaining == 0 => { + UndesirabilityType::ExitRequest(hostname_opt) } - (RouteDirection::Back, _) => LinkType::Relay, + (RouteDirection::Over, _) => UndesirabilityType::Relay, + // The exit-and-relay undesirability is initial_undesirability + (RouteDirection::Back, _) => UndesirabilityType::Relay, }; - let node_undesirability = self.compute_undesirability(node_record, payload_size, link_type); + let node_undesirability = Self::compute_undesirability( + node_record, + payload_size, + undesirability_type, + &self.logger, + ); undesirability + node_undesirability } @@ -1483,11 +1552,11 @@ pub fn regenerate_signed_gossip( (signed_gossip, signature) } -#[derive(PartialEq, Eq)] -enum LinkType { +#[derive(PartialEq, Eq, Debug)] +enum UndesirabilityType<'hostname> { Relay, - Exit, - Origin, + ExitRequest(Option<&'hostname str>), + ExitAndRouteResponse, } struct ComputedRouteSegment<'a> { @@ -2645,7 +2714,9 @@ mod tests { let addr: Addr = subject.start(); let sub: Recipient = addr.recipient::(); - let future = sub.send(RouteQueryMessage::data_indefinite_route_request(5, 400)); + let future = sub.send(RouteQueryMessage::data_indefinite_route_request( + None, 5, 400, + )); System::current().stop_with_code(0); system.run(); @@ -2661,7 +2732,9 @@ mod tests { let addr: Addr = subject.start(); let sub: Recipient = addr.recipient::(); - let future = sub.send(RouteQueryMessage::data_indefinite_route_request(2, 430)); + let future = sub.send(RouteQueryMessage::data_indefinite_route_request( + None, 2, 430, + )); System::current().stop_with_code(0); system.run(); @@ -2684,8 +2757,7 @@ mod tests { subject.consuming_wallet_opt = None; // These happen to be extracted in the desired order. We could not think of a way to guarantee it. let desirable_exit_node = make_node_record(2345, false); - let mut undesirable_exit_node = make_node_record(3456, true); - undesirable_exit_node.set_desirable_for_exit(false); + let undesirable_exit_node = make_node_record(3456, true); let originating_node = &subject.neighborhood_database.root().clone(); { let db = &mut subject.neighborhood_database; @@ -2702,7 +2774,7 @@ mod tests { } let addr: Addr = subject.start(); let sub: Recipient = addr.recipient::(); - let msg = RouteQueryMessage::data_indefinite_route_request(1, 54000); + let msg = RouteQueryMessage::data_indefinite_route_request(None, 1, 54000); let future = sub.send(msg); @@ -2775,7 +2847,7 @@ mod tests { } let addr: Addr = subject.start(); let sub: Recipient = addr.recipient::(); - let msg = RouteQueryMessage::data_indefinite_route_request(1, 10000); + let msg = RouteQueryMessage::data_indefinite_route_request(None, 1, 10000); let future = sub.send(msg); @@ -2792,7 +2864,7 @@ mod tests { let subject = make_standard_subject(); let addr: Addr = subject.start(); let sub: Recipient = addr.recipient::(); - let msg = RouteQueryMessage::data_indefinite_route_request(2, 20000); + let msg = RouteQueryMessage::data_indefinite_route_request(None, 2, 20000); let future = sub.send(msg); @@ -2810,7 +2882,9 @@ mod tests { let addr: Addr = subject.start(); let sub: Recipient = addr.recipient::(); - let future = sub.send(RouteQueryMessage::data_indefinite_route_request(0, 12345)); + let future = sub.send(RouteQueryMessage::data_indefinite_route_request( + None, 0, 12345, + )); System::current().stop_with_code(0); system.run(); @@ -2885,8 +2959,7 @@ mod tests { let q = &make_node_record(3456, true); let r = &make_node_record(4567, false); let s = &make_node_record(5678, false); - let mut t = make_node_record(7777, false); - t.set_desirable_for_exit(false); + let t = make_node_record(7777, false); { let db = &mut subject.neighborhood_database; db.add_node(q.clone()).unwrap(); @@ -2905,7 +2978,9 @@ mod tests { let addr: Addr = subject.start(); let sub: Recipient = addr.recipient::(); - let data_route = sub.send(RouteQueryMessage::data_indefinite_route_request(2, 5000)); + let data_route = sub.send(RouteQueryMessage::data_indefinite_route_request( + None, 2, 5000, + )); System::current().stop_with_code(0); system.run(); @@ -3000,8 +3075,12 @@ mod tests { let addr: Addr = subject.start(); let sub: Recipient = addr.recipient::(); - let data_route_0 = sub.send(RouteQueryMessage::data_indefinite_route_request(2, 2000)); - let data_route_1 = sub.send(RouteQueryMessage::data_indefinite_route_request(2, 3000)); + let data_route_0 = sub.send(RouteQueryMessage::data_indefinite_route_request( + None, 2, 2000, + )); + let data_route_1 = sub.send(RouteQueryMessage::data_indefinite_route_request( + None, 2, 3000, + )); System::current().stop_with_code(0); system.run(); @@ -3051,13 +3130,15 @@ mod tests { ) .unwrap(); - let route_request_1 = - route_sub.send(RouteQueryMessage::data_indefinite_route_request(2, 1000)); + let route_request_1 = route_sub.send(RouteQueryMessage::data_indefinite_route_request( + None, 2, 1000, + )); let _ = set_wallet_sub.try_send(SetConsumingWalletMessage { wallet: expected_new_wallet, }); - let route_request_2 = - route_sub.send(RouteQueryMessage::data_indefinite_route_request(2, 2000)); + let route_request_2 = route_sub.send(RouteQueryMessage::data_indefinite_route_request( + None, 2, 2000, + )); System::current().stop(); system.run(); @@ -3147,30 +3228,35 @@ mod tests { db.add_arbitrary_full_neighbor(s, r); // At least two hops from p to anywhere standard - let route_opt = subject.find_best_route_segment(p, None, 2, 10000, RouteDirection::Over); + let route_opt = + subject.find_best_route_segment(p, None, 2, 10000, RouteDirection::Over, None); assert_eq!(route_opt.unwrap(), vec![p, s, t]); // no [p, r, s] or [p, s, r] because s and r are both neighbors of p and can't exit for it // At least two hops over from p to t - let route_opt = subject.find_best_route_segment(p, Some(t), 2, 10000, RouteDirection::Over); + let route_opt = + subject.find_best_route_segment(p, Some(t), 2, 10000, RouteDirection::Over, None); assert_eq!(route_opt.unwrap(), vec![p, s, t]); // At least two hops over from t to p - let route_opt = subject.find_best_route_segment(t, Some(p), 2, 10000, RouteDirection::Over); + let route_opt = + subject.find_best_route_segment(t, Some(p), 2, 10000, RouteDirection::Over, None); assert_eq!(route_opt, None); // p is consume-only; can't be an exit Node. // At least two hops back from t to p - let route_opt = subject.find_best_route_segment(t, Some(p), 2, 10000, RouteDirection::Back); + let route_opt = + subject.find_best_route_segment(t, Some(p), 2, 10000, RouteDirection::Back, None); assert_eq!(route_opt.unwrap(), vec![t, s, p]); // p is consume-only, but it's the originating Node, so including it is okay // At least two hops from p to Q - impossible - let route_opt = subject.find_best_route_segment(p, Some(q), 2, 10000, RouteDirection::Over); + let route_opt = + subject.find_best_route_segment(p, Some(q), 2, 10000, RouteDirection::Over, None); assert_eq!(route_opt, None); } @@ -3243,7 +3329,7 @@ mod tests { // All the target-designated routes from L to N let route = subject - .find_best_route_segment(&l, Some(&n), 3, 10000, RouteDirection::Back) + .find_best_route_segment(&l, Some(&n), 3, 10000, RouteDirection::Back, None) .unwrap(); let after = Instant::now(); @@ -3277,77 +3363,158 @@ mod tests { db.add_arbitrary_full_neighbor(q, r); // At least two hops from P to anywhere standard - let route_opt = subject.find_best_route_segment(p, None, 2, 10000, RouteDirection::Over); + let route_opt = + subject.find_best_route_segment(p, None, 2, 10000, RouteDirection::Over, None); assert_eq!(route_opt, None); } #[test] - fn compute_undesirability_for_relay_nodes() { + fn computing_undesirability_works_for_relay_on_over_leg() { + let node_record = make_node_record(1234, false); let subject = make_standard_subject(); - let mut node_record = make_node_record(1, false); - node_record.metadata.desirable_for_exit = true; // not used as exit: irrelevant - node_record.inner.rate_pack = RatePack { - routing_byte_rate: 100, - routing_service_rate: 200, - exit_byte_rate: 123456, - exit_service_rate: 234567, - }; - let result = subject.compute_undesirability(&node_record, 10000, LinkType::Relay); + let new_undesirability = subject.compute_new_undesirability( + &node_record, + 1_000_000, // Nonzero undesirability: on our way + None, + 5, // Lots of hops to go yet + 1_000, + RouteDirection::Over, + Some("hostname.com"), + ); - assert_eq!(result, 200 + (100 * 10000)); + let rate_pack = node_record.rate_pack(); + // node_record will charge us for the link beyond + assert_eq!( + new_undesirability, + 1_000_000 // existing undesirability + + rate_pack.routing_charge (1_000) as i64 // charge to route packet + ); } #[test] - fn compute_undesirability_for_exit_nodes() { + fn computing_undesirability_works_for_exit_on_over_leg_for_non_blacklisted_host() { + let node_record = make_node_record(2345, false); let subject = make_standard_subject(); - let mut node_record = make_node_record(1, false); - node_record.metadata.desirable_for_exit = true; // used as exit, but leave out penalty for now - node_record.inner.rate_pack = RatePack { - routing_byte_rate: 123456, - routing_service_rate: 234567, - exit_byte_rate: 100, - exit_service_rate: 200, - }; - let result = subject.compute_undesirability(&node_record, 10000, LinkType::Exit); + let new_undesirability = subject.compute_new_undesirability( + &node_record, + 1_000_000, + None, + 0, // Last hop + 1_000, + RouteDirection::Over, + Some("hostname.com"), + ); - assert_eq!(result, 200 + (100 * 10000)); + let rate_pack = node_record.rate_pack(); + assert_eq!( + new_undesirability, + 1_000_000 // existing undesirability + + rate_pack.exit_charge (1_000) as i64 // charge to exit request + ); } #[test] - fn compute_undesirability_for_origin() { + fn computing_undesirability_works_for_exit_on_over_leg_for_blacklisted_host() { + init_test_logging(); + let mut node_record = make_node_record(2345, false); + node_record + .metadata + .unreachable_hosts + .insert("hostname.com".to_string()); let subject = make_standard_subject(); - let mut node_record = make_node_record(1, false); - node_record.metadata.desirable_for_exit = true; // not used as exit: irrelevant - node_record.inner.rate_pack = RatePack { - routing_byte_rate: 123456, - routing_service_rate: 234567, - exit_byte_rate: 345678, - exit_service_rate: 456789, - }; - let result = subject.compute_undesirability(&node_record, 10000, LinkType::Origin); + let new_undesirability = subject.compute_new_undesirability( + &node_record, + 1_000_000, + None, + 0, // Last hop + 1_000, + RouteDirection::Over, + Some("hostname.com"), + ); + + let rate_pack = node_record.rate_pack(); + assert_eq!( + new_undesirability, + 1_000_000 // existing undesirability + + rate_pack.exit_charge (1_000) as i64 // charge to exit request + + UNREACHABLE_HOST_PENALTY // because host is blacklisted + ); + TestLogHandler::new().exists_log_containing( + "TRACE: Neighborhood: Node with PubKey 0x02030405 \ + failed to reach host \"hostname.com\" during ExitRequest; \ + Undesirability: 2350745 + 100000000 = 102350745", + ); + } + + #[test] + fn computing_initial_undesirability_works_for_origin_on_over_leg() { + let node_record = make_node_record(4567, false); + let mut subject = make_standard_subject(); + subject + .neighborhood_database + .add_node(node_record.clone()) + .unwrap(); + + let initial_undesirability = subject.compute_initial_undesirability( + node_record.public_key(), + 1_000, + RouteDirection::Over, + ); - assert_eq!(result, 0); + assert_eq!( + initial_undesirability, + 0 // Origin does not charge itself for routing + ); } #[test] - fn compute_undesirability_for_undesirable_for_exit_node_used_as_exit() { + fn computing_initial_undesirability_works_for_exit_on_back_leg() { + let node_record = make_node_record(4567, false); + let mut subject = make_standard_subject(); + subject + .neighborhood_database + .add_node(node_record.clone()) + .unwrap(); + + let initial_undesirability = subject.compute_initial_undesirability( + node_record.public_key(), + 1_000, + RouteDirection::Back, + ); + + let rate_pack = node_record.rate_pack(); + assert_eq!( + initial_undesirability, + rate_pack.exit_charge (1_000) as i64 // charge to exit response + + rate_pack.routing_charge (1_000) as i64 // charge to route response + ); + } + + #[test] + fn computing_undesirability_works_for_relay_on_back_leg() { + let node_record = make_node_record(4567, false); let subject = make_standard_subject(); - let mut node_record = make_node_record(1, false); - node_record.metadata.desirable_for_exit = false; // used as exit: penalty is effective - node_record.inner.rate_pack = RatePack { - routing_byte_rate: 123456, - routing_service_rate: 234567, - exit_byte_rate: 1, - exit_service_rate: 1, - }; - let result = subject.compute_undesirability(&node_record, 10000, LinkType::Exit); + let new_undesirability = subject.compute_new_undesirability( + &node_record, + 1_000_000, // Nonzero undesirability: we're on our way + Some(&PublicKey::new(b"Booga")), + 5, // Plenty of hops remaining: not there yet + 1_000, + RouteDirection::Back, + None, + ); - assert_eq!(result, 1 + (1 * 10000) + UNDESIRABLE_FOR_EXIT_PENALTY); + let rate_pack = node_record.rate_pack(); + assert_eq!( + new_undesirability, + 1_000_000 // existing undesirability + + rate_pack.routing_charge (1_000) as i64 // charge to route response + ); } #[test] @@ -4600,6 +4767,7 @@ mod tests { minimum_hop_count: 3, return_component_opt: None, payload_size: 10000, + hostname_opt: None, }; let unsuccessful_three_hop_route = addr.send(three_hop_route_request); let public_key_query = addr.send(NodeQueryMessage::PublicKey(a.public_key().clone())); @@ -4956,6 +5124,7 @@ mod tests { minimum_hop_count, return_component_opt: Some(Component::ProxyServer), payload_size: 10000, + hostname_opt: None, }); assert_eq!( @@ -5002,6 +5171,7 @@ mod tests { minimum_hop_count, return_component_opt: Some(Component::ProxyServer), payload_size: 10000, + hostname_opt: None, }); let next_door_neighbor_cryptde = @@ -5050,8 +5220,8 @@ mod tests { O is the originating Node, X is the exit Node. Minimum hop count is 2. Node A offers low per-packet rates and high per-byte rates; Node B offers - low per-byte rates and high per-packet rates. Large packets should prefer - route O -> A -> X -> A -> O; small packets should prefer route + low per-byte rates and high per-packet rates. Small packets should prefer + route O -> A -> X -> A -> O; large packets should prefer route O -> B -> X -> B -> O. */ @@ -5076,12 +5246,14 @@ mod tests { db.add_arbitrary_full_neighbor(a, x); db.add_arbitrary_full_neighbor(x, b); db.add_arbitrary_full_neighbor(b, o); + // Small packages should prefer A db.node_by_key_mut(a).unwrap().inner.rate_pack = RatePack { routing_byte_rate: 100, // high routing_service_rate: 1000, // low exit_byte_rate: 0, exit_service_rate: 0, }; + // Large packages should prefer B db.node_by_key_mut(b).unwrap().inner.rate_pack = RatePack { routing_byte_rate: 1, // low routing_service_rate: 100_000, // high @@ -5096,6 +5268,7 @@ mod tests { minimum_hop_count: 2, return_component_opt: Some(Component::ProxyServer), payload_size, + hostname_opt: None, }) .unwrap(); @@ -5109,7 +5282,53 @@ mod tests { }; let expected_relay_key = if a_not_b { a.clone() } else { b.clone() }; assert_eq!(extract_key(over), expected_relay_key); - assert_eq!(extract_key(back), expected_relay_key); + // All response packages are "large," so they'll all want B on the way back. + assert_eq!(extract_key(back), *b); + } + + #[test] + fn node_record_metadata_message_is_handled_properly() { + init_test_logging(); + let subject_node = make_global_cryptde_node_record(1345, true); + let public_key = PublicKey::from(&b"exit_node"[..]); + let node_record = NodeRecord::new( + &public_key, + make_wallet("earning"), + rate_pack(100), + true, + true, + 0, + main_cryptde(), + ); + let unreachable_host = String::from("facebook.com"); + let mut subject = neighborhood_from_nodes(&subject_node, None); + let _ = subject.neighborhood_database.add_node(node_record); + let addr = subject.start(); + let system = System::new("test"); + + let _ = addr.try_send(NodeRecordMetadataMessage { + public_key: public_key.clone(), + metadata_change: NRMetadataChange::AddUnreachableHost { + hostname: unreachable_host.clone(), + }, + }); + + let assertions = Box::new(move |actor: &mut Neighborhood| { + let updated_node_record = actor + .neighborhood_database + .node_by_key(&public_key) + .unwrap(); + assert!(updated_node_record + .metadata + .unreachable_hosts + .contains(&unreachable_host)); + TestLogHandler::new().exists_log_matching(&format!( + "DEBUG: Neighborhood: Marking host facebook.com unreachable for the Node with public key 0x657869745F6E6F6465" + )); + }); + addr.try_send(AssertionsMessage { assertions }).unwrap(); + System::current().stop(); + assert_eq!(system.run(), 0); } #[test] diff --git a/node/src/neighborhood/node_record.rs b/node/src/neighborhood/node_record.rs index 28e98ffe9..e490cec1d 100644 --- a/node/src/neighborhood/node_record.rs +++ b/node/src/neighborhood/node_record.rs @@ -244,14 +244,6 @@ impl NodeRecord { &self.inner.rate_pack } - pub fn is_desirable_for_exit(&self) -> bool { - self.metadata.desirable_for_exit - } - - pub fn set_desirable_for_exit(&mut self, is_desirable_for_exit: bool) { - self.metadata.desirable_for_exit = is_desirable_for_exit - } - pub fn update(&mut self, agr: AccessibleGossipRecord) -> Result<(), String> { if &agr.inner.public_key != self.public_key() { return Err(format!( @@ -326,17 +318,17 @@ impl TryFrom<&GossipNodeRecord> for NodeRecord { #[derive(Clone, Debug, Default, PartialEq, Eq)] #[allow(non_camel_case_types)] pub struct NodeRecordMetadata { - pub desirable_for_exit: bool, pub last_update: u32, pub node_addr_opt: Option, + pub unreachable_hosts: HashSet, } impl NodeRecordMetadata { pub fn new() -> NodeRecordMetadata { NodeRecordMetadata { - desirable_for_exit: true, last_update: time_t_timestamp(), node_addr_opt: None, + unreachable_hosts: Default::default(), } } } @@ -805,36 +797,6 @@ mod tests { ); } - #[test] - fn set_desirable_when_no_change_from_default() { - let mut this_node = make_node_record(5432, true); - - assert!( - this_node.is_desirable_for_exit(), - "initial state should have been desirable" - ); - this_node.set_desirable_for_exit(true); - assert!( - this_node.is_desirable_for_exit(), - "Should be desirable after being set to true." - ); - } - - #[test] - fn set_desirable_to_false() { - let mut this_node = make_node_record(5432, true); - - assert!( - this_node.is_desirable_for_exit(), - "initial state should have been desirable" - ); - this_node.set_desirable_for_exit(false); - assert!( - !this_node.is_desirable_for_exit(), - "Should be undesirable after being set to false." - ); - } - #[test] fn update_works_when_immutable_characteristics_dont_change() { let mut subject = make_node_record(1234, true); @@ -951,7 +913,7 @@ mod tests { assert_eq!( result, - Err("Updating a NodeRecord must not change its rate pack: 1235|1236|1237|1238 -> 0|0|0|0".to_string()), + Err("Updating a NodeRecord must not change its rate pack: 1235|1434|1237|1634 -> 0|0|0|0".to_string()), ) } diff --git a/node/src/neighborhood/overall_connection_status.rs b/node/src/neighborhood/overall_connection_status.rs index 7bec57187..3a7e2cf2e 100644 --- a/node/src/neighborhood/overall_connection_status.rs +++ b/node/src/neighborhood/overall_connection_status.rs @@ -338,7 +338,7 @@ mod tests { #[test] #[should_panic( - expected = "Unable to receive node addr for the descriptor NodeDescriptor { blockchain: EthRopsten, encryption_public_key: AAAA, node_addr_opt: None }" + expected = "Unable to receive node addr for the descriptor NodeDescriptor { blockchain: EthRopsten, encryption_public_key: 0x000000, node_addr_opt: None }" )] fn can_not_create_a_new_connection_without_node_addr() { let descriptor_with_no_ip_address = NodeDescriptor { diff --git a/node/src/proxy_client/mod.rs b/node/src/proxy_client/mod.rs index 974c0c89e..fd17d4e72 100644 --- a/node/src/proxy_client/mod.rs +++ b/node/src/proxy_client/mod.rs @@ -59,6 +59,7 @@ pub struct ProxyClient { stream_contexts: HashMap, exit_service_rate: u64, exit_byte_rate: u64, + is_decentralized: bool, crashable: bool, logger: Logger, } @@ -105,18 +106,14 @@ impl Handler> for ProxyClient { msg: ExpiredCoresPackage, _ctx: &mut Self::Context, ) -> Self::Result { - let is_zero_hop = match msg.remaining_route.next_hop(self.cryptde) { - Ok(live_hop) => &live_hop.public_key == self.cryptde.public_key(), - Err(_) => false, - }; let payload = msg.payload; let paying_wallet = msg.paying_wallet; - if paying_wallet.is_some() || is_zero_hop { + if paying_wallet.is_some() || !self.is_decentralized { let pool = self.pool.as_mut().expect("StreamHandlerPool unbound"); let return_route = msg.remaining_route; let latest_stream_context = StreamContext { return_route, - payload_destination_key: payload.originator_public_key.clone(), + payload_destination_key: payload.originator_alias_public_key.clone(), paying_wallet: paying_wallet.clone(), }; debug!( @@ -235,6 +232,7 @@ impl ProxyClient { stream_contexts: HashMap::new(), exit_service_rate: config.exit_service_rate, exit_byte_rate: config.exit_byte_rate, + is_decentralized: config.is_decentralized, crashable: config.crashable, logger: Logger::new("ProxyClient"), } @@ -485,6 +483,26 @@ mod tests { } } + #[test] + fn is_decentralized_flag_is_passed_through_constructor() { + let config_factory = |is_decentralized: bool| ProxyClientConfig { + cryptde: main_cryptde(), + dns_servers: vec![SocketAddr::V4( + SocketAddrV4::from_str("1.2.3.4:4560").unwrap(), + )], + exit_service_rate: 100, + exit_byte_rate: 200, + is_decentralized, + crashable: false, + }; + + let zero_hop = ProxyClient::new(config_factory(false)); + let standard = ProxyClient::new(config_factory(true)); + + assert_eq!(zero_hop.is_decentralized, false); + assert_eq!(standard.is_decentralized, true); + } + #[test] #[should_panic( expected = "panic message (processed with: node_lib::sub_lib::utils::crash_request_analyzer)" @@ -497,6 +515,7 @@ mod tests { )], exit_service_rate: 100, exit_byte_rate: 200, + is_decentralized: true, crashable: true, }); @@ -513,6 +532,7 @@ mod tests { dns_servers: vec![], exit_service_rate: 100, exit_byte_rate: 200, + is_decentralized: true, crashable: false, }); } @@ -541,6 +561,7 @@ mod tests { ], exit_service_rate: 100, exit_byte_rate: 200, + is_decentralized: true, crashable: false, }); subject.resolver_wrapper_factory = Box::new(resolver_wrapper_factory); @@ -589,7 +610,7 @@ mod tests { target_hostname: Some(String::from("target.hostname.com")), target_port: 1234, protocol: ProxyProtocol::HTTP, - originator_public_key: PublicKey::new(&b"originator_public_key"[..]), + originator_alias_public_key: PublicKey::new(&b"originator_public_key"[..]), }; let cryptde = main_cryptde(); let package = ExpiredCoresPackage::new( @@ -605,6 +626,7 @@ mod tests { dns_servers: dnss(), exit_service_rate: 100, exit_byte_rate: 200, + is_decentralized: true, crashable: false, }); let subject_addr: Addr = subject.start(); @@ -628,6 +650,7 @@ mod tests { dns_servers: vec![SocketAddr::from_str("1.1.1.1:53").unwrap()], exit_service_rate: 0, exit_byte_rate: 0, + is_decentralized: true, crashable: false, }); let subject_addr = subject.start(); @@ -668,6 +691,7 @@ mod tests { dns_servers: vec![SocketAddr::from_str("1.1.1.1:53").unwrap()], exit_service_rate: 0, exit_byte_rate: 0, + is_decentralized: true, crashable: false, }); subject.stream_contexts.insert( @@ -729,12 +753,15 @@ mod tests { target_hostname: None, target_port: 0, protocol: ProxyProtocol::HTTP, - originator_public_key: PublicKey::new(&b"originator"[..]), + originator_alias_public_key: PublicKey::new(&b"originator"[..]), }; + let key1 = make_meaningless_public_key(); + let key2 = make_meaningless_public_key(); + let route = make_one_way_route_to_proxy_client(vec![&key1, &key2]); let package = ExpiredCoresPackage::new( SocketAddr::from_str("1.2.3.4:1234").unwrap(), Some(make_wallet("consuming")), - make_meaningless_route(), + route, request.clone().into(), 0, ); @@ -756,6 +783,7 @@ mod tests { dns_servers: dnss(), exit_service_rate: 100, exit_byte_rate: 200, + is_decentralized: true, crashable: false, }); subject.resolver_wrapper_factory = Box::new(resolver_factory); @@ -785,7 +813,7 @@ mod tests { target_hostname: None, target_port: 0, protocol: ProxyProtocol::HTTP, - originator_public_key: PublicKey::new(&b"originator"[..]), + originator_alias_public_key: PublicKey::new(&b"originator"[..]), }; let package = ExpiredCoresPackage::new( SocketAddr::from_str("1.2.3.4:1234").unwrap(), @@ -812,6 +840,7 @@ mod tests { dns_servers: dnss(), exit_service_rate: rate_pack_exit(100), exit_byte_rate: rate_pack_exit_byte(100), + is_decentralized: true, crashable: false, }); subject.resolver_wrapper_factory = Box::new(resolver_factory); @@ -841,7 +870,7 @@ mod tests { target_hostname: None, target_port: 0, protocol: ProxyProtocol::HTTP, - originator_public_key: alias_cryptde.public_key().clone(), + originator_alias_public_key: alias_cryptde.public_key().clone(), }; let zero_hop_remaining_route = Route::one_way( RouteSegment::new( @@ -878,6 +907,7 @@ mod tests { dns_servers: dnss(), exit_service_rate: rate_pack_exit(100), exit_byte_rate: rate_pack_exit_byte(100), + is_decentralized: false, crashable: false, }); subject.resolver_wrapper_factory = Box::new(resolver_factory); @@ -901,17 +931,19 @@ mod tests { let stream_key = make_meaningless_stream_key(); let data: &[u8] = b"An honest politician is one who, when he is bought, will stay bought."; let system = System::new("inbound_server_data_is_translated_to_cores_packages"); + let route = make_meaningless_route(); let mut subject = ProxyClient::new(ProxyClientConfig { cryptde: main_cryptde(), dns_servers: vec![SocketAddr::from_str("8.7.6.5:4321").unwrap()], exit_service_rate: 100, exit_byte_rate: 200, + is_decentralized: true, crashable: false, }); subject.stream_contexts.insert( stream_key.clone(), StreamContext { - return_route: make_meaningless_route(), + return_route: route.clone(), payload_destination_key: PublicKey::new(&b"abcd"[..]), paying_wallet: Some(make_wallet("paying")), }, @@ -969,7 +1001,7 @@ mod tests { hopper_recording.get_record::(0), &IncipientCoresPackage::new( main_cryptde(), - make_meaningless_route(), + route.clone(), MessageType::ClientResponse(VersionedData::new( &crate::sub_lib::migrations::client_response_payload::MIGRATIONS, &ClientResponsePayload_0v1 { @@ -989,7 +1021,7 @@ mod tests { hopper_recording.get_record::(1), &IncipientCoresPackage::new( main_cryptde(), - make_meaningless_route(), + route.clone(), MessageType::ClientResponse(VersionedData::new( &crate::sub_lib::migrations::client_response_payload::MIGRATIONS, &ClientResponsePayload_0v1 { @@ -1052,6 +1084,7 @@ mod tests { dns_servers: vec![SocketAddr::from_str("8.7.6.5:4321").unwrap()], exit_service_rate: 100, exit_byte_rate: 200, + is_decentralized: true, crashable: false, }); subject.stream_contexts.insert( @@ -1103,6 +1136,7 @@ mod tests { dns_servers: vec![SocketAddr::from_str("8.7.6.5:4321").unwrap()], exit_service_rate: 100, exit_byte_rate: 200, + is_decentralized: true, crashable: false, }); subject.stream_contexts.insert( @@ -1152,6 +1186,7 @@ mod tests { dns_servers: vec![SocketAddr::from_str("8.7.6.5:4321").unwrap()], exit_service_rate: 100, exit_byte_rate: 200, + is_decentralized: true, crashable: false, }); let mut process_package_params_arc = Arc::new(Mutex::new(vec![])); @@ -1188,7 +1223,7 @@ mod tests { target_hostname: None, target_port: 0, protocol: ProxyProtocol::HTTP, - originator_public_key: originator_public_key.clone(), + originator_alias_public_key: originator_public_key.clone(), }; let before = SystemTime::now(); diff --git a/node/src/proxy_client/stream_establisher.rs b/node/src/proxy_client/stream_establisher.rs index e798e9847..364e12c1e 100644 --- a/node/src/proxy_client/stream_establisher.rs +++ b/node/src/proxy_client/stream_establisher.rs @@ -189,7 +189,7 @@ mod tests { target_hostname: Some("blah".to_string()), target_port: 0, protocol: ProxyProtocol::HTTP, - originator_public_key: subject.cryptde.public_key().clone(), + originator_alias_public_key: subject.cryptde.public_key().clone(), }, read_stream, SocketAddr::from_str("1.2.3.4:5678").unwrap(), diff --git a/node/src/proxy_client/stream_handler_pool.rs b/node/src/proxy_client/stream_handler_pool.rs index 20d7d6c9f..2334e29fc 100644 --- a/node/src/proxy_client/stream_handler_pool.rs +++ b/node/src/proxy_client/stream_handler_pool.rs @@ -602,7 +602,7 @@ mod tests { target_hostname: Some("www.example.com".to_string()), target_port: HTTP_PORT, protocol: ProxyProtocol::HTTP, - originator_public_key: cryptde.public_key().clone(), + originator_alias_public_key: cryptde.public_key().clone(), }; StreamHandlerPoolReal::process_package(payload, None, Arc::new(Mutex::new(inner))); @@ -635,7 +635,7 @@ mod tests { target_hostname: None, target_port: HTTP_PORT, protocol: ProxyProtocol::HTTP, - originator_public_key: PublicKey::new(&b"men's souls"[..]), + originator_alias_public_key: PublicKey::new(&b"men's souls"[..]), }; let write_parameters = Arc::new(Mutex::new(vec![])); let tx_to_write = Box::new( @@ -697,7 +697,7 @@ mod tests { target_hostname: Some(String::from("that.try")), target_port: HTTP_PORT, protocol: ProxyProtocol::HTTP, - originator_public_key: originator_key, + originator_alias_public_key: originator_key, }; let package = ExpiredCoresPackage::new( SocketAddr::from_str("1.2.3.4:1234").unwrap(), @@ -766,7 +766,7 @@ mod tests { target_hostname: Some(String::from("3.4.5.6:80")), target_port: HTTP_PORT, protocol: ProxyProtocol::HTTP, - originator_public_key: PublicKey::new(&b"men's souls"[..]), + originator_alias_public_key: PublicKey::new(&b"men's souls"[..]), }; let package = ExpiredCoresPackage::new( SocketAddr::from_str("1.2.3.4:1234").unwrap(), @@ -875,7 +875,7 @@ mod tests { target_hostname: Some(String::from("3.4.5.6")), target_port: HTTP_PORT, protocol: ProxyProtocol::HTTP, - originator_public_key: PublicKey::new(&b"men's souls"[..]), + originator_alias_public_key: PublicKey::new(&b"men's souls"[..]), }; let package = ExpiredCoresPackage::new( SocketAddr::from_str("1.2.3.4:1234").unwrap(), @@ -982,7 +982,7 @@ mod tests { target_hostname: None, target_port: HTTP_PORT, protocol: ProxyProtocol::HTTP, - originator_public_key: originator_key, + originator_alias_public_key: originator_key, }; let package = ExpiredCoresPackage::new( SocketAddr::from_str("1.2.3.4:1234").unwrap(), @@ -1051,7 +1051,7 @@ mod tests { target_hostname: Some(String::from("that.try")), target_port: HTTP_PORT, protocol: ProxyProtocol::HTTP, - originator_public_key: PublicKey::new(&b"men's souls"[..]), + originator_alias_public_key: PublicKey::new(&b"men's souls"[..]), }; let package = ExpiredCoresPackage::new( SocketAddr::from_str("1.2.3.4:1234").unwrap(), @@ -1164,7 +1164,7 @@ mod tests { target_hostname: Some(String::from("that.try")), target_port: HTTP_PORT, protocol: ProxyProtocol::HTTP, - originator_public_key: originator_key, + originator_alias_public_key: originator_key, }; let package = ExpiredCoresPackage::new( SocketAddr::from_str("1.2.3.4:1234").unwrap(), @@ -1254,7 +1254,7 @@ mod tests { target_hostname: Some(String::from("that.try")), target_port: HTTP_PORT, protocol: ProxyProtocol::HTTP, - originator_public_key: PublicKey::new(&b"men's souls"[..]), + originator_alias_public_key: PublicKey::new(&b"men's souls"[..]), }; let package = ExpiredCoresPackage::new( @@ -1367,7 +1367,7 @@ mod tests { target_hostname: Some(String::from("that.try")), target_port: HTTP_PORT, protocol: ProxyProtocol::HTTP, - originator_public_key: originator_key, + originator_alias_public_key: originator_key, }; let package = ExpiredCoresPackage::new( SocketAddr::from_str("1.2.3.4:1234").unwrap(), @@ -1429,7 +1429,7 @@ mod tests { target_hostname: Some(String::from("that.try")), target_port: HTTP_PORT, protocol: ProxyProtocol::HTTP, - originator_public_key: PublicKey::new(&b"men's souls"[..]), + originator_alias_public_key: PublicKey::new(&b"men's souls"[..]), }; let package = ExpiredCoresPackage::new( SocketAddr::from_str("1.2.3.4:1234").unwrap(), @@ -1497,7 +1497,7 @@ mod tests { target_hostname: None, target_port: HTTP_PORT, protocol: ProxyProtocol::HTTP, - originator_public_key: PublicKey::new(&b"booga"[..]), + originator_alias_public_key: PublicKey::new(&b"booga"[..]), }; let package = ExpiredCoresPackage::new( SocketAddr::from_str("1.2.3.4:1234").unwrap(), diff --git a/node/src/proxy_server/client_request_payload_factory.rs b/node/src/proxy_server/client_request_payload_factory.rs index 1f8e45689..29d83f137 100644 --- a/node/src/proxy_server/client_request_payload_factory.rs +++ b/node/src/proxy_server/client_request_payload_factory.rs @@ -60,7 +60,7 @@ impl ClientRequestPayloadFactory for ClientRequestPayloadFactoryReal { target_hostname, target_port, protocol: protocol_pack.proxy_protocol(), - originator_public_key: cryptde.public_key().clone(), + originator_alias_public_key: cryptde.public_key().clone(), }) } } @@ -113,7 +113,7 @@ mod tests { target_hostname: Some(String::from("borkoed.com")), target_port: 2345, protocol: ProxyProtocol::HTTP, - originator_public_key: cryptde.public_key().clone(), + originator_alias_public_key: cryptde.public_key().clone(), }) ); } @@ -148,7 +148,7 @@ mod tests { target_hostname: Some(String::from("borkoed.com")), target_port: HTTP_PORT, protocol: ProxyProtocol::HTTP, - originator_public_key: cryptde.public_key().clone(), + originator_alias_public_key: cryptde.public_key().clone(), }) ); } @@ -202,7 +202,7 @@ mod tests { target_hostname: Some(String::from("server.com")), target_port: 443, protocol: ProxyProtocol::TLS, - originator_public_key: cryptde.public_key().clone(), + originator_alias_public_key: cryptde.public_key().clone(), }) ); } @@ -250,7 +250,7 @@ mod tests { target_hostname: None, target_port: 443, protocol: ProxyProtocol::TLS, - originator_public_key: cryptde.public_key().clone(), + originator_alias_public_key: cryptde.public_key().clone(), }) ); } diff --git a/node/src/proxy_server/mod.rs b/node/src/proxy_server/mod.rs index e68cfc4f4..3ca2841ef 100644 --- a/node/src/proxy_server/mod.rs +++ b/node/src/proxy_server/mod.rs @@ -25,10 +25,10 @@ use crate::sub_lib::cryptde::PublicKey; use crate::sub_lib::dispatcher::InboundClientData; use crate::sub_lib::dispatcher::{Endpoint, StreamShutdownMsg}; use crate::sub_lib::hopper::{ExpiredCoresPackage, IncipientCoresPackage}; -use crate::sub_lib::neighborhood::RouteQueryMessage; use crate::sub_lib::neighborhood::RouteQueryResponse; use crate::sub_lib::neighborhood::{ExpectedService, NodeRecordMetadataMessage}; use crate::sub_lib::neighborhood::{ExpectedServices, RatePack}; +use crate::sub_lib::neighborhood::{NRMetadataChange, RouteQueryMessage}; use crate::sub_lib::peer_actors::BindMessage; use crate::sub_lib::proxy_client::{ClientResponsePayload_0v1, DnsResolveFailure_0v1}; use crate::sub_lib::proxy_server::ClientRequestPayload_0v1; @@ -278,19 +278,23 @@ impl ProxyServer { }) .clone() }; + let server_name_opt = return_route_info.server_name_opt.clone(); let response = &msg.payload; match self.keys_and_addrs.a_to_b(&response.stream_key) { Some(socket_addr) => { - self.subs - .as_ref() - .expect("Neighborhood unbound in ProxyServer") - .update_node_record_metadata - .try_send(NodeRecordMetadataMessage::Desirable( - exit_public_key.clone(), - false, - )) - .expect("Neighborhood is dead"); - + if let Some(server_name) = server_name_opt { + self.subs + .as_ref() + .expect("Neighborhood unbound in ProxyServer") + .update_node_record_metadata + .try_send(NodeRecordMetadataMessage { + public_key: exit_public_key.clone(), + metadata_change: NRMetadataChange::AddUnreachableHost { + hostname: server_name, + }, + }) + .expect("Neighborhood is dead"); + } self.report_response_services_consumed(&return_route_info, 0, msg.payload_len); self.subs @@ -305,7 +309,7 @@ impl ProxyServer { .server_impersonator() .dns_resolution_failure_response( &exit_public_key, - return_route_info.server_name.clone(), + return_route_info.server_name_opt.clone(), ), }) .expect("Dispatcher is dead"); @@ -316,13 +320,9 @@ impl ProxyServer { self.purge_stream_key(&response.stream_key); } None => { - let server_name = match &return_route_info.server_name { - Some(name) => format!("\"{}\"", name), - None => "".to_string(), - }; error!(self.logger, "Discarding DnsResolveFailure message for {} from an unrecognized stream key {:?}", - server_name, + server_name_opt.unwrap_or_else(|| "".to_string()), &response.stream_key ) } @@ -553,7 +553,7 @@ impl ProxyServer { return_route_id, expected_services: back, protocol: args.common.payload.protocol, - server_name: args.common.payload.target_hostname.clone(), + server_name_opt: args.common.payload.target_hostname.clone(), }; debug!( args.logger, @@ -563,7 +563,7 @@ impl ProxyServer { .try_send(return_route_info) .expect("ProxyServer is dead"); ProxyServer::transmit_to_hopper( - args.common.cryptde, + args.common.main_cryptde, args.hopper_sub, args.common.timestamp, args.common.payload, @@ -645,7 +645,7 @@ impl ProxyServer { #[allow(clippy::too_many_arguments)] fn transmit_to_hopper( - cryptde: &'static dyn CryptDE, + main_cryptde: &'static dyn CryptDE, hopper: &Recipient, timestamp: SystemTime, payload: ClientRequestPayload_0v1, @@ -664,7 +664,8 @@ impl ProxyServer { _ => None, }) } else { - Some(payload.originator_public_key.clone()) + // In Zero Hop Mode the exit node public key is the same as this public key + Some(main_cryptde.public_key().clone()) }; match destination_key_opt { None => ProxyServer::handle_route_failure(payload, logger, source_addr, dispatcher), @@ -676,7 +677,7 @@ impl ProxyServer { let payload_size = payload.sequenced_packet.data.len(); let stream_key = payload.stream_key; let pkg = IncipientCoresPackage::new( - cryptde, + main_cryptde, route.clone(), payload.into(), &payload_destination_key, @@ -892,7 +893,7 @@ impl IBCDHelper for IBCDHelperReal { }; let local_args = TTHLocalArgs { common: TTHCommonArgs { - cryptde: proxy.main_cryptde, + main_cryptde: proxy.main_cryptde, payload, source_addr, timestamp, @@ -938,6 +939,7 @@ impl IBCDHelperReal { ) -> Result<(), String> { let common_args = args.common_opt.as_ref().expectv("TTH common"); let pld = &common_args.payload; + let hostname_opt = pld.target_hostname.clone(); debug!( args.logger, "Getting route and opening new stream with key {} to transmit: sequence {}, length {}", @@ -949,6 +951,7 @@ impl IBCDHelperReal { tokio::spawn( route_source .send(RouteQueryMessage::data_indefinite_route_request( + hostname_opt, if common_args.is_decentralized { DEFAULT_MINIMUM_HOP_COUNT } else { @@ -1236,7 +1239,7 @@ mod tests { target_hostname: Some(String::from("nowhere.com")), target_port: HTTP_PORT, protocol: ProxyProtocol::HTTP, - originator_public_key: alias_cryptde.public_key().clone(), + originator_alias_public_key: alias_cryptde.public_key().clone(), }; let expected_pkg = IncipientCoresPackage::new( main_cryptde, @@ -1287,7 +1290,11 @@ mod tests { let record = recording.get_record::(0); assert_eq!( record, - &RouteQueryMessage::data_indefinite_route_request(DEFAULT_MINIMUM_HOP_COUNT, 47) + &RouteQueryMessage::data_indefinite_route_request( + Some("nowhere.com".to_string()), + DEFAULT_MINIMUM_HOP_COUNT, + 47 + ) ); let recording = proxy_server_recording_arc.lock().unwrap(); assert_eq!(recording.len(), 0); @@ -1348,7 +1355,7 @@ mod tests { target_hostname: Some(String::from("realdomain.nu")), target_port: 443, protocol: ProxyProtocol::TLS, - originator_public_key: alias_cryptde.public_key().clone(), + originator_alias_public_key: alias_cryptde.public_key().clone(), }; let expected_pkg = IncipientCoresPackage::new( main_cryptde, @@ -1407,7 +1414,11 @@ mod tests { let neighborhood_record = neighborhood_recording.get_record::(0); assert_eq!( neighborhood_record, - &RouteQueryMessage::data_indefinite_route_request(DEFAULT_MINIMUM_HOP_COUNT, 12) + &RouteQueryMessage::data_indefinite_route_request( + Some("realdomain.nu".to_string()), + DEFAULT_MINIMUM_HOP_COUNT, + 12 + ) ); } @@ -1436,7 +1447,7 @@ mod tests { return_route_id: 1234, expected_services: vec![ExpectedService::Nothing], protocol: ProxyProtocol::TLS, - server_name: None, + server_name_opt: None, }, ); let subject_addr: Addr = subject.start(); @@ -1805,6 +1816,7 @@ mod tests { minimum_hop_count: 0, return_component_opt: Some(Component::ProxyServer), payload_size: 47, + hostname_opt: Some("nowhere.com".to_string()) } ); let dispatcher_recording = dispatcher_log_arc.lock().unwrap(); @@ -1823,10 +1835,10 @@ mod tests { target_hostname: Some("nowhere.com".to_string()), target_port: 80, protocol: ProxyProtocol::HTTP, - originator_public_key: alias_cryptde.public_key().clone(), + originator_alias_public_key: alias_cryptde.public_key().clone(), } )), - alias_cryptde.public_key() + main_cryptde.public_key() ) .unwrap() ); @@ -1884,7 +1896,8 @@ mod tests { target_component: Component::ProxyClient, minimum_hop_count: 0, return_component_opt: Some(Component::ProxyServer), - payload_size: 16 + payload_size: 16, + hostname_opt: None } ); let dispatcher_recording = dispatcher_log_arc.lock().unwrap(); @@ -1903,10 +1916,10 @@ mod tests { target_hostname: None, target_port: 443, protocol: ProxyProtocol::TLS, - originator_public_key: alias_cryptde.public_key().clone(), + originator_alias_public_key: alias_cryptde.public_key().clone(), } ),), - alias_cryptde.public_key() + main_cryptde.public_key() ) .unwrap() ); @@ -1954,7 +1967,7 @@ mod tests { target_hostname: Some(String::from("nowhere.com")), target_port: HTTP_PORT, protocol: ProxyProtocol::HTTP, - originator_public_key: alias_cryptde.public_key().clone(), + originator_alias_public_key: alias_cryptde.public_key().clone(), }; let expected_pkg = IncipientCoresPackage::new( main_cryptde, @@ -2035,7 +2048,7 @@ mod tests { target_hostname: Some(String::from("nowhere.com")), target_port: HTTP_PORT, protocol: ProxyProtocol::HTTP, - originator_public_key: alias_cryptde.public_key().clone(), + originator_alias_public_key: alias_cryptde.public_key().clone(), }; let expected_pkg = IncipientCoresPackage::new( main_cryptde, @@ -2153,7 +2166,7 @@ mod tests { target_hostname: Some(String::from("nowhere.com")), target_port: HTTP_PORT, protocol: ProxyProtocol::HTTP, - originator_public_key: alias_cryptde.public_key().clone(), + originator_alias_public_key: alias_cryptde.public_key().clone(), }; let expected_pkg = IncipientCoresPackage::new( main_cryptde, @@ -2194,7 +2207,11 @@ mod tests { let record = recording.get_record::(0); assert_eq!( record, - &RouteQueryMessage::data_indefinite_route_request(3, 47) + &RouteQueryMessage::data_indefinite_route_request( + Some("nowhere.com".to_string()), + 3, + 47 + ) ); } @@ -2298,7 +2315,7 @@ mod tests { target_hostname: Some(String::from("nowhere.com")), target_port: HTTP_PORT, protocol: ProxyProtocol::HTTP, - originator_public_key: alias_cryptde.public_key().clone(), + originator_alias_public_key: alias_cryptde.public_key().clone(), }; let expected_pkg = IncipientCoresPackage::new( main_cryptde, @@ -2413,12 +2430,12 @@ mod tests { target_hostname: Some("nowhere.com".to_string()), target_port: HTTP_PORT, protocol: ProxyProtocol::HTTP, - originator_public_key: PublicKey::new(b"originator_public_key"), + originator_alias_public_key: PublicKey::new(b"originator_public_key"), }; let logger = Logger::new("test"); let local_tth_args = TTHLocalArgs { common: TTHCommonArgs { - cryptde, + main_cryptde: cryptde, payload, source_addr, timestamp: now, @@ -2499,12 +2516,12 @@ mod tests { target_hostname: Some("nowhere.com".to_string()), target_port: HTTP_PORT, protocol: ProxyProtocol::HTTP, - originator_public_key: PublicKey::new(b"originator_public_key"), + originator_alias_public_key: PublicKey::new(b"originator_public_key"), }; let logger = Logger::new("test"); let local_tth_args = TTHLocalArgs { common: TTHCommonArgs { - cryptde, + main_cryptde: cryptde, payload, source_addr, timestamp: SystemTime::now(), @@ -2530,7 +2547,7 @@ mod tests { return_route_id: 0, expected_services: vec![ExpectedService::Nothing], protocol: ProxyProtocol::HTTP, - server_name: Some("nowhere.com".to_string()) + server_name_opt: Some("nowhere.com".to_string()) } ); let record = recording.get_record::(1); @@ -2610,9 +2627,9 @@ mod tests { #[test] #[should_panic( - expected = "Each route must demand an exit service, but this route has no such demand: [Routing(cm91dGluZ19rZXlfMQ, \ + expected = "Each route must demand an exit service, but this route has no such demand: [Routing(0x726F7574696E675F6B65795F31, \ Wallet { kind: Address(0x00000000726f7574696e675f77616c6c65745f31) }, RatePack { routing_byte_rate: 9, \ - routing_service_rate: 10, exit_byte_rate: 11, exit_service_rate: 12 })]" + routing_service_rate: 208, exit_byte_rate: 11, exit_service_rate: 408 })]" )] fn proxy_server_panics_when_exit_services_are_not_requested_in_non_zero_hop_mode() { let expected_services = vec![ExpectedService::Routing( @@ -2626,11 +2643,11 @@ mod tests { #[test] #[should_panic( - expected = "Detected more than one exit service in one-way route: [Exit(ZXhpdCBrZXkgMQ, Wallet { kind: \ + expected = "Detected more than one exit service in one-way route: [Exit(0x65786974206B65792031, Wallet { kind: \ Address(0x00000000000000657869742077616c6c65742031) }, RatePack { routing_byte_rate: 7, routing_service_rate: \ - 8, exit_byte_rate: 9, exit_service_rate: 10 }), Exit(ZXhpdCBrZXkgMg, Wallet { kind: \ + 206, exit_byte_rate: 9, exit_service_rate: 406 }), Exit(0x65786974206B65792032, Wallet { kind: \ Address(0x00000000000000657869742077616c6c65742032) }, RatePack { routing_byte_rate: 6, routing_service_rate: \ - 7, exit_byte_rate: 8, exit_service_rate: 9 })]" + 205, exit_byte_rate: 8, exit_service_rate: 405 })]" )] fn proxy_server_panics_when_there_are_more_than_one_exit_services_in_the_route() { let expected_services = vec![ @@ -2706,7 +2723,11 @@ mod tests { let record = recording.get_record::(0); assert_eq!( record, - &RouteQueryMessage::data_indefinite_route_request(3, 47) + &RouteQueryMessage::data_indefinite_route_request( + Some("nowhere.com".to_string()), + 3, + 47 + ) ); TestLogHandler::new() .exists_log_containing("ERROR: ProxyServer: Failed to find route to nowhere.com"); @@ -2750,13 +2771,13 @@ mod tests { target_hostname: None, target_port: 0, protocol: ProxyProtocol::TLS, - originator_public_key: cryptde.public_key().clone(), + originator_alias_public_key: cryptde.public_key().clone(), }; let logger = Logger::new("ProxyServer"); let source_addr = SocketAddr::from_str("1.2.3.4:5678").unwrap(); let local_tth_args = TTHLocalArgs { common: TTHCommonArgs { - cryptde, + main_cryptde: cryptde, payload, source_addr, timestamp: SystemTime::now(), @@ -2801,7 +2822,7 @@ mod tests { ), ], protocol: ProxyProtocol::HTTP, - server_name: None, + server_name_opt: None, }; subject.report_response_services_consumed(&add_return_route_message, 1234, 3456); @@ -2878,7 +2899,11 @@ mod tests { let record = recording.get_record::(0); assert_eq!( record, - &RouteQueryMessage::data_indefinite_route_request(3, 47) + &RouteQueryMessage::data_indefinite_route_request( + Some("nowhere.com".to_string()), + 3, + 47 + ) ); TestLogHandler::new() .exists_log_containing("ERROR: ProxyServer: Failed to find route to nowhere.com"); @@ -2944,7 +2969,7 @@ mod tests { target_hostname: Some(String::from("server.com")), target_port: TLS_PORT, protocol: ProxyProtocol::TLS, - originator_public_key: alias_cryptde.public_key().clone(), + originator_alias_public_key: alias_cryptde.public_key().clone(), }; let expected_pkg = IncipientCoresPackage::new( main_cryptde, @@ -3030,7 +3055,7 @@ mod tests { target_hostname: None, target_port: TLS_PORT, protocol: ProxyProtocol::TLS, - originator_public_key: alias_cryptde.public_key().clone(), + originator_alias_public_key: alias_cryptde.public_key().clone(), }; let expected_pkg = IncipientCoresPackage::new( main_cryptde, @@ -3114,7 +3139,7 @@ mod tests { target_hostname: None, target_port: TLS_PORT, protocol: ProxyProtocol::TLS, - originator_public_key: alias_cryptde.public_key().clone(), + originator_alias_public_key: alias_cryptde.public_key().clone(), }; let expected_pkg = IncipientCoresPackage::new( main_cryptde, @@ -3250,7 +3275,7 @@ mod tests { return_route_id: 1234, expected_services: vec![ExpectedService::Nothing], protocol: ProxyProtocol::TLS, - server_name: None, + server_name_opt: None, }, ); let subject_addr: Addr = subject.start(); @@ -3321,7 +3346,7 @@ mod tests { return_route_id: 1234, expected_services: vec![], protocol: ProxyProtocol::HTTP, - server_name: None, + server_name_opt: None, }, ); let client_response_payload = ClientResponsePayload_0v1 { @@ -3395,7 +3420,7 @@ mod tests { ExpectedService::Nothing, ], protocol: ProxyProtocol::TLS, - server_name: None, + server_name_opt: None, }, ); let incoming_route_g_wallet = make_wallet("G Earning"); @@ -3427,7 +3452,7 @@ mod tests { ExpectedService::Nothing, ], protocol: ProxyProtocol::TLS, - server_name: None, + server_name_opt: None, }, ); let subject_addr: Addr = subject.start(); @@ -3592,7 +3617,7 @@ mod tests { ), ], protocol: ProxyProtocol::TLS, - server_name: None, + server_name_opt: None, }, ); let subject_addr: Addr = subject.start(); @@ -3699,7 +3724,7 @@ mod tests { rate_pack(10), )], protocol: ProxyProtocol::HTTP, - server_name: Some("server.com".to_string()), + server_name_opt: Some("server.com".to_string()), }) .unwrap(); subject_addr.try_send(expired_cores_package).unwrap(); @@ -3770,7 +3795,7 @@ mod tests { ExpectedService::Nothing, ], protocol: ProxyProtocol::TLS, - server_name: Some("server.com".to_string()), + server_name_opt: Some("server.com".to_string()), }, ); let subject_addr: Addr = subject.start(); @@ -3858,7 +3883,7 @@ mod tests { rate_pack(10), )], protocol: ProxyProtocol::HTTP, - server_name: Some("server.com".to_string()), + server_name_opt: Some("server.com".to_string()), }, ); let subject_addr: Addr = subject.start(); @@ -3885,10 +3910,71 @@ mod tests { let record = neighborhood_recording.get_record::(0); assert_eq!( record, - &NodeRecordMetadataMessage::Desirable(exit_public_key, false) + &NodeRecordMetadataMessage { + public_key: exit_public_key, + metadata_change: NRMetadataChange::AddUnreachableHost { + hostname: "server.com".to_string() + } + } ); } + #[test] + fn handle_dns_resolve_failure_does_not_send_message_to_neighborhood_when_server_is_not_specified( + ) { + let system = System::new("test"); + let (neighborhood, _, neighborhood_recording_arc) = make_recorder(); + let cryptde = main_cryptde(); + let mut subject = ProxyServer::new( + cryptde, + alias_cryptde(), + true, + Some(STANDARD_CONSUMING_WALLET_BALANCE), + false, + ); + let stream_key = make_meaningless_stream_key(); + let socket_addr = SocketAddr::from_str("1.2.3.4:5678").unwrap(); + subject + .keys_and_addrs + .insert(stream_key.clone(), socket_addr); + let exit_public_key = PublicKey::from(&b"exit_key"[..]); + let exit_wallet = make_wallet("exit wallet"); + subject.route_ids_to_return_routes.insert( + 1234, + AddReturnRouteMessage { + return_route_id: 1234, + expected_services: vec![ExpectedService::Exit( + exit_public_key.clone(), + exit_wallet, + rate_pack(10), + )], + protocol: ProxyProtocol::HTTP, + server_name_opt: None, + }, + ); + let subject_addr: Addr = subject.start(); + let dns_resolve_failure = DnsResolveFailure_0v1::new(stream_key); + let expired_cores_package: ExpiredCoresPackage = + ExpiredCoresPackage::new( + SocketAddr::from_str("1.2.3.4:1234").unwrap(), + Some(make_wallet("irrelevant")), + return_route_with_id(cryptde, 1234), + dns_resolve_failure.into(), + 0, + ); + let mut peer_actors = peer_actors_builder().neighborhood(neighborhood).build(); + peer_actors.proxy_server = ProxyServer::make_subs_from(&subject_addr); + + subject_addr.try_send(BindMessage { peer_actors }).unwrap(); + subject_addr.try_send(expired_cores_package).unwrap(); + + System::current().stop(); + system.run(); + let neighborhood_recording = neighborhood_recording_arc.lock().unwrap(); + let record_opt = neighborhood_recording.get_record_opt::(0); + assert_eq!(record_opt, None); + } + #[test] fn handle_dns_resolve_failure_logs_when_stream_key_be_gone_but_server_name_be_not() { init_test_logging(); @@ -3920,7 +4006,7 @@ mod tests { rate_pack(10), )], protocol: ProxyProtocol::HTTP, - server_name: Some("server.com".to_string()), + server_name_opt: Some("server.com".to_string()), }, ); let subject_addr: Addr = subject.start(); @@ -3948,7 +4034,12 @@ mod tests { System::current().stop(); system.run(); TestLogHandler::new().exists_log_containing( - format!("Discarding DnsResolveFailure message for \"server.com\" from an unrecognized stream key {:?}", stream_key).as_str()); + format!( + "Discarding DnsResolveFailure message for {} from an unrecognized stream key {:?}", + "server.com", stream_key + ) + .as_str(), + ); } #[test] @@ -3987,7 +4078,7 @@ mod tests { rate_pack(10), )], protocol: ProxyProtocol::HTTP, - server_name: None, + server_name_opt: None, }, ); @@ -4023,7 +4114,11 @@ mod tests { system.run(); TestLogHandler::new().exists_log_containing( - format!("Discarding DnsResolveFailure message for from an unrecognized stream key {:?}", stream_key).as_str()); + &format!( + "Discarding DnsResolveFailure message for from an unrecognized stream key {:?}", + stream_key + ) + ); } #[test] @@ -4070,7 +4165,7 @@ mod tests { return_route_id: 1234, expected_services: vec![ExpectedService::Nothing, ExpectedService::Nothing], protocol: ProxyProtocol::HTTP, - server_name: None, + server_name_opt: None, }, ); let dns_resolve_failure = DnsResolveFailure_0v1::new(stream_key); @@ -4115,7 +4210,7 @@ mod tests { return_route_id: 4321, expected_services: vec![ExpectedService::Nothing], protocol: ProxyProtocol::HTTP, - server_name: None, + server_name_opt: None, }, ); let subject_addr: Addr = subject.start(); @@ -4305,7 +4400,7 @@ mod tests { return_route_id: 1234, expected_services: vec![], protocol: ProxyProtocol::TLS, - server_name: None, + server_name_opt: None, }, ); let subject_addr: Addr = subject.start(); @@ -4484,7 +4579,7 @@ mod tests { target_hostname: Some(String::from("tunneled.com")), target_port: 443, protocol: ProxyProtocol::TLS, - originator_public_key: alias_cryptde().public_key().clone(), + originator_alias_public_key: alias_cryptde().public_key().clone(), } ), other => panic!("Wrong payload type: {:?}", other), @@ -4601,7 +4696,7 @@ mod tests { target_hostname: None, target_port: HTTP_PORT, protocol: ProxyProtocol::HTTP, - originator_public_key: alias_cryptde().public_key().clone(), + originator_alias_public_key: alias_cryptde().public_key().clone(), } ), other => panic!("Wrong payload type: {:?}", other), diff --git a/node/src/proxy_server/utils.rs b/node/src/proxy_server/utils.rs index 2beab87e7..d42252537 100644 --- a/node/src/proxy_server/utils.rs +++ b/node/src/proxy_server/utils.rs @@ -14,7 +14,7 @@ pub(in crate::proxy_server) mod local { use std::time::SystemTime; pub struct TTHCommonArgs { - pub cryptde: &'static dyn CryptDE, + pub main_cryptde: &'static dyn CryptDE, pub payload: ClientRequestPayload_0v1, pub source_addr: SocketAddr, pub timestamp: SystemTime, diff --git a/node/src/stream_handler_pool.rs b/node/src/stream_handler_pool.rs index 1a28a8358..5156ad2d7 100644 --- a/node/src/stream_handler_pool.rs +++ b/node/src/stream_handler_pool.rs @@ -297,7 +297,7 @@ impl StreamHandlerPool { }; debug!( self.logger, - "Sending node query about {} to Neighborhood", key + "Sending node query about {:?} to Neighborhood", key ); self.ask_neighborhood_opt .as_ref() @@ -408,7 +408,7 @@ impl StreamHandlerPool { Some(Some(tx_box)) => { debug!( self.logger, - "Found already-open stream to {} keyed by {}: using", + "Found already-open stream to {} keyed by {}: using it", tx_box.peer_addr(), sw_key ); diff --git a/node/src/sub_lib/cryptde.rs b/node/src/sub_lib/cryptde.rs index 5ce38dd3b..3e37449d0 100644 --- a/node/src/sub_lib/cryptde.rs +++ b/node/src/sub_lib/cryptde.rs @@ -31,8 +31,8 @@ impl fmt::Debug for PrivateKey { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> Result<(), fmt::Error> { write!( f, - "{}", - base64::encode_config(&self.data, base64::STANDARD_NO_PAD) + "0x{}", + self.data.as_slice().to_hex::().to_uppercase() ) } } @@ -114,8 +114,8 @@ impl fmt::Debug for PublicKey { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> Result<(), fmt::Error> { write!( f, - "{}", - base64::encode_config(&self.data, base64::STANDARD_NO_PAD) + "0x{}", + self.data.as_slice().to_hex::().to_uppercase() ) } } @@ -982,12 +982,12 @@ mod tests { } #[test] - fn public_key_can_be_formatted_as_base_64() { + fn public_key_is_displayed_as_base64_and_debugged_as_hex() { let subject = PublicKey::new(&b"Now is the time for all good men"[..]); let result = format!("{} {:?}", subject, subject); - assert_eq!(result, String::from ("Tm93IGlzIHRoZSB0aW1lIGZvciBhbGwgZ29vZCBtZW4 Tm93IGlzIHRoZSB0aW1lIGZvciBhbGwgZ29vZCBtZW4")); + assert_eq!(result, String::from ("Tm93IGlzIHRoZSB0aW1lIGZvciBhbGwgZ29vZCBtZW4 0x4E6F77206973207468652074696D6520666F7220616C6C20676F6F64206D656E")); } #[derive(PartialEq, Eq, Debug, Serialize, Deserialize)] diff --git a/node/src/sub_lib/cryptde_null.rs b/node/src/sub_lib/cryptde_null.rs index 96825d66c..4b724ca77 100644 --- a/node/src/sub_lib/cryptde_null.rs +++ b/node/src/sub_lib/cryptde_null.rs @@ -23,6 +23,11 @@ pub struct CryptDENull { next_symmetric_key_seed: Arc>, } +/* + Are you here because you're having trouble with a strange byte sequence consisting only of + repeated ASCII '4's or decimal 52s or hex 0x34s? If so, take a look at the implementation of + random() below. +*/ impl CryptDE for CryptDENull { fn encode(&self, public_key: &PublicKey, data: &PlainData) -> Result { Self::encode_with_key_data(&Self::other_key_data(public_key.as_slice()), data) @@ -55,10 +60,14 @@ impl CryptDE for CryptDENull { SymmetricKey::new(&key_data) } - #[allow(clippy::needless_range_loop)] + // Random data in byte arrays can be difficult to tell from purposeful data in byte arrays. + // Hence, for CryptDENull, whenever we are told to generate random data, we produce a string + // of '4' or 52 or 0x34 instead: that way you can look at the data and tell that it came from + // here. Never fear: CryptDEReal will give you real random data. + // Inspiration: https://xkcd.com/221/ fn random(&self, dest: &mut [u8]) { - for i in 0..dest.len() { - dest[i] = b'4' + for byte in dest { + *byte = b'4' } } @@ -94,7 +103,12 @@ impl CryptDE for CryptDENull { signature: &CryptData, public_key: &PublicKey, ) -> bool { - let claimed_hash = match Self::decode_with_key_data(public_key.as_slice(), signature) { + let public_key_bytes = public_key.as_slice(); + let (private_key_bytes, _) = Self::key_and_data(public_key_bytes.len(), signature); + if private_key_bytes != public_key_bytes { + return false; + } + let claimed_hash = match Self::decode_with_key_data(public_key_bytes, signature) { Err(_) => return false, Ok(hash) => CryptData::new(hash.as_slice()), }; @@ -190,6 +204,12 @@ impl CryptDENull { in_key_data.iter().map(|b| (*b).wrapping_add(128)).collect() } + pub fn extract_key_pair(key_length: usize, data: &CryptData) -> (PrivateKey, PublicKey) { + let private = PrivateKey::new(&data.as_slice()[0..key_length]); + let public = CryptDENull::public_from_private(&private); + (private, public) + } + fn encode_with_key_data(key_data: &[u8], data: &PlainData) -> Result { if key_data.is_empty() { Err(CryptdecError::EmptyKey) @@ -200,6 +220,10 @@ impl CryptDENull { } } + fn key_and_data(key_len: usize, data: &CryptData) -> (&[u8], &[u8]) { + data.as_slice().split_at(key_len) + } + fn decode_with_key_data(key_data: &[u8], data: &CryptData) -> Result { if key_data.is_empty() { Err(CryptdecError::EmptyKey) @@ -210,10 +234,9 @@ impl CryptDENull { key_data, data, ))) } else { - let (k, d) = data.as_slice().split_at(key_data.len()); + let (k, d) = Self::key_and_data(key_data.len(), data); if k != key_data { - eprintln!("{}", Self::wrong_key_message(key_data, data)); - Err(CryptdecError::OpeningFailed) + panic!("{}", Self::wrong_key_message(key_data, data)); } else { Ok(PlainData::new(d)) } @@ -243,6 +266,7 @@ mod tests { use crate::test_utils::main_cryptde; use ethsign_crypto::Keccak256; use masq_lib::test_utils::utils::TEST_DEFAULT_CHAIN; + use std::panic::{catch_unwind, AssertUnwindSafe}; #[test] fn encode_with_empty_key() { @@ -304,13 +328,14 @@ mod tests { } #[test] + #[should_panic( + expected = "Could not decrypt with 6261646b6579 data beginning with 6b6579646174" + )] fn decode_with_incorrect_private_key() { let mut subject = CryptDENull::new(TEST_DEFAULT_CHAIN); subject.private_key = PrivateKey::new(b"badkey"); - let result = subject.decode(&CryptData::new(b"keydataxyz")); - - assert_eq!(CryptdecError::OpeningFailed, result.err().unwrap()); + let _ = subject.decode(&CryptData::new(b"keydataxyz")); } #[test] @@ -422,13 +447,14 @@ mod tests { } #[test] + #[should_panic( + expected = "Could not decrypt with 6261644b6579 data beginning with 6b6579646174" + )] fn decode_sym_with_wrong_key() { let subject = main_cryptde().clone(); let key = SymmetricKey::new(b"badKey"); - let result = subject.decode_sym(&key, &CryptData::new(b"keydataxyz")); - - assert_eq!(CryptdecError::OpeningFailed, result.err().unwrap()); + let _ = subject.decode_sym(&key, &CryptData::new(b"keydataxyz")); } #[test] @@ -503,9 +529,21 @@ mod tests { let expected_data = PlainData::new(&b"These are the times that try men's souls"[..]); let encrypted_data = subject.encode_sym(&key1, &expected_data).unwrap(); - let result = subject.decode_sym(&key2, &encrypted_data); + let result = catch_unwind(AssertUnwindSafe(|| { + subject.decode_sym(&key2, &encrypted_data) + })) + .unwrap_err(); - assert_eq!(result, Err(CryptdecError::OpeningFailed)); + let panic_msg = result.downcast_ref::().unwrap(); + let wrong_key = key2.as_slice().to_hex::(); + let right_key = key1.as_slice().to_hex::(); + assert_eq!( + panic_msg, + &format!( + "Could not decrypt with {} data beginning with {}", + wrong_key, right_key + ) + ); } #[test] @@ -709,4 +747,14 @@ mod tests { assert_eq!(subject_one.digest(), subject_two.digest()); assert_eq!(subject_one.digest(), subject_three.digest()); } + + #[test] + fn extract_key_pair_works() { + let data = CryptData::new(&[0x81, 0x82, 0x83, 0x84, 42, 42, 42, 42, 42]); + + let (private, public) = CryptDENull::extract_key_pair(4, &data); + + assert_eq!(private.as_slice(), &[0x81, 0x82, 0x83, 0x84]); + assert_eq!(public.as_slice(), &[1, 2, 3, 4]); + } } diff --git a/node/src/sub_lib/migrations/client_request_payload.rs b/node/src/sub_lib/migrations/client_request_payload.rs index 555755a28..83c9d7b88 100644 --- a/node/src/sub_lib/migrations/client_request_payload.rs +++ b/node/src/sub_lib/migrations/client_request_payload.rs @@ -107,7 +107,7 @@ impl TryFrom<&Value> for ClientRequestPayload_0v1 { target_hostname: target_hostname_opt.expect("target_hostname disappeared"), target_port: target_port_opt.expect("target_port disappeared"), protocol: protocol_opt.expect("protocol disappeared"), - originator_public_key: originator_public_key_opt + originator_alias_public_key: originator_public_key_opt .expect("originator_public_key disappeared"), }) } @@ -150,7 +150,7 @@ mod tests { target_hostname: Some("target.hostname.com".to_string()), target_port: 1234, protocol: ProxyProtocol::HTTP, - originator_public_key: PublicKey::new(&[2, 3, 4, 5]), + originator_alias_public_key: PublicKey::new(&[2, 3, 4, 5]), }; let future_crp = ExampleFutureCRP { stream_key: expected_crp.stream_key.clone(), @@ -158,7 +158,7 @@ mod tests { target_hostname: expected_crp.target_hostname.clone(), target_port: expected_crp.target_port.clone(), protocol: expected_crp.protocol.clone(), - originator_public_key: expected_crp.originator_public_key.clone(), + originator_public_key: expected_crp.originator_alias_public_key.clone(), another_field: "These are the times that try men's souls".to_string(), yet_another_field: 1234567890, }; diff --git a/node/src/sub_lib/neighborhood.rs b/node/src/sub_lib/neighborhood.rs index a31cc3b2a..4a1b7f78f 100644 --- a/node/src/sub_lib/neighborhood.rs +++ b/node/src/sub_lib/neighborhood.rs @@ -58,6 +58,16 @@ pub struct RatePack { pub exit_service_rate: u64, } +impl RatePack { + pub fn routing_charge(&self, payload_size: u64) -> u64 { + self.routing_service_rate + (self.routing_byte_rate * payload_size) + } + + pub fn exit_charge(&self, payload_size: u64) -> u64 { + self.exit_service_rate + (self.exit_byte_rate * payload_size) + } +} + #[derive(Clone, Debug, PartialEq, Eq)] pub enum NeighborhoodMode { Standard(NodeAddr, Vec, RatePack), @@ -436,6 +446,7 @@ pub struct RouteQueryMessage { pub minimum_hop_count: usize, pub return_component_opt: Option, pub payload_size: usize, + pub hostname_opt: Option, } impl Message for RouteQueryMessage { @@ -444,6 +455,7 @@ impl Message for RouteQueryMessage { impl RouteQueryMessage { pub fn data_indefinite_route_request( + hostname_opt: Option, minimum_hop_count: usize, payload_size: usize, ) -> RouteQueryMessage { @@ -453,6 +465,7 @@ impl RouteQueryMessage { minimum_hop_count, return_component_opt: Some(Component::ProxyServer), payload_size, + hostname_opt, } } } @@ -504,8 +517,14 @@ pub struct AskAboutDebutGossipMessage { } #[derive(Clone, Debug, Message, PartialEq, Eq)] -pub enum NodeRecordMetadataMessage { - Desirable(PublicKey, bool), +pub struct NodeRecordMetadataMessage { + pub public_key: PublicKey, + pub metadata_change: NRMetadataChange, +} + +#[derive(Clone, Debug, PartialEq, Eq)] +pub enum NRMetadataChange { + AddUnreachableHost { hostname: String }, } #[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)] @@ -881,6 +900,34 @@ mod tests { ) } + #[test] + fn rate_pack_routing_charge_works() { + let subject = RatePack { + routing_byte_rate: 100, + routing_service_rate: 900_000, + exit_byte_rate: 0, + exit_service_rate: 0, + }; + + let result = subject.routing_charge(1000); + + assert_eq!(result, 1_000_000); + } + + #[test] + fn rate_pack_exit_charge_works() { + let subject = RatePack { + routing_byte_rate: 0, + routing_service_rate: 0, + exit_byte_rate: 100, + exit_service_rate: 900_000, + }; + + let result = subject.exit_charge(1000); + + assert_eq!(result, 1_000_000); + } + #[test] fn node_descriptor_from_key_node_addr_and_mainnet_flag_works() { let cryptde: &dyn CryptDE = main_cryptde(); @@ -956,7 +1003,7 @@ mod tests { #[test] fn data_indefinite_route_request() { - let result = RouteQueryMessage::data_indefinite_route_request(2, 7500); + let result = RouteQueryMessage::data_indefinite_route_request(None, 2, 7500); assert_eq!( result, @@ -966,6 +1013,7 @@ mod tests { minimum_hop_count: 2, return_component_opt: Some(Component::ProxyServer), payload_size: 7500, + hostname_opt: None } ); } diff --git a/node/src/sub_lib/proxy_client.rs b/node/src/sub_lib/proxy_client.rs index bdf15b97b..9428c1ed4 100644 --- a/node/src/sub_lib/proxy_client.rs +++ b/node/src/sub_lib/proxy_client.rs @@ -25,6 +25,7 @@ pub struct ProxyClientConfig { pub dns_servers: Vec, pub exit_service_rate: u64, pub exit_byte_rate: u64, + pub is_decentralized: bool, pub crashable: bool, } diff --git a/node/src/sub_lib/proxy_server.rs b/node/src/sub_lib/proxy_server.rs index 3e7081e26..23b193cd6 100644 --- a/node/src/sub_lib/proxy_server.rs +++ b/node/src/sub_lib/proxy_server.rs @@ -37,7 +37,7 @@ pub struct ClientRequestPayload_0v1 { pub target_hostname: Option, pub target_port: u16, pub protocol: ProxyProtocol, - pub originator_public_key: PublicKey, + pub originator_alias_public_key: PublicKey, } impl From for MessageType { @@ -60,7 +60,7 @@ pub struct AddReturnRouteMessage { pub return_route_id: u32, pub expected_services: Vec, pub protocol: ProxyProtocol, - pub server_name: Option, + pub server_name_opt: Option, } #[derive(Message, Debug, PartialEq, Eq)] diff --git a/node/src/sub_lib/route.rs b/node/src/sub_lib/route.rs index b2fe726cf..8415c758b 100644 --- a/node/src/sub_lib/route.rs +++ b/node/src/sub_lib/route.rs @@ -117,21 +117,23 @@ impl Route { let hop_enc = &most_hops_enc[index]; let cryptde = most_cryptdes[index]; let live_hop_str = match decodex::(cryptde, hop_enc) { - Ok(live_hop) => format!("Encrypted with {}: {:?}", cryptde.public_key(), live_hop), + Ok(live_hop) => { + format!("Encrypted with {:?}: {:?}", cryptde.public_key(), live_hop) + } Err(e) => format!("Error: {:?}", e), }; format!("{}\n{}", sofar, live_hop_str) }); match decodex::(last_cryptde, &last_hop_enc) { Ok(live_hop) => format!( - "{}\nEncrypted with {}: {:?}\n", + "{}\nEncrypted with {:?}: {:?}\n", most_strings, last_cryptde.public_key(), live_hop ), Err(outside) => match decodex::(last_cryptde, &last_hop_enc) { Ok(return_route_id) => format!( - "{}\nEncrypted with {}: Return Route ID: {}\n", + "{}\nEncrypted with {:?}: Return Route ID: {}\n", most_strings, last_cryptde.public_key(), return_route_id @@ -152,7 +154,6 @@ impl Route { if let Some(error) = Route::validate_route_segments(&over, &back) { return Err(CodexError::RoutingError(error)); } - eprintln!("{:?}", contract_address); let over_component = over.recipient; let over_keys = over.keys.iter(); @@ -194,7 +195,7 @@ impl Route { last_key = Some(next_key.clone()); LiveHop::new( next_key, - consuming_wallet_opt.clone().map(|w| { + consuming_wallet_opt.as_ref().map(|w| { w.as_payer( ¤t_key, &contract_address_opt.unwrap_or_else(Address::zero), @@ -375,18 +376,15 @@ mod tests { } #[test] + #[should_panic(expected = "Could not decrypt with ebe5f9a0e2 data beginning with ebe5f9a0e1")] fn id_returns_error_when_the_id_fails_to_decrypt() { let cryptde1 = CryptDENull::from(&PublicKey::new(b"key a"), TEST_DEFAULT_CHAIN); let cryptde2 = CryptDENull::from(&PublicKey::new(b"key b"), TEST_DEFAULT_CHAIN); - let subject = Route { hops: vec![Route::encrypt_return_route_id(42, &cryptde1)], }; - assert_eq!( - subject.id(&cryptde2), - Err("DecryptionError(OpeningFailed)".to_string()) - ); + let _ = subject.id(&cryptde2); } #[test] @@ -783,9 +781,9 @@ mod tests { result, String::from( r#" -Encrypted with AQIDBA: LiveHop { public_key: AgMEBQ, payer: Some(Payer { wallet: Wallet { kind: Address(0x71d0fc7d1c570b1ed786382b551a09391c91e33d) }, proof: Signature { v: 0, r: "8b663e5a10f40c3307e6fb5340482a5e11df78dafc619ceff97f11fa79fea320", s: "8b663e5a10f40c3307e6fb5340482a5e11df78dafc619ceff97f11fa79fea320" } }), component: Hopper } -Encrypted with AgMEBQ: LiveHop { public_key: AwQFBg, payer: Some(Payer { wallet: Wallet { kind: Address(0x71d0fc7d1c570b1ed786382b551a09391c91e33d) }, proof: Signature { v: 1, r: "63be72962f19dda7802220ed48c0d8199d510b45608a3789c50f61912b98a15b", s: "63be72962f19dda7802220ed48c0d8199d510b45608a3789c50f61912b98a15b" } }), component: Hopper } -Encrypted with AwQFBg: LiveHop { public_key: , payer: Some(Payer { wallet: Wallet { kind: Address(0x71d0fc7d1c570b1ed786382b551a09391c91e33d) }, proof: Signature { v: 1, r: "9ca23557adf96d7aed407a06ce96851a4184e947a7b29b6c3872eef902fcba1e", s: "9ca23557adf96d7aed407a06ce96851a4184e947a7b29b6c3872eef902fcba1e" } }), component: Neighborhood } +Encrypted with 0x01020304: LiveHop { public_key: 0x02030405, payer: Some(Payer { wallet: Wallet { kind: Address(0x71d0fc7d1c570b1ed786382b551a09391c91e33d) }, proof: Signature { v: 0, r: "8b663e5a10f40c3307e6fb5340482a5e11df78dafc619ceff97f11fa79fea320", s: "8b663e5a10f40c3307e6fb5340482a5e11df78dafc619ceff97f11fa79fea320" } }), component: Hopper } +Encrypted with 0x02030405: LiveHop { public_key: 0x03040506, payer: Some(Payer { wallet: Wallet { kind: Address(0x71d0fc7d1c570b1ed786382b551a09391c91e33d) }, proof: Signature { v: 1, r: "63be72962f19dda7802220ed48c0d8199d510b45608a3789c50f61912b98a15b", s: "63be72962f19dda7802220ed48c0d8199d510b45608a3789c50f61912b98a15b" } }), component: Hopper } +Encrypted with 0x03040506: LiveHop { public_key: 0x, payer: Some(Payer { wallet: Wallet { kind: Address(0x71d0fc7d1c570b1ed786382b551a09391c91e33d) }, proof: Signature { v: 1, r: "9ca23557adf96d7aed407a06ce96851a4184e947a7b29b6c3872eef902fcba1e", s: "9ca23557adf96d7aed407a06ce96851a4184e947a7b29b6c3872eef902fcba1e" } }), component: Neighborhood } "# ) ); @@ -820,12 +818,12 @@ Encrypted with AwQFBg: LiveHop { public_key: , payer: Some(Payer { wallet: Walle result, String::from( r#" -Encrypted with AQIDBA: LiveHop { public_key: AgMEBQ, payer: Some(Payer { wallet: Wallet { kind: Address(0x71d0fc7d1c570b1ed786382b551a09391c91e33d) }, proof: Signature { v: 0, r: "8b663e5a10f40c3307e6fb5340482a5e11df78dafc619ceff97f11fa79fea320", s: "8b663e5a10f40c3307e6fb5340482a5e11df78dafc619ceff97f11fa79fea320" } }), component: Hopper } -Encrypted with AgMEBQ: LiveHop { public_key: AwQFBg, payer: Some(Payer { wallet: Wallet { kind: Address(0x71d0fc7d1c570b1ed786382b551a09391c91e33d) }, proof: Signature { v: 1, r: "63be72962f19dda7802220ed48c0d8199d510b45608a3789c50f61912b98a15b", s: "63be72962f19dda7802220ed48c0d8199d510b45608a3789c50f61912b98a15b" } }), component: Hopper } -Encrypted with AwQFBg: LiveHop { public_key: AgMEBQ, payer: Some(Payer { wallet: Wallet { kind: Address(0x71d0fc7d1c570b1ed786382b551a09391c91e33d) }, proof: Signature { v: 1, r: "9ca23557adf96d7aed407a06ce96851a4184e947a7b29b6c3872eef902fcba1e", s: "9ca23557adf96d7aed407a06ce96851a4184e947a7b29b6c3872eef902fcba1e" } }), component: ProxyClient } -Encrypted with AgMEBQ: LiveHop { public_key: AQIDBA, payer: Some(Payer { wallet: Wallet { kind: Address(0x71d0fc7d1c570b1ed786382b551a09391c91e33d) }, proof: Signature { v: 1, r: "63be72962f19dda7802220ed48c0d8199d510b45608a3789c50f61912b98a15b", s: "63be72962f19dda7802220ed48c0d8199d510b45608a3789c50f61912b98a15b" } }), component: Hopper } -Encrypted with AQIDBA: LiveHop { public_key: , payer: Some(Payer { wallet: Wallet { kind: Address(0x71d0fc7d1c570b1ed786382b551a09391c91e33d) }, proof: Signature { v: 0, r: "8b663e5a10f40c3307e6fb5340482a5e11df78dafc619ceff97f11fa79fea320", s: "8b663e5a10f40c3307e6fb5340482a5e11df78dafc619ceff97f11fa79fea320" } }), component: ProxyServer } -Encrypted with AQIDBA: Return Route ID: 1234 +Encrypted with 0x01020304: LiveHop { public_key: 0x02030405, payer: Some(Payer { wallet: Wallet { kind: Address(0x71d0fc7d1c570b1ed786382b551a09391c91e33d) }, proof: Signature { v: 0, r: "8b663e5a10f40c3307e6fb5340482a5e11df78dafc619ceff97f11fa79fea320", s: "8b663e5a10f40c3307e6fb5340482a5e11df78dafc619ceff97f11fa79fea320" } }), component: Hopper } +Encrypted with 0x02030405: LiveHop { public_key: 0x03040506, payer: Some(Payer { wallet: Wallet { kind: Address(0x71d0fc7d1c570b1ed786382b551a09391c91e33d) }, proof: Signature { v: 1, r: "63be72962f19dda7802220ed48c0d8199d510b45608a3789c50f61912b98a15b", s: "63be72962f19dda7802220ed48c0d8199d510b45608a3789c50f61912b98a15b" } }), component: Hopper } +Encrypted with 0x03040506: LiveHop { public_key: 0x02030405, payer: Some(Payer { wallet: Wallet { kind: Address(0x71d0fc7d1c570b1ed786382b551a09391c91e33d) }, proof: Signature { v: 1, r: "9ca23557adf96d7aed407a06ce96851a4184e947a7b29b6c3872eef902fcba1e", s: "9ca23557adf96d7aed407a06ce96851a4184e947a7b29b6c3872eef902fcba1e" } }), component: ProxyClient } +Encrypted with 0x02030405: LiveHop { public_key: 0x01020304, payer: Some(Payer { wallet: Wallet { kind: Address(0x71d0fc7d1c570b1ed786382b551a09391c91e33d) }, proof: Signature { v: 1, r: "63be72962f19dda7802220ed48c0d8199d510b45608a3789c50f61912b98a15b", s: "63be72962f19dda7802220ed48c0d8199d510b45608a3789c50f61912b98a15b" } }), component: Hopper } +Encrypted with 0x01020304: LiveHop { public_key: 0x, payer: Some(Payer { wallet: Wallet { kind: Address(0x71d0fc7d1c570b1ed786382b551a09391c91e33d) }, proof: Signature { v: 0, r: "8b663e5a10f40c3307e6fb5340482a5e11df78dafc619ceff97f11fa79fea320", s: "8b663e5a10f40c3307e6fb5340482a5e11df78dafc619ceff97f11fa79fea320" } }), component: ProxyServer } +Encrypted with 0x01020304: Return Route ID: 1234 "# ) ); diff --git a/node/src/sub_lib/wallet.rs b/node/src/sub_lib/wallet.rs index aa8670fb4..26034eb65 100644 --- a/node/src/sub_lib/wallet.rs +++ b/node/src/sub_lib/wallet.rs @@ -674,7 +674,7 @@ mod tests { #[test] #[should_panic( - expected = r#"Trying to sign for AQID encountered Signature("Cannot sign with non-keypair wallet: Uninitialized.")"# + expected = r#"Trying to sign for 0x010203 encountered Signature("Cannot sign with non-keypair wallet: Uninitialized.")"# )] fn sign_with_uninitialized_wallets_panic() { Wallet::new("").as_payer( diff --git a/node/src/test_utils/mod.rs b/node/src/test_utils/mod.rs index e4b4170f5..23034e2de 100644 --- a/node/src/test_utils/mod.rs +++ b/node/src/test_utils/mod.rs @@ -40,6 +40,7 @@ use ethsign_crypto::Keccak256; use lazy_static::lazy_static; use masq_lib::constants::HTTP_PORT; use masq_lib::test_utils::utils::TEST_DEFAULT_CHAIN; +use rand::RngCore; use regex::Regex; use rustc_hex::ToHex; use serde_derive::{Deserialize, Serialize}; @@ -182,12 +183,22 @@ pub fn make_meaningless_message_type() -> MessageType { DnsResolveFailure_0v1::new(make_meaningless_stream_key()).into() } +pub fn make_one_way_route_to_proxy_client(public_keys: Vec<&PublicKey>) -> Route { + Route::one_way( + RouteSegment::new(public_keys, Component::ProxyClient), + main_cryptde(), + Some(make_paying_wallet(b"irrelevant")), + Some(TEST_DEFAULT_CHAIN.rec().contract), + ) + .unwrap() +} + pub fn make_meaningless_route() -> Route { Route::one_way( RouteSegment::new( vec![ - &PublicKey::new(&b"ooga"[..]), - &PublicKey::new(&b"booga"[..]), + &make_meaningless_public_key(), + &make_meaningless_public_key(), ], Component::ProxyClient, ), @@ -199,7 +210,7 @@ pub fn make_meaningless_route() -> Route { } pub fn make_meaningless_public_key() -> PublicKey { - PublicKey::new(&make_garbage_data(8)) + PublicKey::new(&make_garbage_data(main_cryptde().public_key().len())) } pub fn make_meaningless_wallet_private_key() -> PlainData { @@ -211,8 +222,12 @@ pub fn make_meaningless_wallet_private_key() -> PlainData { ) } -pub fn route_to_proxy_client(key: &PublicKey, cryptde: &dyn CryptDE) -> Route { - shift_one_hop(zero_hop_route_response(key, cryptde).route, cryptde) +// TODO: The three functions below should use only one argument, cryptde +pub fn route_to_proxy_client(main_key: &PublicKey, main_cryptde: &dyn CryptDE) -> Route { + shift_one_hop( + zero_hop_route_response(main_key, main_cryptde).route, + main_cryptde, + ) } pub fn route_from_proxy_client(key: &PublicKey, cryptde: &dyn CryptDE) -> Route { @@ -259,7 +274,9 @@ pub fn encrypt_return_route_id(return_route_id: u32, cryptde: &dyn CryptDE) -> C } pub fn make_garbage_data(bytes: usize) -> Vec { - vec![0; bytes] + let mut data = vec![0; bytes]; + rand::thread_rng().fill_bytes(&mut data); + data } pub fn make_request_payload(bytes: usize, cryptde: &dyn CryptDE) -> ClientRequestPayload_0v1 { @@ -272,7 +289,7 @@ pub fn make_request_payload(bytes: usize, cryptde: &dyn CryptDE) -> ClientReques target_hostname: Some("example.com".to_string()), target_port: HTTP_PORT, protocol: ProxyProtocol::HTTP, - originator_public_key: cryptde.public_key().clone(), + originator_alias_public_key: cryptde.public_key().clone(), } } @@ -294,13 +311,13 @@ pub fn rate_pack_routing_byte(base_rate: u64) -> u64 { base_rate + 1 } pub fn rate_pack_routing(base_rate: u64) -> u64 { - base_rate + 2 + base_rate + 200 } pub fn rate_pack_exit_byte(base_rate: u64) -> u64 { base_rate + 3 } pub fn rate_pack_exit(base_rate: u64) -> u64 { - base_rate + 4 + base_rate + 400 } pub fn rate_pack(base_rate: u64) -> RatePack { diff --git a/port_exposer/Cargo.toml b/port_exposer/Cargo.toml index ad667bc26..848efef9b 100644 --- a/port_exposer/Cargo.toml +++ b/port_exposer/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "port_exposer" -version = "0.7.1" +version = "0.7.2" authors = ["Dan Wiebe ", "MASQ"] license = "GPL-3.0-only" copyright = "Copyright (c) 2019, MASQ (https://masq.ai) and/or its affiliates. All rights reserved."