use std::net::{Ipv4Addr, Ipv6Addr};
use ipnet::Ipv6Net;
pub(crate) const WELL_KNOWN_PREFIX: Ipv6Net =
Ipv6Net::new_assert(Ipv6Addr::new(0x64, 0xff9b, 0, 0, 0, 0, 0, 0), 96);
#[cfg(feature = "http")]
pub(crate) const DISCOVERY_NAME: &str = "ipv4only.arpa";
#[cfg(feature = "http")]
const DISCOVERY_ADDRESSES: [Ipv4Addr; 2] =
[Ipv4Addr::new(192, 0, 0, 170), Ipv4Addr::new(192, 0, 0, 171)];
#[cfg(feature = "http")]
const PREFIX_LENGTHS: [u8; 6] = [32, 40, 48, 56, 64, 96];
pub(crate) fn embedded_ipv4(address: Ipv6Addr, length: u8) -> Option<Ipv4Addr> {
let b = address.octets();
if length < 96 && b[8] != 0 {
return None;
}
let v4 = match length {
32 => [b[4], b[5], b[6], b[7]],
40 => [b[5], b[6], b[7], b[9]],
48 => [b[6], b[7], b[9], b[10]],
56 => [b[7], b[9], b[10], b[11]],
64 => [b[9], b[10], b[11], b[12]],
96 => [b[12], b[13], b[14], b[15]],
_ => return None,
};
Some(Ipv4Addr::from(v4))
}
#[cfg(feature = "http")]
pub(crate) fn prefixes_from_discovery(addresses: &[Ipv6Addr]) -> Vec<Ipv6Net> {
let mut prefixes = Vec::new();
for &address in addresses {
for length in PREFIX_LENGTHS {
let Some(v4) = embedded_ipv4(address, length) else {
continue;
};
if !DISCOVERY_ADDRESSES.contains(&v4) {
continue;
}
let Ok(prefix) = Ipv6Net::new(address, length).map(|net| net.trunc()) else {
continue;
};
if !prefixes.contains(&prefix) {
prefixes.push(prefix);
}
}
}
prefixes
}
#[cfg(feature = "http")]
pub(crate) fn translations(address: Ipv6Addr, discovered: &[Ipv6Net]) -> Vec<Ipv4Addr> {
std::iter::once(WELL_KNOWN_PREFIX)
.chain(discovered.iter().copied())
.filter(|prefix| prefix.contains(&address))
.filter_map(|prefix| embedded_ipv4(address, prefix.prefix_len()))
.collect()
}
#[cfg(test)]
mod tests {
use super::*;
fn v6(value: &str) -> Ipv6Addr {
value.parse().unwrap()
}
#[cfg(feature = "http")]
fn net(value: &str) -> Ipv6Net {
value.parse().unwrap()
}
const EXAMPLE: Ipv4Addr = Ipv4Addr::new(192, 0, 2, 33);
#[test]
fn reads_the_ipv4_address_at_every_length_in_the_rfc_examples() {
for (address, length) in [
("2001:db8:c000:221::", 32),
("2001:db8:1c0:2:21::", 40),
("2001:db8:122:c000:2:2100::", 48),
("2001:db8:122:3c0:0:221::", 56),
("2001:db8:122:344:c0:2:2100:0", 64),
("2001:db8:122:344::192.0.2.33", 96),
("64:ff9b::192.0.2.33", 96),
] {
assert_eq!(
embedded_ipv4(v6(address), length),
Some(EXAMPLE),
"{address}/{length}"
);
}
}
#[test]
fn a_length_rfc_6052_does_not_allow_reads_nothing() {
for length in [0, 24, 33, 72, 128] {
assert_eq!(embedded_ipv4(v6("2001:db8::"), length), None, "/{length}");
}
}
#[test]
fn a_short_prefix_with_bits_64_to_71_set_reads_nothing() {
let address = v6("2001:db8:c000:221:ff00::");
assert_eq!(embedded_ipv4(address, 32), None);
assert_eq!(embedded_ipv4(address, 96), Some(Ipv4Addr::new(0, 0, 0, 0)));
}
#[cfg(feature = "http")]
#[test]
fn learns_the_prefix_from_the_answer_for_ipv4only_arpa() {
let answer = [
v6("2001:db8:122:344::192.0.0.170"),
v6("2001:db8:122:344::192.0.0.171"),
];
assert_eq!(
prefixes_from_discovery(&answer),
vec![net("2001:db8:122:344::/96")]
);
}
#[cfg(feature = "http")]
#[test]
fn learns_a_prefix_shorter_than_96() {
let answer = [v6("2001:db8:1c0:0:aa::")];
assert_eq!(
prefixes_from_discovery(&answer),
vec![net("2001:db8:100::/40")]
);
}
#[cfg(feature = "http")]
#[test]
fn an_answer_with_no_known_address_inside_shows_no_prefix() {
assert_eq!(
prefixes_from_discovery(&[v6("2001:db8::1")]),
Vec::<Ipv6Net>::new()
);
assert_eq!(prefixes_from_discovery(&[]), Vec::<Ipv6Net>::new());
}
#[cfg(feature = "http")]
#[test]
fn the_well_known_prefix_is_read_without_discovery() {
assert_eq!(
translations(v6("64:ff9b::a9fe:a9fe"), &[]),
vec![Ipv4Addr::new(169, 254, 169, 254)]
);
}
#[cfg(feature = "http")]
#[test]
fn a_discovered_prefix_is_read_for_an_address_under_it() {
let discovered = [net("2001:db8:122:344::/96")];
assert_eq!(
translations(v6("2001:db8:122:344::a9fe:a9fe"), &discovered),
vec![Ipv4Addr::new(169, 254, 169, 254)]
);
}
#[cfg(feature = "http")]
#[test]
fn an_address_under_no_nat64_prefix_carries_no_ipv4_address() {
let discovered = [net("2001:db8:122:344::/96")];
assert_eq!(
translations(v6("2001:4860:4860::8888"), &discovered),
Vec::<Ipv4Addr>::new()
);
}
}