From ca7db1620c6f8d4e9caed0902b191c5e6def8bc3 Mon Sep 17 00:00:00 2001 From: cloudhead Date: Thu, 16 May 2024 12:53:47 +0200 Subject: [PATCH] node: Improve Tor configuration flexibility *Breaking change* The `tor` configuration field is no longer supported. Users must remove that field from their config. Allows for a mixed mode, where regular addresses bypass the Tor proxy, while onion addresses go through it. When `onion.mode` is set to "forward", Tor connections are fowarded to the global proxy, and if it isn't set, to the OS's DNS resolution. For mixed mode, a global proxy simply isn't set, so that IP/DNS is not proxied. --- radicle-cli/examples/rad-config.md | 1 - radicle-httpd/src/api/v1/profile.rs | 1 - radicle-node/src/service.rs | 6 +-- radicle-node/src/wire/protocol.rs | 61 +++++++++++++---------------- radicle/src/node/config.rs | 36 ++++++++--------- 5 files changed, 47 insertions(+), 58 deletions(-) diff --git a/radicle-cli/examples/rad-config.md b/radicle-cli/examples/rad-config.md index bd3c6840..a8ed742b 100644 --- a/radicle-cli/examples/rad-config.md +++ b/radicle-cli/examples/rad-config.md @@ -29,7 +29,6 @@ $ rad config "db": { "journalMode": "rollback" }, - "tor": null, "network": "main", "log": "INFO", "relay": "auto", diff --git a/radicle-httpd/src/api/v1/profile.rs b/radicle-httpd/src/api/v1/profile.rs index 7814803a..123cbe15 100644 --- a/radicle-httpd/src/api/v1/profile.rs +++ b/radicle-httpd/src/api/v1/profile.rs @@ -90,7 +90,6 @@ mod routes { "connect": [], "externalAddresses": [], "db": { "journalMode": "rollback" }, - "tor": null, "network": "main", "log": "INFO", "relay": "auto", diff --git a/radicle-node/src/service.rs b/radicle-node/src/service.rs index ca666717..e9668e54 100644 --- a/radicle-node/src/service.rs +++ b/radicle-node/src/service.rs @@ -1232,7 +1232,7 @@ where } Entry::Vacant(e) => { if let HostName::Ip(ip) = addr.host { - if !ip.is_loopback() && !self.config.is_proxy_ip(ip) { + if !address::is_local(&ip) { if let Err(e) = self.db .addresses_mut() @@ -2335,8 +2335,8 @@ where .map(|ka| (peer.nid, ka)) }) .filter(|(_, ka)| match AddressType::from(&ka.addr) { - // Only consider Tor addresses if Tor is configured. - AddressType::Onion => self.config.tor.is_some(), + // Only consider onion addresses if configured. + AddressType::Onion => self.config.onion.is_some(), AddressType::Dns | AddressType::Ipv4 | AddressType::Ipv6 => true, }) .take(wanted) diff --git a/radicle-node/src/wire/protocol.rs b/radicle-node/src/wire/protocol.rs index 65e69d84..b04fb7ef 100644 --- a/radicle-node/src/wire/protocol.rs +++ b/radicle-node/src/wire/protocol.rs @@ -21,7 +21,7 @@ use netservices::{NetConnection, NetReader, NetWriter}; use reactor::{ResourceId, ResourceType, Timestamp}; use radicle::collections::RandomMap; -use radicle::node::config::TorConfig; +use radicle::node::config::AddressConfig; use radicle::node::NodeId; use radicle::storage::WriteStorage; @@ -1071,42 +1071,37 @@ pub fn dial>( signer: G, config: &service::Config, ) -> io::Result> { - let inet_addr: NetAddr = match config.tor { - // In Tor proxy mode, simply specify the proxy address for all connections, - // since we'll be routing all connections through the proxy. - Some(TorConfig::Proxy { address: proxy }) => proxy.into(), - // In transparent Tor mode, we treat `.onion` addresses as regular DNS names. - Some(TorConfig::Transparent) => { - let host = match &remote_addr.host { - HostName::Ip(ip) => InetHost::Ip(*ip), - HostName::Dns(dns) => InetHost::Dns(dns.clone()), - HostName::Tor(onion) => InetHost::Dns(onion.to_string()), - _ => { - return Err(io::Error::new( - io::ErrorKind::Unsupported, - "unsupported remote address type", - )) + let inet_addr: NetAddr = match &remote_addr.host { + HostName::Ip(ip) => NetAddr::new(InetHost::Ip(*ip), remote_addr.port), + HostName::Dns(dns) => NetAddr::new(InetHost::Dns(dns.clone()), remote_addr.port), + HostName::Tor(onion) => match config.onion { + // In Tor proxy mode, simply specify the proxy address. + Some(AddressConfig::Proxy { address }) => address.into(), + // In "forward" mode, if a global proxy is set, we use that, otherwise + // we treat `.onion` addresses as regular DNS names. + Some(AddressConfig::Forward) => { + if let Some(proxy) = config.proxy { + proxy.into() + } else { + NetAddr::new(InetHost::Dns(onion.to_string()), remote_addr.port) } - }; - NetAddr::new(host, remote_addr.port) - } - // Without any Tor configuration, refuse to connect to a `.onion` address. - None => { - let host = match &remote_addr.host { - HostName::Ip(ip) => InetHost::Ip(*ip), - HostName::Dns(dns) => InetHost::Dns(dns.clone()), - _ => { - return Err(io::Error::new( - io::ErrorKind::Unsupported, - "unsupported remote address type", - )) - } - }; - NetAddr::new(host, remote_addr.port) + } + None => { + return Err(io::Error::new( + io::ErrorKind::Unsupported, + "no configuration found for .onion addresses", + )); + } + }, + _ => { + return Err(io::Error::new( + io::ErrorKind::Unsupported, + "unsupported remote address type", + )) } }; // Whether to tunnel regular connections through the proxy. - let force_proxy = matches!(config.tor, Some(TorConfig::Proxy { .. })); + let force_proxy = config.proxy.is_some(); // Nb. This timeout is currently not used by the underlying library due to the // `socket2` library not supporting non-blocking connect with timeout. let connection = net::TcpStream::connect_nonblocking(inet_addr, DEFAULT_DIAL_TIMEOUT)?; diff --git a/radicle/src/node/config.rs b/radicle/src/node/config.rs index 5d1b0a39..68fb8da9 100644 --- a/radicle/src/node/config.rs +++ b/radicle/src/node/config.rs @@ -7,7 +7,7 @@ use localtime::LocalDuration; use crate::node; use crate::node::policy::{Policy, Scope}; -use crate::node::{address, db, Address, Alias, NodeId}; +use crate::node::{db, Address, Alias, NodeId}; /// Target number of peers to maintain connections to. pub const TARGET_OUTBOUND_PEERS: usize = 8; @@ -247,17 +247,18 @@ pub enum Relay { Auto, } -/// Tor configuration. +/// Proxy configuration. #[derive(Debug, Clone, serde::Serialize, serde::Deserialize)] #[serde(rename_all = "camelCase", tag = "mode")] -pub enum TorConfig { - /// Connect via SOCKS5 proxy. +pub enum AddressConfig { + /// Proxy connections to this address type. Proxy { - /// Tor proxy address. + /// Proxy address. address: net::SocketAddr, }, - /// Treat Tor onion addresses as DNS names. - Transparent, + /// Forward address to the next layer. Either this is the global proxy, + /// or the operating system, via DNS. + Forward, } /// Database configuration. @@ -290,9 +291,12 @@ pub struct Config { /// Database config. #[serde(default)] pub db: DbConfig, - /// Tor configuration. - #[serde(default)] - pub tor: Option, + /// Global proxy. + #[serde(default, skip_serializing_if = "Option::is_none")] + pub proxy: Option, + /// Onion address config. + #[serde(default, skip_serializing_if = "Option::is_none")] + pub onion: Option, /// Peer-to-peer network. #[serde(default)] pub network: Network, @@ -334,7 +338,8 @@ impl Config { external_addresses: vec![], db: DbConfig::default(), network: Network::default(), - tor: None, + proxy: None, + onion: None, relay: Relay::default(), limits: Limits::default(), workers: DEFAULT_WORKERS, @@ -359,15 +364,6 @@ impl Config { self.peer(id).is_some() } - /// Check if the given IP address is our proxy. - pub fn is_proxy_ip(&self, ip: net::IpAddr) -> bool { - match self.tor { - None => false, - Some(TorConfig::Proxy { address }) => address.ip() == ip, - Some(TorConfig::Transparent) => address::is_local(&ip), - } - } - /// Are we a relay node? This determines what we do with gossip messages from other peers. pub fn is_relay(&self) -> bool { match self.relay {