use ipnetwork::Ipv6Network;
use serde::Deserialize;
use std::io::Write;
#[derive(Debug, Deserialize, Clone)]
struct Ipv6Allocation {
#[serde(rename = "Prefix")]
prefix: Ipv6Network,
#[serde(rename = "Designation")]
designation: String,
#[serde(rename = "Date")]
_date: String,
#[serde(rename = "WHOIS")]
_whois: String,
#[serde(rename = "RDAP")]
_rdap: String,
#[serde(rename = "Status")]
status: String,
#[serde(rename = "Note")]
_note: String,
}
use std::{env, path::Path};
fn main() {
let allocations = get_ipv6_allocations();
let rirs = ["AFRINIC", "APNIC", "ARIN", "LACNIC", "RIPE NCC"];
let networks = allocations
.iter()
.filter(|a| a.status == "ALLOCATED" && rirs.contains(&&*a.designation))
.map(|a| a.prefix)
.collect::<Vec<_>>();
let networks = merge_ranges(networks);
let networks = networks
.into_iter()
.map(four_byte_networks)
.collect::<Vec<_>>();
write_file(networks).unwrap();
println!("cargo:rerun-if-changed=ipv6-unicast-address-assignments.csv");
}
#[cfg(feature = "download")]
fn download_csv() -> Result<&'static str, Box<dyn std::error::Error>> {
let url = "https://www.iana.org/assignments/ipv6-unicast-address-assignments/ipv6-unicast-address-assignments.csv";
let user = format!(
"bogon/{} ({}; {}) Rust/{}",
std::env::var("CARGO_PKG_VERSION").expect("CARGO_PKG_VERSION not set"),
std::env::var("CARGO_CFG_TARGET_OS").expect("CARGO_CFG_TARGET_OS not set"),
std::env::var("CARGO_CFG_TARGET_ARCH").expect("CARGO_CFG_TARGET_ARCH not set"),
rustc_version::version_meta().unwrap().semver
);
let client = reqwest::blocking::Client::builder()
.user_agent(user)
.build()?;
let body = client.get(url).send()?.error_for_status()?;
Ok(body.text()?.leak())
}
fn get_ipv6_allocations() -> Vec<Ipv6Allocation> {
#[cfg(feature = "download")]
let csv = {
let mut retries = 0;
loop {
match download_csv() {
Ok(csv) => break csv,
Err(e) => {
if retries >= 3 {
eprintln!("Failed to download CSV file: {}", e);
std::process::exit(1);
}
retries += 1;
std::thread::sleep(std::time::Duration::from_secs(2u64.pow(retries)));
}
}
}
};
#[cfg(not(feature = "download"))]
let csv = include_str!("ipv6-unicast-address-assignments.csv");
let mut rdr = csv::Reader::from_reader(csv.as_bytes());
rdr.deserialize().map(|result| result.unwrap()).collect()
}
fn merge_ranges(mut ranges: Vec<Ipv6Network>) -> Vec<Ipv6Network> {
ranges.sort();
let ranges: Vec<(u128, u128)> = ranges
.iter()
.map(|range| (range.network().into(), range.broadcast().into()))
.collect::<Vec<_>>();
let mut merged_ranges = vec![ranges[0]];
for &(start, end) in &ranges[1..] {
let (_prev_start, prev_end) = merged_ranges.last_mut().unwrap();
if start <= *prev_end + 1 {
*prev_end = (*prev_end).max(end);
} else {
merged_ranges.push((start, end));
}
}
let mut all_ranges: Vec<_> = merged_ranges
.into_iter()
.flat_map(range_to_networks)
.collect();
all_ranges.sort_by_key(|network| network.prefix());
let mut super_nets: Vec<Ipv6Network> = Vec::new();
for network in &all_ranges {
if super_nets
.iter()
.any(|super_net| super_net.contains(network.network()))
{
continue;
}
super_nets.push(*network);
}
super_nets.sort();
super_nets
}
fn range_to_networks(range: (u128, u128)) -> Vec<Ipv6Network> {
let mut networks = Vec::new();
let mut start = range.0;
let end = range.1;
while start <= end {
let prefix_length = (end - start).leading_zeros();
let network = Ipv6Network::new(start.into(), prefix_length as u8).unwrap();
networks.push(network);
start = u128::from(network.broadcast()) + 1;
}
networks
}
fn four_byte_networks(ip: Ipv6Network) -> (u32, u8) {
let start = (ip.network().to_bits() >> 96) as u32;
(start, ip.prefix())
}
fn write_file(networks: Vec<(u32, u8)>) -> std::io::Result<()> {
let out_dir = env::var_os("OUT_DIR").unwrap();
let path = Path::new(&out_dir).join("ipv6-unicast-address-allocations.rs");
let mut file = std::fs::File::create(path).unwrap();
writeln!(file, "use crate::network::FourByteNetwork;")?;
writeln!(
file,
"pub(crate) static V6_ALLOCATIONS: [FourByteNetwork; {}] = [",
networks.len()
)?;
for (network, prefix) in networks {
writeln!(
file,
" FourByteNetwork::new({:#x}, {}),",
network, prefix
)?;
}
writeln!(file, "];")?;
Ok(())
}