Merge rust-bitcoin/rust-bitcoin#4526: feat(p2p): add AddrV2 <> IpAddr conversions

3c920d1acb fix(p2p): Remove `SocketAddr::V6` <> `AddrV2::Cjdns` conversions (Erick Cestari)
2c236ae24f fuzz: Add p2p address round-trip fuzzing test (Erick Cestari)
7ad89df392 test(p2p): add tests for `AddrV2` <> `IpAddr` conversions (Erick Cestari)
e8a9a89e25 feat(p2p): add `AddrV2` <> `IpAddr` conversions (Erick Cestari)

Pull request description:

  This PR adds conversion traits between `AddrV2` and standard IP types, inspired by #4519.

  ### Changelog

  - Implement `From<IpAddr>` for `AddrV2`.
  -  Implement `From<Ipv4Addr>` for `AddrV2`.
  -  Implement `From<Ipv6Addr>` for `AddrV2.`
  - Implement `TryFrom<AddrV2>` for `IpAddr`.
  - Implement `TryFrom<AddrV2>` for `Ipv4Addr`.
  - Implement `TryFrom<AddrV2>` for `Ipv6Addr`.
  - Implement `AddrV2ToIpAddrError` enum and it's `fmt::Display`.
  - Implement `AddrV2ToIpv4AddrError` enum and it's `fmt::Display`.
  - Implement `AddrV2ToIpv6AddrError` enum and it's `fmt::Display`.
  - Renamed `AddrV2ConversionError` to `AddrV2ToSocketAddrError`
  - Tests for `TryFrom` conversions.

ACKs for top commit:
  apoelstra:
    ACK 3c920d1acb23d84976c0b8e632d8f1068ebbd1fd; successfully ran local tests
  tcharding:
    ACK 3c920d1acb

Tree-SHA512: 6bc8007252ac78fba8dff5de59bbd4134f0dc1f801cdf23676b9b1ea256558cb7ddca81f932cccba7fc34e70d204184f0f9e5f18e4485bd922196fb5f78fd588
This commit is contained in:
merge-script 2025-05-23 14:56:21 +00:00
commit 2f2c9144ba
No known key found for this signature in database
GPG Key ID: C588D63CE41B97C1
4 changed files with 409 additions and 36 deletions

View File

@ -27,6 +27,7 @@ jobs:
bitcoin_deserialize_witness,
bitcoin_deser_net_msg,
bitcoin_outpoint_string,
bitcoin_p2p_address_roundtrip,
bitcoin_script_bytes_to_asm_fmt,
hashes_json,
hashes_ripemd160,

View File

