use std::net::{IpAddr, Ipv4Addr, Ipv6Addr, SocketAddr};
use crate::scope::{fold_ip, scope_of_ip, Scope};
pub const MIN_INDEPENDENT_CLASSES: usize = 2;
pub const PEER_ONLY_MIN_CLASSES: usize = 3;
#[non_exhaustive]
#[derive(Debug, Clone)]
pub struct Reading {
pub source: String,
pub witness: Option<String>,
pub addr: SocketAddr,
}
impl Reading {
pub fn new(source: impl Into<String>, addr: SocketAddr) -> Self {
Reading {
source: source.into(),
witness: None,
addr,
}
}
pub fn with_witness(mut self, witness: impl Into<String>) -> Self {
self.witness = Some(witness.into());
self
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum SourceClass {
Operator {
host: String,
port: u16,
},
Relay {
host: String,
},
Public {
host: String,
},
PeerV4 {
a: u8,
b: u8,
},
PeerV6 {
h0: u16,
h1: u16,
},
}
impl SourceClass {
pub fn operator(host: impl Into<String>, port: u16) -> Self {
SourceClass::Operator {
host: host.into(),
port,
}
}
pub fn relay(host: impl Into<String>) -> Self {
SourceClass::Relay { host: host.into() }
}
pub fn public(host: impl Into<String>) -> Self {
SourceClass::Public { host: host.into() }
}
pub fn peer(ip: IpAddr) -> Self {
match fold_ip(ip) {
IpAddr::V4(v4) => {
let o = v4.octets();
SourceClass::PeerV4 { a: o[0], b: o[1] }
}
IpAddr::V6(v6) => {
let s = v6.segments();
SourceClass::PeerV6 { h0: s[0], h1: s[1] }
}
}
}
fn is_peer(&self) -> bool {
matches!(
self,
SourceClass::PeerV4 { .. } | SourceClass::PeerV6 { .. }
)
}
pub fn parse(s: &str) -> Option<Self> {
if let Some(rest) = s.strip_prefix("operator:") {
let (host, port) = rest.rsplit_once(':')?;
return Some(SourceClass::Operator {
host: host.to_string(),
port: port.parse().ok()?,
});
}
if let Some(host) = s.strip_prefix("relay:") {
return Some(SourceClass::Relay {
host: host.to_string(),
});
}
if let Some(host) = s.strip_prefix("public:") {
return Some(SourceClass::Public {
host: host.to_string(),
});
}
if let Some(rest) = s.strip_prefix("peer:v4:") {
let (a, b) = rest.split_once('.')?;
return Some(SourceClass::PeerV4 {
a: a.parse().ok()?,
b: b.parse().ok()?,
});
}
if let Some(rest) = s.strip_prefix("peer:v6:") {
let (h0, h1) = rest.split_once(':')?;
return Some(SourceClass::PeerV6 {
h0: u16::from_str_radix(h0, 16).ok()?,
h1: u16::from_str_radix(h1, 16).ok()?,
});
}
None
}
}
impl std::fmt::Display for SourceClass {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
SourceClass::Operator { host, port } => write!(f, "operator:{host}:{port}"),
SourceClass::Relay { host } => write!(f, "relay:{host}"),
SourceClass::Public { host } => write!(f, "public:{host}"),
SourceClass::PeerV4 { a, b } => write!(f, "peer:v4:{a}.{b}"),
SourceClass::PeerV6 { h0, h1 } => write!(f, "peer:v6:{h0:04x}:{h1:04x}"),
}
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum FamilyVerdict {
NoReadings,
Disagreement {
addrs: Vec<IpAddr>,
},
Insufficient {
classes: usize,
peer_only: bool,
},
NotGlobal {
ip: IpAddr,
scope: Scope,
},
Established {
ip: IpAddr,
classes: usize,
},
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct Established {
pub ipv6: FamilyVerdict,
pub ipv4: FamilyVerdict,
}
impl Established {
pub fn ipv6_addr(&self) -> Option<Ipv6Addr> {
match &self.ipv6 {
FamilyVerdict::Established {
ip: IpAddr::V6(v6), ..
} => Some(*v6),
_ => None,
}
}
pub fn ipv4_addr(&self) -> Option<Ipv4Addr> {
match &self.ipv4 {
FamilyVerdict::Established {
ip: IpAddr::V4(v4), ..
} => Some(*v4),
_ => None,
}
}
}
pub fn establish(readings: &[Reading]) -> Established {
let mut ipv4: Vec<(IpAddr, &str)> = Vec::new();
let mut ipv6: Vec<(IpAddr, &str)> = Vec::new();
for reading in readings {
let folded = fold_ip(reading.addr.ip());
let bucket = match folded {
IpAddr::V4(_) => &mut ipv4,
IpAddr::V6(_) => &mut ipv6,
};
bucket.push((folded, reading.source.as_str()));
}
Established {
ipv4: verdict_for_family(&ipv4),
ipv6: verdict_for_family(&ipv6),
}
}
fn verdict_for_family(readings: &[(IpAddr, &str)]) -> FamilyVerdict {
if readings.is_empty() {
return FamilyVerdict::NoReadings;
}
let mut addrs: Vec<IpAddr> = readings.iter().map(|(ip, _)| *ip).collect();
addrs.sort();
addrs.dedup();
if addrs.len() > 1 {
return FamilyVerdict::Disagreement { addrs };
}
let ip = addrs[0];
let mut classes: Vec<&str> = readings.iter().map(|(_, source)| *source).collect();
classes.sort_unstable();
classes.dedup();
let peer_only = classes
.iter()
.all(|s| SourceClass::parse(s).map(|c| c.is_peer()).unwrap_or(false));
if classes.len() < MIN_INDEPENDENT_CLASSES {
return FamilyVerdict::Insufficient {
classes: classes.len(),
peer_only,
};
}
if peer_only && classes.len() < PEER_ONLY_MIN_CLASSES {
return FamilyVerdict::Insufficient {
classes: classes.len(),
peer_only: true,
};
}
let scope = scope_of_ip(ip);
if scope != Scope::GlobalUnicast {
return FamilyVerdict::NotGlobal { ip, scope };
}
FamilyVerdict::Established {
ip,
classes: classes.len(),
}
}