radicle/node/address: Skip unknown AddressType value

Since `tor` and `i2p` are features, dependents may compile the
`radicle` crate without these features and will not be able to parse
`AddressType`.

Allow parsing of the `AddressType` to fail, and skip if that is the
case.

Further, the same holds for `Address`, and skip if that fails to parse
as well.

Finally, `Address` may parse an address string successfully to
`HostName`, but it may have an `AddressType` that does not match the
`HostName` variant. Guard against this, again skipping if they are not
equal.
For example, `HostName::Dns` is a `String` underneath, so there is
higher chance that something like `deadbeef.onion` parses to this
variant.
This commit is contained in:
Fintan Halpenny 2026-05-21 15:13:28 +01:00
parent ff8660d872
commit 704b6cdabb
2 changed files with 136 additions and 3 deletions

View File

@ -1,6 +1,8 @@
mod features;
pub mod address;
use address::AddressType;
pub mod command;
pub mod config;
pub mod db;
@ -518,6 +520,22 @@ impl Address {
&self.inner.host
}
/// Return the [`AddressType`] of the [`Address`].
///
/// Returns `None` if the [`AddressType`] is not known.
pub fn address_type(&self) -> Option<AddressType> {
match self.host() {
HostName::Ip(IpAddr::V4(_)) => Some(AddressType::Ipv4),
HostName::Ip(IpAddr::V6(_)) => Some(AddressType::Ipv6),
HostName::Dns(_) => Some(AddressType::Dns),
#[cfg(feature = "tor")]
HostName::Tor(_) => Some(AddressType::Onion),
#[cfg(feature = "i2p")]
HostName::I2p(_) => Some(AddressType::I2p),
_ => None,
}
}
/// Returns `true` if the [`HostName`] is a Tor onion address.
#[cfg(feature = "tor")]
pub fn is_onion(&self) -> bool {

View File

@ -182,9 +182,42 @@ impl Store for Database {
stmt.bind((1, node))?;
for row in stmt.into_iter() {
let row = row?;
let _type = row.try_read::<AddressType, _>("type")?;
let addr = row.try_read::<Address, _>("value")?;
let mut row = row?;
let address_type = match row.try_read::<AddressType, _>("type") {
Ok(address_type) => address_type,
Err(err) => {
let value = match row.take("type") {
sqlite::Value::String(value) => value,
value => format!("{value:?}"),
};
log::debug!("Failed to parse address type '{value}' for {node}: {err}");
continue;
}
};
let addr = match row.try_read::<Address, _>("value") {
Ok(addr) => addr,
Err(err) => {
let value = match row.take("type") {
sqlite::Value::String(value) => value,
value => format!("{value:?}"),
};
log::debug!("Failed to parse address '{value}' for {node}: {err}");
continue;
}
};
match addr.address_type() {
None => {
log::debug!("Address {addr} has unknown address type for {node}");
continue;
}
Some(actual_type) if actual_type != address_type => {
log::debug!(
"The address '{addr}' was expected to be of type '{address_type:?}' but was found to be of type '{actual_type:?}'"
);
continue;
}
_ => {}
}
let source = row.try_read::<Source, _>("source")?;
let last_attempt = row
.read::<Option<i64>, _>("last_attempt")
@ -1066,4 +1099,86 @@ mod test {
node::properties::test_reverse_lookup(&db, input)
}
#[test]
fn skip_invalid_address_type() {
let alice = arbitrary::r#gen::<NodeId>(1);
let mut cache = Database::memory().unwrap();
let timestamp = Timestamp::from(LocalTime::now());
let ua = UserAgent::default();
let features = node::Features::SEED;
let good_addr: Address = "[2001:db8::1]:64312".parse().unwrap();
let good_ka = KnownAddress {
addr: good_addr.clone(),
source: Source::Peer,
last_success: None,
last_attempt: None,
banned: false,
};
cache
.insert(
&alice,
1,
features,
&Alias::new("alice"),
0,
&ua,
timestamp,
[good_ka.clone()],
)
.unwrap();
cache
.db
.execute(format!(
"INSERT INTO addresses (node, type, value, source, timestamp)
VALUES ('{alice}', 'invalid-address-type', 'example.com:64312', 'peer', 0)"
))
.unwrap();
let node = cache.get(&alice).unwrap().unwrap();
assert_eq!(node.addrs.len(), 1);
assert_eq!(node.alias, Alias::new("alice"));
assert_eq!(node.agent, ua);
}
#[test]
fn skip_mismatched_address_type() {
let alice = arbitrary::r#gen::<NodeId>(1);
let mut cache = Database::memory().unwrap();
let timestamp = Timestamp::from(LocalTime::now());
let ua = UserAgent::default();
let features = node::Features::SEED;
let good_addr: Address = "[2001:db8::1]:64312".parse().unwrap();
let good_ka = KnownAddress {
addr: good_addr.clone(),
source: Source::Peer,
last_success: None,
last_attempt: None,
banned: false,
};
cache
.insert(
&alice,
1,
features,
&Alias::new("alice"),
0,
&ua,
timestamp,
[good_ka.clone()],
)
.unwrap();
cache
.db
.execute(format!(
"INSERT INTO addresses (node, type, value, source, timestamp)
VALUES ('{alice}', 'ipv4', 'example.com:64312', 'peer', 0)"
))
.unwrap();
let node = cache.get(&alice).unwrap().unwrap();
assert_eq!(node.addrs.len(), 1);
assert_eq!(node.alias, Alias::new("alice"));
assert_eq!(node.agent, ua);
}
}