2068 lines
71 KiB
Rust
2068 lines
71 KiB
Rust
mod common;
|
|
|
|
use std::collections::VecDeque;
|
|
use std::fs::File;
|
|
use std::io::BufReader;
|
|
use std::net::{Ipv4Addr, Ipv6Addr, SocketAddr};
|
|
use std::path::{Path, PathBuf};
|
|
use std::sync::Arc;
|
|
|
|
use arc_swap::ArcSwap;
|
|
use rustls::{ClientConfig, RootCertStore};
|
|
use rustls_pki_types::{CertificateDer, PrivateKeyDer, ServerName};
|
|
use serde_json::json;
|
|
use tokio::io::{AsyncReadExt, AsyncWrite, AsyncWriteExt};
|
|
use tokio::net::{TcpListener, TcpStream};
|
|
use tokio::sync::{broadcast, watch};
|
|
use tokio::task::JoinHandle;
|
|
use tokio::time::{Duration, timeout};
|
|
use tokio_rustls::{TlsAcceptor, TlsConnector};
|
|
|
|
use common::test_helper::{
|
|
RtrDebugDumper, dump_cache_reset, dump_cache_response, dump_eod_v1, dump_ipv4_prefix,
|
|
dump_ipv6_prefix,
|
|
};
|
|
|
|
use rpki::data_model::resources::as_resources::Asn;
|
|
use rpki::data_model::resources::ip_resources::{IPAddress, IPAddressPrefix};
|
|
use rpki::rtr::cache::{Delta, RtrCacheBuilder, SessionIds, SharedRtrCache, Snapshot};
|
|
use rpki::rtr::error_type::ErrorCode;
|
|
use rpki::rtr::payload::{Aspa, Payload, RouteOrigin, RouterKey, Ski, Timing};
|
|
use rpki::rtr::pdu::{
|
|
Aspa as AspaPdu, CacheReset, CacheResponse, EndOfDataV1, ErrorReport, Header, IPv4Prefix,
|
|
IPv6Prefix, ResetQuery, RouterKey as RouterKeyPdu, SerialNotify, SerialQuery,
|
|
};
|
|
use rpki::rtr::server::connection::handle_tls_connection;
|
|
use rpki::rtr::server::tls::load_rustls_server_config_with_options;
|
|
use rpki::rtr::server::{RtrServiceControl, RtrShutdownReason};
|
|
use rpki::rtr::session::RtrSession;
|
|
use rpki::rtr::store::RtrStore;
|
|
|
|
fn shared_cache(cache: rpki::rtr::cache::RtrCache) -> SharedRtrCache {
|
|
Arc::new(ArcSwap::from_pointee(cache))
|
|
}
|
|
|
|
async fn start_session_server(
|
|
cache: SharedRtrCache,
|
|
) -> (
|
|
SocketAddr,
|
|
broadcast::Sender<()>,
|
|
watch::Sender<RtrServiceControl>,
|
|
JoinHandle<()>,
|
|
) {
|
|
let listener = TcpListener::bind("127.0.0.1:0").await.unwrap();
|
|
let addr = listener.local_addr().unwrap();
|
|
|
|
let (notify_tx, notify_rx) = broadcast::channel(16);
|
|
let (shutdown_tx, shutdown_rx) = watch::channel(RtrServiceControl::Running);
|
|
|
|
let handle = tokio::spawn(async move {
|
|
let Ok((stream, _)) = listener.accept().await else {
|
|
return;
|
|
};
|
|
|
|
let session = RtrSession::new(cache, stream, notify_rx, shutdown_rx);
|
|
let _ = session.run().await;
|
|
});
|
|
|
|
(addr, notify_tx, shutdown_tx, handle)
|
|
}
|
|
|
|
async fn start_session_server_with_transport_timeout(
|
|
cache: SharedRtrCache,
|
|
transport_timeout: Duration,
|
|
) -> (
|
|
SocketAddr,
|
|
broadcast::Sender<()>,
|
|
watch::Sender<RtrServiceControl>,
|
|
JoinHandle<()>,
|
|
) {
|
|
let listener = TcpListener::bind("127.0.0.1:0").await.unwrap();
|
|
let addr = listener.local_addr().unwrap();
|
|
|
|
let (notify_tx, notify_rx) = broadcast::channel(16);
|
|
let (shutdown_tx, shutdown_rx) = watch::channel(RtrServiceControl::Running);
|
|
|
|
let handle = tokio::spawn(async move {
|
|
let Ok((stream, _)) = listener.accept().await else {
|
|
return;
|
|
};
|
|
|
|
let session = RtrSession::new(cache, stream, notify_rx, shutdown_rx)
|
|
.with_transport_timeout(transport_timeout);
|
|
let _ = session.run().await;
|
|
});
|
|
|
|
(addr, notify_tx, shutdown_tx, handle)
|
|
}
|
|
|
|
async fn start_session_server_returning_result(
|
|
cache: SharedRtrCache,
|
|
) -> (
|
|
SocketAddr,
|
|
watch::Sender<RtrServiceControl>,
|
|
JoinHandle<anyhow::Result<()>>,
|
|
) {
|
|
let listener = TcpListener::bind("127.0.0.1:0").await.unwrap();
|
|
let addr = listener.local_addr().unwrap();
|
|
|
|
let (_notify_tx, notify_rx) = broadcast::channel(16);
|
|
let (shutdown_tx, shutdown_rx) = watch::channel(RtrServiceControl::Running);
|
|
|
|
let handle = tokio::spawn(async move {
|
|
let (stream, _) = listener.accept().await.unwrap();
|
|
let session = RtrSession::new(cache, stream, notify_rx, shutdown_rx);
|
|
session.run().await
|
|
});
|
|
|
|
(addr, shutdown_tx, handle)
|
|
}
|
|
|
|
async fn start_tls_session_server(
|
|
cache: SharedRtrCache,
|
|
) -> (SocketAddr, watch::Sender<RtrServiceControl>, JoinHandle<()>) {
|
|
start_tls_session_server_with_cert(cache, "server.crt", "server.key").await
|
|
}
|
|
|
|
async fn start_tls_session_server_with_cert(
|
|
cache: SharedRtrCache,
|
|
cert_name: &str,
|
|
key_name: &str,
|
|
) -> (SocketAddr, watch::Sender<RtrServiceControl>, JoinHandle<()>) {
|
|
let listener = TcpListener::bind("127.0.0.1:0").await.unwrap();
|
|
let addr = listener.local_addr().unwrap();
|
|
|
|
let (_notify_tx, notify_rx) = broadcast::channel(16);
|
|
let (shutdown_tx, shutdown_rx) = watch::channel(RtrServiceControl::Running);
|
|
let tls_config = Arc::new(
|
|
load_rustls_server_config_with_options(
|
|
fixture_path(cert_name),
|
|
fixture_path(key_name),
|
|
fixture_path("client-ca.crt"),
|
|
false,
|
|
)
|
|
.unwrap(),
|
|
);
|
|
let acceptor = TlsAcceptor::from(tls_config);
|
|
|
|
let handle = tokio::spawn(async move {
|
|
let Ok((stream, peer_addr)) = listener.accept().await else {
|
|
return;
|
|
};
|
|
|
|
let _ = handle_tls_connection(
|
|
cache,
|
|
stream,
|
|
peer_addr,
|
|
acceptor,
|
|
true,
|
|
notify_rx,
|
|
shutdown_rx,
|
|
None,
|
|
)
|
|
.await;
|
|
});
|
|
|
|
(addr, shutdown_tx, handle)
|
|
}
|
|
|
|
async fn shutdown_server(
|
|
mut client: TcpStream,
|
|
shutdown_tx: watch::Sender<RtrServiceControl>,
|
|
server_handle: JoinHandle<()>,
|
|
) {
|
|
shutdown_io(&mut client, shutdown_tx, server_handle).await;
|
|
}
|
|
|
|
async fn shutdown_io<S>(
|
|
io: &mut S,
|
|
shutdown_tx: watch::Sender<RtrServiceControl>,
|
|
server_handle: JoinHandle<()>,
|
|
) where
|
|
S: AsyncWrite + Unpin,
|
|
{
|
|
let _ = io.shutdown().await;
|
|
|
|
let _ = shutdown_tx.send(RtrServiceControl::Shutdown(RtrShutdownReason::CloseOnly));
|
|
|
|
match timeout(Duration::from_secs(1), server_handle).await {
|
|
Ok(join_res) => {
|
|
let _ = join_res;
|
|
}
|
|
Err(_) => {
|
|
panic!("server task did not exit within timeout");
|
|
}
|
|
}
|
|
}
|
|
|
|
fn fixture_path(name: &str) -> PathBuf {
|
|
Path::new(env!("CARGO_MANIFEST_DIR"))
|
|
.join("tests")
|
|
.join("fixtures")
|
|
.join("tls")
|
|
.join(name)
|
|
}
|
|
|
|
fn load_pem_certs(path: &Path) -> Vec<CertificateDer<'static>> {
|
|
let file = File::open(path).unwrap();
|
|
let mut reader = BufReader::new(file);
|
|
rustls_pemfile::certs(&mut reader)
|
|
.collect::<Result<Vec<_>, _>>()
|
|
.unwrap()
|
|
}
|
|
|
|
fn load_pem_key(path: &Path) -> PrivateKeyDer<'static> {
|
|
let file = File::open(path).unwrap();
|
|
let mut reader = BufReader::new(file);
|
|
rustls_pemfile::private_key(&mut reader)
|
|
.unwrap()
|
|
.expect("missing PEM private key")
|
|
}
|
|
|
|
async fn connect_tls_client(
|
|
addr: SocketAddr,
|
|
cert_name: &str,
|
|
key_name: &str,
|
|
) -> tokio_rustls::client::TlsStream<TcpStream> {
|
|
connect_tls_client_with_server_name(
|
|
addr,
|
|
cert_name,
|
|
key_name,
|
|
ServerName::IpAddress(addr.ip().into()),
|
|
)
|
|
.await
|
|
}
|
|
|
|
async fn connect_tls_client_with_server_name(
|
|
addr: SocketAddr,
|
|
cert_name: &str,
|
|
key_name: &str,
|
|
server_name: ServerName<'static>,
|
|
) -> tokio_rustls::client::TlsStream<TcpStream> {
|
|
let mut roots = RootCertStore::empty();
|
|
for cert in load_pem_certs(&fixture_path("client-ca.crt")) {
|
|
roots.add(cert).unwrap();
|
|
}
|
|
|
|
let certs = load_pem_certs(&fixture_path(cert_name));
|
|
let key = load_pem_key(&fixture_path(key_name));
|
|
let client_config = ClientConfig::builder()
|
|
.with_root_certificates(roots)
|
|
.with_client_auth_cert(certs, key)
|
|
.unwrap();
|
|
|
|
let connector = TlsConnector::from(Arc::new(client_config));
|
|
let tcp = TcpStream::connect(addr).await.unwrap();
|
|
connector.connect(server_name, tcp).await.unwrap()
|
|
}
|
|
|
|
/// Dump Serial Notify in the same style as the other test helpers.
|
|
fn dump_serial_notify(notify: &SerialNotify) -> serde_json::Value {
|
|
json!({
|
|
"version": notify.version(),
|
|
"pdu": notify.pdu(),
|
|
"pdu_name": "Serial Notify",
|
|
"session_id": notify.session_id(),
|
|
"serial_number": notify.serial_number(),
|
|
})
|
|
}
|
|
|
|
fn assert_error_report_matches(
|
|
report: &ErrorReport,
|
|
version: u8,
|
|
code: ErrorCode,
|
|
offending_pdu: &[u8],
|
|
) {
|
|
assert_eq!(report.version(), version);
|
|
assert_eq!(report.error_code(), Ok(code));
|
|
assert_eq!(report.erroneous_pdu(), offending_pdu);
|
|
}
|
|
|
|
fn serials_all(serial: u32) -> [u32; 3] {
|
|
[serial; 3]
|
|
}
|
|
|
|
fn snapshots_all(snapshot: Snapshot) -> [Snapshot; 3] {
|
|
[snapshot.clone(), snapshot.clone(), snapshot]
|
|
}
|
|
|
|
fn deltas_all(deltas: VecDeque<Arc<Delta>>) -> [VecDeque<Arc<Delta>>; 3] {
|
|
[deltas.clone(), deltas.clone(), deltas]
|
|
}
|
|
|
|
async fn wait_for_store_serials(store: &RtrStore, expected: [u32; 3]) {
|
|
for _ in 0..100 {
|
|
let current = [
|
|
store.get_serial_for_version(0).unwrap(),
|
|
store.get_serial_for_version(1).unwrap(),
|
|
store.get_serial_for_version(2).unwrap(),
|
|
];
|
|
if current == [Some(expected[0]), Some(expected[1]), Some(expected[2])] {
|
|
return;
|
|
}
|
|
tokio::time::sleep(Duration::from_millis(20)).await;
|
|
}
|
|
|
|
panic!(
|
|
"timed out waiting store serials {:?}, current={:?}",
|
|
expected,
|
|
[
|
|
store.get_serial_for_version(0).unwrap(),
|
|
store.get_serial_for_version(1).unwrap(),
|
|
store.get_serial_for_version(2).unwrap(),
|
|
]
|
|
);
|
|
}
|
|
|
|
/// Reset Query returns a full snapshot and finishes with End of Data.
|
|
#[tokio::test]
|
|
async fn reset_query_returns_snapshot_and_end_of_data() {
|
|
let prefix = IPAddressPrefix {
|
|
address: IPAddress::from_ipv4(Ipv4Addr::new(192, 0, 2, 0)),
|
|
prefix_length: 24,
|
|
};
|
|
let origin = RouteOrigin::new(prefix, 24, 64496u32.into());
|
|
|
|
let snapshot = Snapshot::from_payloads(vec![Payload::RouteOrigin(origin)]);
|
|
let cache = RtrCacheBuilder::new()
|
|
.session_ids(SessionIds::from_array([42, 42, 42]))
|
|
.serials(serials_all(100))
|
|
.timing(Timing::new(600, 600, 7200))
|
|
.snapshots(snapshots_all(snapshot))
|
|
.build();
|
|
|
|
let server_cache = shared_cache(cache);
|
|
let (addr, _notify_tx, shutdown_tx, server_handle) = start_session_server(server_cache).await;
|
|
|
|
let mut client = TcpStream::connect(addr).await.unwrap();
|
|
ResetQuery::new(1).write(&mut client).await.unwrap();
|
|
|
|
let mut dump = RtrDebugDumper::new();
|
|
|
|
let response = CacheResponse::read(&mut client).await.unwrap();
|
|
dump.push_value(response.pdu(), dump_cache_response(&response));
|
|
assert_eq!(response.pdu(), 3);
|
|
assert_eq!(response.version(), 1);
|
|
assert_eq!(response.session_id(), 42);
|
|
|
|
let prefix = IPv4Prefix::read(&mut client).await.unwrap();
|
|
dump.push_value(prefix.pdu(), dump_ipv4_prefix(&prefix));
|
|
assert_eq!(prefix.pdu(), 4);
|
|
assert_eq!(prefix.version(), 1);
|
|
assert!(prefix.flag().is_announce());
|
|
assert_eq!(prefix.prefix_len(), 24);
|
|
assert_eq!(prefix.max_len(), 24);
|
|
assert_eq!(prefix.prefix(), Ipv4Addr::new(192, 0, 2, 0));
|
|
assert_eq!(prefix.asn(), 64496u32.into());
|
|
|
|
let eod = EndOfDataV1::read(&mut client).await.unwrap();
|
|
dump.push_value(eod.pdu(), dump_eod_v1(&eod));
|
|
assert_eq!(eod.pdu(), 7);
|
|
assert_eq!(eod.version(), 1);
|
|
assert_eq!(eod.session_id(), 42);
|
|
assert_eq!(eod.serial_number(), 100);
|
|
let timing = eod.timing();
|
|
assert_eq!(timing.refresh, 600);
|
|
assert_eq!(timing.retry, 600);
|
|
assert_eq!(timing.expire, 7200);
|
|
|
|
dump.print_pretty("reset_query_returns_snapshot_and_end_of_data");
|
|
shutdown_server(client, shutdown_tx, server_handle).await;
|
|
}
|
|
|
|
#[tokio::test]
|
|
async fn reset_query_uses_version_specific_session_id() {
|
|
let cache = RtrCacheBuilder::new()
|
|
.session_ids(SessionIds::from_array([40, 41, 42]))
|
|
.serials(serials_all(100))
|
|
.timing(Timing::new(600, 600, 7200))
|
|
.build();
|
|
|
|
let server_cache = shared_cache(cache);
|
|
let (addr, _notify_tx, shutdown_tx, server_handle) = start_session_server(server_cache).await;
|
|
|
|
let mut client = TcpStream::connect(addr).await.unwrap();
|
|
ResetQuery::new(2).write(&mut client).await.unwrap();
|
|
|
|
let mut dump = RtrDebugDumper::new();
|
|
|
|
let response = CacheResponse::read(&mut client).await.unwrap();
|
|
dump.push_value(response.pdu(), dump_cache_response(&response));
|
|
assert_eq!(response.version(), 2);
|
|
assert_eq!(response.session_id(), 42);
|
|
|
|
let eod = EndOfDataV1::read(&mut client).await.unwrap();
|
|
dump.push_value(eod.pdu(), dump_eod_v1(&eod));
|
|
assert_eq!(eod.version(), 2);
|
|
assert_eq!(eod.session_id(), 42);
|
|
assert_eq!(eod.serial_number(), 100);
|
|
|
|
dump.print_pretty("reset_query_uses_version_specific_session_id");
|
|
shutdown_server(client, shutdown_tx, server_handle).await;
|
|
}
|
|
|
|
#[tokio::test]
|
|
async fn version_two_cache_restart_shutdown_sends_error_code_12() {
|
|
let cache = RtrCacheBuilder::new()
|
|
.session_ids(SessionIds::from_array([40, 41, 42]))
|
|
.serials(serials_all(100))
|
|
.timing(Timing::new(600, 600, 7200))
|
|
.build();
|
|
|
|
let server_cache = shared_cache(cache);
|
|
let (addr, _notify_tx, shutdown_tx, server_handle) = start_session_server(server_cache).await;
|
|
|
|
let mut client = TcpStream::connect(addr).await.unwrap();
|
|
ResetQuery::new(2).write(&mut client).await.unwrap();
|
|
let _response = CacheResponse::read(&mut client).await.unwrap();
|
|
let _eod = EndOfDataV1::read(&mut client).await.unwrap();
|
|
|
|
shutdown_tx
|
|
.send(RtrServiceControl::Shutdown(RtrShutdownReason::CacheRestart))
|
|
.unwrap();
|
|
|
|
let report = ErrorReport::read(&mut client).await.unwrap();
|
|
assert_error_report_matches(&report, 2, ErrorCode::CacheRestart, &[]);
|
|
assert!(String::from_utf8_lossy(report.text()).contains("restarting"));
|
|
|
|
timeout(Duration::from_secs(1), server_handle)
|
|
.await
|
|
.expect("server task did not exit within timeout")
|
|
.unwrap();
|
|
}
|
|
|
|
#[tokio::test]
|
|
async fn version_one_cache_restart_shutdown_does_not_send_v2_error_code() {
|
|
let cache = RtrCacheBuilder::new()
|
|
.session_ids(SessionIds::from_array([40, 41, 42]))
|
|
.serials(serials_all(100))
|
|
.timing(Timing::new(600, 600, 7200))
|
|
.build();
|
|
|
|
let server_cache = shared_cache(cache);
|
|
let (addr, _notify_tx, shutdown_tx, server_handle) = start_session_server(server_cache).await;
|
|
|
|
let mut client = TcpStream::connect(addr).await.unwrap();
|
|
ResetQuery::new(1).write(&mut client).await.unwrap();
|
|
let _response = CacheResponse::read(&mut client).await.unwrap();
|
|
let _eod = EndOfDataV1::read(&mut client).await.unwrap();
|
|
|
|
shutdown_tx
|
|
.send(RtrServiceControl::Shutdown(RtrShutdownReason::CacheRestart))
|
|
.unwrap();
|
|
|
|
let read_res = timeout(Duration::from_secs(1), Header::read(&mut client))
|
|
.await
|
|
.expect("timed out waiting for session close");
|
|
assert!(
|
|
read_res.is_err(),
|
|
"v1 session must not receive v2 error code 12"
|
|
);
|
|
|
|
timeout(Duration::from_secs(1), server_handle)
|
|
.await
|
|
.expect("server task did not exit within timeout")
|
|
.unwrap();
|
|
}
|
|
|
|
#[tokio::test]
|
|
async fn restart_restores_versioned_state_and_serves_queries() {
|
|
let dir = tempfile::tempdir().unwrap();
|
|
let store = RtrStore::open(dir.path()).unwrap();
|
|
|
|
let prefix = IPAddressPrefix {
|
|
address: IPAddress::from_ipv4(Ipv4Addr::new(192, 0, 2, 0)),
|
|
prefix_length: 24,
|
|
};
|
|
let origin = Payload::RouteOrigin(RouteOrigin::new(prefix, 24, 64496u32.into()));
|
|
let valid_spki = vec![
|
|
0x30, 0x13, 0x30, 0x0d, 0x06, 0x09, 0x2a, 0x86, 0x48, 0x86, 0xf7, 0x0d, 0x01, 0x01, 0x01,
|
|
0x05, 0x00, 0x03, 0x02, 0x00, 0x00,
|
|
];
|
|
let router_key = Payload::RouterKey(RouterKey::new(
|
|
Ski::default(),
|
|
Asn::from(64496u32),
|
|
valid_spki.clone(),
|
|
));
|
|
let aspa_old = Payload::Aspa(Aspa::new(Asn::from(64496u32), vec![Asn::from(64497u32)]));
|
|
let aspa_new = Payload::Aspa(Aspa::new(
|
|
Asn::from(64496u32),
|
|
vec![Asn::from(64497u32), Asn::from(64498u32)],
|
|
));
|
|
|
|
let initial_payloads = vec![origin.clone(), router_key.clone(), aspa_old];
|
|
let mut cache = rpki::rtr::cache::RtrCache::default()
|
|
.init(&store, 16, false, Timing::new(600, 600, 7200), || {
|
|
Ok(initial_payloads.clone())
|
|
})
|
|
.unwrap();
|
|
|
|
cache
|
|
.update(vec![origin.clone(), router_key.clone(), aspa_new], &store)
|
|
.unwrap();
|
|
wait_for_store_serials(&store, [1, 1, 2]).await;
|
|
|
|
let restored = rpki::rtr::cache::RtrCache::default()
|
|
.init(&store, 16, false, Timing::new(600, 600, 7200), || {
|
|
panic!("file loader should not be used when restoring from store")
|
|
})
|
|
.unwrap();
|
|
assert_eq!(restored.serial_for_version(0), 1);
|
|
assert_eq!(restored.serial_for_version(1), 1);
|
|
assert_eq!(restored.serial_for_version(2), 2);
|
|
|
|
let shared = shared_cache(restored);
|
|
|
|
let (addr, _notify_tx, shutdown_tx, server_handle) = start_session_server(shared.clone()).await;
|
|
let mut client = TcpStream::connect(addr).await.unwrap();
|
|
ResetQuery::new(0).write(&mut client).await.unwrap();
|
|
let response = CacheResponse::read(&mut client).await.unwrap();
|
|
assert_eq!(response.version(), 0);
|
|
let expected_sid_v0 = shared.load_full().session_id_for_version(0);
|
|
assert_eq!(response.session_id(), expected_sid_v0);
|
|
let _v4 = IPv4Prefix::read(&mut client).await.unwrap();
|
|
let eod_v0 = rpki::rtr::pdu::EndOfDataV0::read(&mut client)
|
|
.await
|
|
.unwrap();
|
|
assert_eq!(eod_v0.serial_number(), 1);
|
|
shutdown_server(client, shutdown_tx, server_handle).await;
|
|
|
|
let (addr, _notify_tx, shutdown_tx, server_handle) = start_session_server(shared.clone()).await;
|
|
let mut client = TcpStream::connect(addr).await.unwrap();
|
|
ResetQuery::new(1).write(&mut client).await.unwrap();
|
|
let response = CacheResponse::read(&mut client).await.unwrap();
|
|
assert_eq!(response.version(), 1);
|
|
let expected_sid_v1 = shared.load_full().session_id_for_version(1);
|
|
assert_eq!(response.session_id(), expected_sid_v1);
|
|
let _v4 = IPv4Prefix::read(&mut client).await.unwrap();
|
|
let _rk = RouterKeyPdu::read(&mut client).await.unwrap();
|
|
let eod_v1 = EndOfDataV1::read(&mut client).await.unwrap();
|
|
assert_eq!(eod_v1.serial_number(), 1);
|
|
shutdown_server(client, shutdown_tx, server_handle).await;
|
|
|
|
let (addr, _notify_tx, shutdown_tx, server_handle) = start_session_server(shared.clone()).await;
|
|
let mut client = TcpStream::connect(addr).await.unwrap();
|
|
let sid_v2 = shared.load_full().session_id_for_version(2);
|
|
SerialQuery::new(2, sid_v2, 1)
|
|
.write(&mut client)
|
|
.await
|
|
.unwrap();
|
|
let response = CacheResponse::read(&mut client).await.unwrap();
|
|
assert_eq!(response.version(), 2);
|
|
assert_eq!(response.session_id(), sid_v2);
|
|
let _aspa = AspaPdu::read(&mut client).await.unwrap();
|
|
let eod_v2 = EndOfDataV1::read(&mut client).await.unwrap();
|
|
assert_eq!(eod_v2.serial_number(), 2);
|
|
shutdown_server(client, shutdown_tx, server_handle).await;
|
|
}
|
|
|
|
/// Serial Query returns only End of Data when the client is up to date.
|
|
#[tokio::test]
|
|
async fn serial_query_returns_end_of_data_when_up_to_date() {
|
|
let cache = RtrCacheBuilder::new()
|
|
.session_ids(SessionIds::from_array([42, 42, 42]))
|
|
.serials(serials_all(100))
|
|
.timing(Timing {
|
|
refresh: 600,
|
|
retry: 600,
|
|
expire: 7200,
|
|
})
|
|
.build();
|
|
|
|
let server_cache = shared_cache(cache);
|
|
let (addr, _notify_tx, shutdown_tx, server_handle) = start_session_server(server_cache).await;
|
|
|
|
let mut client = TcpStream::connect(addr).await.unwrap();
|
|
SerialQuery::new(1, 42, 100)
|
|
.write(&mut client)
|
|
.await
|
|
.unwrap();
|
|
|
|
let mut dump = RtrDebugDumper::new();
|
|
|
|
let response = CacheResponse::read(&mut client).await.unwrap();
|
|
dump.push_value(response.pdu(), dump_cache_response(&response));
|
|
assert_eq!(response.pdu(), 3);
|
|
assert_eq!(response.version(), 1);
|
|
assert_eq!(response.session_id(), 42);
|
|
|
|
let eod = EndOfDataV1::read(&mut client).await.unwrap();
|
|
dump.push_value(eod.pdu(), dump_eod_v1(&eod));
|
|
assert_eq!(eod.pdu(), 7);
|
|
assert_eq!(eod.version(), 1);
|
|
assert_eq!(eod.session_id(), 42);
|
|
assert_eq!(eod.serial_number(), 100);
|
|
|
|
let timing = eod.timing();
|
|
assert_eq!(timing.refresh, 600);
|
|
assert_eq!(timing.retry, 600);
|
|
assert_eq!(timing.expire, 7200);
|
|
|
|
dump.print_pretty("serial_query_returns_end_of_data_when_up_to_date");
|
|
shutdown_server(client, shutdown_tx, server_handle).await;
|
|
}
|
|
|
|
/// Established sessions reject mismatched Session IDs with Corrupt Data.
|
|
#[tokio::test]
|
|
async fn serial_query_returns_corrupt_data_when_session_id_mismatch() {
|
|
let cache = RtrCacheBuilder::new()
|
|
.session_ids(SessionIds::from_array([42, 42, 42]))
|
|
.serials(serials_all(100))
|
|
.timing(Timing {
|
|
refresh: 600,
|
|
retry: 600,
|
|
expire: 7200,
|
|
})
|
|
.build();
|
|
|
|
let server_cache = shared_cache(cache);
|
|
let (addr, _notify_tx, shutdown_tx, server_handle) = start_session_server(server_cache).await;
|
|
|
|
let mut client = TcpStream::connect(addr).await.unwrap();
|
|
SerialQuery::new(1, 999, 100)
|
|
.write(&mut client)
|
|
.await
|
|
.unwrap();
|
|
|
|
let mut dump = RtrDebugDumper::new();
|
|
|
|
let report = ErrorReport::read(&mut client).await.unwrap();
|
|
assert_error_report_matches(
|
|
&report,
|
|
1,
|
|
ErrorCode::CorruptData,
|
|
SerialQuery::new(1, 999, 100).as_ref(),
|
|
);
|
|
|
|
let read_res = Header::read(&mut client).await;
|
|
assert!(read_res.is_err());
|
|
|
|
dump.push_value(
|
|
0,
|
|
json!({
|
|
"event": "connection_closed_after_corrupt_session_id",
|
|
"result": "header_read_failed_as_expected"
|
|
}),
|
|
);
|
|
|
|
dump.print_pretty("serial_query_returns_corrupt_data_when_session_id_mismatch");
|
|
shutdown_server(client, shutdown_tx, server_handle).await;
|
|
}
|
|
|
|
/// Serial Query returns Cache Response, delta payloads, and End of Data when incremental updates exist.
|
|
#[tokio::test]
|
|
async fn serial_query_returns_deltas_when_incremental_update_available() {
|
|
let prefix = IPAddressPrefix {
|
|
address: IPAddress::from_ipv4(Ipv4Addr::new(192, 0, 2, 0)),
|
|
prefix_length: 24,
|
|
};
|
|
let origin = RouteOrigin::new(prefix, 24, 64496u32.into());
|
|
|
|
let delta = Arc::new(Delta::new(101, vec![Payload::RouteOrigin(origin)], vec![]));
|
|
|
|
let mut deltas = VecDeque::new();
|
|
deltas.push_back(delta);
|
|
|
|
let cache = RtrCacheBuilder::new()
|
|
.session_ids(SessionIds::from_array([42, 42, 42]))
|
|
.serials(serials_all(101))
|
|
.timing(Timing {
|
|
refresh: 600,
|
|
retry: 600,
|
|
expire: 7200,
|
|
})
|
|
.deltas_by_version(deltas_all(deltas))
|
|
.build();
|
|
|
|
let server_cache = shared_cache(cache);
|
|
let (addr, _notify_tx, shutdown_tx, server_handle) = start_session_server(server_cache).await;
|
|
|
|
let mut client = TcpStream::connect(addr).await.unwrap();
|
|
SerialQuery::new(1, 42, 100)
|
|
.write(&mut client)
|
|
.await
|
|
.unwrap();
|
|
|
|
let mut dump = RtrDebugDumper::new();
|
|
|
|
let response = CacheResponse::read(&mut client).await.unwrap();
|
|
dump.push_value(response.pdu(), dump_cache_response(&response));
|
|
assert_eq!(response.pdu(), 3);
|
|
assert_eq!(response.version(), 1);
|
|
assert_eq!(response.session_id(), 42);
|
|
|
|
let prefix = IPv4Prefix::read(&mut client).await.unwrap();
|
|
dump.push_value(prefix.pdu(), dump_ipv4_prefix(&prefix));
|
|
assert_eq!(prefix.pdu(), 4);
|
|
assert_eq!(prefix.version(), 1);
|
|
assert!(prefix.flag().is_announce());
|
|
assert_eq!(prefix.prefix_len(), 24);
|
|
assert_eq!(prefix.max_len(), 24);
|
|
assert_eq!(prefix.prefix(), Ipv4Addr::new(192, 0, 2, 0));
|
|
assert_eq!(prefix.asn(), 64496u32.into());
|
|
|
|
let eod = EndOfDataV1::read(&mut client).await.unwrap();
|
|
dump.push_value(eod.pdu(), dump_eod_v1(&eod));
|
|
assert_eq!(eod.pdu(), 7);
|
|
assert_eq!(eod.version(), 1);
|
|
assert_eq!(eod.session_id(), 42);
|
|
assert_eq!(eod.serial_number(), 101);
|
|
|
|
let timing = eod.timing();
|
|
assert_eq!(timing.refresh, 600);
|
|
assert_eq!(timing.retry, 600);
|
|
assert_eq!(timing.expire, 7200);
|
|
|
|
dump.print_pretty("serial_query_returns_deltas_when_incremental_update_available");
|
|
shutdown_server(client, shutdown_tx, server_handle).await;
|
|
}
|
|
|
|
#[tokio::test]
|
|
async fn serial_query_returns_deltas_across_serial_wraparound() {
|
|
let first_prefix = IPAddressPrefix {
|
|
address: IPAddress::from_ipv4(Ipv4Addr::new(192, 0, 2, 0)),
|
|
prefix_length: 24,
|
|
};
|
|
let first_origin = RouteOrigin::new(first_prefix, 24, 64496u32.into());
|
|
|
|
let second_prefix = IPAddressPrefix {
|
|
address: IPAddress::from_ipv4(Ipv4Addr::new(198, 51, 100, 0)),
|
|
prefix_length: 24,
|
|
};
|
|
let second_origin = RouteOrigin::new(second_prefix, 24, 64497u32.into());
|
|
|
|
let d_max = Arc::new(Delta::new(
|
|
u32::MAX,
|
|
vec![Payload::RouteOrigin(first_origin.clone())],
|
|
vec![],
|
|
));
|
|
let d_zero = Arc::new(Delta::new(
|
|
0,
|
|
vec![Payload::RouteOrigin(second_origin.clone())],
|
|
vec![],
|
|
));
|
|
|
|
let mut deltas = VecDeque::new();
|
|
deltas.push_back(d_max);
|
|
deltas.push_back(d_zero);
|
|
|
|
let snapshot = Snapshot::from_payloads(vec![
|
|
Payload::RouteOrigin(first_origin),
|
|
Payload::RouteOrigin(second_origin),
|
|
]);
|
|
|
|
let cache = RtrCacheBuilder::new()
|
|
.session_ids(SessionIds::from_array([42, 42, 42]))
|
|
.serials(serials_all(0))
|
|
.timing(Timing::new(600, 600, 7200))
|
|
.snapshots(snapshots_all(snapshot))
|
|
.deltas_by_version(deltas_all(deltas))
|
|
.build();
|
|
|
|
let server_cache = shared_cache(cache);
|
|
let (addr, _notify_tx, shutdown_tx, server_handle) = start_session_server(server_cache).await;
|
|
|
|
let mut client = TcpStream::connect(addr).await.unwrap();
|
|
SerialQuery::new(1, 42, u32::MAX.wrapping_sub(1))
|
|
.write(&mut client)
|
|
.await
|
|
.unwrap();
|
|
|
|
let mut dump = RtrDebugDumper::new();
|
|
|
|
let response = CacheResponse::read(&mut client).await.unwrap();
|
|
dump.push_value(response.pdu(), dump_cache_response(&response));
|
|
assert_eq!(response.version(), 1);
|
|
assert_eq!(response.session_id(), 42);
|
|
|
|
let first = IPv4Prefix::read(&mut client).await.unwrap();
|
|
dump.push_value(first.pdu(), dump_ipv4_prefix(&first));
|
|
assert!(first.flag().is_announce());
|
|
assert_eq!(first.prefix(), Ipv4Addr::new(198, 51, 100, 0));
|
|
|
|
let second = IPv4Prefix::read(&mut client).await.unwrap();
|
|
dump.push_value(second.pdu(), dump_ipv4_prefix(&second));
|
|
assert!(second.flag().is_announce());
|
|
assert_eq!(second.prefix(), Ipv4Addr::new(192, 0, 2, 0));
|
|
|
|
let eod = EndOfDataV1::read(&mut client).await.unwrap();
|
|
dump.push_value(eod.pdu(), dump_eod_v1(&eod));
|
|
assert_eq!(eod.session_id(), 42);
|
|
assert_eq!(eod.serial_number(), 0);
|
|
|
|
dump.print_pretty("serial_query_returns_deltas_across_serial_wraparound");
|
|
shutdown_server(client, shutdown_tx, server_handle).await;
|
|
}
|
|
|
|
#[tokio::test]
|
|
async fn serial_query_returns_cache_reset_for_future_serial_across_wraparound() {
|
|
let cache = RtrCacheBuilder::new()
|
|
.session_ids(SessionIds::from_array([42, 42, 42]))
|
|
.serials(serials_all(u32::MAX))
|
|
.timing(Timing::new(600, 600, 7200))
|
|
.build();
|
|
|
|
let server_cache = shared_cache(cache);
|
|
let (addr, _notify_tx, shutdown_tx, server_handle) = start_session_server(server_cache).await;
|
|
|
|
let mut client = TcpStream::connect(addr).await.unwrap();
|
|
SerialQuery::new(1, 42, 0).write(&mut client).await.unwrap();
|
|
|
|
let mut dump = RtrDebugDumper::new();
|
|
|
|
let reset = CacheReset::read(&mut client).await.unwrap();
|
|
dump.push_value(reset.pdu(), dump_cache_reset(reset.version(), reset.pdu()));
|
|
assert_eq!(reset.pdu(), CacheReset::PDU);
|
|
assert_eq!(reset.version(), 1);
|
|
|
|
dump.print_pretty("serial_query_returns_cache_reset_for_future_serial_across_wraparound");
|
|
shutdown_server(client, shutdown_tx, server_handle).await;
|
|
}
|
|
|
|
/// Reset Query returns payloads in RTR ordering.
|
|
#[tokio::test]
|
|
async fn reset_query_returns_payloads_in_rtr_order() {
|
|
let v4_low_prefix = IPAddressPrefix {
|
|
address: IPAddress::from_ipv4(Ipv4Addr::new(192, 0, 2, 0)),
|
|
prefix_length: 24,
|
|
};
|
|
let v4_low_origin = RouteOrigin::new(v4_low_prefix, 24, 64496u32.into());
|
|
|
|
let v4_high_prefix = IPAddressPrefix {
|
|
address: IPAddress::from_ipv4(Ipv4Addr::new(198, 51, 100, 0)),
|
|
prefix_length: 24,
|
|
};
|
|
let v4_high_origin = RouteOrigin::new(v4_high_prefix, 24, 64497u32.into());
|
|
|
|
let v6_prefix = IPAddressPrefix {
|
|
address: IPAddress::from_ipv6(Ipv6Addr::new(0x2001, 0xdb8, 0, 0, 0, 0, 0, 0)),
|
|
prefix_length: 32,
|
|
};
|
|
let v6_origin = RouteOrigin::new(v6_prefix, 48, 64498u32.into());
|
|
|
|
let snapshot = Snapshot::from_payloads(vec![
|
|
Payload::RouteOrigin(v6_origin),
|
|
Payload::RouteOrigin(v4_low_origin),
|
|
Payload::RouteOrigin(v4_high_origin),
|
|
]);
|
|
|
|
let cache = RtrCacheBuilder::new()
|
|
.session_ids(SessionIds::from_array([42, 42, 42]))
|
|
.serials(serials_all(100))
|
|
.timing(Timing::new(600, 600, 7200))
|
|
.snapshots(snapshots_all(snapshot))
|
|
.build();
|
|
|
|
let server_cache = shared_cache(cache);
|
|
let (addr, _notify_tx, shutdown_tx, server_handle) = start_session_server(server_cache).await;
|
|
|
|
let mut client = TcpStream::connect(addr).await.unwrap();
|
|
ResetQuery::new(1).write(&mut client).await.unwrap();
|
|
|
|
let mut dump = RtrDebugDumper::new();
|
|
|
|
let response = CacheResponse::read(&mut client).await.unwrap();
|
|
dump.push_value(response.pdu(), dump_cache_response(&response));
|
|
assert_eq!(response.pdu(), 3);
|
|
assert_eq!(response.version(), 1);
|
|
assert_eq!(response.session_id(), 42);
|
|
|
|
let first = IPv4Prefix::read(&mut client).await.unwrap();
|
|
dump.push_value(first.pdu(), dump_ipv4_prefix(&first));
|
|
assert_eq!(first.pdu(), 4);
|
|
assert_eq!(first.version(), 1);
|
|
assert!(first.flag().is_announce());
|
|
assert_eq!(first.prefix(), Ipv4Addr::new(198, 51, 100, 0));
|
|
assert_eq!(first.prefix_len(), 24);
|
|
assert_eq!(first.max_len(), 24);
|
|
assert_eq!(first.asn(), 64497u32.into());
|
|
|
|
let second = IPv4Prefix::read(&mut client).await.unwrap();
|
|
dump.push_value(second.pdu(), dump_ipv4_prefix(&second));
|
|
assert_eq!(second.pdu(), 4);
|
|
assert_eq!(second.version(), 1);
|
|
assert!(second.flag().is_announce());
|
|
assert_eq!(second.prefix(), Ipv4Addr::new(192, 0, 2, 0));
|
|
assert_eq!(second.prefix_len(), 24);
|
|
assert_eq!(second.max_len(), 24);
|
|
assert_eq!(second.asn(), 64496u32.into());
|
|
|
|
assert!(u32::from(first.prefix()) > u32::from(second.prefix()));
|
|
|
|
let third = IPv6Prefix::read(&mut client).await.unwrap();
|
|
dump.push_value(third.pdu(), dump_ipv6_prefix(&third));
|
|
assert_eq!(third.pdu(), 6);
|
|
assert_eq!(third.version(), 1);
|
|
assert!(third.flag().is_announce());
|
|
assert_eq!(
|
|
third.prefix(),
|
|
Ipv6Addr::new(0x2001, 0xdb8, 0, 0, 0, 0, 0, 0)
|
|
);
|
|
assert_eq!(third.prefix_len(), 32);
|
|
assert_eq!(third.max_len(), 48);
|
|
assert_eq!(third.asn(), 64498u32.into());
|
|
|
|
let eod = EndOfDataV1::read(&mut client).await.unwrap();
|
|
dump.push_value(eod.pdu(), dump_eod_v1(&eod));
|
|
assert_eq!(eod.pdu(), 7);
|
|
assert_eq!(eod.version(), 1);
|
|
assert_eq!(eod.session_id(), 42);
|
|
assert_eq!(eod.serial_number(), 100);
|
|
|
|
let timing = eod.timing();
|
|
assert_eq!(timing.refresh, 600);
|
|
assert_eq!(timing.retry, 600);
|
|
assert_eq!(timing.expire, 7200);
|
|
|
|
dump.print_pretty("reset_query_returns_payloads_in_rtr_order");
|
|
shutdown_server(client, shutdown_tx, server_handle).await;
|
|
}
|
|
|
|
/// Serial Query returns announcements before withdrawals, with each side sorted.
|
|
#[tokio::test]
|
|
async fn serial_query_returns_announcements_before_withdrawals() {
|
|
let announced_low_prefix = IPAddressPrefix {
|
|
address: IPAddress::from_ipv4(Ipv4Addr::new(192, 0, 2, 0)),
|
|
prefix_length: 24,
|
|
};
|
|
let announced_low_origin = RouteOrigin::new(announced_low_prefix, 24, 64496u32.into());
|
|
|
|
let announced_high_prefix = IPAddressPrefix {
|
|
address: IPAddress::from_ipv4(Ipv4Addr::new(198, 51, 100, 0)),
|
|
prefix_length: 24,
|
|
};
|
|
let announced_high_origin = RouteOrigin::new(announced_high_prefix, 24, 64497u32.into());
|
|
|
|
let withdrawn_low_prefix = IPAddressPrefix {
|
|
address: IPAddress::from_ipv4(Ipv4Addr::new(10, 0, 0, 0)),
|
|
prefix_length: 24,
|
|
};
|
|
let withdrawn_low_origin = RouteOrigin::new(withdrawn_low_prefix, 24, 64500u32.into());
|
|
|
|
let withdrawn_high_prefix = IPAddressPrefix {
|
|
address: IPAddress::from_ipv4(Ipv4Addr::new(203, 0, 113, 0)),
|
|
prefix_length: 24,
|
|
};
|
|
let withdrawn_high_origin = RouteOrigin::new(withdrawn_high_prefix, 24, 64501u32.into());
|
|
|
|
let delta = Arc::new(Delta::new(
|
|
101,
|
|
vec![
|
|
Payload::RouteOrigin(announced_low_origin),
|
|
Payload::RouteOrigin(announced_high_origin),
|
|
],
|
|
vec![
|
|
Payload::RouteOrigin(withdrawn_high_origin),
|
|
Payload::RouteOrigin(withdrawn_low_origin),
|
|
],
|
|
));
|
|
|
|
let mut deltas = VecDeque::new();
|
|
deltas.push_back(delta);
|
|
|
|
let cache = RtrCacheBuilder::new()
|
|
.session_ids(SessionIds::from_array([42, 42, 42]))
|
|
.serials(serials_all(101))
|
|
.timing(Timing {
|
|
refresh: 600,
|
|
retry: 600,
|
|
expire: 7200,
|
|
})
|
|
.deltas_by_version(deltas_all(deltas))
|
|
.build();
|
|
|
|
let server_cache = shared_cache(cache);
|
|
let (addr, _notify_tx, shutdown_tx, server_handle) = start_session_server(server_cache).await;
|
|
|
|
let mut client = TcpStream::connect(addr).await.unwrap();
|
|
SerialQuery::new(1, 42, 100)
|
|
.write(&mut client)
|
|
.await
|
|
.unwrap();
|
|
|
|
let mut dump = RtrDebugDumper::new();
|
|
|
|
let response = CacheResponse::read(&mut client).await.unwrap();
|
|
dump.push_value(response.pdu(), dump_cache_response(&response));
|
|
assert_eq!(response.pdu(), 3);
|
|
assert_eq!(response.version(), 1);
|
|
assert_eq!(response.session_id(), 42);
|
|
|
|
let first = IPv4Prefix::read(&mut client).await.unwrap();
|
|
dump.push_value(first.pdu(), dump_ipv4_prefix(&first));
|
|
assert_eq!(first.pdu(), 4);
|
|
assert_eq!(first.version(), 1);
|
|
assert!(first.flag().is_announce());
|
|
assert_eq!(first.prefix(), Ipv4Addr::new(198, 51, 100, 0));
|
|
assert_eq!(first.prefix_len(), 24);
|
|
assert_eq!(first.max_len(), 24);
|
|
assert_eq!(first.asn(), 64497u32.into());
|
|
|
|
let second = IPv4Prefix::read(&mut client).await.unwrap();
|
|
dump.push_value(second.pdu(), dump_ipv4_prefix(&second));
|
|
assert_eq!(second.pdu(), 4);
|
|
assert_eq!(second.version(), 1);
|
|
assert!(second.flag().is_announce());
|
|
assert_eq!(second.prefix(), Ipv4Addr::new(192, 0, 2, 0));
|
|
assert_eq!(second.prefix_len(), 24);
|
|
assert_eq!(second.max_len(), 24);
|
|
assert_eq!(second.asn(), 64496u32.into());
|
|
|
|
assert!(u32::from(first.prefix()) > u32::from(second.prefix()));
|
|
|
|
let third = IPv4Prefix::read(&mut client).await.unwrap();
|
|
dump.push_value(third.pdu(), dump_ipv4_prefix(&third));
|
|
assert_eq!(third.pdu(), 4);
|
|
assert_eq!(third.version(), 1);
|
|
assert!(!third.flag().is_announce());
|
|
assert_eq!(third.prefix(), Ipv4Addr::new(10, 0, 0, 0));
|
|
assert_eq!(third.prefix_len(), 24);
|
|
assert_eq!(third.max_len(), 24);
|
|
assert_eq!(third.asn(), 64500u32.into());
|
|
|
|
let fourth = IPv4Prefix::read(&mut client).await.unwrap();
|
|
dump.push_value(fourth.pdu(), dump_ipv4_prefix(&fourth));
|
|
assert_eq!(fourth.pdu(), 4);
|
|
assert_eq!(fourth.version(), 1);
|
|
assert!(!fourth.flag().is_announce());
|
|
assert_eq!(fourth.prefix(), Ipv4Addr::new(203, 0, 113, 0));
|
|
assert_eq!(fourth.prefix_len(), 24);
|
|
assert_eq!(fourth.max_len(), 24);
|
|
assert_eq!(fourth.asn(), 64501u32.into());
|
|
|
|
assert!(u32::from(third.prefix()) < u32::from(fourth.prefix()));
|
|
assert!(first.flag().is_announce());
|
|
assert!(second.flag().is_announce());
|
|
assert!(!third.flag().is_announce());
|
|
assert!(!fourth.flag().is_announce());
|
|
|
|
let eod = EndOfDataV1::read(&mut client).await.unwrap();
|
|
dump.push_value(eod.pdu(), dump_eod_v1(&eod));
|
|
assert_eq!(eod.pdu(), 7);
|
|
assert_eq!(eod.version(), 1);
|
|
assert_eq!(eod.session_id(), 42);
|
|
assert_eq!(eod.serial_number(), 101);
|
|
|
|
let timing = eod.timing();
|
|
assert_eq!(timing.refresh, 600);
|
|
assert_eq!(timing.retry, 600);
|
|
assert_eq!(timing.expire, 7200);
|
|
|
|
dump.print_pretty("serial_query_returns_announcements_before_withdrawals");
|
|
shutdown_server(client, shutdown_tx, server_handle).await;
|
|
}
|
|
|
|
/// Established sessions send Serial Notify after receiving cache update notifications.
|
|
#[tokio::test]
|
|
async fn established_session_sends_serial_notify() {
|
|
let cache = RtrCacheBuilder::new()
|
|
.session_ids(SessionIds::from_array([42, 42, 42]))
|
|
.serials(serials_all(100))
|
|
.timing(Timing::new(600, 600, 7200))
|
|
.build();
|
|
|
|
let server_cache = shared_cache(cache);
|
|
let (addr, notify_tx, shutdown_tx, server_handle) = start_session_server(server_cache).await;
|
|
|
|
let mut client = TcpStream::connect(addr).await.unwrap();
|
|
|
|
ResetQuery::new(1).write(&mut client).await.unwrap();
|
|
|
|
let mut dump = RtrDebugDumper::new();
|
|
|
|
let response = CacheResponse::read(&mut client).await.unwrap();
|
|
dump.push_value(response.pdu(), dump_cache_response(&response));
|
|
assert_eq!(response.version(), 1);
|
|
assert_eq!(response.session_id(), 42);
|
|
|
|
let eod = EndOfDataV1::read(&mut client).await.unwrap();
|
|
dump.push_value(eod.pdu(), dump_eod_v1(&eod));
|
|
assert_eq!(eod.version(), 1);
|
|
assert_eq!(eod.session_id(), 42);
|
|
assert_eq!(eod.serial_number(), 100);
|
|
|
|
notify_tx.send(()).unwrap();
|
|
|
|
let notify = SerialNotify::read(&mut client).await.unwrap();
|
|
dump.push_value(notify.pdu(), dump_serial_notify(¬ify));
|
|
assert_eq!(notify.pdu(), SerialNotify::PDU);
|
|
assert_eq!(notify.version(), 1);
|
|
assert_eq!(notify.session_id(), 42);
|
|
assert_eq!(notify.serial_number(), 100);
|
|
|
|
dump.print_pretty("established_session_sends_serial_notify");
|
|
shutdown_server(client, shutdown_tx, server_handle).await;
|
|
}
|
|
|
|
/// A too-high first-PDU protocol version returns Unsupported Protocol Version.
|
|
#[tokio::test]
|
|
async fn first_pdu_with_too_high_version_returns_unsupported_version_error() {
|
|
let cache = RtrCacheBuilder::new()
|
|
.session_ids(SessionIds::from_array([42, 42, 42]))
|
|
.serials(serials_all(100))
|
|
.timing(Timing::new(600, 600, 7200))
|
|
.build();
|
|
|
|
let server_cache = shared_cache(cache);
|
|
let (addr, _notify_tx, shutdown_tx, server_handle) = start_session_server(server_cache).await;
|
|
|
|
let mut client = TcpStream::connect(addr).await.unwrap();
|
|
let mut dump = RtrDebugDumper::new();
|
|
|
|
ResetQuery::new(3).write(&mut client).await.unwrap();
|
|
|
|
let report = ErrorReport::read(&mut client).await.unwrap();
|
|
assert_error_report_matches(
|
|
&report,
|
|
2,
|
|
ErrorCode::UnsupportedProtocolVersion,
|
|
ResetQuery::new(3).as_ref(),
|
|
);
|
|
|
|
let read_res = Header::read(&mut client).await;
|
|
assert!(read_res.is_err());
|
|
|
|
dump.push_value(
|
|
0,
|
|
json!({
|
|
"event": "connection_closed_after_unsupported_protocol_version",
|
|
"result": "header_read_failed_as_expected"
|
|
}),
|
|
);
|
|
|
|
dump.print_pretty("first_pdu_with_too_high_version_returns_unsupported_version_error");
|
|
shutdown_server(client, shutdown_tx, server_handle).await;
|
|
}
|
|
|
|
/// Established sessions reject later protocol version changes.
|
|
#[tokio::test]
|
|
async fn session_rejects_version_change_after_negotiation() {
|
|
let cache = RtrCacheBuilder::new()
|
|
.session_ids(SessionIds::from_array([42, 42, 42]))
|
|
.serials(serials_all(100))
|
|
.timing(Timing::new(600, 600, 7200))
|
|
.build();
|
|
|
|
let server_cache = shared_cache(cache);
|
|
let (addr, _notify_tx, shutdown_tx, server_handle) = start_session_server(server_cache).await;
|
|
|
|
let mut client = TcpStream::connect(addr).await.unwrap();
|
|
let mut dump = RtrDebugDumper::new();
|
|
|
|
ResetQuery::new(1).write(&mut client).await.unwrap();
|
|
|
|
let response = CacheResponse::read(&mut client).await.unwrap();
|
|
dump.push_value(response.pdu(), dump_cache_response(&response));
|
|
assert_eq!(response.version(), 1);
|
|
assert_eq!(response.session_id(), 42);
|
|
|
|
let eod = EndOfDataV1::read(&mut client).await.unwrap();
|
|
dump.push_value(eod.pdu(), dump_eod_v1(&eod));
|
|
assert_eq!(eod.version(), 1);
|
|
assert_eq!(eod.session_id(), 42);
|
|
assert_eq!(eod.serial_number(), 100);
|
|
|
|
ResetQuery::new(2).write(&mut client).await.unwrap();
|
|
|
|
let report = ErrorReport::read(&mut client).await.unwrap();
|
|
assert_error_report_matches(
|
|
&report,
|
|
1,
|
|
ErrorCode::UnexpectedProtocolVersion,
|
|
ResetQuery::new(2).as_ref(),
|
|
);
|
|
|
|
let read_res = Header::read(&mut client).await;
|
|
assert!(read_res.is_err());
|
|
|
|
dump.push_value(
|
|
0,
|
|
json!({
|
|
"event": "connection_closed_after_unexpected_protocol_version",
|
|
"result": "header_read_failed_as_expected"
|
|
}),
|
|
);
|
|
|
|
dump.print_pretty("session_rejects_version_change_after_negotiation");
|
|
shutdown_server(client, shutdown_tx, server_handle).await;
|
|
}
|
|
|
|
/// Notifications before version negotiation do not send Serial Notify.
|
|
#[tokio::test]
|
|
async fn notify_is_not_sent_before_version_negotiation() {
|
|
let cache = RtrCacheBuilder::new()
|
|
.session_ids(SessionIds::from_array([42, 42, 42]))
|
|
.serials(serials_all(100))
|
|
.timing(Timing::new(600, 600, 7200))
|
|
.build();
|
|
|
|
let server_cache = shared_cache(cache);
|
|
let (addr, notify_tx, shutdown_tx, server_handle) = start_session_server(server_cache).await;
|
|
|
|
let mut client = TcpStream::connect(addr).await.unwrap();
|
|
|
|
let mut dump = RtrDebugDumper::new();
|
|
|
|
notify_tx.send(()).unwrap();
|
|
|
|
let res = timeout(Duration::from_millis(100), SerialNotify::read(&mut client)).await;
|
|
assert!(
|
|
res.is_err(),
|
|
"serial notify should not be sent before version negotiation"
|
|
);
|
|
|
|
dump.push_value(
|
|
0,
|
|
json!({
|
|
"event": "notify_before_version_negotiation",
|
|
"result": "no_serial_notify_received_within_timeout",
|
|
"timeout_ms": 100
|
|
}),
|
|
);
|
|
|
|
dump.print_pretty("notify_is_not_sent_before_version_negotiation");
|
|
shutdown_server(client, shutdown_tx, server_handle).await;
|
|
}
|
|
|
|
/// Serial Notify is rate-limited to once per minute per session.
|
|
#[tokio::test]
|
|
async fn serial_notify_is_rate_limited_to_once_per_minute() {
|
|
let cache = RtrCacheBuilder::new()
|
|
.session_ids(SessionIds::from_array([42, 42, 42]))
|
|
.serials(serials_all(100))
|
|
.timing(Timing::new(600, 600, 7200))
|
|
.build();
|
|
|
|
let server_cache = shared_cache(cache);
|
|
let (addr, notify_tx, shutdown_tx, server_handle) = start_session_server(server_cache).await;
|
|
|
|
let mut client = TcpStream::connect(addr).await.unwrap();
|
|
|
|
ResetQuery::new(1).write(&mut client).await.unwrap();
|
|
|
|
let mut dump = RtrDebugDumper::new();
|
|
|
|
let response = CacheResponse::read(&mut client).await.unwrap();
|
|
dump.push_value(response.pdu(), dump_cache_response(&response));
|
|
assert_eq!(response.pdu(), 3);
|
|
assert_eq!(response.version(), 1);
|
|
assert_eq!(response.session_id(), 42);
|
|
|
|
let eod = EndOfDataV1::read(&mut client).await.unwrap();
|
|
dump.push_value(eod.pdu(), dump_eod_v1(&eod));
|
|
assert_eq!(eod.pdu(), 7);
|
|
assert_eq!(eod.version(), 1);
|
|
assert_eq!(eod.session_id(), 42);
|
|
assert_eq!(eod.serial_number(), 100);
|
|
|
|
notify_tx.send(()).unwrap();
|
|
|
|
let first_notify = SerialNotify::read(&mut client).await.unwrap();
|
|
dump.push_value(first_notify.pdu(), dump_serial_notify(&first_notify));
|
|
assert_eq!(first_notify.pdu(), SerialNotify::PDU);
|
|
assert_eq!(first_notify.version(), 1);
|
|
assert_eq!(first_notify.session_id(), 42);
|
|
assert_eq!(first_notify.serial_number(), 100);
|
|
|
|
notify_tx.send(()).unwrap();
|
|
|
|
let second_res = timeout(Duration::from_millis(100), SerialNotify::read(&mut client)).await;
|
|
assert!(
|
|
second_res.is_err(),
|
|
"second serial notify should be rate-limited within one minute"
|
|
);
|
|
|
|
dump.push_value(
|
|
0,
|
|
json!({
|
|
"event": "second_notify_within_rate_limit_window",
|
|
"result": "no_second_serial_notify_received_within_timeout",
|
|
"timeout_ms": 100
|
|
}),
|
|
);
|
|
|
|
dump.print_pretty("serial_notify_is_rate_limited_to_once_per_minute");
|
|
shutdown_server(client, shutdown_tx, server_handle).await;
|
|
}
|
|
|
|
#[tokio::test]
|
|
async fn reset_query_returns_no_data_available_when_cache_is_unavailable() {
|
|
let cache = RtrCacheBuilder::new()
|
|
.availability(rpki::rtr::cache::CacheAvailability::NoDataAvailable)
|
|
.session_ids(SessionIds::from_array([42, 42, 42]))
|
|
.serials(serials_all(100))
|
|
.timing(Timing::new(600, 600, 7200))
|
|
.build();
|
|
|
|
let server_cache = shared_cache(cache);
|
|
let (addr, _notify_tx, shutdown_tx, server_handle) = start_session_server(server_cache).await;
|
|
|
|
let mut client = TcpStream::connect(addr).await.unwrap();
|
|
ResetQuery::new(1).write(&mut client).await.unwrap();
|
|
|
|
let report = ErrorReport::read(&mut client).await.unwrap();
|
|
assert_error_report_matches(
|
|
&report,
|
|
1,
|
|
ErrorCode::NoDataAvailable,
|
|
ResetQuery::new(1).as_ref(),
|
|
);
|
|
|
|
ResetQuery::new(1).write(&mut client).await.unwrap();
|
|
let second = ErrorReport::read(&mut client).await.unwrap();
|
|
assert_error_report_matches(
|
|
&second,
|
|
1,
|
|
ErrorCode::NoDataAvailable,
|
|
ResetQuery::new(1).as_ref(),
|
|
);
|
|
|
|
shutdown_server(client, shutdown_tx, server_handle).await;
|
|
}
|
|
|
|
#[tokio::test]
|
|
async fn serial_query_returns_no_data_available_when_cache_is_unavailable() {
|
|
let cache = RtrCacheBuilder::new()
|
|
.availability(rpki::rtr::cache::CacheAvailability::NoDataAvailable)
|
|
.session_ids(SessionIds::from_array([42, 42, 42]))
|
|
.serials(serials_all(100))
|
|
.timing(Timing::new(600, 600, 7200))
|
|
.build();
|
|
|
|
let server_cache = shared_cache(cache);
|
|
let (addr, _notify_tx, shutdown_tx, server_handle) = start_session_server(server_cache).await;
|
|
|
|
let mut client = TcpStream::connect(addr).await.unwrap();
|
|
SerialQuery::new(1, 42, 100)
|
|
.write(&mut client)
|
|
.await
|
|
.unwrap();
|
|
|
|
let report = ErrorReport::read(&mut client).await.unwrap();
|
|
assert_error_report_matches(
|
|
&report,
|
|
1,
|
|
ErrorCode::NoDataAvailable,
|
|
SerialQuery::new(1, 42, 100).as_ref(),
|
|
);
|
|
|
|
SerialQuery::new(1, 42, 100)
|
|
.write(&mut client)
|
|
.await
|
|
.unwrap();
|
|
let second = ErrorReport::read(&mut client).await.unwrap();
|
|
assert_error_report_matches(
|
|
&second,
|
|
1,
|
|
ErrorCode::NoDataAvailable,
|
|
SerialQuery::new(1, 42, 100).as_ref(),
|
|
);
|
|
|
|
shutdown_server(client, shutdown_tx, server_handle).await;
|
|
}
|
|
|
|
#[tokio::test]
|
|
async fn first_pdu_with_invalid_length_returns_corrupt_data() {
|
|
let cache = RtrCacheBuilder::new()
|
|
.session_ids(SessionIds::from_array([42, 42, 42]))
|
|
.serials(serials_all(100))
|
|
.timing(Timing::new(600, 600, 7200))
|
|
.build();
|
|
|
|
let server_cache = shared_cache(cache);
|
|
let (addr, _notify_tx, _shutdown_tx, server_handle) = start_session_server(server_cache).await;
|
|
|
|
let mut client = TcpStream::connect(addr).await.unwrap();
|
|
let mut dump = RtrDebugDumper::new();
|
|
|
|
let header = Header::new(1, ResetQuery::PDU, 0, 9);
|
|
let mut request = Vec::from(header.as_ref());
|
|
request.push(0);
|
|
|
|
timeout(Duration::from_secs(1), client.write_all(&request))
|
|
.await
|
|
.expect("write_all timed out")
|
|
.unwrap();
|
|
dump.push_value(
|
|
ResetQuery::PDU,
|
|
json!({
|
|
"event": "invalid_first_pdu_sent",
|
|
"raw_hex": common::test_helper::bytes_to_hex(&request),
|
|
"length": request.len(),
|
|
}),
|
|
);
|
|
|
|
let report = timeout(Duration::from_secs(1), ErrorReport::read(&mut client))
|
|
.await
|
|
.expect("timed out waiting for ErrorReport")
|
|
.unwrap();
|
|
dump.push_value(
|
|
ErrorReport::PDU,
|
|
json!({
|
|
"version": report.version(),
|
|
"pdu": ErrorReport::PDU,
|
|
"pdu_name": "Error Report",
|
|
"error_code": report.error_code().map(|code| code.as_u16()).unwrap_or_else(|code| code),
|
|
"erroneous_pdu_len": report.erroneous_pdu().len(),
|
|
"erroneous_pdu_hex": common::test_helper::bytes_to_hex(report.erroneous_pdu()),
|
|
"text": String::from_utf8_lossy(report.text()),
|
|
}),
|
|
);
|
|
|
|
assert_error_report_matches(&report, 1, ErrorCode::CorruptData, &request);
|
|
assert!(
|
|
std::str::from_utf8(report.text())
|
|
.unwrap()
|
|
.contains("invalid length")
|
|
);
|
|
|
|
let read_res = timeout(Duration::from_secs(1), Header::read(&mut client))
|
|
.await
|
|
.expect("timed out waiting for connection close");
|
|
assert!(read_res.is_err());
|
|
dump.push_value(
|
|
0,
|
|
json!({
|
|
"event": "connection_closed_after_invalid_first_pdu",
|
|
"result": "header_read_failed_as_expected"
|
|
}),
|
|
);
|
|
dump.print_pretty("first_pdu_with_invalid_length_returns_corrupt_data");
|
|
|
|
drop(client);
|
|
server_handle.abort();
|
|
let _ = server_handle.await;
|
|
}
|
|
|
|
#[tokio::test]
|
|
async fn established_session_closes_after_receiving_error_report() {
|
|
let cache = RtrCacheBuilder::new()
|
|
.session_ids(SessionIds::from_array([42, 42, 42]))
|
|
.serials(serials_all(100))
|
|
.timing(Timing::new(600, 600, 7200))
|
|
.build();
|
|
|
|
let server_cache = shared_cache(cache);
|
|
let (addr, _notify_tx, shutdown_tx, server_handle) = start_session_server(server_cache).await;
|
|
|
|
let mut client = TcpStream::connect(addr).await.unwrap();
|
|
let mut dump = RtrDebugDumper::new();
|
|
ResetQuery::new(1).write(&mut client).await.unwrap();
|
|
|
|
let response = CacheResponse::read(&mut client).await.unwrap();
|
|
dump.push_value(response.pdu(), dump_cache_response(&response));
|
|
|
|
let eod = EndOfDataV1::read(&mut client).await.unwrap();
|
|
dump.push_value(eod.pdu(), dump_eod_v1(&eod));
|
|
|
|
let report = ErrorReport::new(1, ErrorCode::InternalError.as_u16(), [], b"peer error");
|
|
client.write_all(report.as_ref()).await.unwrap();
|
|
dump.push_value(
|
|
ErrorReport::PDU,
|
|
json!({
|
|
"event": "peer_error_report_sent",
|
|
"version": report.version(),
|
|
"error_code": report.error_code().map(|code| code.as_u16()).unwrap_or_else(|code| code),
|
|
"text": String::from_utf8_lossy(report.text()),
|
|
}),
|
|
);
|
|
|
|
let read_res = timeout(Duration::from_secs(1), Header::read(&mut client))
|
|
.await
|
|
.unwrap();
|
|
assert!(read_res.is_err());
|
|
dump.push_value(
|
|
0,
|
|
json!({
|
|
"event": "connection_closed_after_receiving_peer_error_report",
|
|
"result": "header_read_failed_as_expected"
|
|
}),
|
|
);
|
|
dump.print_pretty("established_session_closes_after_receiving_error_report");
|
|
|
|
shutdown_server(client, shutdown_tx, server_handle).await;
|
|
}
|
|
|
|
#[tokio::test]
|
|
async fn established_session_invalid_header_returns_corrupt_data() {
|
|
let cache = RtrCacheBuilder::new()
|
|
.session_ids(SessionIds::from_array([42, 42, 42]))
|
|
.serials(serials_all(100))
|
|
.timing(Timing::new(600, 600, 7200))
|
|
.build();
|
|
|
|
let server_cache = shared_cache(cache);
|
|
let (addr, _notify_tx, shutdown_tx, server_handle) = start_session_server(server_cache).await;
|
|
|
|
let mut client = TcpStream::connect(addr).await.unwrap();
|
|
let mut dump = RtrDebugDumper::new();
|
|
ResetQuery::new(1).write(&mut client).await.unwrap();
|
|
|
|
let response = CacheResponse::read(&mut client).await.unwrap();
|
|
dump.push_value(response.pdu(), dump_cache_response(&response));
|
|
|
|
let eod = EndOfDataV1::read(&mut client).await.unwrap();
|
|
dump.push_value(eod.pdu(), dump_eod_v1(&eod));
|
|
|
|
let invalid_header = Header::new(1, SerialQuery::PDU, 42, 7);
|
|
client.write_all(invalid_header.as_ref()).await.unwrap();
|
|
dump.push_value(
|
|
SerialQuery::PDU,
|
|
json!({
|
|
"event": "invalid_header_sent_after_establishment",
|
|
"raw_hex": common::test_helper::bytes_to_hex(invalid_header.as_ref()),
|
|
"length": invalid_header.length(),
|
|
}),
|
|
);
|
|
|
|
let report = ErrorReport::read(&mut client).await.unwrap();
|
|
dump.push_value(
|
|
ErrorReport::PDU,
|
|
json!({
|
|
"version": report.version(),
|
|
"pdu": ErrorReport::PDU,
|
|
"pdu_name": "Error Report",
|
|
"error_code": report.error_code().map(|code| code.as_u16()).unwrap_or_else(|code| code),
|
|
"erroneous_pdu_len": report.erroneous_pdu().len(),
|
|
"erroneous_pdu_hex": common::test_helper::bytes_to_hex(report.erroneous_pdu()),
|
|
"text": String::from_utf8_lossy(report.text()),
|
|
}),
|
|
);
|
|
assert_error_report_matches(&report, 1, ErrorCode::CorruptData, invalid_header.as_ref());
|
|
assert!(
|
|
std::str::from_utf8(report.text())
|
|
.unwrap()
|
|
.contains("invalid PDU length")
|
|
);
|
|
|
|
let read_res = Header::read(&mut client).await;
|
|
assert!(read_res.is_err());
|
|
dump.push_value(
|
|
0,
|
|
json!({
|
|
"event": "connection_closed_after_invalid_established_header",
|
|
"result": "header_read_failed_as_expected"
|
|
}),
|
|
);
|
|
dump.print_pretty("established_session_invalid_header_returns_corrupt_data");
|
|
|
|
shutdown_server(client, shutdown_tx, server_handle).await;
|
|
}
|
|
|
|
#[tokio::test]
|
|
async fn established_session_unknown_pdu_returns_unsupported_pdu_type() {
|
|
let cache = RtrCacheBuilder::new()
|
|
.session_ids(SessionIds::from_array([42, 42, 42]))
|
|
.serials(serials_all(100))
|
|
.timing(Timing::new(600, 600, 7200))
|
|
.build();
|
|
|
|
let server_cache = shared_cache(cache);
|
|
let (addr, _notify_tx, shutdown_tx, server_handle) = start_session_server(server_cache).await;
|
|
|
|
let mut client = TcpStream::connect(addr).await.unwrap();
|
|
let mut dump = RtrDebugDumper::new();
|
|
ResetQuery::new(1).write(&mut client).await.unwrap();
|
|
|
|
let response = CacheResponse::read(&mut client).await.unwrap();
|
|
dump.push_value(response.pdu(), dump_cache_response(&response));
|
|
|
|
let eod = EndOfDataV1::read(&mut client).await.unwrap();
|
|
dump.push_value(eod.pdu(), dump_eod_v1(&eod));
|
|
|
|
let unknown_pdu = Header::new(1, 12, 0, 8);
|
|
client.write_all(unknown_pdu.as_ref()).await.unwrap();
|
|
dump.push_value(
|
|
12,
|
|
json!({
|
|
"event": "unknown_pdu_sent_after_establishment",
|
|
"raw_hex": common::test_helper::bytes_to_hex(unknown_pdu.as_ref()),
|
|
"length": unknown_pdu.length(),
|
|
}),
|
|
);
|
|
|
|
let report = ErrorReport::read(&mut client).await.unwrap();
|
|
dump.push_value(
|
|
ErrorReport::PDU,
|
|
json!({
|
|
"version": report.version(),
|
|
"pdu": ErrorReport::PDU,
|
|
"pdu_name": "Error Report",
|
|
"error_code": report.error_code().map(|code| code.as_u16()).unwrap_or_else(|code| code),
|
|
"erroneous_pdu_len": report.erroneous_pdu().len(),
|
|
"erroneous_pdu_hex": common::test_helper::bytes_to_hex(report.erroneous_pdu()),
|
|
"text": String::from_utf8_lossy(report.text()),
|
|
}),
|
|
);
|
|
|
|
assert_error_report_matches(
|
|
&report,
|
|
1,
|
|
ErrorCode::UnsupportedPduType,
|
|
unknown_pdu.as_ref(),
|
|
);
|
|
|
|
let read_res = Header::read(&mut client).await;
|
|
assert!(read_res.is_err());
|
|
dump.push_value(
|
|
0,
|
|
json!({
|
|
"event": "connection_closed_after_unknown_pdu",
|
|
"result": "header_read_failed_as_expected"
|
|
}),
|
|
);
|
|
dump.print_pretty("established_session_unknown_pdu_returns_unsupported_pdu_type");
|
|
|
|
shutdown_server(client, shutdown_tx, server_handle).await;
|
|
}
|
|
|
|
#[tokio::test]
|
|
async fn version_zero_does_not_send_router_key_or_aspa() {
|
|
let router_key = RouterKey::new(Ski::default(), Asn::from(64496u32), vec![1u8; 32]);
|
|
let aspa = Aspa::new(
|
|
Asn::from(64496u32),
|
|
vec![Asn::from(64497u32), Asn::from(64498u32)],
|
|
);
|
|
let snapshot =
|
|
Snapshot::from_payloads(vec![Payload::RouterKey(router_key), Payload::Aspa(aspa)]);
|
|
|
|
let cache = RtrCacheBuilder::new()
|
|
.session_ids(SessionIds::from_array([42, 42, 42]))
|
|
.serials(serials_all(100))
|
|
.timing(Timing::new(600, 600, 7200))
|
|
.snapshots(snapshots_all(snapshot))
|
|
.build();
|
|
|
|
let server_cache = shared_cache(cache);
|
|
let (addr, _notify_tx, shutdown_tx, server_handle) = start_session_server(server_cache).await;
|
|
|
|
let mut client = TcpStream::connect(addr).await.unwrap();
|
|
let mut dump = RtrDebugDumper::new();
|
|
ResetQuery::new(0).write(&mut client).await.unwrap();
|
|
|
|
let response = CacheResponse::read(&mut client).await.unwrap();
|
|
dump.push_value(response.pdu(), dump_cache_response(&response));
|
|
|
|
let eod = rpki::rtr::pdu::EndOfDataV0::read(&mut client)
|
|
.await
|
|
.unwrap();
|
|
dump.push_value(
|
|
eod.pdu(),
|
|
json!({
|
|
"version": eod.version(),
|
|
"pdu": eod.pdu(),
|
|
"pdu_name": "End of Data",
|
|
"session_id": eod.session_id(),
|
|
"serial_number": eod.serial_number(),
|
|
}),
|
|
);
|
|
|
|
let res = timeout(Duration::from_millis(100), Header::read(&mut client)).await;
|
|
assert!(
|
|
res.is_err(),
|
|
"version 0 response should not contain RouterKey or ASPA PDUs"
|
|
);
|
|
dump.print_pretty("version_zero_does_not_send_router_key_or_aspa");
|
|
|
|
shutdown_server(client, shutdown_tx, server_handle).await;
|
|
}
|
|
|
|
#[tokio::test]
|
|
async fn version_two_aspa_withdraw_has_empty_provider_list() {
|
|
let aspa = Aspa::new(
|
|
Asn::from(64496u32),
|
|
vec![Asn::from(64497u32), Asn::from(64498u32)],
|
|
);
|
|
let delta = Arc::new(Delta::new(101, vec![], vec![Payload::Aspa(aspa)]));
|
|
let mut deltas = VecDeque::new();
|
|
deltas.push_back(delta);
|
|
|
|
let cache = RtrCacheBuilder::new()
|
|
.session_ids(SessionIds::from_array([42, 42, 42]))
|
|
.serials(serials_all(101))
|
|
.timing(Timing::new(600, 600, 7200))
|
|
.deltas_by_version(deltas_all(deltas))
|
|
.build();
|
|
|
|
let server_cache = shared_cache(cache);
|
|
let (addr, _notify_tx, shutdown_tx, server_handle) = start_session_server(server_cache).await;
|
|
|
|
let mut client = TcpStream::connect(addr).await.unwrap();
|
|
let mut dump = RtrDebugDumper::new();
|
|
SerialQuery::new(2, 42, 100)
|
|
.write(&mut client)
|
|
.await
|
|
.unwrap();
|
|
|
|
let response = CacheResponse::read(&mut client).await.unwrap();
|
|
dump.push_value(response.pdu(), dump_cache_response(&response));
|
|
|
|
let header = Header::read(&mut client).await.unwrap();
|
|
assert_eq!(header.pdu(), AspaPdu::PDU);
|
|
assert_eq!(header.length(), 12);
|
|
let mut body = [0u8; 4];
|
|
client.read_exact(&mut body).await.unwrap();
|
|
assert_eq!(u32::from_be_bytes(body), 64496);
|
|
dump.push_value(
|
|
AspaPdu::PDU,
|
|
json!({
|
|
"version": header.version(),
|
|
"pdu": header.pdu(),
|
|
"length": header.length(),
|
|
"customer_asn": u32::from_be_bytes(body),
|
|
"withdraw": true,
|
|
}),
|
|
);
|
|
|
|
let eod = EndOfDataV1::read(&mut client).await.unwrap();
|
|
dump.push_value(eod.pdu(), dump_eod_v1(&eod));
|
|
dump.print_pretty("version_two_aspa_withdraw_has_empty_provider_list");
|
|
|
|
shutdown_server(client, shutdown_tx, server_handle).await;
|
|
}
|
|
|
|
#[tokio::test]
|
|
async fn version_one_sends_router_key_but_not_aspa() {
|
|
let valid_spki = vec![
|
|
0x30, 0x13, 0x30, 0x0d, 0x06, 0x09, 0x2a, 0x86, 0x48, 0x86, 0xf7, 0x0d, 0x01, 0x01, 0x01,
|
|
0x05, 0x00, 0x03, 0x02, 0x00, 0x00,
|
|
];
|
|
let router_key = RouterKey::new(Ski::default(), Asn::from(64496u32), valid_spki);
|
|
let aspa = Aspa::new(Asn::from(64496u32), vec![Asn::from(64497u32)]);
|
|
let snapshot =
|
|
Snapshot::from_payloads(vec![Payload::RouterKey(router_key), Payload::Aspa(aspa)]);
|
|
|
|
let cache = RtrCacheBuilder::new()
|
|
.session_ids(SessionIds::from_array([42, 42, 42]))
|
|
.serials(serials_all(100))
|
|
.timing(Timing::new(600, 600, 7200))
|
|
.snapshots(snapshots_all(snapshot))
|
|
.build();
|
|
|
|
let server_cache = shared_cache(cache);
|
|
let (addr, _notify_tx, shutdown_tx, server_handle) = start_session_server(server_cache).await;
|
|
|
|
let mut client = TcpStream::connect(addr).await.unwrap();
|
|
let mut dump = RtrDebugDumper::new();
|
|
ResetQuery::new(1).write(&mut client).await.unwrap();
|
|
|
|
let response = CacheResponse::read(&mut client).await.unwrap();
|
|
dump.push_value(response.pdu(), dump_cache_response(&response));
|
|
|
|
let header = Header::read(&mut client).await.unwrap();
|
|
assert_eq!(header.pdu(), RouterKeyPdu::PDU);
|
|
dump.push_value(
|
|
RouterKeyPdu::PDU,
|
|
json!({
|
|
"version": header.version(),
|
|
"pdu": header.pdu(),
|
|
"length": header.length(),
|
|
}),
|
|
);
|
|
|
|
let payload_len = header.length() as usize - 8;
|
|
let mut payload = vec![0u8; payload_len];
|
|
client.read_exact(&mut payload).await.unwrap();
|
|
|
|
let eod = EndOfDataV1::read(&mut client).await.unwrap();
|
|
dump.push_value(eod.pdu(), dump_eod_v1(&eod));
|
|
|
|
let res = timeout(Duration::from_millis(100), Header::read(&mut client)).await;
|
|
assert!(
|
|
res.is_err(),
|
|
"version 1 response should not contain ASPA PDUs"
|
|
);
|
|
dump.print_pretty("version_one_sends_router_key_but_not_aspa");
|
|
|
|
shutdown_server(client, shutdown_tx, server_handle).await;
|
|
}
|
|
|
|
#[tokio::test]
|
|
async fn established_session_idle_does_not_trigger_transport_failed() {
|
|
let cache = RtrCacheBuilder::new()
|
|
.session_ids(SessionIds::from_array([42, 42, 42]))
|
|
.serials(serials_all(100))
|
|
.timing(Timing::new(600, 600, 7200))
|
|
.build();
|
|
|
|
let server_cache = shared_cache(cache);
|
|
let (addr, _notify_tx, shutdown_tx, server_handle) =
|
|
start_session_server_with_transport_timeout(server_cache, Duration::from_millis(100)).await;
|
|
|
|
let mut client = TcpStream::connect(addr).await.unwrap();
|
|
let mut dump = RtrDebugDumper::new();
|
|
ResetQuery::new(1).write(&mut client).await.unwrap();
|
|
|
|
let response = CacheResponse::read(&mut client).await.unwrap();
|
|
dump.push_value(response.pdu(), dump_cache_response(&response));
|
|
|
|
let eod = EndOfDataV1::read(&mut client).await.unwrap();
|
|
dump.push_value(eod.pdu(), dump_eod_v1(&eod));
|
|
|
|
let idle_res = timeout(Duration::from_millis(300), Header::read(&mut client)).await;
|
|
assert!(
|
|
idle_res.is_err(),
|
|
"established session should stay idle without sending TransportFailed"
|
|
);
|
|
dump.push_value(
|
|
0,
|
|
json!({
|
|
"event": "no_transport_failed_on_established_idle",
|
|
"result": "no_pdu_received_within_timeout",
|
|
"timeout_ms": 300
|
|
}),
|
|
);
|
|
dump.print_pretty("established_session_idle_does_not_trigger_transport_failed");
|
|
|
|
shutdown_server(client, shutdown_tx, server_handle).await;
|
|
}
|
|
|
|
#[tokio::test]
|
|
async fn tls_client_with_matching_san_ip_is_accepted() {
|
|
let cache = RtrCacheBuilder::new()
|
|
.session_ids(SessionIds::from_array([42, 42, 42]))
|
|
.serials(serials_all(100))
|
|
.timing(Timing::new(600, 600, 7200))
|
|
.build();
|
|
|
|
let server_cache = shared_cache(cache);
|
|
let (addr, shutdown_tx, server_handle) = start_tls_session_server(server_cache).await;
|
|
|
|
let mut client = connect_tls_client(addr, "client-good.crt", "client-good.key").await;
|
|
let mut dump = RtrDebugDumper::new();
|
|
ResetQuery::new(1).write(&mut client).await.unwrap();
|
|
|
|
let response = CacheResponse::read(&mut client).await.unwrap();
|
|
dump.push_value(response.pdu(), dump_cache_response(&response));
|
|
assert_eq!(response.version(), 1);
|
|
assert_eq!(response.session_id(), 42);
|
|
|
|
let eod = EndOfDataV1::read(&mut client).await.unwrap();
|
|
dump.push_value(eod.pdu(), dump_eod_v1(&eod));
|
|
assert_eq!(eod.version(), 1);
|
|
assert_eq!(eod.session_id(), 42);
|
|
assert_eq!(eod.serial_number(), 100);
|
|
|
|
dump.print_pretty("tls_client_with_matching_san_ip_is_accepted");
|
|
shutdown_io(&mut client, shutdown_tx, server_handle).await;
|
|
}
|
|
|
|
#[tokio::test]
|
|
async fn tls_client_accepts_server_certificate_with_dns_san() {
|
|
let cache = RtrCacheBuilder::new()
|
|
.session_ids(SessionIds::from_array([42, 42, 42]))
|
|
.serials(serials_all(100))
|
|
.timing(Timing::new(600, 600, 7200))
|
|
.build();
|
|
|
|
let server_cache = shared_cache(cache);
|
|
let (addr, shutdown_tx, server_handle) =
|
|
start_tls_session_server_with_cert(server_cache, "server-dns.crt", "server-dns.key").await;
|
|
|
|
let mut client = connect_tls_client_with_server_name(
|
|
addr,
|
|
"client-good.crt",
|
|
"client-good.key",
|
|
ServerName::try_from("localhost").unwrap(),
|
|
)
|
|
.await;
|
|
let mut dump = RtrDebugDumper::new();
|
|
ResetQuery::new(1).write(&mut client).await.unwrap();
|
|
|
|
let response = CacheResponse::read(&mut client).await.unwrap();
|
|
dump.push_value(response.pdu(), dump_cache_response(&response));
|
|
assert_eq!(response.version(), 1);
|
|
assert_eq!(response.session_id(), 42);
|
|
|
|
let eod = EndOfDataV1::read(&mut client).await.unwrap();
|
|
dump.push_value(eod.pdu(), dump_eod_v1(&eod));
|
|
assert_eq!(eod.version(), 1);
|
|
assert_eq!(eod.session_id(), 42);
|
|
assert_eq!(eod.serial_number(), 100);
|
|
|
|
dump.print_pretty("tls_client_accepts_server_certificate_with_dns_san");
|
|
shutdown_io(&mut client, shutdown_tx, server_handle).await;
|
|
}
|
|
|
|
#[tokio::test]
|
|
async fn tls_server_dns_name_san_strict_mode_rejects_ip_only_certificate() {
|
|
let cache = RtrCacheBuilder::new()
|
|
.session_ids(SessionIds::from_array([42, 42, 42]))
|
|
.serials(serials_all(100))
|
|
.timing(Timing::new(600, 600, 7200))
|
|
.build();
|
|
|
|
let err = load_rustls_server_config_with_options(
|
|
fixture_path("server.crt"),
|
|
fixture_path("server.key"),
|
|
fixture_path("client-ca.crt"),
|
|
true,
|
|
)
|
|
.unwrap_err();
|
|
|
|
assert!(
|
|
err.to_string()
|
|
.contains("does not contain a subjectAltName dNSName entry")
|
|
);
|
|
|
|
let _ = cache;
|
|
}
|
|
|
|
#[tokio::test]
|
|
async fn tls_client_with_mismatched_san_ip_is_rejected() {
|
|
let cache = RtrCacheBuilder::new()
|
|
.session_ids(SessionIds::from_array([42, 42, 42]))
|
|
.serials(serials_all(100))
|
|
.timing(Timing::new(600, 600, 7200))
|
|
.build();
|
|
|
|
let server_cache = shared_cache(cache);
|
|
let (addr, shutdown_tx, server_handle) = start_tls_session_server(server_cache).await;
|
|
|
|
let mut client = connect_tls_client(addr, "client-bad.crt", "client-bad.key").await;
|
|
let mut dump = RtrDebugDumper::new();
|
|
ResetQuery::new(1).write(&mut client).await.unwrap();
|
|
dump.push_value(
|
|
ResetQuery::PDU,
|
|
json!({
|
|
"event": "tls_reset_query_sent_with_bad_client_cert",
|
|
"version": 1,
|
|
}),
|
|
);
|
|
|
|
let read_res = timeout(Duration::from_secs(1), Header::read(&mut client))
|
|
.await
|
|
.expect("timed out waiting for TLS session close");
|
|
assert!(
|
|
read_res.is_err(),
|
|
"server should close TLS session when client SAN IP mismatches"
|
|
);
|
|
dump.push_value(
|
|
0,
|
|
json!({
|
|
"event": "tls_session_closed_after_san_ip_mismatch",
|
|
"result": "header_read_failed_as_expected"
|
|
}),
|
|
);
|
|
dump.print_pretty("tls_client_with_mismatched_san_ip_is_rejected");
|
|
|
|
shutdown_io(&mut client, shutdown_tx, server_handle).await;
|
|
}
|
|
|
|
#[tokio::test]
|
|
async fn invalid_timing_prevents_end_of_data_response() {
|
|
let cache = RtrCacheBuilder::new()
|
|
.session_ids(SessionIds::from_array([42, 42, 42]))
|
|
.serials(serials_all(100))
|
|
.timing(Timing::new(600, 8000, 7200))
|
|
.build();
|
|
|
|
let server_cache = shared_cache(cache);
|
|
let (addr, shutdown_tx, server_handle) =
|
|
start_session_server_returning_result(server_cache).await;
|
|
|
|
let mut client = TcpStream::connect(addr).await.unwrap();
|
|
ResetQuery::new(1).write(&mut client).await.unwrap();
|
|
|
|
let response = CacheResponse::read(&mut client).await.unwrap();
|
|
assert_eq!(response.version(), 1);
|
|
assert_eq!(response.session_id(), 42);
|
|
|
|
let read_res = timeout(Duration::from_secs(1), Header::read(&mut client))
|
|
.await
|
|
.expect("timed out waiting for server close");
|
|
assert!(
|
|
read_res.is_err(),
|
|
"server should close instead of sending invalid EndOfData"
|
|
);
|
|
|
|
let _ = shutdown_tx.send(RtrServiceControl::Shutdown(RtrShutdownReason::CloseOnly));
|
|
let join = timeout(Duration::from_secs(1), server_handle)
|
|
.await
|
|
.expect("server task did not exit within timeout")
|
|
.unwrap();
|
|
let err = join.expect_err("session should fail on invalid timing");
|
|
assert!(err.to_string().contains("retry interval"));
|
|
}
|
|
|
|
#[tokio::test]
|
|
async fn invalid_aspa_prevents_snapshot_response() {
|
|
let snapshot =
|
|
Snapshot::from_payloads(vec![Payload::Aspa(Aspa::new(Asn::from(64496u32), vec![]))]);
|
|
let cache = RtrCacheBuilder::new()
|
|
.session_ids(SessionIds::from_array([42, 42, 42]))
|
|
.serials(serials_all(100))
|
|
.timing(Timing::new(600, 600, 7200))
|
|
.snapshots(snapshots_all(snapshot))
|
|
.build();
|
|
|
|
let server_cache = shared_cache(cache);
|
|
let (addr, shutdown_tx, server_handle) =
|
|
start_session_server_returning_result(server_cache).await;
|
|
|
|
let mut client = TcpStream::connect(addr).await.unwrap();
|
|
ResetQuery::new(2).write(&mut client).await.unwrap();
|
|
|
|
let response = CacheResponse::read(&mut client).await.unwrap();
|
|
assert_eq!(response.version(), 2);
|
|
assert_eq!(response.session_id(), 42);
|
|
|
|
let read_res = timeout(Duration::from_secs(1), Header::read(&mut client))
|
|
.await
|
|
.expect("timed out waiting for server close");
|
|
assert!(
|
|
read_res.is_err(),
|
|
"server should close instead of sending invalid ASPA"
|
|
);
|
|
|
|
let _ = shutdown_tx.send(RtrServiceControl::Shutdown(RtrShutdownReason::CloseOnly));
|
|
let join = timeout(Duration::from_secs(1), server_handle)
|
|
.await
|
|
.expect("server task did not exit within timeout")
|
|
.unwrap();
|
|
let err = join.expect_err("session should fail on invalid ASPA");
|
|
assert!(err.to_string().contains("ASPA announcement"));
|
|
}
|
|
|
|
#[tokio::test]
|
|
async fn invalid_router_key_prevents_snapshot_response() {
|
|
let snapshot = Snapshot::from_payloads(vec![Payload::RouterKey(RouterKey::new(
|
|
Ski::default(),
|
|
Asn::from(64496u32),
|
|
vec![0xde, 0xad, 0xbe, 0xef],
|
|
))]);
|
|
let cache = RtrCacheBuilder::new()
|
|
.session_ids(SessionIds::from_array([42, 42, 42]))
|
|
.serials(serials_all(100))
|
|
.timing(Timing::new(600, 600, 7200))
|
|
.snapshots(snapshots_all(snapshot))
|
|
.build();
|
|
|
|
let server_cache = shared_cache(cache);
|
|
let (addr, shutdown_tx, server_handle) =
|
|
start_session_server_returning_result(server_cache).await;
|
|
|
|
let mut client = TcpStream::connect(addr).await.unwrap();
|
|
ResetQuery::new(1).write(&mut client).await.unwrap();
|
|
|
|
let response = CacheResponse::read(&mut client).await.unwrap();
|
|
assert_eq!(response.version(), 1);
|
|
assert_eq!(response.session_id(), 42);
|
|
|
|
let read_res = timeout(Duration::from_secs(1), Header::read(&mut client))
|
|
.await
|
|
.expect("timed out waiting for server close");
|
|
assert!(
|
|
read_res.is_err(),
|
|
"server should close instead of sending invalid RouterKey"
|
|
);
|
|
|
|
let _ = shutdown_tx.send(RtrServiceControl::Shutdown(RtrShutdownReason::CloseOnly));
|
|
let join = timeout(Duration::from_secs(1), server_handle)
|
|
.await
|
|
.expect("server task did not exit within timeout")
|
|
.unwrap();
|
|
let err = join.expect_err("session should fail on invalid RouterKey");
|
|
assert!(err.to_string().contains("RouterKey SPKI"));
|
|
}
|