node: Parse IPv6 addresses in square brackets

This commit is contained in:
Defelo 2026-02-24 17:22:58 +01:00 committed by Fintan Halpenny
parent 7c92360842
commit df8e4e6c88
2 changed files with 51 additions and 2 deletions

View File

@ -36,6 +36,12 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
This would result in timeouts when commands are run from the `rad` CLI.
The `Service` has now learned to return results when an error occurs which
will be reported back to the user.
- Parsing addresses involving an IPv6 host failed if they were enclosed in
square brackets, e.g. in `rad node connect z6Mk...@[::1]:8776`.
Also, ambiguous addresses would parse, e.g. `::1:8776` would be
indistinguishable from `[::1]:8776`. Since a port number is always required
along a host when providing an address, IPv6 addresses now always require
brackets to avoid confusion.
## Deprecations

View File

@ -19,6 +19,8 @@ pub mod timestamp;
use std::collections::{BTreeMap, BTreeSet, HashMap, HashSet, VecDeque};
use std::io::{BufRead, BufReader};
use std::marker::PhantomData;
use std::net::IpAddr;
use std::net::Ipv6Addr;
use std::ops::{ControlFlow, Deref};
use std::path::{Path, PathBuf};
use std::str::FromStr;
@ -30,7 +32,7 @@ use std::os::unix::net::UnixStream;
use uds_windows::UnixStream;
use amplify::WrapperMut;
use cyphernet::addr::NetAddr;
use cyphernet::addr::{AddrParseError, NetAddr};
use localtime::{LocalDuration, LocalTime};
use serde::de::DeserializeOwned;
use serde::{Deserialize, Serialize};
@ -422,7 +424,7 @@ impl TryFrom<&sqlite::Value> for Alias {
/// Peer public protocol address.
#[derive(Clone, Eq, PartialEq, Debug, Hash, From, Wrapper, WrapperMut, Serialize, Deserialize)]
#[wrapper(Deref, Display, FromStr)]
#[wrapper(Deref, Display)]
#[wrapper_mut(DerefMut)]
#[cfg_attr(
feature = "schemars",
@ -490,6 +492,31 @@ impl Address {
}
}
impl FromStr for Address {
type Err = AddrParseError;
fn from_str(s: &str) -> Result<Self, Self::Err> {
let (host, port) = s.rsplit_once(':').ok_or(AddrParseError::PortAbsent)?;
let host = if let Some(host) = host
.strip_prefix('[')
.and_then(|host| host.strip_suffix(']'))
{
HostName::Ip(host.parse::<Ipv6Addr>()?.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),
})?
};
let port = port.parse().map_err(|_| AddrParseError::InvalidPort)?;
Ok(Self(NetAddr::new(host, port)))
}
}
impl cyphernet::addr::Host for Address {
fn requires_proxy(&self) -> bool {
self.0.requires_proxy()
@ -1448,6 +1475,22 @@ mod test {
assert!(Alias::from_str("cloudhead\n").is_err());
}
#[test]
fn test_address() {
assert!(Address::from_str("127.0.0.1:8776").is_ok());
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("").is_err());
assert!(Address::from_str(":").is_err());
assert!(Address::from_str("127.0.0.1").is_err());
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]
fn test_command_result() {
#[derive(Debug, PartialEq, Eq, Serialize, Deserialize)]