Kernel and RPKI: say what went wrong, and once
The RPKI client had one Timeout error whose text said "did not answer within 60s" whatever had run out: the connect timeout, the time for a whole answer, or the data's lifetime. These are what /api/rpki shows as the last error; each is its own now. The protocol version shown is the one the cache answered in, not the one being tried. The VRP set, hashed by the hundred thousand with every answer, uses the quick hash the ASPA table already did. In the kernel installer the writer task's error was worked out in three places, and the next-hop resolution checked for our own networks twice on every cache miss. A refusal was logged twice, once with the status lock held. The interfaces are listed in order of index by the structure that holds them, from a function that can be tested on any OS, and is. EIO is with the other errnos; Plan has no installed_prefixes (no callers) and clears its records in one place, with the order of Unresolved::ALL asserted at compile time since indexing depends on it. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
This commit is contained in:
1 parent
3b22e318a2
commit
b467b136e4
8 files changed
+179
-111
No files matched your search
+38
-18
@@ -17,7 +17,6 @@
|
||||
//! worse: it would go on refusing routes whose holders have since
|
||||
//! authorised them.
|
||||
|
||||
use std::collections::HashSet;
|
||||
use std::io;
|
||||
use std::net::SocketAddr;
|
||||
use std::sync::atomic::{AtomicU64, Ordering};
|
||||
@@ -37,6 +36,10 @@ use crate::aspa::AspaTable;
|
||||
use crate::rtr::{Pdu, RtrError, Timers, VERSION_0, VERSION_2, error_code};
|
||||
use crate::vrp::{Vrp, VrpTable};
|
||||
|
||||
// Hashed by the hundred thousand with every answer, and told apart from
|
||||
// what there was with every reset: a quick hash matters here.
|
||||
type HashSet<K> = std::collections::HashSet<K, foldhash::fast::RandomState>;
|
||||
|
||||
const CONNECT_TIMEOUT: Duration = Duration::from_secs(10);
|
||||
|
||||
/// How long a cache may take to send the next part of an answer.
|
||||
@@ -226,8 +229,14 @@ enum SessionError {
|
||||
Malformed(#[from] RtrError),
|
||||
#[error("the cache closed the connection")]
|
||||
Closed,
|
||||
#[error("the cache did not answer within {RESPONSE_TIMEOUT:?}")]
|
||||
Timeout,
|
||||
#[error("the cache did not accept the connection within {0:?}")]
|
||||
ConnectTimeout(Duration),
|
||||
#[error("the cache sent nothing for {0:?} in the middle of an answer")]
|
||||
ResponseTimeout(Duration),
|
||||
#[error("the cache did not finish its answer within {0:?}")]
|
||||
AnswerTimeout(Duration),
|
||||
#[error("the cache confirmed nothing for so long that the data expired")]
|
||||
Expired,
|
||||
#[error("the cache reported an error: {text} (code {code})")]
|
||||
Reported { code: u16, text: String },
|
||||
#[error("the cache sent {0} where it has no place")]
|
||||
@@ -249,8 +258,14 @@ impl SessionError {
|
||||
Self::Malformed(e) => Some(e.code()),
|
||||
Self::Unexpected(_) => Some(error_code::CORRUPT_DATA),
|
||||
Self::Inconsistent(problem) => Some(problem.code),
|
||||
Self::Io(_) | Self::Closed | Self::Timeout | Self::Reported { .. } => None,
|
||||
Self::Downgrade(_) => None,
|
||||
Self::Io(_)
|
||||
| Self::Closed
|
||||
| Self::ConnectTimeout(_)
|
||||
| Self::ResponseTimeout(_)
|
||||
| Self::AnswerTimeout(_)
|
||||
| Self::Expired
|
||||
| Self::Reported { .. }
|
||||
| Self::Downgrade(_) => None,
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -488,17 +503,14 @@ impl Client {
|
||||
let stream = match timeout(CONNECT_TIMEOUT, TcpStream::connect(cache)).await {
|
||||
Ok(Ok(stream)) => stream,
|
||||
Ok(Err(e)) => return e.into(),
|
||||
Err(_) => return SessionError::Timeout,
|
||||
Err(_) => return SessionError::ConnectTimeout(CONNECT_TIMEOUT),
|
||||
};
|
||||
let mut connection = Connection {
|
||||
stream,
|
||||
buf: BytesMut::with_capacity(64 * 1024),
|
||||
};
|
||||
info!(%cache, "Connected to RPKI cache");
|
||||
self.status(|status| {
|
||||
status.state = CacheState::Synchronising;
|
||||
status.protocol_version = self.protocol_version;
|
||||
});
|
||||
self.status(|status| status.state = CacheState::Synchronising);
|
||||
|
||||
loop {
|
||||
if let Err(e) = self.synchronise(cache, &mut connection).await {
|
||||
@@ -527,7 +539,7 @@ impl Client {
|
||||
() = sleep_until(ask) => {}
|
||||
() = sleep_until(expires), if expires < ask => {
|
||||
self.expired();
|
||||
return SessionError::Timeout;
|
||||
return SessionError::Expired;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -592,10 +604,13 @@ impl Client {
|
||||
// one a minute would keep the old data in use for ever.
|
||||
let deadline = Instant::now() + ANSWER_TIMEOUT;
|
||||
let mut next = async || {
|
||||
let wait = RESPONSE_TIMEOUT.min(deadline.saturating_duration_since(Instant::now()));
|
||||
match timeout(wait, connection.next()).await {
|
||||
let left = deadline.saturating_duration_since(Instant::now());
|
||||
match timeout(RESPONSE_TIMEOUT.min(left), connection.next()).await {
|
||||
Ok(pdu) => pdu,
|
||||
Err(_) => Err(SessionError::Timeout),
|
||||
Err(_) if left <= RESPONSE_TIMEOUT => {
|
||||
Err(SessionError::AnswerTimeout(ANSWER_TIMEOUT))
|
||||
}
|
||||
Err(_) => Err(SessionError::ResponseTimeout(RESPONSE_TIMEOUT)),
|
||||
}
|
||||
};
|
||||
|
||||
@@ -630,6 +645,10 @@ impl Client {
|
||||
_ => return Err(SessionError::Unexpected("data before a Cache Response")),
|
||||
}
|
||||
};
|
||||
// The cache has answered in the version asked for: that is the
|
||||
// version of the session.
|
||||
let protocol_version = self.protocol_version;
|
||||
self.status(|status| status.protocol_version = protocol_version);
|
||||
if since.is_some_and(|place| place.session_id != session_id) {
|
||||
// The cache has started over; its serials mean something else
|
||||
// now.
|
||||
@@ -711,7 +730,8 @@ impl Client {
|
||||
serial,
|
||||
});
|
||||
self.timers = timers.unwrap_or_default();
|
||||
self.expires = Some(Instant::now() + self.expire());
|
||||
let expires_in = self.expire();
|
||||
self.expires = Some(Instant::now() + expires_in);
|
||||
let changes = built.changed.vrps.len() + built.changed.aspa_customers.len();
|
||||
if first || changes > 0 {
|
||||
self.publish(built.vrps, (!first).then_some(built.changed));
|
||||
@@ -724,13 +744,13 @@ impl Client {
|
||||
changes,
|
||||
"RPKI data is up to date"
|
||||
);
|
||||
let expires_at = SystemTime::now() + self.expire();
|
||||
let updated_at = SystemTime::now();
|
||||
self.status(|status| {
|
||||
status.state = CacheState::Synchronised;
|
||||
status.session_id = Some(session_id);
|
||||
status.serial = Some(serial);
|
||||
status.updated_at = Some(SystemTime::now());
|
||||
status.expires_at = Some(expires_at);
|
||||
status.updated_at = Some(updated_at);
|
||||
status.expires_at = Some(updated_at + expires_in);
|
||||
status.last_error = None;
|
||||
});
|
||||
Ok(Answer::Complete)
|
||||
|
||||
@@ -8,7 +8,6 @@
|
||||
//! tell a route whose origin is authorised from one whose origin is not:
|
||||
//! the commonest kind of hijack and of mistake.
|
||||
//!
|
||||
//!
|
||||
//! The same way, a network can sign a statement of who its providers are
|
||||
//! (an ASPA). With those a router can tell a path that could have come
|
||||
//! about by networks passing routes on as they are paid to from one that
|
||||
|
||||
@@ -425,7 +425,10 @@ async fn aspas_are_taken_replaced_and_withdrawn() {
|
||||
PathValidity::Valid
|
||||
);
|
||||
let changed = update.changed.expect("a change to what there was");
|
||||
assert_eq!(*changed.aspa_customers, [3333, 2121, 1101].into());
|
||||
assert_eq!(
|
||||
*changed.aspa_customers,
|
||||
[3333, 2121, 1101].into_iter().collect()
|
||||
);
|
||||
assert!(changed.vrps.is_empty(), "no VRP came or went");
|
||||
assert_eq!(update.vrps.len(), 1);
|
||||
|
||||
@@ -536,7 +539,7 @@ async fn a_cache_that_speaks_version_one_has_no_aspas() {
|
||||
assert!(update.aspas.is_empty());
|
||||
assert_eq!(update.vrps.len(), 1);
|
||||
let changed = update.changed.expect("the difference to what there was");
|
||||
assert_eq!(*changed.aspa_customers, [3333].into());
|
||||
assert_eq!(*changed.aspa_customers, [3333].into_iter().collect());
|
||||
assert!(changed.vrps.is_empty());
|
||||
let status = rpki.status();
|
||||
assert_eq!((status.protocol_version, status.aspas), (1, 0));
|
||||
|
||||
Reference in new issue
Block a user