Radish alpha
h
rad:z3gqcJUoA1n9HaHKufZs5FCSGazv5
Radicle Heartwood Protocol & Stack
Radicle
Git
cargo: Add feature for Tor support
Lorenz Leutgeb committed 29 days ago
commit 1e132685bc17f9b0a796ca58d7c64b4c0f81c4bc
parent 8bac24d
13 files changed +44 -8
modified crates/radicle-cli/Cargo.toml
@@ -13,6 +13,10 @@ rust-version.workspace = true
name = "rad"
path = "src/main.rs"

+
[features]
+
default = ["tor"]
+
tor = ["radicle/tor"]
+

[dependencies]
anyhow = "1"
chrono = { workspace = true, features = ["clock", "std"] }
modified crates/radicle-node/Cargo.toml
@@ -10,9 +10,10 @@ build = "build.rs"
rust-version.workspace = true

[features]
-
default = ["backtrace", "systemd", "structured-logger", "socket2"]
+
default = ["backtrace", "systemd", "structured-logger", "socket2", "tor"]
systemd = ["dep:radicle-systemd"]
test = ["radicle/test", "radicle-crypto/test", "radicle-crypto/cyphernet", "radicle-protocol/test", "qcheck", "snapbox"]
+
tor = ["cyphernet/tor", "radicle/tor", "radicle-protocol/tor"]

