Radish alpha
h
rad:z3gqcJUoA1n9HaHKufZs5FCSGazv5
Radicle Heartwood Protocol & Stack
Radicle
Git
node: Parse IPv6 addresses in square brackets
Defelo committed 1 month ago
commit df8e4e6c88a8bfb6c1ec6b07dcda64093b477cbe
parent 7c92360
2 files changed +51 -2
modified CHANGELOG.md
@@ -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

modified crates/radicle/src/node.rs
@@ -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()
@@ -1449,6 +1476,22 @@ mod test {
    }

    #[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)]
        struct Test {