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.
This commit is contained in:
cloudhead 2024-05-16 12:53:47 +02:00
parent cb2cbf014a
commit ca7db1620c
No known key found for this signature in database
5 changed files with 47 additions and 58 deletions

View File

@ -29,7 +29,6 @@ $ rad config
"db": {
"journalMode": "rollback"
},
"tor": null,
"network": "main",
"log": "INFO",
"relay": "auto",

View File

@ -90,7 +90,6 @@ mod routes {
"connect": [],
"externalAddresses": [],
"db": { "journalMode": "rollback" },
"tor": null,
"network": "main",
"log": "INFO",
"relay": "auto",

View File

@ -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)

View File

@ -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",
))
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)
}
// 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)?;

View File

@ -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 {