use serde::{Deserialize, Serialize};
use crate::error::Result;
pub async fn check_rpki(req: &RpkiCheckRequest) -> Result<RpkiCheckResult> {
let ip = &req.ip;
match check_rpki_validity(ip).await {
Ok(result) => Ok(result),
Err(e) => Ok(RpkiCheckResult {
ip: ip.clone(),
asn: None,
prefix: None,
roa_state: "error".to_string(),
roa_valid: false,
error: Some(e.to_string()),
}),
}
}
async fn check_rpki_validity(ip: &str) -> Result<RpkiCheckResult> {
use reqwest::Client;
use std::str::FromStr;
std::net::IpAddr::from_str(ip)
.map_err(|_| crate::error::ShoheError::Parse(format!("Invalid IP address: {}", ip)))?;
let client = Client::new();
let bgp_req = crate::api::BgpRouteRequest {
ip: ip.to_string(),
timeout_secs: 10,
};
let bgp_result = crate::api::check_bgp_route(&bgp_req).await.ok();
let asn = bgp_result.as_ref().and_then(|r| r.asn);
let prefix = bgp_result.as_ref().and_then(|r| r.prefix.clone());
let prefix = match prefix {
Some(p) => p,
None => {
return Ok(RpkiCheckResult {
ip: ip.to_string(),
asn,
prefix: None,
roa_state: "unable_to_determine".to_string(),
roa_valid: false,
error: Some("Could not determine prefix length from BGP — RPKI validation unreliable".to_string()),
});
}
};
let rpki_url = if let Some(asn_num) = asn {
format!(
"https://rpki.cloudflare.com/api/v1/validity/AS{}/{}",
asn_num, crate::api::helpers::percent_encode(&prefix)
)
} else {
return Ok(RpkiCheckResult {
ip: ip.to_string(),
asn,
prefix: Some(prefix),
roa_state: "not-found".to_string(),
roa_valid: false,
error: Some("Could not determine ASN".to_string()),
});
};
let rpki_response = client
.get(&rpki_url)
.timeout(std::time::Duration::from_secs(10))
.send()
.await;
match rpki_response {
Ok(resp) => {
if let Ok(json) = resp.json::<serde_json::Value>().await {
let state = json
.get("state")
.and_then(|v| v.as_str())
.unwrap_or("unknown")
.to_string();
let valid = state == "valid";
Ok(RpkiCheckResult {
ip: ip.to_string(),
asn,
prefix: Some(prefix),
roa_state: state,
roa_valid: valid,
error: None,
})
} else {
Ok(RpkiCheckResult {
ip: ip.to_string(),
asn,
prefix: Some(prefix),
roa_state: "unknown".to_string(),
roa_valid: false,
error: Some("Invalid response from RPKI API".to_string()),
})
}
}
Err(e) => Ok(RpkiCheckResult {
ip: ip.to_string(),
asn,
prefix: Some(prefix),
roa_state: "error".to_string(),
roa_valid: false,
error: Some(format!("RPKI API error: {}", e)),
}),
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct RpkiCheckRequest {
pub ip: String,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct RpkiCheckResult {
pub ip: String,
pub asn: Option<u32>,
pub prefix: Option<String>,
pub roa_state: String,
pub roa_valid: bool,
pub error: Option<String>,
}