Verify AS paths with ASPAs, over RTR version 2
Origin validation says who may originate a prefix and nothing about the
rest of the path: it does not catch a route leak, nor a hijack that keeps
the right origin at the end of a made-up path. ASPAs, in which a network
lists its providers, do.
bgp-rpki:
- aspa: the ASPAs and the upstream and downstream verification procedures
of draft-ietf-sidrops-aspa-verification-24, tested against the draft's
wording as written.
- rtr: protocol version 2 (draft-ietf-sidrops-8210bis-23) with the ASPA
PDU and the new error codes.
- client: asks for version 2 and falls back to whatever older version the
cache speaks; keeps ASPAs with the VRPs, applied whole or not at all;
publishes which customers' ASPAs changed; tells the cache why it is
being left (Error Report); does not ask one version's serial of another.
Server:
- A peer has a role (provider, customer, peer, route server, route server
client), which decides how its paths are verified. Without one they are
not.
- A route whose path is invalid is refused ("ASPA invalid") and held, like
one with an invalid origin; accept_aspa_invalid exempts a peer.
- When ASPAs change, routes in the table whose path has a changed customer
on it are verified again, and held routes are released when they pass
both checks.
- The revalidator finds a route's peer by index instead of searching a
list for every route.
The status page and API show each peer's role, each route's path validity,
the number of ASPAs and the RTR version in use; bgp_rpki_aspas is on
/metricz.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
This commit is contained in:
1 parent
bdaf226a16
commit
8086aa994f
35 files changed
+2484
-359
No files matched your search
@@ -1,11 +1,11 @@
|
||||
//! An RPKI cache for tests: an RTR server that hands out whatever VRPs the
|
||||
//! test says there are.
|
||||
//! An RPKI cache for tests: an RTR server that hands out whatever VRPs and
|
||||
//! ASPAs the test says there are.
|
||||
|
||||
use std::net::{Ipv4Addr, SocketAddr};
|
||||
use std::sync::{Arc, Mutex};
|
||||
|
||||
use bgp_rpki::Vrp;
|
||||
use bgp_rpki::rtr::{Pdu, Timers};
|
||||
use bgp_rpki::rtr::{Pdu, Timers, VERSION_2};
|
||||
use bytes::BytesMut;
|
||||
use tokio::io::{AsyncReadExt, AsyncWriteExt};
|
||||
use tokio::net::{TcpListener, TcpStream};
|
||||
@@ -14,11 +14,19 @@ use tokio::task::JoinHandle;
|
||||
|
||||
const SESSION: u16 = 0x1234;
|
||||
|
||||
/// What the cache has to give.
|
||||
#[derive(Clone, Default)]
|
||||
struct Data {
|
||||
vrps: Vec<Vrp>,
|
||||
/// Customer ASes and their providers.
|
||||
aspas: Vec<(u32, Vec<u32>)>,
|
||||
serial: u32,
|
||||
}
|
||||
|
||||
/// The cache. Stops, closing its connections, when dropped.
|
||||
pub struct TestCache {
|
||||
address: SocketAddr,
|
||||
/// The VRPs there are, and their serial.
|
||||
data: Arc<Mutex<(Vec<Vrp>, u32)>>,
|
||||
data: Arc<Mutex<Data>>,
|
||||
/// The serial, for telling connected routers there is something new.
|
||||
serial: watch::Sender<u32>,
|
||||
task: JoinHandle<()>,
|
||||
@@ -26,9 +34,18 @@ pub struct TestCache {
|
||||
|
||||
impl TestCache {
|
||||
pub async fn start(vrps: Vec<Vrp>) -> Self {
|
||||
Self::start_with(vrps, vec![]).await
|
||||
}
|
||||
|
||||
/// A cache with ASPAs too: customer ASes and their providers.
|
||||
pub async fn start_with(vrps: Vec<Vrp>, aspas: Vec<(u32, Vec<u32>)>) -> Self {
|
||||
let listener = TcpListener::bind((Ipv4Addr::LOCALHOST, 0)).await.unwrap();
|
||||
let address = listener.local_addr().unwrap();
|
||||
let data = Arc::new(Mutex::new((vrps, 1)));
|
||||
let data = Arc::new(Mutex::new(Data {
|
||||
vrps,
|
||||
aspas,
|
||||
serial: 1,
|
||||
}));
|
||||
let (serial, _) = watch::channel(1);
|
||||
let task = tokio::spawn({
|
||||
let (data, serial) = (Arc::clone(&data), serial.clone());
|
||||
@@ -57,11 +74,20 @@ impl TestCache {
|
||||
|
||||
/// Replaces the VRPs and tells connected routers.
|
||||
pub fn set(&self, vrps: Vec<Vrp>) {
|
||||
self.change(|data| data.vrps = vrps);
|
||||
}
|
||||
|
||||
/// Replaces the ASPAs and tells connected routers.
|
||||
pub fn set_aspas(&self, aspas: Vec<(u32, Vec<u32>)>) {
|
||||
self.change(|data| data.aspas = aspas);
|
||||
}
|
||||
|
||||
fn change(&self, change: impl FnOnce(&mut Data)) {
|
||||
let serial = {
|
||||
let mut data = self.data.lock().unwrap();
|
||||
data.0 = vrps;
|
||||
data.1 += 1;
|
||||
data.1
|
||||
change(&mut data);
|
||||
data.serial += 1;
|
||||
data.serial
|
||||
};
|
||||
self.serial.send_replace(serial);
|
||||
}
|
||||
@@ -75,12 +101,10 @@ impl Drop for TestCache {
|
||||
|
||||
/// One router's connection. Asked for changes, this cache always says to
|
||||
/// ask for everything: the router works out what changed.
|
||||
async fn serve(
|
||||
mut stream: TcpStream,
|
||||
data: Arc<Mutex<(Vec<Vrp>, u32)>>,
|
||||
mut serial: watch::Receiver<u32>,
|
||||
) {
|
||||
async fn serve(mut stream: TcpStream, data: Arc<Mutex<Data>>, mut serial: watch::Receiver<u32>) {
|
||||
let mut buf = BytesMut::new();
|
||||
// The version the router speaks, once it has said something.
|
||||
let mut spoken = VERSION_2;
|
||||
loop {
|
||||
let mut out = BytesMut::new();
|
||||
tokio::select! {
|
||||
@@ -90,17 +114,25 @@ async fn serve(
|
||||
}
|
||||
while let Ok(Some((version, pdu, used))) = Pdu::decode(&buf) {
|
||||
let _ = buf.split_to(used);
|
||||
spoken = version;
|
||||
match pdu {
|
||||
Pdu::SerialQuery { .. } => Pdu::CacheReset.encode(version, &mut out),
|
||||
Pdu::ResetQuery => {
|
||||
let (vrps, serial) = data.lock().unwrap().clone();
|
||||
let data = data.lock().unwrap().clone();
|
||||
Pdu::CacheResponse { session_id: SESSION }.encode(version, &mut out);
|
||||
for vrp in vrps {
|
||||
for vrp in data.vrps {
|
||||
Pdu::Prefix { announce: true, vrp }.encode(version, &mut out);
|
||||
}
|
||||
// Only version 2 has ASPAs.
|
||||
for (customer, providers) in data.aspas {
|
||||
if version >= VERSION_2 {
|
||||
Pdu::Aspa { announce: true, customer, providers }
|
||||
.encode(version, &mut out);
|
||||
}
|
||||
}
|
||||
Pdu::EndOfData {
|
||||
session_id: SESSION,
|
||||
serial,
|
||||
serial: data.serial,
|
||||
timers: Some(Timers::default()),
|
||||
}
|
||||
.encode(version, &mut out);
|
||||
@@ -114,7 +146,7 @@ async fn serve(
|
||||
return;
|
||||
}
|
||||
let serial = *serial.borrow_and_update();
|
||||
Pdu::SerialNotify { session_id: SESSION, serial }.encode(1, &mut out);
|
||||
Pdu::SerialNotify { session_id: SESSION, serial }.encode(spoken, &mut out);
|
||||
}
|
||||
}
|
||||
if stream.write_all(&out).await.is_err() {
|
||||
|
||||
@@ -48,6 +48,7 @@ async fn server_offering(
|
||||
families: FamilySet::supported(),
|
||||
add_path,
|
||||
import: ImportPolicy::permissive(),
|
||||
role: None,
|
||||
}];
|
||||
c.originate = originate;
|
||||
// Keep the dialer out of the way.
|
||||
|
||||
@@ -26,6 +26,7 @@ fn peer_at(endpoint: SocketAddr) -> PeerConfig {
|
||||
families: FamilySet::supported(),
|
||||
add_path: None,
|
||||
import: ImportPolicy::permissive(),
|
||||
role: None,
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,352 @@
|
||||
//! AS path verification with ASPAs, end to end: a server that gets ASPAs
|
||||
//! from a cache, a peer that announces routes, and the ASPAs changing
|
||||
//! under both.
|
||||
|
||||
use std::collections::BTreeSet;
|
||||
use std::net::Ipv4Addr;
|
||||
use std::time::Duration;
|
||||
|
||||
use bgp_integration_tests::test_cache::TestCache;
|
||||
use bgp_integration_tests::test_peer::TestPeer;
|
||||
use bgp_integration_tests::test_server::TestServer;
|
||||
use bgp_packet::message::{
|
||||
AsPathAttribute, Message, NextHopPathAttribute, OriginPathAttribute, PathAttribute,
|
||||
UpdateMessage,
|
||||
};
|
||||
use bgp_primitives::ip_prefix::IpPrefix;
|
||||
use bgp_rpki::{CacheState, RpkiConfig};
|
||||
use bgp_server::config::PeerConfig;
|
||||
use bgp_server::family::FamilySet;
|
||||
use bgp_server::policy::{ImportPolicy, PeerRole};
|
||||
use eyre::Result;
|
||||
use serde_json::Value;
|
||||
use tokio::net::TcpListener;
|
||||
|
||||
const LOCAL_ASN: u32 = 1299;
|
||||
const PEER_ASN: u32 = 3333;
|
||||
const TIMEOUT: Duration = Duration::from_secs(5);
|
||||
|
||||
fn prefix(s: &str) -> IpPrefix {
|
||||
IpPrefix::try_from(s).unwrap()
|
||||
}
|
||||
|
||||
/// What a test holds on to: the server, and the listener its dialling
|
||||
/// goes to.
|
||||
struct Setup {
|
||||
server: TestServer,
|
||||
_listener: TcpListener,
|
||||
}
|
||||
|
||||
/// A server that validates against `cache`, with one configured peer at
|
||||
/// the address the test connects from, which is `role` to it. Returns once
|
||||
/// the server has the cache's data.
|
||||
async fn server_with(
|
||||
cache: &TestCache,
|
||||
role: Option<PeerRole>,
|
||||
import: ImportPolicy,
|
||||
) -> Result<Setup> {
|
||||
let listener = TcpListener::bind((Ipv4Addr::LOCALHOST, 0)).await?;
|
||||
let endpoint = listener.local_addr()?;
|
||||
let cache = cache.address();
|
||||
let server = TestServer::start_with(LOCAL_ASN, |c| {
|
||||
c.accept_unconfigured_peers = false;
|
||||
c.rpki = Some(RpkiConfig {
|
||||
caches: vec![cache],
|
||||
retry: Some(Duration::from_millis(100)),
|
||||
..RpkiConfig::default()
|
||||
});
|
||||
c.peers = vec![PeerConfig {
|
||||
name: "r1".into(),
|
||||
endpoint,
|
||||
remote_asn: PEER_ASN,
|
||||
hold_time_secs: 90,
|
||||
local_address: None,
|
||||
families: FamilySet::supported(),
|
||||
add_path: None,
|
||||
import,
|
||||
role,
|
||||
}];
|
||||
})
|
||||
.await?;
|
||||
let rpki = server.rpki.clone().expect("validation is configured");
|
||||
tokio::time::timeout(TIMEOUT, async {
|
||||
while rpki.status().state != CacheState::Synchronised {
|
||||
tokio::time::sleep(Duration::from_millis(5)).await;
|
||||
}
|
||||
})
|
||||
.await?;
|
||||
Ok(Setup {
|
||||
server,
|
||||
_listener: listener,
|
||||
})
|
||||
}
|
||||
|
||||
async fn established(server: &TestServer) -> Result<TestPeer> {
|
||||
let mut peer = TestPeer::connect(server.addr).await?;
|
||||
peer.handshake(PEER_ASN, Ipv4Addr::new(10, 0, 0, 2), 90)
|
||||
.await?;
|
||||
tokio::time::timeout(TIMEOUT, async {
|
||||
while server.registry.list().is_empty() {
|
||||
tokio::time::sleep(Duration::from_millis(5)).await;
|
||||
}
|
||||
})
|
||||
.await?;
|
||||
Ok(peer)
|
||||
}
|
||||
|
||||
/// Announces `prefixes` with the AS path `path`.
|
||||
async fn announce(peer: &mut TestPeer, prefixes: &[&str], path: &[u32]) -> Result<()> {
|
||||
peer.send(&Message::Update(UpdateMessage {
|
||||
withdrawn_routes: vec![],
|
||||
path_attributes: vec![
|
||||
PathAttribute::Origin(OriginPathAttribute::IGP),
|
||||
AsPathAttribute::from_asns(path.to_vec()),
|
||||
PathAttribute::NextHop(NextHopPathAttribute(Ipv4Addr::new(192, 0, 2, 1))),
|
||||
],
|
||||
announced_routes: prefixes.iter().map(|p| prefix(p).into()).collect(),
|
||||
}))
|
||||
.await
|
||||
}
|
||||
|
||||
/// Waits until the table holds exactly `expected` and the peer has `held`
|
||||
/// routes held aside.
|
||||
async fn table_becomes(server: &TestServer, expected: &[&str], held: u64) -> Result<()> {
|
||||
let expected: BTreeSet<String> = expected.iter().map(|p| p.to_string()).collect();
|
||||
let table = || -> BTreeSet<String> {
|
||||
server
|
||||
.received_routes()
|
||||
.iter()
|
||||
.map(|route| route.prefix.to_string())
|
||||
.collect()
|
||||
};
|
||||
let held_now = || {
|
||||
server
|
||||
.registry
|
||||
.list()
|
||||
.first()
|
||||
.map_or(0, |session| session.counters.held())
|
||||
};
|
||||
tokio::time::timeout(TIMEOUT, async {
|
||||
while table() != expected || held_now() != held {
|
||||
tokio::time::sleep(Duration::from_millis(5)).await;
|
||||
}
|
||||
})
|
||||
.await
|
||||
.map_err(|_| {
|
||||
eyre::eyre!(
|
||||
"the table has {:?} with {} held; expected {expected:?} with {held} held",
|
||||
table(),
|
||||
held_now()
|
||||
)
|
||||
})
|
||||
}
|
||||
|
||||
/// AS 15169 buys from 3356 and says so; 3356 and the peer, 3333, have no
|
||||
/// providers.
|
||||
fn aspas() -> Vec<(u32, Vec<u32>)> {
|
||||
vec![(15169, vec![3356]), (3356, vec![0]), (PEER_ASN, vec![0])]
|
||||
}
|
||||
|
||||
/// A peer that hands us a route it got from one of its own peers is
|
||||
/// leaking it. The route is refused; routes whose paths are attested, or
|
||||
/// that nothing is known about, are taken.
|
||||
#[tokio::test]
|
||||
async fn test_a_leaked_route_is_refused_and_the_rest_are_taken() -> Result<()> {
|
||||
let cache = TestCache::start_with(vec![], aspas()).await;
|
||||
let Setup { server, .. } =
|
||||
server_with(&cache, Some(PeerRole::Peer), ImportPolicy::default()).await?;
|
||||
let (base, _http) = server.serve_http().await?;
|
||||
let mut peer = established(&server).await?;
|
||||
|
||||
// The peer's own route; one of a customer of its that has no ASPA;
|
||||
// and 15169's route by way of 3356, which is not for 3333 to pass on
|
||||
// to a peer: 3356 is not its customer.
|
||||
announce(&mut peer, &["193.0.0.0/21"], &[PEER_ASN]).await?;
|
||||
announce(&mut peer, &["194.0.0.0/24"], &[PEER_ASN, 2121]).await?;
|
||||
announce(&mut peer, &["8.8.8.0/24"], &[PEER_ASN, 3356, 15169]).await?;
|
||||
table_becomes(&server, &["193.0.0.0/21", "194.0.0.0/24"], 1).await?;
|
||||
|
||||
let client = reqwest::Client::new();
|
||||
let get = |path: &str| {
|
||||
let request = client.get(format!("{base}{path}"));
|
||||
async move { Ok::<Value, eyre::Report>(request.send().await?.error_for_status()?.json().await?) }
|
||||
};
|
||||
let routes = get("/api/routes/received").await?;
|
||||
let validity: Vec<(&str, &str)> = routes
|
||||
.as_array()
|
||||
.unwrap()
|
||||
.iter()
|
||||
.map(|r| (r["prefix"].as_str().unwrap(), r["aspa"].as_str().unwrap()))
|
||||
.collect();
|
||||
assert_eq!(
|
||||
validity,
|
||||
[("193.0.0.0/21", "valid"), ("194.0.0.0/24", "unknown")]
|
||||
);
|
||||
let refused = get("/api/routes/rejected").await?;
|
||||
assert_eq!(
|
||||
refused[0]["by_reason"],
|
||||
serde_json::json!({"ASPA invalid": 1})
|
||||
);
|
||||
let peers = get("/api/peers").await?;
|
||||
assert_eq!(peers[0]["role"], "peer");
|
||||
assert_eq!(peers[0]["held_routes"], 1);
|
||||
let rpki = get("/api/rpki").await?;
|
||||
assert_eq!(rpki["aspas"], 3);
|
||||
assert_eq!(rpki["protocol_version"], 2);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// What a path may look like depends on who it comes from. The same route
|
||||
/// that a peer may not pass on is in order from a provider, which may hand
|
||||
/// us anything it has. Where it is not said what the peer is, paths are
|
||||
/// not verified.
|
||||
#[tokio::test]
|
||||
async fn test_a_route_that_has_crossed_a_peering_is_taken_from_a_provider_only() -> Result<()> {
|
||||
for (role, taken) in [
|
||||
(Some(PeerRole::Provider), true),
|
||||
(Some(PeerRole::Customer), false),
|
||||
(Some(PeerRole::Peer), false),
|
||||
(Some(PeerRole::RouteServerClient), false),
|
||||
(None, true),
|
||||
] {
|
||||
let cache = TestCache::start_with(vec![], aspas()).await;
|
||||
let Setup { server, .. } = server_with(&cache, role, ImportPolicy::default()).await?;
|
||||
let mut peer = established(&server).await?;
|
||||
announce(&mut peer, &["8.8.8.0/24"], &[PEER_ASN, 3356, 15169]).await?;
|
||||
let expected = if taken {
|
||||
table_becomes(&server, &["8.8.8.0/24"], 0).await
|
||||
} else {
|
||||
table_becomes(&server, &[], 1).await
|
||||
};
|
||||
expected.map_err(|e| eyre::eyre!("from a peer that is {role:?}: {e}"))?;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// An Internet exchange's route server does not put its AS on the path, so
|
||||
/// the path need not begin with the route server's AS. A leak is refused
|
||||
/// across it all the same.
|
||||
#[tokio::test]
|
||||
async fn test_paths_from_a_route_server_do_not_begin_with_its_as() -> Result<()> {
|
||||
let cache = TestCache::start_with(vec![], aspas()).await;
|
||||
let import = ImportPolicy {
|
||||
accept_any_first_as: true,
|
||||
..ImportPolicy::default()
|
||||
};
|
||||
let Setup { server, .. } = server_with(&cache, Some(PeerRole::RouteServer), import).await?;
|
||||
let mut peer = established(&server).await?;
|
||||
|
||||
// 3356's customer route, as 3356 gave it to the route server.
|
||||
announce(&mut peer, &["8.8.8.0/24"], &[3356, 15169]).await?;
|
||||
// The same route leaked to the route server by another member.
|
||||
announce(&mut peer, &["8.8.4.0/24"], &[2121, 3356, 15169]).await?;
|
||||
table_becomes(&server, &["8.8.8.0/24"], 1).await
|
||||
}
|
||||
|
||||
/// Validity changes with the ASPAs, and the routes follow: one that was
|
||||
/// taken goes when its path becomes invalid, and one that was refused is
|
||||
/// taken when it no longer is. The peer sends nothing more meanwhile.
|
||||
#[tokio::test]
|
||||
async fn test_routes_follow_the_aspas() -> Result<()> {
|
||||
let cache = TestCache::start_with(vec![], aspas()).await;
|
||||
let Setup { server, .. } =
|
||||
server_with(&cache, Some(PeerRole::Peer), ImportPolicy::default()).await?;
|
||||
let mut peer = established(&server).await?;
|
||||
|
||||
announce(&mut peer, &["194.0.0.0/24"], &[PEER_ASN, 2121]).await?;
|
||||
announce(&mut peer, &["8.8.8.0/24"], &[PEER_ASN, 3356, 15169]).await?;
|
||||
table_becomes(&server, &["194.0.0.0/24"], 1).await?;
|
||||
|
||||
// 2121 says that its only provider is 1101: its route by way of 3333
|
||||
// is now invalid, and goes.
|
||||
let mut changed = aspas();
|
||||
changed.push((2121, vec![1101]));
|
||||
cache.set_aspas(changed);
|
||||
table_becomes(&server, &[], 2).await?;
|
||||
let refused = &server.refusals.peers()[0].1;
|
||||
assert_eq!(refused.total(), 2);
|
||||
assert_eq!(refused.recent()[0].prefix, prefix("194.0.0.0/24"));
|
||||
assert_eq!(&refused.recent()[0].as_path[..], [PEER_ASN, 2121]);
|
||||
|
||||
// 3356 becomes a customer of 3333, and 2121 too: both routes come
|
||||
// back, with the paths they were announced with.
|
||||
cache.set_aspas(vec![
|
||||
(15169, vec![3356]),
|
||||
(3356, vec![PEER_ASN]),
|
||||
(PEER_ASN, vec![0]),
|
||||
(2121, vec![1101, PEER_ASN]),
|
||||
]);
|
||||
table_becomes(&server, &["194.0.0.0/24", "8.8.8.0/24"], 0).await?;
|
||||
let paths: BTreeSet<Vec<u32>> = server
|
||||
.received_routes()
|
||||
.iter()
|
||||
.flat_map(|route| &route.attributes)
|
||||
.filter_map(|attribute| match attribute {
|
||||
PathAttribute::AsPath(path) => Some(path.segments[0].path.clone()),
|
||||
_ => None,
|
||||
})
|
||||
.collect();
|
||||
assert_eq!(
|
||||
paths,
|
||||
[vec![PEER_ASN, 2121], vec![PEER_ASN, 3356, 15169]].into()
|
||||
);
|
||||
|
||||
// The validator loses its ASPAs altogether: nothing is known about
|
||||
// any path, and nothing is refused.
|
||||
cache.set_aspas(vec![
|
||||
(15169, vec![3356]),
|
||||
(3356, vec![0]),
|
||||
(PEER_ASN, vec![0]),
|
||||
]);
|
||||
table_becomes(&server, &["194.0.0.0/24"], 1).await?;
|
||||
cache.set_aspas(vec![]);
|
||||
table_becomes(&server, &["194.0.0.0/24", "8.8.8.0/24"], 0).await
|
||||
}
|
||||
|
||||
/// A peer can be exempt: its routes with invalid paths are taken, and
|
||||
/// shown for what they are.
|
||||
#[tokio::test]
|
||||
async fn test_a_peer_can_be_exempt_from_path_verification() -> Result<()> {
|
||||
let cache = TestCache::start_with(vec![], aspas()).await;
|
||||
let import = ImportPolicy {
|
||||
accept_aspa_invalid: true,
|
||||
..ImportPolicy::default()
|
||||
};
|
||||
let Setup { server, .. } = server_with(&cache, Some(PeerRole::Peer), import).await?;
|
||||
let (base, _http) = server.serve_http().await?;
|
||||
let mut peer = established(&server).await?;
|
||||
|
||||
announce(&mut peer, &["8.8.8.0/24"], &[PEER_ASN, 3356, 15169]).await?;
|
||||
table_becomes(&server, &["8.8.8.0/24"], 0).await?;
|
||||
let routes: Value = reqwest::get(format!("{base}/api/routes/received"))
|
||||
.await?
|
||||
.json()
|
||||
.await?;
|
||||
assert_eq!(routes[0]["aspa"], "invalid");
|
||||
assert!(server.refusals.peers().is_empty());
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// A route can fail both checks. It is taken only when it passes both.
|
||||
#[tokio::test]
|
||||
async fn test_a_route_is_held_until_origin_and_path_are_both_in_order() -> Result<()> {
|
||||
let google = |asn| bgp_rpki::Vrp {
|
||||
prefix: prefix("8.8.8.0/24"),
|
||||
max_length: 24,
|
||||
asn,
|
||||
};
|
||||
let cache = TestCache::start_with(vec![google(13335)], aspas()).await;
|
||||
let Setup { server, .. } =
|
||||
server_with(&cache, Some(PeerRole::Peer), ImportPolicy::default()).await?;
|
||||
let mut peer = established(&server).await?;
|
||||
announce(&mut peer, &["8.8.8.0/24"], &[PEER_ASN, 3356, 15169]).await?;
|
||||
table_becomes(&server, &[], 1).await?;
|
||||
|
||||
// The origin is put right; the path is still a leak.
|
||||
cache.set(vec![google(15169)]);
|
||||
tokio::time::sleep(Duration::from_millis(500)).await;
|
||||
table_becomes(&server, &[], 1).await?;
|
||||
|
||||
cache.set_aspas(vec![(15169, vec![3356]), (3356, vec![PEER_ASN])]);
|
||||
table_becomes(&server, &["8.8.8.0/24"], 0).await
|
||||
}
|
||||
@@ -44,6 +44,7 @@ async fn server_dialing(listener: &TcpListener) -> Result<TestServer> {
|
||||
families: FamilySet::supported(),
|
||||
add_path: None,
|
||||
import: ImportPolicy::permissive(),
|
||||
role: None,
|
||||
}];
|
||||
})
|
||||
.await
|
||||
|
||||
@@ -175,6 +175,7 @@ async fn test_refused_routes_can_be_seen_and_counted() -> Result<()> {
|
||||
max_prefixes: Some(100),
|
||||
..ImportPolicy::default()
|
||||
},
|
||||
role: None,
|
||||
}];
|
||||
})
|
||||
.await?;
|
||||
|
||||
@@ -35,6 +35,7 @@ async fn server_dialing(listener: &TcpListener, remote_asn: u32) -> Result<TestS
|
||||
families: FamilySet::supported(),
|
||||
add_path: None,
|
||||
import: ImportPolicy::permissive(),
|
||||
role: None,
|
||||
}];
|
||||
})
|
||||
.await
|
||||
@@ -190,6 +191,7 @@ async fn test_inbound_from_configured_address_uses_config() -> Result<()> {
|
||||
families: FamilySet::supported(),
|
||||
add_path: None,
|
||||
import: ImportPolicy::permissive(),
|
||||
role: None,
|
||||
}];
|
||||
})
|
||||
.await?;
|
||||
@@ -239,6 +241,7 @@ async fn test_local_address_is_used_as_the_source() -> Result<()> {
|
||||
families: FamilySet::supported(),
|
||||
add_path: None,
|
||||
import: ImportPolicy::permissive(),
|
||||
role: None,
|
||||
}];
|
||||
})
|
||||
.await?;
|
||||
@@ -257,6 +260,7 @@ async fn test_local_address_is_used_as_the_source() -> Result<()> {
|
||||
families: FamilySet::supported(),
|
||||
add_path: None,
|
||||
import: ImportPolicy::permissive(),
|
||||
role: None,
|
||||
}];
|
||||
})
|
||||
.await?;
|
||||
|
||||
@@ -48,6 +48,7 @@ async fn server_with(import: ImportPolicy) -> Result<(TestServer, TcpListener)>
|
||||
families: FamilySet::supported(),
|
||||
add_path: None,
|
||||
import,
|
||||
role: None,
|
||||
}];
|
||||
})
|
||||
.await?;
|
||||
|
||||
@@ -191,6 +191,7 @@ async fn test_no_family_in_common_refuses_the_session() -> Result<()> {
|
||||
families: only(AfiSafi::IPV6_UNICAST),
|
||||
add_path: None,
|
||||
import: ImportPolicy::permissive(),
|
||||
role: None,
|
||||
}];
|
||||
// Keep the dialer out of the way.
|
||||
c.connect_retry = Duration::from_secs(3600);
|
||||
|
||||
@@ -73,6 +73,7 @@ async fn server_with(
|
||||
families: FamilySet::supported(),
|
||||
add_path: None,
|
||||
import,
|
||||
role: None,
|
||||
}];
|
||||
})
|
||||
.await?;
|
||||
@@ -335,6 +336,6 @@ async fn test_held_routes_are_taken_when_the_validation_data_expires() -> Result
|
||||
|
||||
drop(cache);
|
||||
table_becomes(&server, &["193.0.4.0/24"], 0).await?;
|
||||
assert!(server.rpki.as_ref().unwrap().current().table.is_empty());
|
||||
assert!(server.rpki.as_ref().unwrap().current().vrps.is_empty());
|
||||
Ok(())
|
||||
}
|
||||
Reference in new issue
Block a user