use std::net::{IpAddr, Ipv6Addr};
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub struct HostKey {
pub key: IpAddr,
pub loopback: bool,
}
pub fn host_key(ip: IpAddr) -> HostKey {
let ip = ip.to_canonical();
let key = match ip {
IpAddr::V4(_) => ip,
IpAddr::V6(v6) => {
let [a, b, c, d, ..] = v6.segments();
IpAddr::V6(Ipv6Addr::new(a, b, c, d, 0, 0, 0, 0))
}
};
HostKey {
key,
loopback: ip.is_loopback(),
}
}
#[cfg(test)]
mod tests {
use super::*;
fn of(text: &str) -> HostKey {
host_key(text.parse().expect("an address"))
}
fn address(text: &str) -> IpAddr {
text.parse().expect("an address")
}
#[test]
fn an_ipv4_peer_is_its_own_key() {
assert_eq!(of("192.0.2.1").key, address("192.0.2.1"));
assert_ne!(of("192.0.2.1").key, of("192.0.2.2").key);
}
#[test]
fn an_ipv6_peer_is_its_slash_64() {
let host = of("2001:db8:0:1::1").key;
assert_eq!(host, address("2001:db8:0:1::"));
assert_eq!(host, of("2001:db8:0:1:ffff:ffff:ffff:ffff").key);
assert_eq!(host, of("2001:db8:0:1:dead:beef:0:7").key);
assert_ne!(
host,
of("2001:db8:0:2::1").key,
"the next /64 is another host"
);
assert_ne!(
host,
of("2001:db8:1:1::1").key,
"a difference in the prefix is another host"
);
}
#[test]
fn an_ipv4_mapped_address_is_the_ipv4_host_it_carries() {
assert_eq!(of("::ffff:192.0.2.1").key, of("192.0.2.1").key);
assert_eq!(of("::ffff:192.0.2.1").key, address("192.0.2.1"));
assert_ne!(of("::ffff:192.0.2.1").key, of("::ffff:192.0.2.2").key);
}
#[test]
fn loopback_is_judged_on_the_address_and_not_on_the_key() {
for local in ["127.0.0.1", "127.255.255.254", "::1", "::ffff:127.0.0.1"] {
assert!(of(local).loopback, "{local} is loopback");
}
for remote in ["192.0.2.1", "2001:db8::1", "::", "::2", "::ffff:192.0.2.1"] {
assert!(!of(remote).loopback, "{remote} is not loopback");
}
assert_eq!(of("::1").key, of("::2").key);
assert_ne!(of("::1").loopback, of("::2").loopback);
}
}