rpki/src/rtr/loader.rs
2026-05-07 16:47:08 +08:00

305 lines
8.7 KiB
Rust
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

use std::fs;
use std::net::IpAddr;
use std::path::Path;
use std::str::FromStr;
use anyhow::{Context, Result, anyhow};
use crate::data_model::resources::as_resources::Asn;
use crate::data_model::resources::ip_resources::{IPAddress, IPAddressPrefix};
use crate::rtr::payload::{Aspa, Payload, RouteOrigin, RouterKey, Ski};
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct ParsedVrp {
pub prefix_addr: IpAddr,
pub prefix_len: u8,
pub max_len: u8,
pub asn: u32,
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct ParsedAspa {
pub customer_asn: u32,
pub provider_asns: Vec<u32>,
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct ParsedRouterKey {
pub ski: [u8; 20],
pub asn: u32,
pub spki: Vec<u8>,
}
/// 从文本文件中加载 VRP并转换成 RTR Payload::RouteOrigin。
///
/// 文件格式:
///
/// ```text
/// # prefix,max_len,asn
/// 10.0.0.0/24,24,65001
/// 10.0.1.0/24,24,65002
/// 2001:db8::/32,48,65003
/// ```
pub fn load_vrps_from_file(path: impl AsRef<Path>) -> Result<Vec<Payload>> {
let path = path.as_ref();
let content = fs::read_to_string(path)
.with_context(|| format!("failed to read VRP file: {}", path.display()))?;
let mut payloads = Vec::new();
for (idx, raw_line) in content.lines().enumerate() {
let line_no = idx + 1;
let line = raw_line.trim();
if line.is_empty() || line.starts_with('#') {
continue;
}
let vrp = parse_vrp_line(line)
.with_context(|| format!("invalid VRP line {}: {}", line_no, raw_line))?;
payloads.push(Payload::RouteOrigin(build_route_origin(vrp)?));
}
Ok(payloads)
}
pub fn load_aspas_from_file(path: impl AsRef<Path>) -> Result<Vec<Payload>> {
let path = path.as_ref();
let content = fs::read_to_string(path)
.with_context(|| format!("failed to read ASPA file: {}", path.display()))?;
let mut payloads = Vec::new();
for (idx, raw_line) in content.lines().enumerate() {
let line_no = idx + 1;
let line = raw_line.trim();
if line.is_empty() || line.starts_with('#') {
continue;
}
let aspa = parse_aspa_line(line)
.with_context(|| format!("invalid ASPA line {}: {}", line_no, raw_line))?;
payloads.push(Payload::Aspa(build_aspa(aspa)?));
}
Ok(payloads)
}
pub fn load_router_keys_from_file(path: impl AsRef<Path>) -> Result<Vec<Payload>> {
let path = path.as_ref();
let content = fs::read_to_string(path)
.with_context(|| format!("failed to read Router Key file: {}", path.display()))?;
let mut payloads = Vec::new();
for (idx, raw_line) in content.lines().enumerate() {
let line_no = idx + 1;
let line = raw_line.trim();
if line.is_empty() || line.starts_with('#') {
continue;
}
let router_key = parse_router_key_line(line)
.with_context(|| format!("invalid Router Key line {}: {}", line_no, raw_line))?;
payloads.push(Payload::RouterKey(build_router_key(router_key)?));
}
Ok(payloads)
}
/// 解析单行 VRP。
///
/// 格式:
/// `prefix/prefix_len,max_len,asn`
///
/// 例如:
/// `10.0.0.0/24,24,65001`
pub fn parse_vrp_line(line: &str) -> Result<ParsedVrp> {
let parts: Vec<_> = line.split(',').map(|s| s.trim()).collect();
if parts.len() != 3 {
return Err(anyhow!(
"expected format: <prefix>/<prefix_len>,<max_len>,<asn>"
));
}
let prefix_part = parts[0];
let max_len =
u8::from_str(parts[1]).with_context(|| format!("invalid max_len: {}", parts[1]))?;
let asn = u32::from_str(parts[2]).with_context(|| format!("invalid asn: {}", parts[2]))?;
let (addr_str, prefix_len_str) = prefix_part
.split_once('/')
.ok_or_else(|| anyhow!("prefix must be in CIDR form, e.g. 10.0.0.0/24"))?;
let prefix_addr = IpAddr::from_str(addr_str.trim())
.with_context(|| format!("invalid IP address: {}", addr_str))?;
let prefix_len = u8::from_str(prefix_len_str.trim())
.with_context(|| format!("invalid prefix length: {}", prefix_len_str))?;
validate_vrp(prefix_addr, prefix_len, max_len)?;
Ok(ParsedVrp {
prefix_addr,
prefix_len,
max_len,
asn,
})
}
pub fn parse_aspa_line(line: &str) -> Result<ParsedAspa> {
let parts: Vec<_> = line.split(',').map(|s| s.trim()).collect();
if parts.len() != 2 {
return Err(anyhow!(
"expected format: <customer_asn>,<provider_asn> [provider_asn ...]"
));
}
let customer_asn =
u32::from_str(parts[0]).with_context(|| format!("invalid customer_asn: {}", parts[0]))?;
let provider_asns = parts[1]
.split_whitespace()
.map(|provider| {
u32::from_str(provider).with_context(|| format!("invalid provider_asn: {}", provider))
})
.collect::<Result<Vec<_>>>()?;
validate_aspa(customer_asn, &provider_asns)?;
Ok(ParsedAspa {
customer_asn,
provider_asns,
})
}
pub fn parse_router_key_line(line: &str) -> Result<ParsedRouterKey> {
let parts: Vec<_> = line.split(',').map(|s| s.trim()).collect();
if parts.len() != 3 {
return Err(anyhow!("expected format: <ski_hex>,<asn>,<spki_hex>"));
}
let ski_vec = decode_hex(parts[0]).with_context(|| format!("invalid SKI hex: {}", parts[0]))?;
if ski_vec.len() != 20 {
return Err(anyhow!("SKI must be exactly 20 bytes"));
}
let mut ski = [0u8; 20];
ski.copy_from_slice(&ski_vec);
let asn = u32::from_str(parts[1]).with_context(|| format!("invalid asn: {}", parts[1]))?;
let spki = decode_hex(parts[2]).with_context(|| format!("invalid SPKI hex: {}", parts[2]))?;
validate_router_key(asn, &spki)?;
Ok(ParsedRouterKey { ski, asn, spki })
}
fn validate_vrp(prefix_addr: IpAddr, prefix_len: u8, max_len: u8) -> Result<()> {
match prefix_addr {
IpAddr::V4(_) => {
if prefix_len > 32 {
return Err(anyhow!("IPv4 prefix length must be <= 32"));
}
if max_len > 32 {
return Err(anyhow!("IPv4 max_len must be <= 32"));
}
if max_len < prefix_len {
return Err(anyhow!("IPv4 max_len must be >= prefix length"));
}
}
IpAddr::V6(_) => {
if prefix_len > 128 {
return Err(anyhow!("IPv6 prefix length must be <= 128"));
}
if max_len > 128 {
return Err(anyhow!("IPv6 max_len must be <= 128"));
}
if max_len < prefix_len {
return Err(anyhow!("IPv6 max_len must be >= prefix length"));
}
}
}
Ok(())
}
fn validate_aspa(customer_asn: u32, provider_asns: &[u32]) -> Result<()> {
if customer_asn == 0 {
return Err(anyhow!("customer_asn must not be AS0"));
}
if provider_asns.is_empty() {
return Err(anyhow!("provider list must not be empty"));
}
if provider_asns.iter().any(|asn| *asn == 0)
&& !(provider_asns.len() == 1 && provider_asns[0] == 0)
{
return Err(anyhow!(
"provider list containing AS0 must be exactly [0]"
));
}
Ok(())
}
fn validate_router_key(asn: u32, spki: &[u8]) -> Result<()> {
crate::rtr::payload::RouterKey::new(Ski::default(), Asn::from(asn), spki.to_vec())
.validate()
.map_err(|err| anyhow!(err.to_string()))?;
Ok(())
}
pub fn build_route_origin(vrp: ParsedVrp) -> Result<RouteOrigin> {
let address = match vrp.prefix_addr {
IpAddr::V4(addr) => IPAddress::from_ipv4(addr),
IpAddr::V6(addr) => IPAddress::from_ipv6(addr),
};
let prefix = IPAddressPrefix::new(address, vrp.prefix_len);
let asn = Asn::from(vrp.asn);
Ok(RouteOrigin::new(prefix, vrp.max_len, asn))
}
pub fn build_aspa(aspa: ParsedAspa) -> Result<Aspa> {
let customer_asn = Asn::from(aspa.customer_asn);
let provider_asns = aspa
.provider_asns
.into_iter()
.map(Asn::from)
.collect::<Vec<_>>();
let aspa = Aspa::new(customer_asn, provider_asns);
aspa.validate_announcement()?;
Ok(aspa)
}
pub fn build_router_key(router_key: ParsedRouterKey) -> Result<RouterKey> {
let asn = Asn::from(router_key.asn);
let ski = Ski::from_bytes(router_key.ski);
let router_key = RouterKey::new(ski, asn, router_key.spki);
Ok(router_key)
}
fn decode_hex(input: &str) -> Result<Vec<u8>> {
let trimmed = input.trim();
if trimmed.len() % 2 != 0 {
return Err(anyhow!("hex string must have even length"));
}
(0..trimmed.len())
.step_by(2)
.map(|idx| {
u8::from_str_radix(&trimmed[idx..idx + 2], 16)
.map_err(|err| anyhow!("invalid hex at byte {}: {}", idx / 2, err))
})
.collect()
}