node: Support "transparent" Tor

This allows for configuring your node such that `.onion` addresses
encountered are treated as regular DNS names.
This commit is contained in:
cloudhead 2024-04-24 18:55:05 +02:00
parent 70d2e1a0db
commit deeb39c558
No known key found for this signature in database
8 changed files with 69 additions and 32 deletions

View File

@ -26,6 +26,7 @@ $ rad config
}, },
"connect": [], "connect": [],
"externalAddresses": [], "externalAddresses": [],
"tor": null,
"network": "main", "network": "main",
"relay": true, "relay": true,
"limits": { "limits": {

View File

@ -89,6 +89,7 @@ mod routes {
}, },
"connect": [], "connect": [],
"externalAddresses": [], "externalAddresses": [],
"tor": null,
"network": "main", "network": "main",
"relay": true, "relay": true,
"limits": { "limits": {

View File

@ -115,7 +115,6 @@ fn execute() -> anyhow::Result<()> {
// Add the preferred seeds as persistent peers so that we reconnect to them automatically. // Add the preferred seeds as persistent peers so that we reconnect to them automatically.
config.node.connect.extend(config.preferred_seeds); config.node.connect.extend(config.preferred_seeds);
let proxy = net::SocketAddr::new(net::Ipv4Addr::LOCALHOST.into(), 9050);
let listen: Vec<std::net::SocketAddr> = if !options.listen.is_empty() { let listen: Vec<std::net::SocketAddr> = if !options.listen.is_empty() {
options.listen.clone() options.listen.clone()
} else { } else {
@ -133,7 +132,7 @@ fn execute() -> anyhow::Result<()> {
log::debug!(target: "node", "Removing existing control socket.."); log::debug!(target: "node", "Removing existing control socket..");
fs::remove_file(home.socket()).ok(); fs::remove_file(home.socket()).ok();
} }
Runtime::init(home, config.node, listen, proxy, signals, signer)?.run()?; Runtime::init(home, config.node, listen, signals, signer)?.run()?;
Ok(()) Ok(())
} }

View File

@ -139,7 +139,6 @@ impl Runtime {
home: Home, home: Home,
config: service::Config, config: service::Config,
listen: Vec<net::SocketAddr>, listen: Vec<net::SocketAddr>,
proxy: net::SocketAddr,
signals: chan::Receiver<()>, signals: chan::Receiver<()>,
signer: G, signer: G,
) -> Result<Runtime, Error> ) -> Result<Runtime, Error>
@ -238,7 +237,7 @@ impl Runtime {
service.initialize(clock)?; service.initialize(clock)?;
let (worker_send, worker_recv) = chan::unbounded::<worker::Task>(); let (worker_send, worker_recv) = chan::unbounded::<worker::Task>();
let mut wire = Wire::new(service, worker_send, signer.clone(), proxy); let mut wire = Wire::new(service, worker_send, signer.clone());
let mut local_addrs = Vec::new(); let mut local_addrs = Vec::new();
for addr in listen { for addr in listen {

View File

@ -552,6 +552,11 @@ where
&mut self.outbox &mut self.outbox
} }
/// Get configuration.
pub fn config(&self) -> &Config {
&self.config
}
/// Lookup a repository, both locally and in the routing table. /// Lookup a repository, both locally and in the routing table.
pub fn lookup(&self, rid: RepoId) -> Result<Lookup, LookupError> { pub fn lookup(&self, rid: RepoId) -> Result<Lookup, LookupError> {
let remote = self.db.routing().get(&rid)?.iter().cloned().collect(); let remote = self.db.routing().get(&rid)?.iter().cloned().collect();

View File

@ -492,13 +492,11 @@ impl<G: cyphernet::Ecdh<Pk = NodeId> + Signer + Clone> Node<G> {
/// Spawn a node in its own thread. /// Spawn a node in its own thread.
pub fn spawn(self) -> NodeHandle<G> { pub fn spawn(self) -> NodeHandle<G> {
let listen = vec![([0, 0, 0, 0], 0).into()]; let listen = vec![([0, 0, 0, 0], 0).into()];
let proxy = net::SocketAddr::new(net::Ipv4Addr::LOCALHOST.into(), 9050);
let (_, signals) = chan::bounded(1); let (_, signals) = chan::bounded(1);
let rt = Runtime::init( let rt = Runtime::init(
self.home.clone(), self.home.clone(),
self.config, self.config,
listen, listen,
proxy,
signals, signals,
self.signer.clone(), self.signer.clone(),
) )

View File

@ -21,6 +21,7 @@ use netservices::{NetConnection, NetProtocol, NetReader, NetWriter};
use reactor::{ResourceId, ResourceType, Timestamp}; use reactor::{ResourceId, ResourceType, Timestamp};
use radicle::collections::RandomMap; use radicle::collections::RandomMap;
use radicle::node::config::TorConfig;
use radicle::node::NodeId; use radicle::node::NodeId;
use radicle::storage::WriteStorage; use radicle::storage::WriteStorage;
@ -318,8 +319,6 @@ pub struct Wire<D, S, G: Signer + Ecdh> {
listening: RandomMap<RawFd, net::SocketAddr>, listening: RandomMap<RawFd, net::SocketAddr>,
/// Peer (established) sessions. /// Peer (established) sessions.
peers: Peers, peers: Peers,
/// SOCKS5 proxy address.
proxy: net::SocketAddr,
} }
impl<D, S, G> Wire<D, S, G> impl<D, S, G> Wire<D, S, G>
@ -328,19 +327,13 @@ where
S: WriteStorage + 'static, S: WriteStorage + 'static,
G: Signer + Ecdh<Pk = NodeId>, G: Signer + Ecdh<Pk = NodeId>,
{ {
pub fn new( pub fn new(service: Service<D, S, G>, worker: chan::Sender<Task>, signer: G) -> Self {
service: Service<D, S, G>,
worker: chan::Sender<Task>,
signer: G,
proxy: net::SocketAddr,
) -> Self {
assert!(service.started().is_some(), "Service must be initialized"); assert!(service.started().is_some(), "Service must be initialized");
Self { Self {
service, service,
worker, worker,
signer, signer,
proxy,
actions: VecDeque::new(), actions: VecDeque::new(),
inbound: RandomMap::default(), inbound: RandomMap::default(),
outbound: RandomMap::default(), outbound: RandomMap::default(),
@ -954,8 +947,7 @@ where
addr.to_inner(), addr.to_inner(),
node_id, node_id,
self.signer.clone(), self.signer.clone(),
self.proxy.into(), self.service.config(),
false,
) )
.and_then(|session| { .and_then(|session| {
NetTransport::<WireSession<G>>::with_session(session, Link::Outbound) NetTransport::<WireSession<G>>::with_session(session, Link::Outbound)
@ -1057,19 +1049,46 @@ pub fn dial<G: Signer + Ecdh<Pk = NodeId>>(
remote_addr: NetAddr<HostName>, remote_addr: NetAddr<HostName>,
remote_id: <G as EcSk>::Pk, remote_id: <G as EcSk>::Pk,
signer: G, signer: G,
proxy_addr: NetAddr<InetHost>, config: &service::Config,
force_proxy: bool,
) -> io::Result<WireSession<G>> { ) -> io::Result<WireSession<G>> {
let connection = if force_proxy { // Convert the remote address into an internet protocol address (IP or DNS).
// Nb. This timeout is currently not used by the underlying library due to the let inet_addr: NetAddr<InetHost> = match config.tor {
// `socket2` library not supporting non-blocking connect with timeout. // In Tor proxy mode, simply specify a different connection address.
net::TcpStream::connect_nonblocking(proxy_addr, DEFAULT_DIAL_TIMEOUT)? Some(TorConfig::Proxy { address: proxy }) => remote_addr.connection_addr(proxy.into()),
} else { // In transparent Tor mode, we treat `.onion` addresses as regular DNS names.
net::TcpStream::connect_nonblocking( Some(TorConfig::Transparent) => {
remote_addr.connection_addr(proxy_addr), let host = match &remote_addr.host {
DEFAULT_DIAL_TIMEOUT, 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",
))
}
};
NetAddr::new(host, remote_addr.port)
}
}; };
// 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)?;
connection.set_read_timeout(Some(DEFAULT_CONNECTION_TIMEOUT))?; connection.set_read_timeout(Some(DEFAULT_CONNECTION_TIMEOUT))?;
connection.set_write_timeout(Some(DEFAULT_CONNECTION_TIMEOUT))?; connection.set_write_timeout(Some(DEFAULT_CONNECTION_TIMEOUT))?;
@ -1078,7 +1097,6 @@ pub fn dial<G: Signer + Ecdh<Pk = NodeId>>(
Some(remote_id), Some(remote_id),
connection, connection,
signer, signer,
force_proxy,
)) ))
} }
@ -1088,7 +1106,7 @@ pub fn accept<G: Signer + Ecdh<Pk = NodeId>>(
connection: net::TcpStream, connection: net::TcpStream,
signer: G, signer: G,
) -> WireSession<G> { ) -> WireSession<G> {
session::<G>(remote_addr, None, connection, signer, false) session::<G>(remote_addr, None, connection, signer)
} }
/// Create a new [`WireSession`]. /// Create a new [`WireSession`].
@ -1097,9 +1115,8 @@ fn session<G: Signer + Ecdh<Pk = NodeId>>(
remote_id: Option<NodeId>, remote_id: Option<NodeId>,
connection: net::TcpStream, connection: net::TcpStream,
signer: G, signer: G,
force_proxy: bool,
) -> WireSession<G> { ) -> WireSession<G> {
let socks5 = socks5::Socks5::with(remote_addr, force_proxy); let socks5 = socks5::Socks5::with(remote_addr, false);
let proxy = Socks5Session::with(connection, socks5); let proxy = Socks5Session::with(connection, socks5);
let pair = G::generate_keypair(); let pair = G::generate_keypair();
let keyset = Keyset { let keyset = Keyset {

View File

@ -234,6 +234,19 @@ impl Default for PeerConfig {
} }
} }
/// Tor configuration.
#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
#[serde(rename_all = "camelCase", tag = "mode")]
pub enum TorConfig {
/// Connect via SOCKS5 proxy.
Proxy {
/// Tor proxy address.
address: net::SocketAddr,
},
/// Treat Tor onion addresses as DNS names.
Transparent,
}
/// Service configuration. /// Service configuration.
#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)] #[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
#[serde(rename_all = "camelCase")] #[serde(rename_all = "camelCase")]
@ -253,6 +266,9 @@ pub struct Config {
/// Specify the node's public addresses /// Specify the node's public addresses
#[serde(default)] #[serde(default)]
pub external_addresses: Vec<Address>, pub external_addresses: Vec<Address>,
/// Tor configuration.
#[serde(default)]
pub tor: Option<TorConfig>,
/// Peer-to-peer network. /// Peer-to-peer network.
#[serde(default)] #[serde(default)]
pub network: Network, pub network: Network,
@ -289,6 +305,7 @@ impl Config {
connect: HashSet::default(), connect: HashSet::default(),
external_addresses: vec![], external_addresses: vec![],
network: Network::default(), network: Network::default(),
tor: None,
relay: true, relay: true,
limits: Limits::default(), limits: Limits::default(),
workers: DEFAULT_WORKERS, workers: DEFAULT_WORKERS,