From e5ab7e96978bcfd9645f3ad17adae50b8a90b353 Mon Sep 17 00:00:00 2001 From: Jude Nelson Date: Mon, 2 Oct 2023 15:45:18 -0400 Subject: [PATCH] refactor: put PeerHost and PeerAddress into stacks-common --- stacks-common/src/types/net.rs | 389 +++++++++++++++++++++++++++++++++ 1 file changed, 389 insertions(+) create mode 100644 stacks-common/src/types/net.rs diff --git a/stacks-common/src/types/net.rs b/stacks-common/src/types/net.rs new file mode 100644 index 000000000..890db4b3f --- /dev/null +++ b/stacks-common/src/types/net.rs @@ -0,0 +1,389 @@ +// Copyright (C) 2013-2020 Blockstack PBC, a public benefit corporation +// Copyright (C) 2020-2023 Stacks Open Internet Foundation +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . + +use std::fmt; +use std::hash::Hash; +use std::hash::Hasher; +use std::str::FromStr; + +use std::net::IpAddr; +use std::net::Ipv4Addr; +use std::net::Ipv6Addr; +use std::net::SocketAddr; + +use serde::de::Deserialize; +use serde::ser::Serialize; + +use crate::util::hash::to_bin; + +use serde::de::Error as de_Error; + +#[derive(Debug)] +pub enum Error { + DecodeError(String), +} + +impl fmt::Display for Error { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + match self { + Error::DecodeError(msg) => write!(f, "{}", &msg), + } + } +} + +impl std::error::Error for Error { + fn cause(&self) -> Option<&dyn std::error::Error> { + match self { + Error::DecodeError(_) => None, + } + } +} + +/// A container for an IPv4 or IPv6 address. +/// Rules: +/// -- If this is an IPv6 address, the octets are in network byte order +/// -- If this is an IPv4 address, the octets must encode an IPv6-to-IPv4-mapped address +pub struct PeerAddress(pub [u8; 16]); +impl_array_newtype!(PeerAddress, u8, 16); +impl_array_hexstring_fmt!(PeerAddress); +impl_byte_array_newtype!(PeerAddress, u8, 16); +impl_byte_array_message_codec!(PeerAddress, 16); + +impl Serialize for PeerAddress { + fn serialize(&self, s: S) -> Result { + let inst = format!("{}", self.to_socketaddr(0).ip()); + s.serialize_str(inst.as_str()) + } +} + +impl<'de> Deserialize<'de> for PeerAddress { + fn deserialize>(d: D) -> Result { + let inst = String::deserialize(d)?; + let ip = inst.parse::().map_err(de_Error::custom)?; + + Ok(PeerAddress::from_ip(&ip)) + } +} + +impl PeerAddress { + pub fn from_slice(bytes: &[u8]) -> Option { + if bytes.len() != 16 { + return None; + } + + let mut bytes16 = [0u8; 16]; + bytes16.copy_from_slice(&bytes[0..16]); + Some(PeerAddress(bytes16)) + } + + /// Is this an IPv4 address? + pub fn is_ipv4(&self) -> bool { + self.ipv4_octets().is_some() + } + + /// Get the octet representation of this peer address as an IPv4 address. + /// The last 4 bytes of the list contain the IPv4 address. + /// This method returns None if the bytes don't encode a valid IPv4-mapped address (i.e. ::ffff:0:0/96) + pub fn ipv4_octets(&self) -> Option<[u8; 4]> { + if self.0[0..12] + != [ + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xff, 0xff, + ] + { + return None; + } + let mut ret = [0u8; 4]; + ret.copy_from_slice(&self.0[12..16]); + Some(ret) + } + + /// Return the bit representation of this peer address as an IPv4 address, in network byte + /// order. Return None if this is not an IPv4 address. + pub fn ipv4_bits(&self) -> Option { + let octets_opt = self.ipv4_octets(); + if octets_opt.is_none() { + return None; + } + + let octets = octets_opt.unwrap(); + Some( + ((octets[0] as u32) << 24) + | ((octets[1] as u32) << 16) + | ((octets[2] as u32) << 8) + | (octets[3] as u32), + ) + } + + /// Convert to SocketAddr + pub fn to_socketaddr(&self, port: u16) -> SocketAddr { + if self.is_ipv4() { + SocketAddr::new( + IpAddr::V4(Ipv4Addr::new( + self.0[12], self.0[13], self.0[14], self.0[15], + )), + port, + ) + } else { + let addr_words: [u16; 8] = [ + ((self.0[0] as u16) << 8) | (self.0[1] as u16), + ((self.0[2] as u16) << 8) | (self.0[3] as u16), + ((self.0[4] as u16) << 8) | (self.0[5] as u16), + ((self.0[6] as u16) << 8) | (self.0[7] as u16), + ((self.0[8] as u16) << 8) | (self.0[9] as u16), + ((self.0[10] as u16) << 8) | (self.0[11] as u16), + ((self.0[12] as u16) << 8) | (self.0[13] as u16), + ((self.0[14] as u16) << 8) | (self.0[15] as u16), + ]; + + SocketAddr::new( + IpAddr::V6(Ipv6Addr::new( + addr_words[0], + addr_words[1], + addr_words[2], + addr_words[3], + addr_words[4], + addr_words[5], + addr_words[6], + addr_words[7], + )), + port, + ) + } + } + + /// Convert from socket address + pub fn from_socketaddr(addr: &SocketAddr) -> PeerAddress { + PeerAddress::from_ip(&addr.ip()) + } + + /// Convert from IP address + pub fn from_ip(addr: &IpAddr) -> PeerAddress { + match addr { + IpAddr::V4(ref addr) => { + let octets = addr.octets(); + PeerAddress([ + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xff, 0xff, + octets[0], octets[1], octets[2], octets[3], + ]) + } + IpAddr::V6(ref addr) => { + let words = addr.segments(); + PeerAddress([ + (words[0] >> 8) as u8, + (words[0] & 0xff) as u8, + (words[1] >> 8) as u8, + (words[1] & 0xff) as u8, + (words[2] >> 8) as u8, + (words[2] & 0xff) as u8, + (words[3] >> 8) as u8, + (words[3] & 0xff) as u8, + (words[4] >> 8) as u8, + (words[4] & 0xff) as u8, + (words[5] >> 8) as u8, + (words[5] & 0xff) as u8, + (words[6] >> 8) as u8, + (words[6] & 0xff) as u8, + (words[7] >> 8) as u8, + (words[7] & 0xff) as u8, + ]) + } + } + } + + /// Convert from ipv4 octets + pub fn from_ipv4(o1: u8, o2: u8, o3: u8, o4: u8) -> PeerAddress { + PeerAddress([ + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xff, 0xff, o1, o2, o3, o4, + ]) + } + + /// Is this the any-network address? i.e. 0.0.0.0 (v4) or :: (v6)? + pub fn is_anynet(&self) -> bool { + self.0 == [0x00; 16] || self == &PeerAddress::from_ipv4(0, 0, 0, 0) + } + + /// Is this a private IP address? + pub fn is_in_private_range(&self) -> bool { + if self.is_ipv4() { + // 10.0.0.0/8, 172.16.0.0/12, or 192.168.0.0/16 + self.0[12] == 10 + || (self.0[12] == 172 && self.0[13] >= 16 && self.0[13] <= 31) + || (self.0[12] == 192 && self.0[13] == 168) + } else { + self.0[0] >= 0xfc + } + } + + pub fn to_bin(&self) -> String { + to_bin(&self.0) + } +} + +/// Peer address variants for the Host: header +#[derive(Clone, PartialEq)] +pub enum PeerHost { + DNS(String, u16), + IP(PeerAddress, u16), +} + +impl fmt::Display for PeerHost { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + match *self { + PeerHost::DNS(ref s, ref p) => write!(f, "{}:{}", s, p), + PeerHost::IP(ref a, ref p) => write!(f, "{}", a.to_socketaddr(*p)), + } + } +} + +impl fmt::Debug for PeerHost { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + match *self { + PeerHost::DNS(ref s, ref p) => write!(f, "PeerHost::DNS({},{})", s, p), + PeerHost::IP(ref a, ref p) => write!(f, "PeerHost::IP({:?},{})", a, p), + } + } +} + +impl Hash for PeerHost { + fn hash(&self, state: &mut H) { + match *self { + PeerHost::DNS(ref name, ref port) => { + "DNS".hash(state); + name.hash(state); + port.hash(state); + } + PeerHost::IP(ref addrbytes, ref port) => { + "IP".hash(state); + addrbytes.hash(state); + port.hash(state); + } + } + } +} + +impl FromStr for PeerHost { + type Err = Error; + + fn from_str(header: &str) -> Result { + // we're looser than the RFC allows for DNS names -- anything that doesn't parse to an IP + // address will be parsed to a DNS name. + // try as IP:port + match header.parse::() { + Ok(socketaddr) => Ok(PeerHost::IP( + PeerAddress::from_socketaddr(&socketaddr), + socketaddr.port(), + )), + Err(_) => { + // maybe missing :port + let hostport = format!("{}:80", header); + match hostport.parse::() { + Ok(socketaddr) => Ok(PeerHost::IP( + PeerAddress::from_socketaddr(&socketaddr), + socketaddr.port(), + )), + Err(_) => { + // try as DNS-name:port + let host; + let port; + let parts: Vec<&str> = header.split(":").collect(); + if parts.len() == 0 { + return Err(Error::DecodeError( + "Failed to parse PeerHost: no parts".to_string(), + )); + } else if parts.len() == 1 { + // no port + host = Some(parts[0].to_string()); + port = Some(80); + } else { + let np = parts.len(); + if parts[np - 1].chars().all(char::is_numeric) { + // ends in :port + let host_str = parts[0..np - 1].join(":"); + if host_str.len() == 0 { + return Err(Error::DecodeError("Empty host".to_string())); + } + host = Some(host_str); + + let port_res = parts[np - 1].parse::(); + port = match port_res { + Ok(p) => Some(p), + Err(_) => { + return Err(Error::DecodeError( + "Failed to parse PeerHost: invalid port".to_string(), + )); + } + }; + } else { + // only host + host = Some(header.to_string()); + port = Some(80); + } + } + + match (host, port) { + (Some(h), Some(p)) => Ok(PeerHost::DNS(h, p)), + (_, _) => Err(Error::DecodeError( + "Failed to parse PeerHost: failed to extract host and/or port" + .to_string(), + )), // I don't think this is reachable + } + } + } + } + } + } +} + +impl PeerHost { + pub fn hostname(&self) -> String { + match *self { + PeerHost::DNS(ref s, _) => s.clone(), + PeerHost::IP(ref a, ref p) => format!("{}", a.to_socketaddr(*p).ip()), + } + } + + pub fn port(&self) -> u16 { + match *self { + PeerHost::DNS(_, ref p) => *p, + PeerHost::IP(_, ref p) => *p, + } + } + + pub fn from_host_port(host: String, port: u16) -> PeerHost { + // try as IP, and fall back to DNS + match host.parse::() { + Ok(addr) => PeerHost::IP(PeerAddress::from_ip(&addr), port), + Err(_) => PeerHost::DNS(host, port), + } + } + + pub fn from_socketaddr(socketaddr: &SocketAddr) -> PeerHost { + PeerHost::IP(PeerAddress::from_socketaddr(socketaddr), socketaddr.port()) + } + + pub fn to_host_port(&self) -> (String, u16) { + match *self { + PeerHost::DNS(ref s, ref p) => (s.clone(), *p), + PeerHost::IP(ref i, ref p) => (format!("{}", i.to_socketaddr(0).ip()), *p), + } + } +} + +impl From for PeerHost { + fn from(addr: SocketAddr) -> PeerHost { + PeerHost::from_socketaddr(&addr) + } +}