p2p: remove io::Error from exposed API
What changed, and why it matters
This commit refactors how Bitcoin peer-to-peer address errors are reported. It replaces a generic input/output error type with a new, more specific error type for addresses that cannot be routed over normal internet connections (Tor, I2P, CJDNS). This is a routine API cleanup, not a fix for an active security vulnerability. The change is part of a larger effort to reduce internal package dependencies.
No immediate security action required. Treat as a normal API-breaking change. Downstream users relying on the exact `std::io::Error` returned by `socket_addr()` or on `AddrV2ToIpAddrError` will need to update their code. Review release notes for migration guidance when this change ships.
Security signals we found
API surface reduction: removes a generic I/O error type from public P2P address methods
Error semantics change: Tor V2 onion addresses now produce a dedicated error variant instead of a generic address-not-available error
No input validation, parsing bounds, or cryptographic logic changes observed
No unsafe code, no allocator changes, no network buffer handling changes
Evidence from the diff
The patch removes std::io::Error from the public return types of Address::socket_addr() and AddrV2Message::socket_addr(), and from TryFrom<AddrV2> for IpAddr. It introduces UnroutableAddressError, an enum covering TorV2, TorV3, I2P, Cjdns, and Unknown address types. The ToSocketAddrs implementations still expose std::io::Error because that trait requires it, but they now wrap the new error inside io::ErrorKind::InvalidInput instead of using AddrNotAvailable. The old AddrV2ToIpAddrError enum is left in place (likely for backward compatibility) but is no longer used as the error type for the TryFrom conversion. No memory-safety, cryptographic, or network-handling behavior changes are visible in the diff.
Changed components
rust-bitcoin p2p/src/address.rsAddress::socket_addr()AddrV2Message::socket_addr()TryFrom<AddrV2> for IpAddrToSocketAddrs implementations for Address and AddrV2MessageInspect captured patch +71 / −21
diff --git a/p2p/src/address.rs b/p2p/src/address.rs
index d03ef9dc..945e02d4 100644
--- a/p2p/src/address.rs
+++ b/p2p/src/address.rs
@@ -47,12 +47,14 @@ impl Address {
}
/// Extracts socket address from an [Address] message.
- /// This will return [io::Error] [io::ErrorKind::AddrNotAvailable]
- /// if the message contains a Tor address.
- pub fn socket_addr(&self) -> Result<SocketAddr, io::Error> {
+ ///
+ /// # Errors
+ ///
+ /// Returns an error if the message contains a Tor V2 onion address.
+ pub fn socket_addr(&self) -> Result<SocketAddr, UnroutableAddressError> {
let addr = &self.address;
if addr[0..3] == ONION {
- return Err(io::Error::from(io::ErrorKind::AddrNotAvailable));
+ return Err(UnroutableAddressError::TorV2);
}
let ipv6 =
Ipv6Addr::new(addr[0], addr[1], addr[2], addr[3], addr[4], addr[5], addr[6], addr[7]);
@@ -126,7 +128,9 @@ impl fmt::Debug for Address {
impl ToSocketAddrs for Address {
type Iter = iter::Once<SocketAddr>;
fn to_socket_addrs(&self) -> Result<Self::Iter, std::io::Error> {
- Ok(iter::once(self.socket_addr()?))
+ self.socket_addr()
+ .map(iter::once)
+ .map_err(|e| std::io::Error::new(std::io::ErrorKind::InvalidInput, e))
}
}
@@ -148,16 +152,16 @@ pub enum AddrV2 {
}
impl TryFrom<AddrV2> for IpAddr {
- type Error = AddrV2ToIpAddrError;
+ type Error = UnroutableAddressError;
fn try_from(addr: AddrV2) -> Result<Self, Self::Error> {
match addr {
AddrV2::Ipv4(ip) => Ok(Self::V4(ip)),
AddrV2::Ipv6(ip) => Ok(Self::V6(ip)),
- AddrV2::Cjdns(_) => Err(AddrV2ToIpAddrError::Cjdns),
- AddrV2::TorV3(_) => Err(AddrV2ToIpAddrError::TorV3),
- AddrV2::I2p(_) => Err(AddrV2ToIpAddrError::I2p),
- AddrV2::Unknown(_, _) => Err(AddrV2ToIpAddrError::Unknown),
+ AddrV2::Cjdns(_) => Err(UnroutableAddressError::Cjdns),
+ AddrV2::TorV3(_) => Err(UnroutableAddressError::TorV3),
+ AddrV2::I2p(_) => Err(UnroutableAddressError::I2p),
+ AddrV2::Unknown(_, _) => Err(UnroutableAddressError::Unknown),
}
}
}
@@ -202,11 +206,15 @@ impl From<IpAddr> for AddrV2 {
}
impl From<Ipv4Addr> for AddrV2 {
- fn from(addr: Ipv4Addr) -> Self { Self::Ipv4(addr) }
+ fn from(addr: Ipv4Addr) -> Self {
+ Self::Ipv4(addr)
+ }
}
impl From<Ipv6Addr> for AddrV2 {
- fn from(addr: Ipv6Addr) -> Self { Self::Ipv6(addr) }
+ fn from(addr: Ipv6Addr) -> Self {
+ Self::Ipv6(addr)
+ }
}
impl Encodable for AddrV2 {
@@ -317,13 +325,19 @@ pub struct AddrV2Message {
impl AddrV2Message {
/// Extracts socket address from an [AddrV2Message] message.
- /// This will return [io::Error] [io::ErrorKind::AddrNotAvailable]
- /// if the address type can't be converted into a [SocketAddr].
- pub fn socket_addr(&self) -> Result<SocketAddr, io::Error> {
+ ///
+ /// # Errors
+ ///
+ /// Returns an error if the address type cannot be converted to a socket address
+ /// (e.g. Tor, I2P, CJDNS addresses).
+ pub fn socket_addr(&self) -> Result<SocketAddr, UnroutableAddressError> {
match self.addr {
AddrV2::Ipv4(addr) => Ok(SocketAddr::V4(SocketAddrV4::new(addr, self.port))),
AddrV2::Ipv6(addr) => Ok(SocketAddr::V6(SocketAddrV6::new(addr, self.port, 0, 0))),
- _ => Err(io::Error::from(io::ErrorKind::AddrNotAvailable)),
+ AddrV2::TorV3(_) => Err(UnroutableAddressError::TorV3),
+ AddrV2::I2p(_) => Err(UnroutableAddressError::I2p),
+ AddrV2::Cjdns(_) => Err(UnroutableAddressError::Cjdns),
+ AddrV2::Unknown(_, _) => Err(UnroutableAddressError::Unknown),
}
}
}
@@ -356,10 +370,46 @@ impl Decodable for AddrV2Message {
impl ToSocketAddrs for AddrV2Message {
type Iter = iter::Once<SocketAddr>;
fn to_socket_addrs(&self) -> Result<Self::Iter, std::io::Error> {
- Ok(iter::once(self.socket_addr()?))
+ self.socket_addr()
+ .map(iter::once)
+ .map_err(|e| std::io::Error::new(std::io::ErrorKind::InvalidInput, e))
}
}
+/// Error returned when an address cannot be converted to an IP-based address.
+///
+/// Addresses like Tor, I2P, and CJDNS use different routing mechanisms
+/// and cannot be represented as standard IP addresses or socket addresses.
+#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
+#[non_exhaustive]
+pub enum UnroutableAddressError {
+ /// Tor V2 onion address.
+ TorV2,
+ /// Tor V3 onion address.
+ TorV3,
+ /// I2P address.
+ I2p,
+ /// CJDNS address.
+ Cjdns,
+ /// Unknown address type.
+ Unknown,
+}
+
+impl fmt::Display for UnroutableAddressError {
+ fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
+ match self {
+ Self::TorV2 => write!(f, "Tor v2 addresses cannot be converted to IP addresses"),
+ Self::TorV3 => write!(f, "Tor v3 addresses cannot be converted to IP addresses"),
+ Self::I2p => write!(f, "I2P addresses cannot be converted to IP addresses"),
+ Self::Cjdns => write!(f, "CJDNS addresses cannot be converted to IP addresses"),
+ Self::Unknown => write!(f, "unknown address type cannot be converted to IP addresses"),
+ }
+ }
+}
+
+#[cfg(feature = "std")]
+impl std::error::Error for UnroutableAddressError {}
+
/// Error types for [`AddrV2`] to [`IpAddr`] conversion.
#[derive(Debug, PartialEq, Eq)]
pub enum AddrV2ToIpAddrError {
@@ -794,7 +844,7 @@ mod test {
let result = IpAddr::try_from(addr);
assert!(result.is_err());
- assert_eq!(result.unwrap_err(), AddrV2ToIpAddrError::Cjdns);
+ assert_eq!(result.unwrap_err(), UnroutableAddressError::Cjdns);
}
#[test]
@@ -803,7 +853,7 @@ mod test {
let result = IpAddr::try_from(addr);
assert!(result.is_err());
- assert_eq!(result.unwrap_err(), AddrV2ToIpAddrError::TorV3);
+ assert_eq!(result.unwrap_err(), UnroutableAddressError::TorV3);
}
#[test]
@@ -812,7 +862,7 @@ mod test {
let result = IpAddr::try_from(addr);
assert!(result.is_err());
- assert_eq!(result.unwrap_err(), AddrV2ToIpAddrError::I2p);
+ assert_eq!(result.unwrap_err(), UnroutableAddressError::I2p);
}
#[test]
@@ -821,7 +871,7 @@ mod test {
let result = IpAddr::try_from(addr);
assert!(result.is_err());
- assert_eq!(result.unwrap_err(), AddrV2ToIpAddrError::Unknown);
+ assert_eq!(result.unwrap_err(), UnroutableAddressError::Unknown);
}
#[test]
Why this scored 19/100
Community notes
Notes can correct, qualify, or add evidence to the AI analysis. Every note shown here has been validated by a human moderator.
The AI analysis stands alone for now. Submit a note if you can add evidence or important context.