@ -6,7 +6,7 @@
//! network addresses in Bitcoin messages.
use core::{fmt, iter};
use std::net::{Ipv4Addr, Ipv6Addr, SocketAddr, SocketAddrV4, SocketAddrV6, ToSocketAddrs};
use std::net::{IpAddr, Ipv4Addr, Ipv6Addr, SocketAddr, SocketAddrV4, SocketAddrV6, ToSocketAddrs};
use io::{BufRead, Read, Write};
@ -140,7 +140,7 @@ pub enum AddrV2 {
/// Error types for [`AddrV2`] to [`SocketAddr`] conversion.
#[derive(Debug, PartialEq, Eq)]
pub enum AddrV2ConversionError {
pub enum AddrV2ToSocketAddrError {
/// A [`AddrV2::TorV3`] address cannot be converted to a [`SocketAddr`].
TorV3NotSupported,
/// A [`AddrV2::I2p`] address cannot be converted to a [`SocketAddr`].
@ -152,7 +152,7 @@ pub enum AddrV2ConversionError {
UnknownNotSupported,
}
impl fmt::Display for AddrV2ConversionError {
impl fmt::Display for AddrV2ToSocketAddrError {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
Self::TorV3NotSupported => write!(f, "TorV3 addresses cannot be converted to SocketAddr"),
@ -163,41 +163,94 @@ impl fmt::Display for AddrV2ConversionError {
}
}
impl std::error::Error for AddrV2ConversionError {}
impl std::error::Error for AddrV2ToSocketAddrError {}
impl From<SocketAddr> for AddrV2 {
fn from(addr: SocketAddr) -> Self {
match addr {
SocketAddr::V4(sock) => AddrV2::Ipv4(*sock.ip()),
SocketAddr::V6(sock) => {
// CJDNS uses the IPv6 network `fc00::/8`
// All CJDNS addresses must have `0xfc00` as the first and second octets
let ip = *sock.ip();
if ip.octets()[0] == 0xfc && ip.octets()[1] == 0x00 {
AddrV2::Cjdns(ip)
} else {
AddrV2::Ipv6(ip)
}
}
SocketAddr::V6(sock) => AddrV2::Ipv6(*sock.ip()),
}
}
}
impl TryFrom<AddrV2> for SocketAddr {
type Error = AddrV2ConversionError;
type Error = AddrV2ToSocketAddrError;
fn try_from(addr: AddrV2) -> Result<SocketAddr, Self::Error> {
match addr {
AddrV2::Ipv4(ip) => Ok(SocketAddr::V4(SocketAddrV4::new(ip, 0))),
AddrV2::Ipv6(ip) => Ok(SocketAddr::V6(SocketAddrV6::new(ip, 0, 0, 0))),
AddrV2::Cjdns(_) => Err(AddrV2ConversionError::CjdnsNotRecommended),
AddrV2::TorV3(_) => Err(AddrV2ConversionError::TorV3NotSupported),
AddrV2::I2p(_) => Err(AddrV2ConversionError::I2pNotSupported),
AddrV2::Unknown(_, _) => Err(AddrV2ConversionError::UnknownNotSupported),
AddrV2::Cjdns(_) => Err(AddrV2ToSocketAddrError::CjdnsNotRecommended),
AddrV2::TorV3(_) => Err(AddrV2ToSocketAddrError::TorV3NotSupported),
AddrV2::I2p(_) => Err(AddrV2ToSocketAddrError::I2pNotSupported),
AddrV2::Unknown(_, _) => Err(AddrV2ToSocketAddrError::UnknownNotSupported),
}
}
}
impl TryFrom<AddrV2> for IpAddr {
type Error = AddrV2ToIpAddrError;
fn try_from(addr: AddrV2) -> Result<IpAddr, Self::Error> {
match addr {
AddrV2::Ipv4(ip) => Ok(IpAddr::V4(ip)),
AddrV2::Ipv6(ip) => Ok(IpAddr::V6(ip)),
AddrV2::Cjdns(_) => Err(AddrV2ToIpAddrError::Cjdns),
AddrV2::TorV3(_) => Err(AddrV2ToIpAddrError::TorV3),
AddrV2::I2p(_) => Err(AddrV2ToIpAddrError::I2p),
AddrV2::Unknown(_, _) => Err(AddrV2ToIpAddrError::Unknown),
}
}
}
impl TryFrom<AddrV2> for Ipv4Addr {
type Error = AddrV2ToIpv4AddrError;
fn try_from(addr: AddrV2) -> Result<Ipv4Addr, Self::Error> {
match addr {
AddrV2::Ipv4(ip) => Ok(ip),
AddrV2::Ipv6(_) => Err(AddrV2ToIpv4AddrError::Ipv6),
AddrV2::Cjdns(_) => Err(AddrV2ToIpv4AddrError::Cjdns),
AddrV2::TorV3(_) => Err(AddrV2ToIpv4AddrError::TorV3),
AddrV2::I2p(_) => Err(AddrV2ToIpv4AddrError::I2p),
AddrV2::Unknown(_, _) => Err(AddrV2ToIpv4AddrError::Unknown),
}
}
}
impl TryFrom<AddrV2> for Ipv6Addr {
type Error = AddrV2ToIpv6AddrError;
fn try_from(addr: AddrV2) -> Result<Ipv6Addr, Self::Error> {
match addr {
AddrV2::Ipv6(ip) => Ok(ip),
AddrV2::Cjdns(_) => Err(AddrV2ToIpv6AddrError::Cjdns),
AddrV2::Ipv4(_) => Err(AddrV2ToIpv6AddrError::Ipv4),
AddrV2::TorV3(_) => Err(AddrV2ToIpv6AddrError::TorV3),
AddrV2::I2p(_) => Err(AddrV2ToIpv6AddrError::I2p),
AddrV2::Unknown(_, _) => Err(AddrV2ToIpv6AddrError::Unknown),
}
}
}
impl From<IpAddr> for AddrV2 {
fn from(addr: IpAddr) -> Self {
match addr {
IpAddr::V4(ip) => AddrV2::Ipv4(ip),
IpAddr::V6(ip) => AddrV2::Ipv6(ip),
}
}
}
impl From<Ipv4Addr> for AddrV2 {
fn from(addr: Ipv4Addr) -> Self { AddrV2::Ipv4(addr) }
}
impl From<Ipv6Addr> for AddrV2 {
fn from(addr: Ipv6Addr) -> Self { AddrV2::Ipv6(addr) }
}
impl Encodable for AddrV2 {
fn consensus_encode<W: Write + ?Sized>(&self, w: &mut W) -> Result<usize, io::Error> {
fn encode_addr<W: Write + ?Sized>(
@ -348,6 +401,90 @@ impl ToSocketAddrs for AddrV2Message {
}
}
/// Error types for [`AddrV2`] to [`IpAddr`] conversion.
#[derive(Debug, PartialEq, Eq)]
pub enum AddrV2ToIpAddrError {
/// A [`AddrV2::TorV3`] address cannot be converted to a [`IpAddr`].
TorV3,
/// A [`AddrV2::I2p`] address cannot be converted to a [`IpAddr`].
I2p,
/// A [`AddrV2::Cjdns`] address cannot be converted to a [`IpAddr`],
Cjdns,
/// A [`AddrV2::Unknown`] address cannot be converted to a [`IpAddr`].
Unknown,
}
impl fmt::Display for AddrV2ToIpAddrError {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
Self::TorV3 => write!(f, "TorV3 addresses cannot be converted to IpAddr"),
Self::I2p => write!(f, "I2P addresses cannot be converted to IpAddr"),
Self::Cjdns => write!(f, "Cjdns addresses cannot be converted to IpAddr"),
Self::Unknown => write!(f, "Unknown address type cannot be converted to IpAddr"),
}
}
}
impl std::error::Error for AddrV2ToIpAddrError {}
/// Error types for [`AddrV2`] to [`Ipv4Addr`] conversion.
#[derive(Debug, PartialEq, Eq)]
pub enum AddrV2ToIpv4AddrError {
/// A [`AddrV2::Ipv6`] address cannot be converted to a [`Ipv4Addr`].
Ipv6,
/// A [`AddrV2::TorV3`] address cannot be converted to a [`Ipv4Addr`].
TorV3,
/// A [`AddrV2::I2p`] address cannot be converted to a [`Ipv4Addr`].
I2p,
/// A [`AddrV2::Cjdns`] address cannot be converted to a [`Ipv4Addr`],
Cjdns,
/// A [`AddrV2::Unknown`] address cannot be converted to a [`Ipv4Addr`].
Unknown,
}
impl fmt::Display for AddrV2ToIpv4AddrError {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
Self::Ipv6 => write!(f, "Ipv6 addresses cannot be converted to Ipv4Addr"),
Self::TorV3 => write!(f, "TorV3 addresses cannot be converted to Ipv4Addr"),
Self::I2p => write!(f, "I2P addresses cannot be converted to Ipv4Addr"),
Self::Cjdns => write!(f, "Cjdns addresses cannot be converted to Ipv4Addr"),
Self::Unknown => write!(f, "Unknown address type cannot be converted to Ipv4Addr"),
}
}
}
impl std::error::Error for AddrV2ToIpv4AddrError {}
/// Error types for [`AddrV2`] to [`Ipv6Addr`] conversion.
#[derive(Debug, PartialEq, Eq)]
pub enum AddrV2ToIpv6AddrError {
/// A [`AddrV2::Ipv4`] address cannot be converted to a [`Ipv6Addr`].
Ipv4,
/// A [`AddrV2::TorV3`] address cannot be converted to a [`Ipv6Addr`].
TorV3,
/// A [`AddrV2::I2p`] address cannot be converted to a [`Ipv6Addr`].
I2p,
/// A [`AddrV2::Cjdns`] address cannot be converted to a [`Ipv6Addr`],
Cjdns,
/// A [`AddrV2::Unknown`] address cannot be converted to a [`Ipv6Addr`].
Unknown,
}
impl fmt::Display for AddrV2ToIpv6AddrError {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
Self::Ipv4 => write!(f, "Ipv addresses cannot be converted to Ipv6Addr"),
Self::TorV3 => write!(f, "TorV3 addresses cannot be converted to Ipv6Addr"),
Self::I2p => write!(f, "I2P addresses cannot be converted to Ipv6Addr"),
Self::Cjdns => write!(f, "Cjdns addresses cannot be converted to Ipv6Addr"),
Self::Unknown => write!(f, "Unknown address type cannot be converted to Ipv6Addr"),
}
}
}
impl std::error::Error for AddrV2ToIpv6AddrError {}
#[cfg(test)]
mod test {
use std::net::IpAddr;
@ -615,19 +752,6 @@ mod test {
assert_eq!(addr, AddrV2::Ipv6(Ipv6Addr::new(0x2001, 0xdb8, 0, 0, 0, 0, 0, 1)));
}
#[test]
fn socketaddr_to_addrv2_cjdns() {
let socket = SocketAddr::V6(SocketAddrV6::new(
Ipv6Addr::new(0xfc00, 0, 0, 0, 0, 0, 0, 1),
8333,
0,
0,
));
let addr = AddrV2::from(socket);
assert_eq!(addr, AddrV2::Cjdns(Ipv6Addr::new(0xfc00, 0, 0, 0, 0, 0, 0, 1)));
}
#[test]
fn addrv2_to_socketaddr_ipv4() {
let addr = AddrV2::Ipv4(Ipv4Addr::new(192, 168, 1, 1));
@ -658,7 +782,7 @@ mod test {
let result = SocketAddr::try_from(addr);
assert!(result.is_err());
assert_eq!(result.unwrap_err(), AddrV2ConversionError::CjdnsNotRecommended);
assert_eq!(result.unwrap_err(), AddrV2ToSocketAddrError::CjdnsNotRecommended);
}
#[test]
@ -667,7 +791,7 @@ mod test {
let result = SocketAddr::try_from(addr);
assert!(result.is_err());
assert_eq!(result.unwrap_err(), AddrV2ConversionError::TorV3NotSupported);
assert_eq!(result.unwrap_err(), AddrV2ToSocketAddrError::TorV3NotSupported);
}
#[test]
@ -676,7 +800,7 @@ mod test {
let result = SocketAddr::try_from(addr);
assert!(result.is_err());
assert_eq!(result.unwrap_err(), AddrV2ConversionError::I2pNotSupported);
assert_eq!(result.unwrap_err(), AddrV2ToSocketAddrError::I2pNotSupported);
}
#[test]
@ -685,6 +809,164 @@ mod test {
let result = SocketAddr::try_from(addr);
assert!(result.is_err());
assert_eq!(result.unwrap_err(), AddrV2ConversionError::UnknownNotSupported);
assert_eq!(result.unwrap_err(), AddrV2ToSocketAddrError::UnknownNotSupported);
}
#[test]
fn addrv2_to_ipaddr_ipv4() {
let addr = AddrV2::Ipv4(Ipv4Addr::new(192, 168, 1, 1));
let ip_addr = IpAddr::try_from(addr).unwrap();
assert_eq!(ip_addr, IpAddr::V4(Ipv4Addr::new(192, 168, 1, 1)));
}
#[test]
fn addrv2_to_ipaddr_ipv6() {
let addr = AddrV2::Ipv6(Ipv6Addr::new(0x2001, 0xdb8, 0, 0, 0, 0, 0, 1));
let ip_addr = IpAddr::try_from(addr).unwrap();
assert_eq!(ip_addr, IpAddr::V6(Ipv6Addr::new(0x2001, 0xdb8, 0, 0, 0, 0, 0, 1)));
}
#[test]
fn addrv2_to_ipaddr_cjdns() {
let addr = AddrV2::Cjdns(Ipv6Addr::new(0xfc00, 0, 0, 0, 0, 0, 0, 1));
let result = IpAddr::try_from(addr);
assert!(result.is_err());
assert_eq!(result.unwrap_err(), AddrV2ToIpAddrError::Cjdns);
}
#[test]
fn addrv2_to_ipaddr_torv3() {
let addr = AddrV2::TorV3([0; 32]);
let result = IpAddr::try_from(addr);
assert!(result.is_err());
assert_eq!(result.unwrap_err(), AddrV2ToIpAddrError::TorV3);
}
#[test]
fn addrv2_to_ipaddr_i2p() {
let addr = AddrV2::I2p([0; 32]);
let result = IpAddr::try_from(addr);
assert!(result.is_err());
assert_eq!(result.unwrap_err(), AddrV2ToIpAddrError::I2p);
}
#[test]
fn addrv2_to_ipaddr_unknown() {
let addr = AddrV2::Unknown(42, vec![1, 2, 3, 4]);
let result = IpAddr::try_from(addr);
assert!(result.is_err());
assert_eq!(result.unwrap_err(), AddrV2ToIpAddrError::Unknown);
}
#[test]
fn addrv2_to_ipv4addr_ipv4() {
let addr = AddrV2::Ipv4(Ipv4Addr::new(192, 168, 1, 1));
let ip_addr = Ipv4Addr::try_from(addr).unwrap();
assert_eq!(ip_addr, Ipv4Addr::new(192, 168, 1, 1));
}
#[test]
fn addrv2_to_ipv4addr_ipv6() {
let addr = AddrV2::Ipv6(Ipv6Addr::new(0x2001, 0xdb8, 0, 0, 0, 0, 0, 1));
let result = Ipv4Addr::try_from(addr);
assert!(result.is_err());
assert_eq!(result.unwrap_err(), AddrV2ToIpv4AddrError::Ipv6);
}
#[test]
fn addrv2_to_ipv4addr_cjdns() {
let addr = AddrV2::Cjdns(Ipv6Addr::new(0xfc00, 0, 0, 0, 0, 0, 0, 1));
let result = Ipv4Addr::try_from(addr);
assert!(result.is_err());
assert_eq!(result.unwrap_err(), AddrV2ToIpv4AddrError::Cjdns);
}
#[test]
fn addrv2_to_ipv4addr_torv3() {
let addr = AddrV2::TorV3([0; 32]);
let result = Ipv4Addr::try_from(addr);
assert!(result.is_err());
assert_eq!(result.unwrap_err(), AddrV2ToIpv4AddrError::TorV3);
}
#[test]
fn addrv2_to_ipv4addr_i2p() {
let addr = AddrV2::I2p([0; 32]);
let result = Ipv4Addr::try_from(addr);
assert!(result.is_err());
assert_eq!(result.unwrap_err(), AddrV2ToIpv4AddrError::I2p);
}
#[test]
fn addrv2_to_ipv4addr_unknown() {
let addr = AddrV2::Unknown(42, vec![1, 2, 3, 4]);
let result = Ipv4Addr::try_from(addr);
assert!(result.is_err());
assert_eq!(result.unwrap_err(), AddrV2ToIpv4AddrError::Unknown);
}
#[test]
fn addrv2_to_ipv6addr_ipv4() {
let addr = AddrV2::Ipv4(Ipv4Addr::new(192, 168, 1, 1));
let result = Ipv6Addr::try_from(addr);
assert!(result.is_err());
assert_eq!(result.unwrap_err(), AddrV2ToIpv6AddrError::Ipv4);
}
#[test]
fn addrv2_to_ipv6addr_ipv6() {
let addr = AddrV2::Ipv6(Ipv6Addr::new(0x2001, 0xdb8, 0, 0, 0, 0, 0, 1));
let ip_addr = Ipv6Addr::try_from(addr).unwrap();
assert_eq!(ip_addr, Ipv6Addr::new(0x2001, 0xdb8, 0, 0, 0, 0, 0, 1));
}
#[test]
fn addrv2_to_ipv6addr_cjdns() {
let addr = AddrV2::Cjdns(Ipv6Addr::new(0xfc00, 0, 0, 0, 0, 0, 0, 1));
let result = Ipv6Addr::try_from(addr);
assert!(result.is_err());
assert_eq!(result.unwrap_err(), AddrV2ToIpv6AddrError::Cjdns);
}
#[test]
fn addrv2_to_ipv6addr_torv3() {
let addr = AddrV2::TorV3([0; 32]);
let result = Ipv6Addr::try_from(addr);
assert!(result.is_err());
assert_eq!(result.unwrap_err(), AddrV2ToIpv6AddrError::TorV3);
}
#[test]
fn addrv2_to_ipv6addr_i2p() {
let addr = AddrV2::I2p([0; 32]);
let result = Ipv6Addr::try_from(addr);
assert!(result.is_err());
assert_eq!(result.unwrap_err(), AddrV2ToIpv6AddrError::I2p);
}
#[test]
fn addrv2_to_ipv6addr_unknown() {
let addr = AddrV2::Unknown(42, vec![1, 2, 3, 4]);
let result = Ipv6Addr::try_from(addr);
assert!(result.is_err());
assert_eq!(result.unwrap_err(), AddrV2ToIpv6AddrError::Unknown);
}
}

View File

@ -85,3 +85,7 @@ path = "fuzz_targets/hashes/sha512.rs"
[[bin]]
name = "units_deserialize_amount"
path = "fuzz_targets/units/deserialize_amount.rs"
[[bin]]
name = "bitcoin_p2p_address_roundtrip"
path = "fuzz_targets/bitcoin/p2p_address_roundtrip.rs"

View File

@ -0,0 +1,86 @@
use std::convert::TryFrom;
use std::net::{IpAddr, Ipv4Addr, Ipv6Addr, SocketAddr};
use bitcoin::consensus::Decodable;
use bitcoin::p2p::address::AddrV2;
use honggfuzz::fuzz;
fn do_test(data: &[u8]) {
if data.len() < 2 {
return;
}
let mut cursor = std::io::Cursor::new(data);
let addr_v2 = if let Ok(addr) = AddrV2::consensus_decode(&mut cursor) {
addr
} else {
return;
};
if let Ok(ip_addr) = IpAddr::try_from(addr_v2.clone()) {
let round_trip: AddrV2 = AddrV2::from(ip_addr);
assert_eq!(
addr_v2, round_trip,
"AddrV2 -> IpAddr -> AddrV2 should round-trip correctly"
);
}
if let Ok(ip_addr) = Ipv4Addr::try_from(addr_v2.clone()) {
let round_trip: AddrV2 = AddrV2::from(ip_addr);
assert_eq!(
addr_v2, round_trip,
"AddrV2 -> Ipv4Addr -> AddrV2 should round-trip correctly"
);
}
if let Ok(ip_addr) = Ipv6Addr::try_from(addr_v2.clone()) {
let round_trip: AddrV2 = AddrV2::from(ip_addr);
assert_eq!(
addr_v2, round_trip,
"AddrV2 -> Ipv6Addr -> AddrV2 should round-trip correctly"
);
}
if let Ok(socket_addr) = SocketAddr::try_from(addr_v2.clone()) {
let round_trip: AddrV2 = AddrV2::from(socket_addr);
assert_eq!(
addr_v2, round_trip,
"AddrV2 -> SocketAddr -> AddrV2 should round-trip correctly"
);
}
}
fn main() {
loop {
fuzz!(|data| {
do_test(data);
});
}
}
#[cfg(all(test, fuzzing))]
mod tests {
fn extend_vec_from_hex(hex: &str, out: &mut Vec<u8>) {
let mut b = 0;
for (idx, c) in hex.as_bytes().iter().enumerate() {
b <<= 4;
match *c {
b'A'..=b'F' => b |= c - b'A' + 10,
b'a'..=b'f' => b |= c - b'a' + 10,
b'0'..=b'9' => b |= c - b'0',
_ => panic!("Bad hex"),
}
if (idx & 1) == 1 {
out.push(b);
b = 0;
}
}
}
#[test]
fn duplicate_crash() {
let mut a = Vec::new();
extend_vec_from_hex("00", &mut a);
super::do_test(&a);
}
}