From 6a5982e2814fe4bd3e4d33f5d4770db2bb28d7e2 Mon Sep 17 00:00:00 2001 From: Stan Bondi Date: Wed, 6 Oct 2021 15:08:35 +0400 Subject: [PATCH] feat: allow tor proxy to be bypassed for outbound tcp connections (#3424) Description --- - Adds `tor_proxy_bypass_for_outbound_tcp` setting that allows direct outbound connections to be made for TCP nodes, bypassing tor - Mobile will have this set for now until it can be configured by the apps Motivation and Context --- By default, the most private mode is set and all traffic is routed through tor. With this option set outbound tcp traffic bypasses tor for outbound TCP peer addresses in exchange for better bandwidth and latency. How Has This Been Tested? --- Some unit tests + manually --- .../tari_app_utilities/src/utilities.rs | 8 +- base_layer/p2p/src/initialization.rs | 6 +- base_layer/p2p/src/transport.rs | 12 +- base_layer/wallet_ffi/src/lib.rs | 2 + .../config/presets/tari_config_example.toml | 4 + common/config/presets/tari_igor_config.toml | 12 +- common/src/configuration/global.rs | 5 + comms/examples/stress/node.rs | 4 +- comms/src/macros.rs | 8 +- comms/src/tor/hidden_service/builder.rs | 23 ++- comms/src/tor/hidden_service/controller.rs | 11 +- comms/src/tor/hidden_service/mod.rs | 6 +- comms/src/tor/hidden_service/proxy_opts.rs | 139 ++++++++++++++++++ comms/src/transports/dns/tor.rs | 6 +- comms/src/transports/mod.rs | 3 +- .../transports/{helpers.rs => predicate.rs} | 30 ++++ comms/src/transports/socks.rs | 36 +++-- comms/src/transports/tcp_with_tor.rs | 2 +- 18 files changed, 275 insertions(+), 42 deletions(-) create mode 100644 comms/src/tor/hidden_service/proxy_opts.rs rename comms/src/transports/{helpers.rs => predicate.rs} (81%) diff --git a/applications/tari_app_utilities/src/utilities.rs b/applications/tari_app_utilities/src/utilities.rs index 23a1ebf9ab..71f3933719 100644 --- a/applications/tari_app_utilities/src/utilities.rs +++ b/applications/tari_app_utilities/src/utilities.rs @@ -22,6 +22,7 @@ use futures::future::Either; use log::*; +use std::sync::Arc; use thiserror::Error; use tokio::{runtime, runtime::Runtime}; @@ -42,6 +43,7 @@ use tari_p2p::transport::{TorConfig, TransportType}; use crate::identity_management::load_from_json; use tari_common_types::emoji::EmojiId; +use tari_comms::transports::predicate::FalsePredicate; pub const LOG_TARGET: &str = "tari::application"; @@ -185,7 +187,7 @@ pub fn create_transport_type(config: &GlobalConfig) -> TransportType { tor_socks_config: tor_socks_address.map(|proxy_address| SocksConfig { proxy_address, authentication: tor_socks_auth.map(convert_socks_authentication).unwrap_or_default(), - proxy_bypass_addresses: vec![], + proxy_bypass_predicate: Arc::new(FalsePredicate::new()), }), }, CommsTransport::TorHiddenService { @@ -195,6 +197,7 @@ pub fn create_transport_type(config: &GlobalConfig) -> TransportType { auth, onion_port, tor_proxy_bypass_addresses, + tor_proxy_bypass_for_outbound_tcp, } => { let identity = Some(&config.base_node_tor_identity_file) .filter(|p| p.exists()) @@ -227,6 +230,7 @@ pub fn create_transport_type(config: &GlobalConfig) -> TransportType { socks_address_override, socks_auth: socks::Authentication::None, tor_proxy_bypass_addresses, + tor_proxy_bypass_for_outbound_tcp, }) }, CommsTransport::Socks5 { @@ -237,7 +241,7 @@ pub fn create_transport_type(config: &GlobalConfig) -> TransportType { socks_config: SocksConfig { proxy_address, authentication: convert_socks_authentication(auth), - proxy_bypass_addresses: vec![], + proxy_bypass_predicate: Arc::new(FalsePredicate::new()), }, listener_address, }, diff --git a/base_layer/p2p/src/initialization.rs b/base_layer/p2p/src/initialization.rs index 68cc39c710..84b431ca99 100644 --- a/base_layer/p2p/src/initialization.rs +++ b/base_layer/p2p/src/initialization.rs @@ -305,7 +305,11 @@ async fn initialize_hidden_service( .with_socks_authentication(config.socks_auth) .with_control_server_auth(config.control_server_auth) .with_control_server_address(config.control_server_addr) - .with_bypass_proxy_addresses(config.tor_proxy_bypass_addresses); + .with_bypass_proxy_addresses(config.tor_proxy_bypass_addresses.into()); + + if config.tor_proxy_bypass_for_outbound_tcp { + builder = builder.bypass_tor_for_tcp_addresses(); + } if let Some(identity) = config.identity { builder = builder.with_tor_identity(*identity); diff --git a/base_layer/p2p/src/transport.rs b/base_layer/p2p/src/transport.rs index 4dabf712da..4e5f9d97b3 100644 --- a/base_layer/p2p/src/transport.rs +++ b/base_layer/p2p/src/transport.rs @@ -61,14 +61,22 @@ pub struct TorConfig { /// If the underlying SOCKS transport encounters these addresses, bypass the proxy and dial directly using the /// TcpTransport pub tor_proxy_bypass_addresses: Vec, + /// Use a direct TCP/IP connection if a TCP address is given instead of the tor proxy. This is worse for privacy + /// but can use the full available connection bandwidth + pub tor_proxy_bypass_for_outbound_tcp: bool, } impl fmt::Display for TorConfig { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { write!( f, - "control_server_addr = {}, control_server_auth = {}, {}, socks_address_override = {:?}", - self.control_server_addr, self.control_server_auth, self.port_mapping, self.socks_address_override + "control_server_addr: {}, control_server_auth: {}, {}, socks_address_override: {:?}, \ + tor_proxy_bypass_outbound_tcp_addresses = {:?}", + self.control_server_addr, + self.control_server_auth, + self.port_mapping, + self.socks_address_override, + self.tor_proxy_bypass_for_outbound_tcp ) } } diff --git a/base_layer/wallet_ffi/src/lib.rs b/base_layer/wallet_ffi/src/lib.rs index 178b9a98be..fc0b69c7fc 100644 --- a/base_layer/wallet_ffi/src/lib.rs +++ b/base_layer/wallet_ffi/src/lib.rs @@ -2424,6 +2424,8 @@ pub unsafe extern "C" fn transport_tor_create( socks_address_override: None, socks_auth: authentication, tor_proxy_bypass_addresses: vec![], + // Prefer performance + tor_proxy_bypass_for_outbound_tcp: true, }; let transport = TariTransportType::Tor(tor_config); diff --git a/common/config/presets/tari_config_example.toml b/common/config/presets/tari_config_example.toml index 263ed8cf17..a4522a4a67 100644 --- a/common/config/presets/tari_config_example.toml +++ b/common/config/presets/tari_config_example.toml @@ -232,6 +232,8 @@ tor_control_auth = "none" # or "password=xxxxxx" # When these addresses are encountered when dialing another peer, the tor proxy is bypassed and the connection is made # direcly over TCP. /ip4, /ip6, /dns, /dns4 and /dns6 are supported. # tor_proxy_bypass_addresses = ["/dns4/my-foo-base-node/tcp/9998"] +# When using the tor transport and set to true, outbound TCP connections bypass the tor proxy. Defaults to false for better privacy +# tor_proxy_bypass_for_outbound_tcp = false; ######################################################################################################################## # # @@ -409,6 +411,8 @@ console_wallet_tor_identity_file = "config/console_wallet_tor.json" # When these addresses are encountered when dialing another peer, the tor proxy is bypassed and the connection is made # direcly over TCP. /ip4, /ip6, /dns, /dns4 and /dns6 are supported. # tor_proxy_bypass_addresses = ["/dns4/my-foo-base-node/tcp/9998"] +# When using the tor transport and set to true, outbound TCP connections bypass the tor proxy. Defaults to false for better privacy +# tor_proxy_bypass_for_outbound_tcp = false; ######################################################################################################################## # # diff --git a/common/config/presets/tari_igor_config.toml b/common/config/presets/tari_igor_config.toml index c252d1b85e..615485b1a2 100644 --- a/common/config/presets/tari_igor_config.toml +++ b/common/config/presets/tari_igor_config.toml @@ -232,6 +232,8 @@ tor_control_auth = "none" # or "password=xxxxxx" # When these addresses are encountered when dialing another peer, the tor proxy is bypassed and the connection is made # direcly over TCP. /ip4, /ip6, /dns, /dns4 and /dns6 are supported. # tor_proxy_bypass_addresses = ["/dns4/my-foo-base-node/tcp/9998"] +# When using the tor transport and set to true, outbound TCP connections bypass the tor proxy. Defaults to false for better privacy +# tor_proxy_bypass_for_outbound_tcp = false; ######################################################################################################################## # # @@ -281,10 +283,10 @@ data_dir = "igor" # new nodes can use to introduce themselves to the network. # peer_seeds = ["public_key1::address1", "public_key2::address2",... ] peer_seeds = [ - "8e7eb81e512f3d6347bf9b1ca9cd67d2c8e29f2836fc5bd608206505cc72af34::/onion3/l4wouomx42nezhzexjdzfh7pcou5l7df24ggmwgekuih7tkv2rsaokqd:18141", - "00b35047a341401bcd336b2a3d564280a72f6dc72ec4c739d30c502acce4e803::/onion3/ojhxd7z6ga7qrvjlr3px66u7eiwasmffnuklscbh5o7g6wrbysj45vid:18141", - "40a9d8573745072534bce7d0ecafe882b1c79570375a69841c08a98dee9ecb5f::/onion3/io37fylc2pupg4cte4siqlsmuszkeythgjsxs2i3prm6jyz2dtophaad:18141", - "126c7ee64f71aca36398b977dd31fbbe9f9dad615df96473fb655bef5709c540::/onion3/6ilmgndocop7ybgmcvivbdsetzr5ggj4hhsivievoa2dx2b43wqlrlid:18141", + "8e7eb81e512f3d6347bf9b1ca9cd67d2c8e29f2836fc5bd608206505cc72af34::/onion3/l4wouomx42nezhzexjdzfh7pcou5l7df24ggmwgekuih7tkv2rsaokqd:18141", + "00b35047a341401bcd336b2a3d564280a72f6dc72ec4c739d30c502acce4e803::/onion3/ojhxd7z6ga7qrvjlr3px66u7eiwasmffnuklscbh5o7g6wrbysj45vid:18141", + "40a9d8573745072534bce7d0ecafe882b1c79570375a69841c08a98dee9ecb5f::/onion3/io37fylc2pupg4cte4siqlsmuszkeythgjsxs2i3prm6jyz2dtophaad:18141", + "126c7ee64f71aca36398b977dd31fbbe9f9dad615df96473fb655bef5709c540::/onion3/6ilmgndocop7ybgmcvivbdsetzr5ggj4hhsivievoa2dx2b43wqlrlid:18141", ] # This allowlist provides a method to force syncing from any known nodes you may choose, for example if you have a @@ -392,6 +394,8 @@ console_wallet_tor_identity_file = "config/console_wallet_tor.json" # When these addresses are encountered when dialing another peer, the tor proxy is bypassed and the connection is made # direcly over TCP. /ip4, /ip6, /dns, /dns4 and /dns6 are supported. # tor_proxy_bypass_addresses = ["/dns4/my-foo-base-node/tcp/9998"] +# When using the tor transport and set to true, outbound TCP connections bypass the tor proxy. Defaults to false for better privacy +# tor_proxy_bypass_for_outbound_tcp = false; ######################################################################################################################## # # diff --git a/common/src/configuration/global.rs b/common/src/configuration/global.rs index 4f97031d75..c7c4a5ae82 100644 --- a/common/src/configuration/global.rs +++ b/common/src/configuration/global.rs @@ -904,6 +904,9 @@ fn network_transport_config( None => None, }; + let key = config_string(app_str, network, "tor_proxy_bypass_for_outbound_tcp"); + let tor_proxy_bypass_for_outbound_tcp = optional(cfg.get_bool(&key))?.unwrap_or(false); + Ok(CommsTransport::TorHiddenService { control_server_address, auth, @@ -911,6 +914,7 @@ fn network_transport_config( forward_address, onion_port, tor_proxy_bypass_addresses, + tor_proxy_bypass_for_outbound_tcp, }) }, "socks5" => { @@ -1051,6 +1055,7 @@ pub enum CommsTransport { auth: TorControlAuthentication, onion_port: NonZeroU16, tor_proxy_bypass_addresses: Vec, + tor_proxy_bypass_for_outbound_tcp: bool, }, /// Use a SOCKS5 proxy transport. This transport recognises any addresses supported by the proxy. Socks5 { diff --git a/comms/examples/stress/node.rs b/comms/examples/stress/node.rs index 595cf44a2c..bab50293f3 100644 --- a/comms/examples/stress/node.rs +++ b/comms/examples/stress/node.rs @@ -32,7 +32,7 @@ use tari_comms::{ protocol::{messaging::MessagingProtocolExtension, ProtocolNotification, Protocols}, tor, tor::{HsFlags, TorIdentity}, - transports::{SocksConfig, TcpWithTorTransport}, + transports::{predicate::FalsePredicate, SocksConfig, TcpWithTorTransport}, CommsBuilder, CommsNode, NodeIdentity, @@ -123,7 +123,7 @@ pub async fn create( .spawn_with_transport(TcpWithTorTransport::with_tor_socks_proxy(SocksConfig { proxy_address: TOR_SOCKS_ADDR.parse().unwrap(), authentication: Default::default(), - proxy_bypass_addresses: vec![], + proxy_bypass_predicate: Arc::new(FalsePredicate::new()), })) .await .unwrap() diff --git a/comms/src/macros.rs b/comms/src/macros.rs index 47e4832186..b81f77b411 100644 --- a/comms/src/macros.rs +++ b/comms/src/macros.rs @@ -25,21 +25,21 @@ macro_rules! setter { ( $(#[$outer:meta])* - $func:ident, $name: ident, Option<$type: ty> + $func:ident, $($name: ident).+, Option<$type: ty> ) => { $(#[$outer])* pub fn $func(mut self, val: $type) -> Self { - self.$name = Some(val); + self.$($name).+ = Some(val); self } }; ( $(#[$outer:meta])* - $func:ident, $name: ident, $type: ty + $func:ident, $($name: ident).+, $type: ty ) => { $(#[$outer])* pub fn $func(mut self, val: $type) -> Self { - self.$name = val; + self.$($name).+ = val; self } }; diff --git a/comms/src/tor/hidden_service/builder.rs b/comms/src/tor/hidden_service/builder.rs index 062f2a4084..af80a25070 100644 --- a/comms/src/tor/hidden_service/builder.rs +++ b/comms/src/tor/hidden_service/builder.rs @@ -24,10 +24,16 @@ use super::controller::HiddenServiceControllerError; use crate::{ multiaddr::Multiaddr, socks, - tor::{hidden_service::controller::HiddenServiceController, Authentication, PortMapping, TorIdentity}, + tor::{ + hidden_service::{controller::HiddenServiceController, TorProxyOpts}, + Authentication, + PortMapping, + TorIdentity, + }, }; use bitflags::bitflags; use log::*; +use std::sync::Arc; use tari_shutdown::{OptionalShutdownSignal, ShutdownSignal}; use thiserror::Error; @@ -60,7 +66,7 @@ pub struct HiddenServiceBuilder { port_mapping: Option, socks_addr_override: Option, control_server_addr: Option, - proxy_bypass_addresses: Vec, + proxy_opts: TorProxyOpts, control_server_auth: Authentication, socks_auth: socks::Authentication, hs_flags: HsFlags, @@ -84,8 +90,8 @@ impl HiddenServiceBuilder { setter!( /// Configure the underlying SOCKS transport to bypass the proxy and connect directly to these addresses with_bypass_proxy_addresses, - proxy_bypass_addresses, - Vec + proxy_opts.bypass_addresses, + Arc> ); setter!( @@ -117,6 +123,13 @@ impl HiddenServiceBuilder { HsFlags ); + /// Use a direct TCP/IP connection if a TCP address is given instead of the tor proxy. This is worse for privacy + /// but can use the full available connection bandwidth + pub fn bypass_tor_for_tcp_addresses(mut self) -> Self { + self.proxy_opts.bypass_for_tcpip = true; + self + } + /// The address of the SOCKS5 server. If an address is None, the hidden service builder will use the SOCKS /// listener address as given by the tor control port. pub fn with_shutdown_signal(mut self, shutdown_signal: ShutdownSignal) -> Self { @@ -164,7 +177,7 @@ impl HiddenServiceBuilder { self.socks_auth, self.identity, self.hs_flags, - self.proxy_bypass_addresses, + self.proxy_opts, self.shutdown_signal, ); diff --git a/comms/src/tor/hidden_service/controller.rs b/comms/src/tor/hidden_service/controller.rs index 74b89808fb..ff8986b4f0 100644 --- a/comms/src/tor/hidden_service/controller.rs +++ b/comms/src/tor/hidden_service/controller.rs @@ -29,6 +29,7 @@ use crate::{ commands::{AddOnionFlag, AddOnionResponse}, TorControlEvent, }, + hidden_service::TorProxyOpts, Authentication, HiddenService, HsFlags, @@ -42,7 +43,7 @@ use crate::{ }; use futures::{future, future::Either, pin_mut, StreamExt}; use log::*; -use std::{net::SocketAddr, time::Duration}; +use std::{net::SocketAddr, sync::Arc, time::Duration}; use tari_shutdown::OptionalShutdownSignal; use thiserror::Error; use tokio::{sync::broadcast, time}; @@ -75,7 +76,7 @@ pub struct HiddenServiceController { identity: Option, hs_flags: HsFlags, is_authenticated: bool, - proxy_bypass_addresses: Vec, + proxy_opts: TorProxyOpts, shutdown_signal: OptionalShutdownSignal, } @@ -89,7 +90,7 @@ impl HiddenServiceController { socks_auth: socks::Authentication, identity: Option, hs_flags: HsFlags, - proxy_bypass_addresses: Vec, + proxy_opts: TorProxyOpts, shutdown_signal: OptionalShutdownSignal, ) -> Self { Self { @@ -102,7 +103,7 @@ impl HiddenServiceController { hs_flags, identity, is_authenticated: false, - proxy_bypass_addresses, + proxy_opts, shutdown_signal, } } @@ -119,7 +120,7 @@ impl HiddenServiceController { Ok(SocksTransport::new(SocksConfig { proxy_address: socks_addr, authentication: self.socks_auth.clone(), - proxy_bypass_addresses: self.proxy_bypass_addresses.clone(), + proxy_bypass_predicate: Arc::new(self.proxy_opts.to_bypass_predicate()), })) } diff --git a/comms/src/tor/hidden_service/mod.rs b/comms/src/tor/hidden_service/mod.rs index f9a1ede96f..588b5794cd 100644 --- a/comms/src/tor/hidden_service/mod.rs +++ b/comms/src/tor/hidden_service/mod.rs @@ -24,11 +24,15 @@ mod builder; pub use builder::{HiddenServiceBuilder, HiddenServiceBuilderError, HsFlags}; mod controller; +pub use controller::{HiddenServiceController, HiddenServiceControllerError}; + +mod proxy_opts; +pub use proxy_opts::TorProxyOpts; + use crate::{ multiaddr::Multiaddr, tor::{PrivateKey, TorClientError}, }; -pub use controller::{HiddenServiceController, HiddenServiceControllerError}; use serde_derive::{Deserialize, Serialize}; use std::fmt; use tari_shutdown::OptionalShutdownSignal; diff --git a/comms/src/tor/hidden_service/proxy_opts.rs b/comms/src/tor/hidden_service/proxy_opts.rs new file mode 100644 index 0000000000..bc71c449df --- /dev/null +++ b/comms/src/tor/hidden_service/proxy_opts.rs @@ -0,0 +1,139 @@ +// Copyright 2021, The Tari Project +// +// Redistribution and use in source and binary forms, with or without modification, are permitted provided that the +// following conditions are met: +// +// 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following +// disclaimer. +// +// 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the +// following disclaimer in the documentation and/or other materials provided with the distribution. +// +// 3. Neither the name of the copyright holder nor the names of its contributors may be used to endorse or promote +// products derived from this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, +// INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +// DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR +// SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, +// WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE +// USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +use crate::transports::predicate::Predicate; +use multiaddr::{Multiaddr, Protocol}; +use std::sync::Arc; + +#[derive(Debug, Clone)] +pub struct TorProxyOpts { + /// If the dialed address matches any of these addresses, the SOCKS proxy is bypassed and direct TCP connection is + /// used. + pub bypass_addresses: Arc>, + /// Use a direct TCP/IP connection if a TCP address is given instead of the tor proxy. + pub bypass_for_tcpip: bool, +} + +impl TorProxyOpts { + pub fn to_bypass_predicate(&self) -> impl Predicate { + let config = self.clone(); + move |addr: &Multiaddr| -> bool { + config.bypass_addresses.contains(addr) || (config.bypass_for_tcpip && is_tcp_address(addr)) + } + } +} + +impl Default for TorProxyOpts { + fn default() -> Self { + Self { + bypass_addresses: Arc::new(vec![]), + // Private by default + bypass_for_tcpip: false, + } + } +} + +fn is_tcp_address(addr: &Multiaddr) -> bool { + use Protocol::*; + let mut iter = addr.iter(); + let protocol = iter.next(); + if !matches!(protocol, Some(Ip4(_)) | Some(Ip6(_)) | Some(Dns4(_)) | Some(Dns6(_))) { + return false; + } + + let protocol = iter.next(); + matches!(protocol, Some(Tcp(_))) +} +#[cfg(test)] +mod test { + use super::*; + + #[test] + fn is_tcpip_address() { + let expect_false = [ + "/onion/aaimaq4ygg2iegci:1234", + "/onion3/vww6ybal4bd7szmgncyruucpgfkqahzddi37ktceo3ah7ngmcopnpyyd:1234", + ]; + + let expect_true = [ + "/ip4/1.2.3.4/tcp/1234", + "/ip4/127.0.0.1/tcp/9998", + "/dns4/tari.com/tcp/80", + ]; + + expect_true.iter().for_each(|addr| { + let addr = addr.parse().unwrap(); + assert!(super::is_tcp_address(&addr)); + }); + + expect_false.iter().for_each(|addr| { + let addr = addr.parse().unwrap(); + assert!(!super::is_tcp_address(&addr)); + }); + } + + #[test] + fn proxy_opts() { + let expect_false = [ + "/onion/aaimaq4ygg2iegci:1234", + "/onion3/vww6ybal4bd7szmgncyruucpgfkqahzddi37ktceo3ah7ngmcopnpyyd:1234", + ] + .iter() + .map(|a| a.parse().unwrap()) + .collect::>(); + + let expect_true = [ + "/ip4/1.2.3.4/tcp/1234", + "/ip4/127.0.0.1/tcp/9998", + "/dns4/tari.com/tcp/80", + ] + .iter() + .map(|a| a.parse().unwrap()) + .collect::>(); + + let opts = TorProxyOpts { + bypass_addresses: expect_false.clone().into(), + ..Default::default() + }; + let predicate = opts.to_bypass_predicate(); + expect_false.iter().for_each(|addr| { + assert!(predicate.check(addr)); + }); + + expect_true.iter().for_each(|addr| { + assert!(!predicate.check(addr)); + }); + + let opts = TorProxyOpts { + bypass_for_tcpip: true, + ..Default::default() + }; + let predicate = opts.to_bypass_predicate(); + expect_true.iter().for_each(|addr| { + assert!(predicate.check(addr)); + }); + + expect_false.iter().for_each(|addr| { + assert!(!predicate.check(addr)); + }); + } +} diff --git a/comms/src/transports/dns/tor.rs b/comms/src/transports/dns/tor.rs index e198efdc0b..5dbcb858d7 100644 --- a/comms/src/transports/dns/tor.rs +++ b/comms/src/transports/dns/tor.rs @@ -33,7 +33,7 @@ use std::{io, net::SocketAddr}; const LOG_TARGET: &str = "comms::dns::tor_resolver"; /// Resolves DNS addresses using the tor proxy -#[derive(Debug, Clone)] +#[derive(Clone)] pub struct TorDnsResolver { socks_config: SocksConfig, } @@ -86,6 +86,8 @@ impl DnsResolver for TorDnsResolver { #[cfg(test)] mod test { use super::*; + use crate::transports::predicate::FalsePredicate; + use std::sync::Arc; // This only works when a tor proxy is running #[ignore] @@ -94,7 +96,7 @@ mod test { let resolver = TorDnsResolver::new(SocksConfig { proxy_address: "/ip4/127.0.0.1/tcp/9050".parse().unwrap(), authentication: Default::default(), - proxy_bypass_addresses: vec![], + proxy_bypass_predicate: Arc::new(FalsePredicate::new()), }); let addr = resolver diff --git a/comms/src/transports/mod.rs b/comms/src/transports/mod.rs index 75b16db975..38a0abc0df 100644 --- a/comms/src/transports/mod.rs +++ b/comms/src/transports/mod.rs @@ -28,7 +28,8 @@ use multiaddr::Multiaddr; use tokio_stream::Stream; mod dns; -mod helpers; + +pub mod predicate; mod memory; pub use memory::MemoryTransport; diff --git a/comms/src/transports/helpers.rs b/comms/src/transports/predicate.rs similarity index 81% rename from comms/src/transports/helpers.rs rename to comms/src/transports/predicate.rs index a9f811f622..6ea558caa6 100644 --- a/comms/src/transports/helpers.rs +++ b/comms/src/transports/predicate.rs @@ -21,6 +21,36 @@ // USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. use crate::multiaddr::{Multiaddr, Protocol}; +use std::marker::PhantomData; + +pub trait Predicate { + fn check(&self, arg: &A) -> bool; +} + +impl Predicate for T +where + T: Fn(&A) -> bool, + A: ?Sized, +{ + fn check(&self, arg: &A) -> bool { + (self)(arg) + } +} + +#[derive(Debug, Default)] +pub struct FalsePredicate<'a, A>(PhantomData<&'a A>); + +impl<'a, A> FalsePredicate<'a, A> { + pub fn new() -> Self { + Self(PhantomData) + } +} + +impl Predicate for FalsePredicate<'_, A> { + fn check(&self, _: &A) -> bool { + false + } +} pub fn is_onion_address(addr: &Multiaddr) -> bool { let protocol = addr.iter().next(); diff --git a/comms/src/transports/socks.rs b/comms/src/transports/socks.rs index 7dc87ef0e4..0803a7fa29 100644 --- a/comms/src/transports/socks.rs +++ b/comms/src/transports/socks.rs @@ -24,21 +24,33 @@ use crate::{ multiaddr::Multiaddr, socks, socks::Socks5Client, - transports::{dns::SystemDnsResolver, tcp::TcpTransport, Transport}, + transports::{dns::SystemDnsResolver, predicate::Predicate, tcp::TcpTransport, Transport}, +}; +use log::*; +use std::{ + fmt::{Debug, Formatter}, + io, + sync::Arc, }; -use std::io; use tokio::net::TcpStream; -// /// SO_KEEPALIVE setting for the SOCKS TCP connection -// const SOCKS_SO_KEEPALIVE: Duration = Duration::from_millis(1500); +const LOG_TARGET: &str = "comms::transports::socks"; -#[derive(Clone, Debug)] +#[derive(Clone)] pub struct SocksConfig { pub proxy_address: Multiaddr, pub authentication: socks::Authentication, - /// If the dialed address matches any of these addresses, the SOCKS proxy is bypassed and direct TCP connection is - /// used. - pub proxy_bypass_addresses: Vec, + pub proxy_bypass_predicate: Arc + Send + Sync>, +} + +impl Debug for SocksConfig { + fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { + f.debug_struct("SocksConfig") + .field("proxy_address", &self.proxy_address) + .field("authentication", &self.authentication) + .field("proxy_bypass_predicate", &"...") + .finish() + } } #[derive(Clone)] @@ -58,7 +70,6 @@ impl SocksTransport { pub fn create_socks_tcp_transport() -> TcpTransport { let mut tcp_transport = TcpTransport::new(); tcp_transport.set_nodelay(true); - // .set_keepalive(Some(SOCKS_SO_KEEPALIVE)) tcp_transport.set_dns_resolver(SystemDnsResolver); tcp_transport } @@ -96,7 +107,8 @@ impl Transport for SocksTransport { async fn dial(&self, addr: Multiaddr) -> Result { // Bypass the SOCKS proxy and connect to the address directly - if self.socks_config.proxy_bypass_addresses.contains(&addr) { + if self.socks_config.proxy_bypass_predicate.check(&addr) { + debug!(target: LOG_TARGET, "SOCKS proxy bypassed for '{}'. Using TCP.", addr); return self.tcp_transport.dial(addr).await; } @@ -108,7 +120,7 @@ impl Transport for SocksTransport { #[cfg(test)] mod test { use super::*; - use crate::socks::Authentication; + use crate::{socks::Authentication, transports::predicate::FalsePredicate}; #[test] fn new() { @@ -116,7 +128,7 @@ mod test { let transport = SocksTransport::new(SocksConfig { proxy_address: proxy_address.clone(), authentication: Default::default(), - proxy_bypass_addresses: vec![], + proxy_bypass_predicate: Arc::new(FalsePredicate::new()), }); assert_eq!(transport.socks_config.proxy_address, proxy_address); diff --git a/comms/src/transports/tcp_with_tor.rs b/comms/src/transports/tcp_with_tor.rs index be800d9bfb..a816299244 100644 --- a/comms/src/transports/tcp_with_tor.rs +++ b/comms/src/transports/tcp_with_tor.rs @@ -21,7 +21,7 @@ // USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. use super::Transport; -use crate::transports::{dns::TorDnsResolver, helpers::is_onion_address, SocksConfig, SocksTransport, TcpTransport}; +use crate::transports::{dns::TorDnsResolver, predicate::is_onion_address, SocksConfig, SocksTransport, TcpTransport}; use multiaddr::Multiaddr; use std::io; use tokio::net::TcpStream;