From d01ff2e79d7159fd96b5b580b59224562d66d840 Mon Sep 17 00:00:00 2001 From: Fintan Halpenny Date: Thu, 19 Mar 2026 09:38:12 +0000 Subject: [PATCH] radicle/node: Do not error on IPv6 without `[]` IPv6 addresses are already persisted in `config.json` files. The parsing of these old addresses, e.g. `FE80::0202:B3FF:FE1E:8329:5976` should remain valid. Emit a warning when they are found. The output of the address will still enclose the host in `[]`. --- CHANGELOG.md | 10 ++++++++++ crates/radicle/src/node.rs | 13 ++++++++----- crates/radicle/src/node/config.rs | 30 +++++++++++++++++++++++++++++- 3 files changed, 47 insertions(+), 6 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 35b68c60..10c6ef35 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,16 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [Unreleased] +## Fixed Bugs + +- The fix to ambiguous IPv6 addresses, e.g. `::1:8776` vs. `[::1]:8776`, + resulted in backward incompatibility. Configuration files containing addresses + in the ambiguous format could not be parsed anymore. Partially undo this change + to stay backward compatible, but log a warning in case ambiguous addresses are + encountered. + +## 1.7.0 + ## Release Highlights - The "Signed References" feature was reimplemented. The commits in diff --git a/crates/radicle/src/node.rs b/crates/radicle/src/node.rs index dce92dd9..90627ac8 100644 --- a/crates/radicle/src/node.rs +++ b/crates/radicle/src/node.rs @@ -548,10 +548,13 @@ impl FromStr for Address { { HostName::Ip(host.parse::()?.into()) } else { - // Require IPv6 addresses to always be enclosed in `[` and `]`. - host.parse().and_then(|host| match host { - HostName::Ip(IpAddr::V6(_)) => Err(AddrParseError::UnknownAddressFormat), - host => Ok(host), + // Warn on IPv6 addresses that are not enclosed in `[` and `]`. + host.parse().map(|host| match host { + HostName::Ip(IpAddr::V6(addr)) => { + log::warn!("Address format will change in the future. '{s}' should be changed to '[{addr}]:{port}' to stay compatible. Refer to RFC 5926, Sec. 6 as well as RFC 3986, Sec. D.1. and RFC 2732, Sec. 2."); + host + }, + host => host, })? }; @@ -1525,6 +1528,7 @@ mod test { assert!(Address::from_str("[::1]:8776").is_ok()); assert!(Address::from_str("[::ffff:127.0.0.1]:8776").is_ok()); assert!(Address::from_str("localhost:8776").is_ok()); + assert!(Address::from_str("::1:8776").is_ok()); // Backwards-compatibility assert!(Address::from_str("").is_err()); assert!(Address::from_str(":").is_err()); @@ -1532,7 +1536,6 @@ mod test { assert!(Address::from_str("127.0.0.1:xyz").is_err()); assert!(Address::from_str("[invalid]:8776").is_err()); assert!(Address::from_str("[127.0.0.1]:8776").is_err()); - assert!(Address::from_str("::1:8776").is_err()); } #[test] diff --git a/crates/radicle/src/node/config.rs b/crates/radicle/src/node/config.rs index 373e2c4a..b9815e07 100644 --- a/crates/radicle/src/node/config.rs +++ b/crates/radicle/src/node/config.rs @@ -712,7 +712,7 @@ wrapper!( #[allow(clippy::unwrap_used)] mod test { use super::{DefaultSeedingPolicy, Scope}; - use crate::node::policy; + use crate::node::{policy, Alias}; use serde_json::json; #[test] @@ -833,4 +833,32 @@ mod test { .unwrap() ); } + + #[test] + fn regression_ipv6_address_brackets() { + let address = "[2001:db8::1]:5976".to_string(); + let config = json!({ + "alias": "radicle", + "externalAddresses": [address], + }); + let got: super::Config = serde_json::from_value(config).unwrap(); + let mut expected = super::Config::new(Alias::new("radicle")); + expected.external_addresses = vec![address.parse().unwrap()]; + assert_eq!(got.alias, expected.alias); + assert_eq!(got.external_addresses, expected.external_addresses); + } + + #[test] + fn regression_ipv6_address_no_brackets() { + let address = "2001:db8::1:5976".to_string(); + let config = json!({ + "alias": "radicle", + "externalAddresses": [address], + }); + let got: super::Config = serde_json::from_value(config).unwrap(); + let mut expected = super::Config::new(Alias::new("radicle")); + expected.external_addresses = vec![address.parse().unwrap()]; + assert_eq!(got.alias, expected.alias); + assert_eq!(got.external_addresses, expected.external_addresses); + } }