Radish alpha
h
rad:z3gqcJUoA1n9HaHKufZs5FCSGazv5
Radicle Heartwood Protocol & Stack
Radicle
Git
node: Improve Tor configuration flexibility
Merged did:key:z6MksFqX...wzpT opened 1 year ago

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.

5 files changed +47 -58 cb2cbf01 ca7db162
modified radicle-cli/examples/rad-config.md
@@ -29,7 +29,6 @@ $ rad config
    "db": {
      "journalMode": "rollback"
    },
-
    "tor": null,
    "network": "main",
    "log": "INFO",
    "relay": "auto",
modified 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",
modified 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)
modified 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<G: Signer + Ecdh<Pk = NodeId>>(
    signer: G,
    config: &service::Config,
) -> io::Result<WireSession<G>> {
-
    let inet_addr: NetAddr<InetHost> = 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",
-
                    ))
-
                }
-
            };
-
            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",
-
                    ))
+
    let inet_addr: NetAddr<InetHost> = 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)
+
            }
+
            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)?;
modified 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<TorConfig>,
+
    /// Global proxy.
+
    #[serde(default, skip_serializing_if = "Option::is_none")]
+
    pub proxy: Option<net::SocketAddr>,
+
    /// Onion address config.
+
    #[serde(default, skip_serializing_if = "Option::is_none")]
+
    pub onion: Option<AddressConfig>,
    /// 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 {