node: Explicit default for `AddressConfig`

The intent to drop outgoing connections is modeled as `Option::None`
which is brittle and easy to miss.

Extend `enum AddressConfig` with a variant that is more explicit.
This commit is contained in:
Lorenz Leutgeb 2026-02-15 13:13:04 +01:00
parent fb1808395e
commit 10a82958ca
No known key found for this signature in database
4 changed files with 54 additions and 16 deletions

View File

@ -247,14 +247,7 @@ $ rad config schema
}, },
"onion": { "onion": {
"description": "Onion address config.", "description": "Onion address config.",
"anyOf": [ "$ref": "#/$defs/AddressConfig"
{
"$ref": "#/$defs/AddressConfig"
},
{
"type": "null"
}
]
}, },
"network": { "network": {
"description": "Peer-to-peer network.", "description": "Peer-to-peer network.",
@ -410,6 +403,19 @@ $ rad config schema
"required": [ "required": [
"mode" "mode"
] ]
},
{
"description": "Drop connections to this address type.",
"type": "object",
"properties": {
"mode": {
"type": "string",
"const": "drop"
}
},
"required": [
"mode"
]
} }
] ]
}, },

View File

@ -1096,10 +1096,10 @@ pub fn dial<G: Ecdh<Pk = NodeId>>(
(HostName::Tor(onion), proxy) => match config.onion { (HostName::Tor(onion), proxy) => match config.onion {
// In onion proxy mode, simply use the configured proxy address. // In onion proxy mode, simply use the configured proxy address.
// This takes precedence over any global proxy. // This takes precedence over any global proxy.
Some(AddressConfig::Proxy { address }) => address.into(), AddressConfig::Proxy { address } => address.into(),
// In "forward" mode, if a global proxy is set, we use that, otherwise // In "forward" mode, if a global proxy is set, we use that, otherwise
// we treat `.onion` addresses as regular DNS names. // we treat `.onion` addresses as regular DNS names.
Some(AddressConfig::Forward) => { AddressConfig::Forward => {
if let Some(proxy) = proxy { if let Some(proxy) = proxy {
proxy.into() proxy.into()
} else { } else {
@ -1107,7 +1107,7 @@ pub fn dial<G: Ecdh<Pk = NodeId>>(
} }
} }
// If onion address support isn't configured, refuse to connect. // If onion address support isn't configured, refuse to connect.
None => { AddressConfig::Drop => {
return Err(io::Error::new( return Err(io::Error::new(
io::ErrorKind::Unsupported, io::ErrorKind::Unsupported,
"no configuration found for .onion addresses", "no configuration found for .onion addresses",

View File

@ -30,6 +30,8 @@ use radicle::node;
use radicle::node::address; use radicle::node::address;
use radicle::node::address::Store as _; use radicle::node::address::Store as _;
use radicle::node::address::{AddressBook, AddressType, KnownAddress}; use radicle::node::address::{AddressBook, AddressType, KnownAddress};
#[cfg(feature = "tor")]
use radicle::node::config::AddressConfig;
use radicle::node::config::{PeerConfig, RateLimit}; use radicle::node::config::{PeerConfig, RateLimit};
use radicle::node::device::Device; use radicle::node::device::Device;
use radicle::node::refs::Store as _; use radicle::node::refs::Store as _;
@ -2656,7 +2658,7 @@ where
match AddressType::from(address) { match AddressType::from(address) {
// Only consider onion addresses if configured. // Only consider onion addresses if configured.
#[cfg(feature = "tor")] #[cfg(feature = "tor")]
AddressType::Onion => self.config.onion.is_some(), AddressType::Onion => self.config.onion != AddressConfig::Drop,
AddressType::Dns | AddressType::Ipv4 | AddressType::Ipv6 => true, AddressType::Dns | AddressType::Ipv4 | AddressType::Ipv6 => true,
} }
} }

View File

@ -355,7 +355,7 @@ pub enum Relay {
} }
/// Proxy configuration. /// Proxy configuration.
#[derive(Debug, Clone, Serialize, Deserialize)] #[derive(Debug, Copy, Clone, Default, PartialEq, Serialize, Deserialize)]
#[serde(rename_all = "camelCase", tag = "mode")] #[serde(rename_all = "camelCase", tag = "mode")]
#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))] #[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
#[cfg(feature = "tor")] #[cfg(feature = "tor")]
@ -368,6 +368,9 @@ pub enum AddressConfig {
/// Forward address to the next layer. Either this is the global proxy, /// Forward address to the next layer. Either this is the global proxy,
/// or the operating system, via DNS. /// or the operating system, via DNS.
Forward, Forward,
/// Drop connections to this address type.
#[default]
Drop,
} }
/// Default seeding policy. Applies when no repository policies for the given repo are found. /// Default seeding policy. Applies when no repository policies for the given repo are found.
@ -546,8 +549,12 @@ pub struct Config {
pub proxy: Option<net::SocketAddr>, pub proxy: Option<net::SocketAddr>,
/// Onion address config. /// Onion address config.
#[cfg(feature = "tor")] #[cfg(feature = "tor")]
#[serde(default, skip_serializing_if = "Option::is_none")] #[serde(
pub onion: Option<AddressConfig>, default,
skip_serializing_if = "crate::serde_ext::is_default",
deserialize_with = "crate::serde_ext::null_to_default"
)]
pub onion: AddressConfig,
/// Peer-to-peer network. /// Peer-to-peer network.
#[serde(default)] #[serde(default)]
pub network: Network, pub network: Network,
@ -603,7 +610,7 @@ impl Config {
network: Network::default(), network: Network::default(),
proxy: None, proxy: None,
#[cfg(feature = "tor")] #[cfg(feature = "tor")]
onion: None, onion: AddressConfig::Drop,
relay: Relay::default(), relay: Relay::default(),
limits: Limits::default(), limits: Limits::default(),
workers: Workers::default(), workers: Workers::default(),
@ -944,4 +951,27 @@ mod test {
crate::storage::refs::FeatureLevel::Parent crate::storage::refs::FeatureLevel::Parent
); );
} }
#[cfg(feature = "tor")]
#[test]
fn onion_absent() {
let actual: super::Config = serde_json::from_value(json!({
"alias": "radicle",
}))
.unwrap();
assert_eq!(super::AddressConfig::Drop, actual.onion);
}
#[cfg(feature = "tor")]
#[test]
fn onion_null() {
// Backwards compatibility: Prior versions allowed to set `onion` to `null`,
// which should be treated the same as the default, i.e. `Drop`.
let actual: super::Config = serde_json::from_value(json!({
"alias": "radicle",
"onion": null,
}))
.unwrap();
assert_eq!(super::AddressConfig::Drop, actual.onion);
}
} }