[dependencies]
backtrace = { version = "0.3.75", optional = true }
@@ -21,7 +22,7 @@ bytes = { workspace = true }
chrono = { workspace = true, features = ["clock"] }
colored = { workspace = true }
crossbeam-channel = { workspace = true }
-
cyphernet = { workspace = true, features = ["tor", "dns", "ed25519", "p2p-ed25519", "noise-framework", "noise_sha2"] }
+
cyphernet = { workspace = true, features = ["dns", "ed25519", "p2p-ed25519", "noise-framework", "noise_sha2"] }
fastrand = { workspace = true }
gix-packetline = { workspace = true, features = ["blocking-io"] }
lexopt = { workspace = true }
modified crates/radicle-node/src/wire.rs
@@ -21,6 +21,7 @@ use radicle::collections::{RandomMap, RandomSet};
use radicle::crypto;
use radicle::node::Link;
use radicle::node::NodeId;
+
#[cfg(feature = "tor")]
use radicle::node::config::AddressConfig;
use radicle::storage::WriteStorage;
use radicle_protocol::deserializer::Deserializer;
@@ -1091,6 +1092,7 @@ pub fn dial<G: Ecdh<Pk = NodeId>>(
        (HostName::Dns(_), Some(proxy)) => proxy.into(),
        (HostName::Dns(dns), None) => NetAddr::new(InetHost::Dns(dns.clone()), remote_addr.port),
        // For onion addresses, handle with care.
+
        #[cfg(feature = "tor")]
        (HostName::Tor(onion), proxy) => match config.onion {
            // In onion proxy mode, simply use the configured proxy address.
            // This takes precedence over any global proxy.
modified crates/radicle-protocol/Cargo.toml
@@ -10,12 +10,13 @@ rust-version.workspace = true

[features]
test = ["radicle/test", "radicle-crypto/test", "radicle-crypto/cyphernet", "qcheck"]
+
tor = ["cypheraddr/tor", "radicle/tor"]

[dependencies]
bloomy = "1.2"
bytes = { workspace = true }
crossbeam-channel = { workspace = true }
-
cypheraddr = { workspace = true, features = ["serde", "tor"] }
+
cypheraddr = { workspace = true, features = ["serde"] }
fastrand = { workspace = true }
log = { workspace = true, features = ["std"] }
nonempty = { workspace = true, features = ["serialize"] }
modified crates/radicle-protocol/src/service.rs
@@ -2655,6 +2655,7 @@ where
    fn is_supported_address(&self, address: &Address) -> bool {
        match AddressType::from(address) {
            // Only consider onion addresses if configured.
+
            #[cfg(feature = "tor")]
            AddressType::Onion => self.config.onion.is_some(),
            AddressType::Dns | AddressType::Ipv4 | AddressType::Ipv6 => true,
        }
modified crates/radicle-protocol/src/wire.rs
@@ -14,6 +14,7 @@ use std::string::FromUtf8Error;

use bytes::{Buf, BufMut};

+
#[cfg(feature = "tor")]
use cypheraddr::tor;

use radicle::crypto::{PublicKey, Signature};
@@ -56,6 +57,7 @@ pub enum Invalid {
    Alias(#[from] node::AliasError),
    #[error("invalid user agent string: {err}")]
    InvalidUserAgent { err: String },
+
    #[cfg(feature = "tor")]
    #[error("invalid onion address: {0}")]
    OnionAddr(#[from] tor::OnionAddrDecodeError),
    #[error("invalid timestamp: {actual_millis} millis")]
@@ -257,6 +259,7 @@ impl Encode for Refs {
    }
}

+
#[cfg(feature = "tor")]
impl Encode for cypheraddr::tor::OnionAddrV3 {
    fn encode(&self, buf: &mut impl BufMut) {
        self.into_raw_bytes().encode(buf)
@@ -518,6 +521,7 @@ impl Decode for node::Features {
    }
}

+
#[cfg(feature = "tor")]
impl Decode for tor::OnionAddrV3 {
    fn decode(buf: &mut impl Buf) -> Result<Self, Error> {
        let bytes: [u8; tor::ONION_V3_RAW_LEN] = Decode::decode(buf)?;
modified crates/radicle-protocol/src/wire/message.rs
@@ -2,7 +2,9 @@ use std::{mem, net};

use bytes::Buf;
use bytes::BufMut;
-
use cypheraddr::{HostName, NetAddr, tor};
+
#[cfg(feature = "tor")]
+
use cypheraddr::tor;
+
use cypheraddr::{HostName, NetAddr};
use radicle::crypto::Signature;
use radicle::git::Oid;
use radicle::identity::RepoId;
@@ -79,6 +81,7 @@ pub enum AddressType {
    Ipv4 = 1,
    Ipv6 = 2,
    Dns = 3,
+
    #[cfg(feature = "tor")]
    Onion = 4,
}

@@ -94,6 +97,7 @@ impl From<&Address> for AddressType {
            HostName::Ip(net::IpAddr::V4(_)) => AddressType::Ipv4,
            HostName::Ip(net::IpAddr::V6(_)) => AddressType::Ipv6,
            HostName::Dns(_) => AddressType::Dns,
+
            #[cfg(feature = "tor")]
            HostName::Tor(_) => AddressType::Onion,
            _ => todo!(), // FIXME(cloudhead): Maxim will remove `non-exhaustive`
        }
@@ -108,6 +112,7 @@ impl TryFrom<u8> for AddressType {
            1 => Ok(AddressType::Ipv4),
            2 => Ok(AddressType::Ipv6),
            3 => Ok(AddressType::Dns),
+
            #[cfg(feature = "tor")]
            4 => Ok(AddressType::Onion),
            _ => Err(other),
        }
@@ -356,6 +361,7 @@ impl wire::Encode for Address {
                u8::from(AddressType::Dns).encode(buf);
                dns.encode(buf);
            }
+
            #[cfg(feature = "tor")]
            HostName::Tor(addr) => {
                u8::from(AddressType::Onion).encode(buf);
                addr.encode(buf);
@@ -393,6 +399,7 @@ impl wire::Decode for Address {

                HostName::Dns(dns)
            }
+
            #[cfg(feature = "tor")]
            Ok(AddressType::Onion) => {
                let onion: tor::OnionAddrV3 = wire::Decode::decode(buf)?;

modified crates/radicle/Cargo.toml
@@ -24,6 +24,7 @@ schemars = [
  "radicle-localtime/schemars",
  "dep:schemars"
]
+
tor = ["cyphernet/tor"]

[dependencies]
amplify = { workspace = true, features = ["std"] }
@@ -32,7 +33,7 @@ bytesize = { version = "2", features = ["serde"] }
chrono = { workspace = true, features = ["clock"], optional = true }
colored = { workspace = true, optional = true }
crossbeam-channel = { workspace = true }
-
cyphernet = { workspace = true, features = ["tor", "dns", "p2p-ed25519"] }
+
cyphernet = { workspace = true, features = ["dns", "p2p-ed25519"] }
dunce = { workspace = true }
fast-glob = { version = "0.3.2" }
fastrand = { workspace = true, features = ["std"] }
modified crates/radicle/src/node.rs
@@ -476,6 +476,7 @@ impl Address {
    }

    /// Returns `true` if the [`HostName`] is a Tor onion address.
+
    #[cfg(feature = "tor")]
    pub fn is_onion(&self) -> bool {
        match self.0.host {
            HostName::Tor(_) => true,
@@ -493,6 +494,7 @@ impl Address {
            HostName::Ip(IpAddr::V4(ip)) => ip.to_string(),
            HostName::Ip(IpAddr::V6(ip)) => format!("[{ip}]"),
            HostName::Dns(dns) => dns.clone(),
+
            #[cfg(feature = "tor")]
            HostName::Tor(onion) => {
                let onion = onion.to_string();
                let start = onion.chars().take(8).collect::<String>();
modified crates/radicle/src/node/address.rs
@@ -201,6 +201,7 @@ pub enum AddressType {
    Ipv4 = 1,
    Ipv6 = 2,
    Dns = 3,
+
    #[cfg(feature = "tor")]
    Onion = 4,
}

@@ -216,6 +217,7 @@ impl From<&Address> for AddressType {
            HostName::Ip(net::IpAddr::V4(_)) => AddressType::Ipv4,
            HostName::Ip(net::IpAddr::V6(_)) => AddressType::Ipv6,
            HostName::Dns(_) => AddressType::Dns,
+
            #[cfg(feature = "tor")]
            HostName::Tor(_) => AddressType::Onion,
            _ => todo!(), // FIXME(cloudhead): Maxim will remove `non-exhaustive`
        }
@@ -230,6 +232,7 @@ impl TryFrom<u8> for AddressType {
            1 => Ok(AddressType::Ipv4),
            2 => Ok(AddressType::Ipv6),
            3 => Ok(AddressType::Dns),
+
            #[cfg(feature = "tor")]
            4 => Ok(AddressType::Onion),
            _ => Err(other),
        }
modified crates/radicle/src/node/address/store.rs
@@ -535,6 +535,7 @@ impl TryFrom<&sql::Value> for AddressType {
                "ipv4" => Ok(AddressType::Ipv4),
                "ipv6" => Ok(AddressType::Ipv6),
                "dns" => Ok(AddressType::Dns),
+
                #[cfg(feature = "tor")]
                "onion" => Ok(AddressType::Onion),
                _ => Err(err),
            },
@@ -549,6 +550,7 @@ impl sql::BindableWithIndex for AddressType {
            Self::Ipv4 => "ipv4".bind(stmt, i),
            Self::Ipv6 => "ipv6".bind(stmt, i),
            Self::Dns => "dns".bind(stmt, i),
+
            #[cfg(feature = "tor")]
            Self::Onion => "onion".bind(stmt, i),
        }
    }
modified crates/radicle/src/node/config.rs
@@ -22,7 +22,9 @@ pub type ProtocolVersion = u8;
pub mod seeds {
    use std::{str::FromStr, sync::LazyLock};

-
    use cyphernet::addr::{HostName, NetAddr, tor::OnionAddrV3};
+
    #[cfg(feature = "tor")]
+
    use cyphernet::addr::tor::OnionAddrV3;
+
    use cyphernet::addr::{HostName, NetAddr};

    use super::{ConnectAddress, NodeId, PeerAddr};

@@ -41,6 +43,7 @@ pub mod seeds {
            NodeId::from_str("z6MkrLMMsiPWUcNPHcRajuMi9mDfYckSoJyPwwnknocNYPm7").unwrap(),
            vec![
                HostName::Dns("iris.radicle.xyz".to_owned()),
+
                #[cfg(feature = "tor")]
                #[allow(clippy::unwrap_used)] // Value is manually verified.
                OnionAddrV3::from_str(
                    "irisradizskwweumpydlj4oammoshkxxjur3ztcmo7cou5emc6s5lfid.onion",
@@ -58,6 +61,7 @@ pub mod seeds {
            NodeId::from_str("z6Mkmqogy2qEM2ummccUthFEaaHvyYmYBYh3dbe9W4ebScxo").unwrap(),
            vec![
                HostName::Dns("rosa.radicle.xyz".to_owned()),
+
                #[cfg(feature = "tor")]
                #[allow(clippy::unwrap_used)] // Value is manually verified.
                OnionAddrV3::from_str(
                    "rosarad5bxgdlgjnzzjygnsxrwxmoaj4vn7xinlstwglxvyt64jlnhyd.onion",
@@ -354,6 +358,7 @@ pub enum Relay {
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(rename_all = "camelCase", tag = "mode")]
#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
+
#[cfg(feature = "tor")]
pub enum AddressConfig {
    /// Proxy connections to this address type.
    Proxy {
@@ -540,6 +545,7 @@ pub struct Config {
    #[serde(default, skip_serializing_if = "Option::is_none")]
    pub proxy: Option<net::SocketAddr>,
    /// Onion address config.
+
    #[cfg(feature = "tor")]
    #[serde(default, skip_serializing_if = "Option::is_none")]
    pub onion: Option<AddressConfig>,
    /// Peer-to-peer network.
@@ -596,6 +602,7 @@ impl Config {
            external_addresses: vec![],
            network: Network::default(),
            proxy: None,
+
            #[cfg(feature = "tor")]
            onion: None,
            relay: Relay::default(),
            limits: Limits::default(),
modified crates/radicle/src/test/arbitrary.rs
@@ -5,8 +5,8 @@ use std::str::FromStr;
use std::{iter, net};

use crypto::PublicKey;
-
use cyphernet::EcPk;
-
use cyphernet::addr::tor::OnionAddrV3;
+
#[cfg(feature = "tor")]
+
use cyphernet::{EcPk, addr::tor::OnionAddrV3};
use qcheck::Arbitrary;

use crate::identity::doc::Visibility;
@@ -229,6 +229,7 @@ impl Arbitrary for Address {
                    .unwrap()
                    .to_string(),
            ),
+
            #[cfg(feature = "tor")]
            AddressType::Onion => {
                let pk = PublicKey::arbitrary(g);
                let addr = OnionAddrV3::from(