diff --git a/deploy/server/docker-compose.tls.yml b/deploy/server/docker-compose.tls.yml index cdd7119..a299d7a 100644 --- a/deploy/server/docker-compose.tls.yml +++ b/deploy/server/docker-compose.tls.yml @@ -1,5 +1,3 @@ -version: "3.9" - services: rpki-rtr: build: @@ -9,12 +7,12 @@ services: container_name: rpki-rtr-tls restart: no ports: - - "323:323" +# - "323:323" - "324:324" environment: RPKI_RTR_ENABLE_TLS: "true" RPKI_RTR_ENABLE_SSH: "false" - RPKI_RTR_TCP_ADDR: "0.0.0.0:323" +# RPKI_RTR_TCP_ADDR: "0.0.0.0:323" RPKI_RTR_TLS_ADDR: "0.0.0.0:324" RPKI_RTR_TLS_CERT_PATH: "${RPKI_RTR_TLS_CERT_PATH:-/app/certs/server-dns.crt}" RPKI_RTR_TLS_KEY_PATH: "${RPKI_RTR_TLS_KEY_PATH:-/app/certs/server-dns.key}" diff --git a/src/rtr/loader.rs b/src/rtr/loader.rs deleted file mode 100644 index 70807eb..0000000 --- a/src/rtr/loader.rs +++ /dev/null @@ -1,304 +0,0 @@ -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, -} - -#[derive(Debug, Clone, PartialEq, Eq)] -pub struct ParsedRouterKey { - pub ski: [u8; 20], - pub asn: u32, - pub spki: Vec, -} - -/// 从文本文件中加载 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) -> Result> { - 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) -> Result> { - 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) -> Result> { - 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 { - let parts: Vec<_> = line.split(',').map(|s| s.trim()).collect(); - if parts.len() != 3 { - return Err(anyhow!( - "expected format: /,," - )); - } - - 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 { - let parts: Vec<_> = line.split(',').map(|s| s.trim()).collect(); - if parts.len() != 2 { - return Err(anyhow!( - "expected format: , [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::>>()?; - - validate_aspa(customer_asn, &provider_asns)?; - - Ok(ParsedAspa { - customer_asn, - provider_asns, - }) -} - -pub fn parse_router_key_line(line: &str) -> Result { - let parts: Vec<_> = line.split(',').map(|s| s.trim()).collect(); - if parts.len() != 3 { - return Err(anyhow!("expected format: ,,")); - } - - 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 { - 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 { - let customer_asn = Asn::from(aspa.customer_asn); - let provider_asns = aspa - .provider_asns - .into_iter() - .map(Asn::from) - .collect::>(); - let aspa = Aspa::new(customer_asn, provider_asns); - aspa.validate_announcement()?; - Ok(aspa) -} - -pub fn build_router_key(router_key: ParsedRouterKey) -> Result { - 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> { - 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() -} diff --git a/src/rtr/mod.rs b/src/rtr/mod.rs index 1cbfcbb..11ed2dd 100644 --- a/src/rtr/mod.rs +++ b/src/rtr/mod.rs @@ -1,6 +1,5 @@ pub mod cache; pub mod error_type; -pub mod loader; pub mod payload; pub mod pdu; pub mod server; diff --git a/src/source/ccr.rs b/src/source/ccr.rs index 3936880..a217ecd 100644 --- a/src/source/ccr.rs +++ b/src/source/ccr.rs @@ -6,12 +6,27 @@ use anyhow::{Context, Result, anyhow}; use der_parser::ber::{BerObject, BerObjectContent}; use der_parser::der::parse_der; -use crate::rtr::loader::{ParsedAspa, ParsedVrp, build_aspa, build_route_origin}; -use crate::rtr::payload::Payload; +use crate::data_model::resources::as_resources::Asn; +use crate::data_model::resources::ip_resources::{IPAddress, IPAddressPrefix}; +use crate::rtr::payload::{Aspa, Payload, RouteOrigin}; const VRPS_INDEX: usize = 3; const VAPS_INDEX: usize = 4; +#[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, +} + #[derive(Clone, Debug, Eq, PartialEq)] pub struct ParsedCcrSnapshot { pub content_type_oid: String, @@ -397,3 +412,27 @@ fn contains_ccr_file(dir: &Path) -> Result { Ok(false) } + +fn build_route_origin(vrp: ParsedVrp) -> Result { + 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)) +} + +fn build_aspa(aspa: ParsedAspa) -> Result { + let customer_asn = Asn::from(aspa.customer_asn); + let provider_asns = aspa + .provider_asns + .into_iter() + .map(Asn::from) + .collect::>(); + let aspa = Aspa::new(customer_asn, provider_asns); + aspa.validate_announcement()?; + Ok(aspa) +} diff --git a/tests/test_ccr.rs b/tests/test_ccr.rs index d676b0f..270cdb1 100644 --- a/tests/test_ccr.rs +++ b/tests/test_ccr.rs @@ -1,10 +1,11 @@ use std::fs; use std::path::PathBuf; -use rpki::rtr::loader::{ParsedAspa, ParsedVrp}; use tempfile::tempdir; -use rpki::source::ccr::{find_latest_ccr_file, load_ccr_snapshot_from_file, - snapshot_to_payloads_with_options, ParsedCcrSnapshot}; +use rpki::source::ccr::{ + ParsedAspa, ParsedCcrSnapshot, ParsedVrp, find_latest_ccr_file, load_ccr_snapshot_from_file, + snapshot_to_payloads_with_options, +}; fn fixture_path(name: &str) -> PathBuf { PathBuf::from(env!("CARGO_MANIFEST_DIR")).join("data").join(name) diff --git a/tests/test_loader.rs b/tests/test_loader.rs deleted file mode 100644 index ebcd2f1..0000000 --- a/tests/test_loader.rs +++ /dev/null @@ -1,123 +0,0 @@ -use std::net::IpAddr; -use std::str::FromStr; - -use rpki::rtr::loader::{ - parse_aspa_line, parse_router_key_line, parse_vrp_line, ParsedAspa, ParsedVrp, -}; - -#[test] -fn parse_ipv4_vrp_line() { - let got = parse_vrp_line("10.0.0.0/24,24,65001").unwrap(); - assert_eq!( - got, - ParsedVrp { - prefix_addr: IpAddr::from_str("10.0.0.0").unwrap(), - prefix_len: 24, - max_len: 24, - asn: 65001, - } - ); -} - -#[test] -fn parse_ipv6_vrp_line() { - let got = parse_vrp_line("2001:db8::/32,48,65003").unwrap(); - assert_eq!( - got, - ParsedVrp { - prefix_addr: IpAddr::from_str("2001:db8::").unwrap(), - prefix_len: 32, - max_len: 48, - asn: 65003, - } - ); -} - -#[test] -fn parse_rejects_invalid_max_len() { - let err = parse_vrp_line("10.0.0.0/24,16,65001").unwrap_err(); - assert!(err.to_string().contains("max_len")); -} - -#[test] -fn parse_rejects_invalid_ip() { - let err = parse_vrp_line("10.0.0.999/24,24,65001").unwrap_err(); - assert!(err.to_string().contains("invalid IP")); -} - -#[test] -fn parse_rejects_invalid_format() { - let err = parse_vrp_line("10.0.0.0/24,24").unwrap_err(); - assert!(err.to_string().contains("expected format")); -} - -#[test] -fn parse_aspa_line_ok() { - let got = parse_aspa_line("64496,64497 64498").unwrap(); - assert_eq!( - got, - ParsedAspa { - customer_asn: 64496, - provider_asns: vec![64497, 64498], - } - ); -} - -#[test] -fn parse_aspa_rejects_empty_provider_list() { - let err = parse_aspa_line("64496,").unwrap_err(); - assert!(err.to_string().contains("provider list")); -} - -#[test] -fn parse_aspa_rejects_as0() { - let err = parse_aspa_line("0,64497").unwrap_err(); - assert!(err.to_string().contains("AS0")); - - let err = parse_aspa_line("64496,0").unwrap_err(); - assert!(err.to_string().contains("AS0")); -} - -#[test] -fn parse_router_key_line_ok() { - let got = parse_router_key_line( - "00112233445566778899aabbccddeeff00112233,64496,3013300d06092a864886f70d010101050003020000", - ) - .unwrap(); - assert_eq!(got.asn, 64496); - assert_eq!( - got.ski, - [ - 0x00, 0x11, 0x22, 0x33, 0x44, 0x55, 0x66, 0x77, 0x88, 0x99, 0xaa, 0xbb, 0xcc, - 0xdd, 0xee, 0xff, 0x00, 0x11, 0x22, 0x33, - ] - ); - assert_eq!( - got.spki, - vec![ - 0x30, 0x13, 0x30, 0x0d, 0x06, 0x09, 0x2a, 0x86, 0x48, 0x86, 0xf7, 0x0d, 0x01, - 0x01, 0x01, 0x05, 0x00, 0x03, 0x02, 0x00, 0x00, - ] - ); -} - -#[test] -fn parse_router_key_rejects_invalid_ski_length() { - let err = parse_router_key_line("0011,64496,deadbeef").unwrap_err(); - assert!(err.to_string().contains("SKI")); -} - -#[test] -fn parse_router_key_rejects_empty_spki() { - let err = - parse_router_key_line("00112233445566778899aabbccddeeff00112233,64496,").unwrap_err(); - assert!(err.to_string().contains("SPKI")); -} - -#[test] -fn parse_router_key_rejects_invalid_spki_der() { - let err = - parse_router_key_line("00112233445566778899aabbccddeeff00112233,64496,deadbeef") - .unwrap_err(); - assert!(err.to_string().contains("valid DER")); -}