One word for a route that is not taken: rejected
ci / check (push) Canceled after 0s
ci / docs (push) Canceled after 0s
ci / interop (push) Canceled after 0s
ci / fuzz (push) Canceled after 0s

The API, the metrics, the status page and the documentation say a route
is "rejected" (/api/routes/rejected, rejected_routes,
bgp_routes_rejected_total, "Rejected routes"), and the reason type is
Rejection. The code that keeps the record said "refused": RefusalLog,
PeerRefusals, RefusedRoute, RefusalsView, RefusedRouteView, the refusals
module. Those are RejectionLog, PeerRejections, RejectedRoute,
RejectionsView, RejectedRouteView and rejections now, and the log line
says "Rejected a route". Nothing the API or the metrics expose changes.

Refusing stays what it was: a connection turned away at the door
(server::Refusal), a kernel that would not take a route, an OPEN we
would not have.

The two aliases of SendReceive, AddPathSendRecv and OrfSendRecv, are
gone; it is one type.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
This commit is contained in:
Rayhaan JaufeerallyandClaude Fable 5.1 committed 2026-10-06 12:10:58 +02:00
1 parent c218ea4d85
commit 23e8b4aadb
21 files changed
+146 -154

No files matched your search

+3 -3
View File
@@ -728,7 +728,7 @@ impl Task {
Op::Remove { prefix } => format!("remove {prefix}"), Op::Remove { prefix } => format!("remove {prefix}"),
}; };
// Counted always, said out loud once in a while: a table's worth // Counted always, said out loud once in a while: a table's worth
// of refusals is a table's worth of lines. // of rejections is a table's worth of lines.
ERRORS.inc(); ERRORS.inc();
let said = format!("the kernel refused to {what}: {error}"); let said = format!("the kernel refused to {what}: {error}");
let before = self.fib.update(|status| { let before = self.fib.update(|status| {
@@ -738,9 +738,9 @@ impl Task {
before before
}); });
if before == 0 || before.is_power_of_two() { if before == 0 || before.is_power_of_two() {
warn!(refusals = before + 1, "{said}"); warn!(rejections = before + 1, "{said}");
} else { } else {
debug!(refusals = before + 1, "{said}"); debug!(rejections = before + 1, "{said}");
} }
Ok(self.plan.refused_by_kernel(op)) Ok(self.plan.refused_by_kernel(op))
} }
+1 -1
View File
@@ -463,7 +463,7 @@ async fn many_routes_are_installed_and_removed() {
/// Someone empties the table behind our back (`ip route flush`), and then /// Someone empties the table behind our back (`ip route flush`), and then
/// the peers withdraw everything: the kernel refuses every removal, a /// the peers withdraw everything: the kernel refuses every removal, a
/// table's worth of refusals at once. That is more than the socket holds /// table's worth of rejections at once. That is more than the socket holds
/// answers for, and must be no more than a reason to look again. /// answers for, and must be no more than a reason to look again.
#[tokio::test(flavor = "multi_thread")] #[tokio::test(flavor = "multi_thread")]
#[ignore = "changes the machine's routes; run scripts/kernel-tests.sh"] #[ignore = "changes the machine's routes; run scripts/kernel-tests.sh"]
+5 -5
View File
@@ -11,7 +11,7 @@ use bgp_rib::table::{Event, PeerIndex, Reader, RibTable};
use bgp_rpki::Rpki; use bgp_rpki::Rpki;
use bgp_server::config::{Config, PeerConfig}; use bgp_server::config::{Config, PeerConfig};
use bgp_server::error::ServerError; use bgp_server::error::ServerError;
use bgp_server::refusals::RefusalLog; use bgp_server::rejections::RejectionLog;
use bgp_server::restart::Restarts; use bgp_server::restart::Restarts;
use bgp_server::server::BgpServer; use bgp_server::server::BgpServer;
use bgp_server::session::{SessionHandle, SessionRegistry}; use bgp_server::session::{SessionHandle, SessionRegistry};
@@ -36,7 +36,7 @@ pub struct TestServer {
/// Live view of the server's established sessions. /// Live view of the server's established sessions.
pub registry: SessionRegistry, pub registry: SessionRegistry,
/// What peers announced that was not taken. /// What peers announced that was not taken.
pub refusals: RefusalLog, pub rejections: RejectionLog,
/// The data for route origin validation, if the server validates. /// The data for route origin validation, if the server validates.
pub rpki: Option<Rpki>, pub rpki: Option<Rpki>,
/// What keeps the best routes in the kernel, if the server does. /// What keeps the best routes in the kernel, if the server does.
@@ -92,7 +92,7 @@ impl TestServer {
let rib = Arc::clone(&bound.rib); let rib = Arc::clone(&bound.rib);
let local_rib = bound.local_rib.clone(); let local_rib = bound.local_rib.clone();
let registry = bound.registry.clone(); let registry = bound.registry.clone();
let refusals = bound.refusals.clone(); let rejections = bound.rejections.clone();
let rpki = bound.rpki.clone(); let rpki = bound.rpki.clone();
let kernel = bound.kernel.clone(); let kernel = bound.kernel.clone();
let restarts = Arc::clone(&bound.restarts); let restarts = Arc::clone(&bound.restarts);
@@ -104,7 +104,7 @@ impl TestServer {
rib, rib,
local_rib, local_rib,
registry, registry,
refusals, rejections,
rpki, rpki,
kernel, kernel,
restarts, restarts,
@@ -348,7 +348,7 @@ impl TestServer {
registry: self.registry.clone(), registry: self.registry.clone(),
local_rib: self.local_rib.clone(), local_rib: self.local_rib.clone(),
rib: Arc::clone(&self.rib), rib: Arc::clone(&self.rib),
refusals: self.refusals.clone(), rejections: self.rejections.clone(),
rpki: self.rpki.clone(), rpki: self.rpki.clone(),
kernel: self.kernel.clone(), kernel: self.kernel.clone(),
restarts: Arc::clone(&self.restarts), restarts: Arc::clone(&self.restarts),
+11 -14
View File
@@ -9,7 +9,7 @@ use bgp_integration_tests::test_peer::TestPeer;
use bgp_integration_tests::test_server::TestServer; use bgp_integration_tests::test_server::TestServer;
use bgp_integration_tests::{peer_config, prefix, wait_until}; use bgp_integration_tests::{peer_config, prefix, wait_until};
use bgp_packet::message::PathAttribute; use bgp_packet::message::PathAttribute;
use bgp_packet::open::{AddPathEntry, AddPathSendRecv, Capability, OpenMessage}; use bgp_packet::open::{AddPathEntry, Capability, OpenMessage, SendReceive};
use bgp_primitives::constants::AfiSafi; use bgp_primitives::constants::AfiSafi;
use bgp_primitives::ip_prefix::Nlri; use bgp_primitives::ip_prefix::Nlri;
use bgp_rib::{LocalRoute, LocalRouteAttrs}; use bgp_rib::{LocalRoute, LocalRouteAttrs};
@@ -24,10 +24,7 @@ const PEER_ASN: u32 = 65001;
/// A server on which connections from this machine are those of a /// A server on which connections from this machine are those of a
/// configured peer, with ADD-PATH offered to it as `add_path` says. /// configured peer, with ADD-PATH offered to it as `add_path` says.
async fn server_offering( async fn server_offering(add_path: Option<SendReceive>, originate: &[&str]) -> Result<TestServer> {
add_path: Option<AddPathSendRecv>,
originate: &[&str],
) -> Result<TestServer> {
let originate: Vec<LocalRoute> = originate let originate: Vec<LocalRoute> = originate
.iter() .iter()
.map(|p| LocalRoute::new(prefix(p), LocalRouteAttrs::default())) .map(|p| LocalRoute::new(prefix(p), LocalRouteAttrs::default()))
@@ -50,7 +47,7 @@ async fn server_offering(
/// IPv4 unicast. Returns the peer and the server's OPEN. /// IPv4 unicast. Returns the peer and the server's OPEN.
async fn peer_offering( async fn peer_offering(
server: &TestServer, server: &TestServer,
offer: Option<AddPathSendRecv>, offer: Option<SendReceive>,
) -> Result<(TestPeer, OpenMessage)> { ) -> Result<(TestPeer, OpenMessage)> {
let mut capabilities = vec![Capability::FourByteAsn { asn: PEER_ASN }]; let mut capabilities = vec![Capability::FourByteAsn { asn: PEER_ASN }];
capabilities.extend(SUPPORTED.into_iter().map(Capability::multiprotocol)); capabilities.extend(SUPPORTED.into_iter().map(Capability::multiprotocol));
@@ -71,7 +68,7 @@ async fn peer_offering(
Ok((peer, open)) Ok((peer, open))
} }
fn add_path_offered(open: &OpenMessage) -> Vec<(AfiSafi, AddPathSendRecv)> { fn add_path_offered(open: &OpenMessage) -> Vec<(AfiSafi, SendReceive)> {
open.capabilities() open.capabilities()
.filter_map(|capability| match capability { .filter_map(|capability| match capability {
Capability::AddPath { entries } => Some(entries), Capability::AddPath { entries } => Some(entries),
@@ -111,14 +108,14 @@ fn held(server: &TestServer) -> Vec<(u32, Vec<u32>, bool)> {
/// of them is chosen, and each is withdrawn on its own. /// of them is chosen, and each is withdrawn on its own.
#[tokio::test] #[tokio::test]
async fn test_several_routes_for_a_prefix_from_one_peer() -> Result<()> { async fn test_several_routes_for_a_prefix_from_one_peer() -> Result<()> {
let server = server_offering(Some(AddPathSendRecv::Receive), &[]).await?; let server = server_offering(Some(SendReceive::Receive), &[]).await?;
let (base, _http) = server.serve_http().await?; let (base, _http) = server.serve_http().await?;
let (mut peer, open) = peer_offering(&server, Some(AddPathSendRecv::Send)).await?; let (mut peer, open) = peer_offering(&server, Some(SendReceive::Send)).await?;
// The server offered to receive, for every family it offers. // The server offered to receive, for every family it offers.
assert_eq!( assert_eq!(
add_path_offered(&open), add_path_offered(&open),
SUPPORTED.map(|family| (family, AddPathSendRecv::Receive)) SUPPORTED.map(|family| (family, SendReceive::Receive))
); );
// In use for the one family the peer sends for, and in that direction. // In use for the one family the peer sends for, and in that direction.
let session = &server.registry.list()[0]; let session = &server.registry.list()[0];
@@ -195,7 +192,7 @@ async fn test_several_routes_for_a_prefix_from_one_peer() -> Result<()> {
#[tokio::test] #[tokio::test]
async fn test_add_path_is_not_used_unless_both_sides_offer_it() -> Result<()> { async fn test_add_path_is_not_used_unless_both_sides_offer_it() -> Result<()> {
let server = server_offering(None, &["10.100.0.0/16"]).await?; let server = server_offering(None, &["10.100.0.0/16"]).await?;
let (mut peer, open) = peer_offering(&server, Some(AddPathSendRecv::Both)).await?; let (mut peer, open) = peer_offering(&server, Some(SendReceive::Both)).await?;
assert!(add_path_offered(&open).is_empty()); assert!(add_path_offered(&open).is_empty());
let session = &server.registry.list()[0]; let session = &server.registry.list()[0];
assert!(session.add_path_receive.is_empty() && session.add_path_send.is_empty()); assert!(session.add_path_receive.is_empty() && session.add_path_send.is_empty());
@@ -209,7 +206,7 @@ async fn test_add_path_is_not_used_unless_both_sides_offer_it() -> Result<()> {
assert_eq!(held(&server), [(0, vec![PEER_ASN], true)]); assert_eq!(held(&server), [(0, vec![PEER_ASN], true)]);
// And the other way round: offered by the server, not by the peer. // And the other way round: offered by the server, not by the peer.
let server = server_offering(Some(AddPathSendRecv::Both), &["10.100.0.0/16"]).await?; let server = server_offering(Some(SendReceive::Both), &["10.100.0.0/16"]).await?;
let (mut peer, open) = peer_offering(&server, None).await?; let (mut peer, open) = peer_offering(&server, None).await?;
assert_eq!(add_path_offered(&open).len(), SUPPORTED.len()); assert_eq!(add_path_offered(&open).len(), SUPPORTED.len());
let update = peer.recv_update().await?; let update = peer.recv_update().await?;
@@ -222,8 +219,8 @@ async fn test_add_path_is_not_used_unless_both_sides_offer_it() -> Result<()> {
/// understands them. /// understands them.
#[tokio::test] #[tokio::test]
async fn test_prefixes_are_sent_with_path_identifiers_when_agreed() -> Result<()> { async fn test_prefixes_are_sent_with_path_identifiers_when_agreed() -> Result<()> {
let server = server_offering(Some(AddPathSendRecv::Both), &["10.100.0.0/16"]).await?; let server = server_offering(Some(SendReceive::Both), &["10.100.0.0/16"]).await?;
let (mut peer, _) = peer_offering(&server, Some(AddPathSendRecv::Receive)).await?; let (mut peer, _) = peer_offering(&server, Some(SendReceive::Receive)).await?;
let session = &server.registry.list()[0]; let session = &server.registry.list()[0];
assert_eq!( assert_eq!(
session.add_path_send, session.add_path_send,
+2 -2
View File
@@ -182,7 +182,7 @@ async fn test_routes_follow_the_aspas() -> Result<()> {
changed.push((2121, vec![1101])); changed.push((2121, vec![1101]));
cache.set_aspas(changed); cache.set_aspas(changed);
server.table_becomes(&[], 2).await?; server.table_becomes(&[], 2).await?;
let refused = &server.refusals.peers()[0].1; let refused = &server.rejections.peers()[0].1;
assert_eq!(refused.total(), 2); assert_eq!(refused.total(), 2);
assert_eq!(refused.recent()[0].prefix, prefix("194.0.0.0/24")); assert_eq!(refused.recent()[0].prefix, prefix("194.0.0.0/24"));
assert_eq!(&refused.recent()[0].as_path[..], [PEER_ASN, 2121]); assert_eq!(&refused.recent()[0].as_path[..], [PEER_ASN, 2121]);
@@ -246,7 +246,7 @@ async fn test_a_peer_can_be_exempt_from_path_verification() -> Result<()> {
.json() .json()
.await?; .await?;
assert_eq!(routes["routes"][0]["aspa"], "invalid"); assert_eq!(routes["routes"][0]["aspa"], "invalid");
assert!(server.refusals.peers().is_empty()); assert!(server.rejections.peers().is_empty());
Ok(()) Ok(())
} }
+7 -4
View File
@@ -202,8 +202,11 @@ async fn test_a_marked_route_from_a_customer_is_a_leak() -> Result<()> {
marks(&server, 1).await?, marks(&server, 1).await?,
[("194.0.0.0/24".to_string(), None)] [("194.0.0.0/24".to_string(), None)]
); );
wait_until("a route is refused", || !server.refusals.peers().is_empty()).await?; wait_until("a route is refused", || {
let refused = &server.refusals.peers()[0].1; !server.rejections.peers().is_empty()
})
.await?;
let refused = &server.rejections.peers()[0].1;
assert_eq!(refused.total(), 1); assert_eq!(refused.total(), 1);
assert_eq!(refused.recent()[0].prefix, prefix("8.8.8.0/24")); assert_eq!(refused.recent()[0].prefix, prefix("8.8.8.0/24"));
assert_eq!( assert_eq!(
@@ -238,7 +241,7 @@ async fn test_a_route_marked_by_someone_else_is_a_leak_from_a_peer() -> Result<(
("195.0.0.0/24".to_string(), Some(PEER_ASN)), ("195.0.0.0/24".to_string(), Some(PEER_ASN)),
] ]
); );
assert_eq!(server.refusals.peers()[0].1.total(), 1); assert_eq!(server.rejections.peers()[0].1.total(), 1);
Ok(()) Ok(())
} }
@@ -288,7 +291,7 @@ async fn test_routes_from_a_provider_are_marked_on_the_way_in() -> Result<()> {
], ],
"four-octet AS numbers: {four_octet}" "four-octet AS numbers: {four_octet}"
); );
assert!(server.refusals.peers().is_empty()); assert!(server.rejections.peers().is_empty());
} }
Ok(()) Ok(())
} }
+2 -2
View File
@@ -146,7 +146,7 @@ async fn test_routes_follow_the_validation_data() -> Result<()> {
// table is now invalid, and goes. // table is now invalid, and goes.
cache.set(vec![ripe, vrp("8.8.8.0/24", 24, 13335)]); cache.set(vec![ripe, vrp("8.8.8.0/24", 24, 13335)]);
server.table_becomes(&[], 2).await?; server.table_becomes(&[], 2).await?;
let refused = &server.refusals.peers()[0].1; let refused = &server.rejections.peers()[0].1;
assert_eq!(refused.total(), 2); assert_eq!(refused.total(), 2);
assert_eq!(refused.recent()[0].prefix, prefix("8.8.8.0/24")); assert_eq!(refused.recent()[0].prefix, prefix("8.8.8.0/24"));
assert_eq!(&refused.recent()[0].as_path[..], [PEER_ASN, 15169]); assert_eq!(&refused.recent()[0].as_path[..], [PEER_ASN, 15169]);
@@ -225,7 +225,7 @@ async fn test_a_peer_can_be_exempt_from_validation() -> Result<()> {
.json() .json()
.await?; .await?;
assert_eq!(routes["routes"][0]["rpki"], "invalid"); assert_eq!(routes["routes"][0]["rpki"], "invalid");
assert!(server.refusals.peers().is_empty()); assert!(server.rejections.peers().is_empty());
Ok(()) Ok(())
} }
+11 -15
View File
@@ -452,7 +452,7 @@ impl Capability {
Some(AddPathEntry { Some(AddPathEntry {
afi: Afi(u16::from_be_bytes([entry[0], entry[1]])), afi: Afi(u16::from_be_bytes([entry[0], entry[1]])),
safi: Safi(entry[2]), safi: Safi(entry[2]),
send_recv: AddPathSendRecv::try_from(entry[3]).ok()?, send_recv: SendReceive::try_from(entry[3]).ok()?,
}) })
}) })
.collect(); .collect();
@@ -589,7 +589,7 @@ impl OrfFamily {
for _ in 0..count { for _ in 0..count {
let (rest, orf_type) = be_u8(buf)?; let (rest, orf_type) = be_u8(buf)?;
let (rest, send_recv) = be_u8(rest)?; let (rest, send_recv) = be_u8(rest)?;
let send_recv = OrfSendRecv::try_from(send_recv) let send_recv = SendReceive::try_from(send_recv)
.map_err(|e| malformed(format!("Failed to parse ORF send/recv: {e}")))?; .map_err(|e| malformed(format!("Failed to parse ORF send/recv: {e}")))?;
orfs.push(OutboundRouteFilterEntry { orfs.push(OutboundRouteFilterEntry {
r#type: OrfType(orf_type), r#type: OrfType(orf_type),
@@ -622,7 +622,7 @@ impl OrfFamily {
#[derive(Debug, Clone, Serialize, PartialEq, Eq)] #[derive(Debug, Clone, Serialize, PartialEq, Eq)]
pub struct OutboundRouteFilterEntry { pub struct OutboundRouteFilterEntry {
pub r#type: OrfType, pub r#type: OrfType,
pub send_recv: OrfSendRecv, pub send_recv: SendReceive,
} }
/// An ORF type. Any number is one: most are unassigned or vendor-specific. /// An ORF type. Any number is one: most are unassigned or vendor-specific.
@@ -647,9 +647,6 @@ pub enum SendReceive {
Both = 3, Both = 3,
} }
pub type OrfSendRecv = SendReceive;
pub type AddPathSendRecv = SendReceive;
impl SendReceive { impl SendReceive {
/// Whether the speaker that advertised this sends. /// Whether the speaker that advertised this sends.
pub fn sends(self) -> bool { pub fn sends(self) -> bool {
@@ -683,7 +680,7 @@ impl TryFrom<u8> for SendReceive {
pub struct AddPathEntry { pub struct AddPathEntry {
pub afi: Afi, pub afi: Afi,
pub safi: Safi, pub safi: Safi,
pub send_recv: AddPathSendRecv, pub send_recv: SendReceive,
} }
impl AddPathEntry { impl AddPathEntry {
@@ -780,9 +777,8 @@ mod tests {
use eyre::Result; use eyre::Result;
use crate::open::{ use crate::open::{
AddPathEntry, AddPathSendRecv, Capability, ExtendedNextHopEntry, GracefulRestartEntry, AddPathEntry, Capability, ExtendedNextHopEntry, GracefulRestartEntry, MultiLabelEntry,
MultiLabelEntry, OpenMessage, OpenOption, OrfFamily, OrfSendRecv, OrfType, OpenMessage, OpenOption, OrfFamily, OrfType, OutboundRouteFilterEntry, SendReceive,
OutboundRouteFilterEntry,
}; };
use bgp_primitives::constants::{Afi, Safi}; use bgp_primitives::constants::{Afi, Safi};
@@ -1005,7 +1001,7 @@ mod tests {
entries: vec![AddPathEntry { entries: vec![AddPathEntry {
afi: evpn.0, afi: evpn.0,
safi: evpn.1, safi: evpn.1,
send_recv: AddPathSendRecv::Both, send_recv: SendReceive::Both,
}], }],
}, },
), ),
@@ -1054,12 +1050,12 @@ mod tests {
AddPathEntry { AddPathEntry {
afi: Afi::IPV4, afi: Afi::IPV4,
safi: Safi::UNICAST, safi: Safi::UNICAST,
send_recv: AddPathSendRecv::Receive, send_recv: SendReceive::Receive,
}, },
AddPathEntry { AddPathEntry {
afi: Afi::IPV6, afi: Afi::IPV6,
safi: Safi::UNICAST, safi: Safi::UNICAST,
send_recv: AddPathSendRecv::Both, send_recv: SendReceive::Both,
}, },
], ],
} }
@@ -1119,8 +1115,8 @@ mod tests {
assert_eq!( assert_eq!(
cap, cap,
Capability::OutboundRouteFilter(vec![ Capability::OutboundRouteFilter(vec![
family(Afi::IPV4, OrfSendRecv::Both), family(Afi::IPV4, SendReceive::Both),
family(Afi::IPV6, OrfSendRecv::Receive), family(Afi::IPV6, SendReceive::Receive),
]) ])
); );
let mut out = BytesMut::new(); let mut out = BytesMut::new();
+9 -9
View File
@@ -25,9 +25,9 @@ use bgp_packet::message::{
PathAttributeFlags, RouteRefreshMessage, UpdateMessage, PathAttributeFlags, RouteRefreshMessage, UpdateMessage,
}; };
use bgp_packet::open::{ use bgp_packet::open::{
AddPathEntry, AddPathSendRecv, BgpRole, BgpSecHeader, Capability, ExtendedNextHopEntry, AddPathEntry, BgpRole, BgpSecHeader, Capability, ExtendedNextHopEntry, GracefulRestartEntry,
GracefulRestartEntry, LongLivedGracefulRestartEntry, MultiLabelEntry, OpenMessage, OpenOption, LongLivedGracefulRestartEntry, MultiLabelEntry, OpenMessage, OpenOption, OrfFamily, OrfType,
OrfFamily, OrfSendRecv, OrfType, OutboundRouteFilterEntry, OutboundRouteFilterEntry, SendReceive,
}; };
use bgp_primitives::constants::{Afi, AfiSafi, IpVersion, Safi}; use bgp_primitives::constants::{Afi, AfiSafi, IpVersion, Safi};
use bgp_primitives::ip_prefix::{IpPrefix, Nlri}; use bgp_primitives::ip_prefix::{IpPrefix, Nlri};
@@ -377,16 +377,16 @@ fn capability() -> impl Strategy<Value = Capability> {
let orf = ( let orf = (
any::<u8>().prop_map(OrfType), any::<u8>().prop_map(OrfType),
prop_oneof![ prop_oneof![
Just(OrfSendRecv::Receive), Just(SendReceive::Receive),
Just(OrfSendRecv::Send), Just(SendReceive::Send),
Just(OrfSendRecv::Both) Just(SendReceive::Both)
], ],
) )
.prop_map(|(r#type, send_recv)| OutboundRouteFilterEntry { r#type, send_recv }); .prop_map(|(r#type, send_recv)| OutboundRouteFilterEntry { r#type, send_recv });
let add_path = prop_oneof![ let add_path = prop_oneof![
Just(AddPathSendRecv::Receive), Just(SendReceive::Receive),
Just(AddPathSendRecv::Send), Just(SendReceive::Send),
Just(AddPathSendRecv::Both) Just(SendReceive::Both)
]; ];
prop_oneof![ prop_oneof![
(afi(), safi()).prop_map(|(afi, safi)| Capability::MultiProtocol { afi, safi }), (afi(), safi()).prop_map(|(afi, safi)| Capability::MultiProtocol { afi, safi }),
+7 -10
View File
@@ -6,7 +6,7 @@ use std::time::Duration;
use bgp_fib::FibConfig; use bgp_fib::FibConfig;
use bgp_packet::community; use bgp_packet::community;
use bgp_packet::message::OriginPathAttribute; use bgp_packet::message::OriginPathAttribute;
use bgp_packet::open::AddPathSendRecv; use bgp_packet::open::SendReceive;
use bgp_primitives::constants::{AfiSafi, IpVersion}; use bgp_primitives::constants::{AfiSafi, IpVersion};
use bgp_primitives::ip_prefix::IpPrefix; use bgp_primitives::ip_prefix::IpPrefix;
use bgp_rib::{LocalRoute, LocalRouteAttrs}; use bgp_rib::{LocalRoute, LocalRouteAttrs};
@@ -87,7 +87,7 @@ pub struct PeerConfig {
pub families: FamilySet, pub families: FamilySet,
/// The directions ADD-PATH (RFC 7911) is offered to this peer for, on /// The directions ADD-PATH (RFC 7911) is offered to this peer for, on
/// every one of `families`. `None` offers it for neither. /// every one of `families`. `None` offers it for neither.
pub add_path: Option<AddPathSendRecv>, pub add_path: Option<SendReceive>,
/// Which of the peer's routes are taken. /// Which of the peer's routes are taken.
pub import: ImportPolicy, pub import: ImportPolicy,
/// What the peer is to us, if that was said. /// What the peer is to us, if that was said.
@@ -466,9 +466,9 @@ fn parse_peer(peer: Peer, defaults: &PeerDefaults) -> Result<PeerConfig, PeerPro
let add_path = match peer.add_path.enum_value() { let add_path = match peer.add_path.enum_value() {
Ok(proto::AddPath::ADD_PATH_OFF) => None, Ok(proto::AddPath::ADD_PATH_OFF) => None,
Ok(proto::AddPath::ADD_PATH_RECEIVE) => Some(AddPathSendRecv::Receive), Ok(proto::AddPath::ADD_PATH_RECEIVE) => Some(SendReceive::Receive),
Ok(proto::AddPath::ADD_PATH_SEND) => Some(AddPathSendRecv::Send), Ok(proto::AddPath::ADD_PATH_SEND) => Some(SendReceive::Send),
Ok(proto::AddPath::ADD_PATH_BOTH) => Some(AddPathSendRecv::Both), Ok(proto::AddPath::ADD_PATH_BOTH) => Some(SendReceive::Both),
Err(_) => return Err(PeerProblem::AddPath), Err(_) => return Err(PeerProblem::AddPath),
}; };
@@ -1019,12 +1019,9 @@ mod tests {
assert_eq!(add_path("add_path: ADD_PATH_OFF"), None); assert_eq!(add_path("add_path: ADD_PATH_OFF"), None);
assert_eq!( assert_eq!(
add_path("add_path: ADD_PATH_RECEIVE"), add_path("add_path: ADD_PATH_RECEIVE"),
Some(AddPathSendRecv::Receive) Some(SendReceive::Receive)
);
assert_eq!(
add_path("add_path: ADD_PATH_BOTH"),
Some(AddPathSendRecv::Both)
); );
assert_eq!(add_path("add_path: ADD_PATH_BOTH"), Some(SendReceive::Both));
} }
#[test] #[test]
+16 -16
View File
@@ -19,7 +19,7 @@ use bgp_packet::message::RouteRefreshMessage;
use bgp_packet::message::{ use bgp_packet::message::{
BGP_HEADER_LEN, BGP_MAX_MSG_LEN, Decoded, Message, PathAttribute, UpdateMessage, BGP_HEADER_LEN, BGP_MAX_MSG_LEN, Decoded, Message, PathAttribute, UpdateMessage,
}; };
use bgp_packet::open::AddPathSendRecv; use bgp_packet::open::SendReceive;
use bgp_packet::update_decode::{UpdateError, merge_as4_path}; use bgp_packet::update_decode::{UpdateError, merge_as4_path};
use bgp_primitives::constants::{AfiSafi, IpVersion}; use bgp_primitives::constants::{AfiSafi, IpVersion};
use bgp_primitives::ip_prefix::Nlri; use bgp_primitives::ip_prefix::Nlri;
@@ -49,7 +49,7 @@ use crate::metrics::{
use crate::panic::contained; use crate::panic::contained;
use crate::policy::{self, ExportLimits, ExportPolicy, ImportPolicy, PeerRole}; use crate::policy::{self, ExportLimits, ExportPolicy, ImportPolicy, PeerRole};
use crate::reach::{Reach, neighbor_at}; use crate::reach::{Reach, neighbor_at};
use crate::refusals::{PeerRefusals, RefusalLog}; use crate::rejections::{PeerRejections, RejectionLog};
use crate::restart::{self, Agreed, GracefulRestartConfig, Leave, Restarts}; use crate::restart::{self, Agreed, GracefulRestartConfig, Leave, Restarts};
use crate::routes::{self, Held}; use crate::routes::{self, Held};
use crate::rpki::{Checks, Subject, Validator}; use crate::rpki::{Checks, Subject, Validator};
@@ -84,7 +84,7 @@ pub struct ServerContext {
/// Peers that are being kept down for going over their limits. /// Peers that are being kept down for going over their limits.
pub hold_downs: HoldDowns, pub hold_downs: HoldDowns,
/// What peers announced that was not taken. /// What peers announced that was not taken.
pub refusals: RefusalLog, pub rejections: RejectionLog,
/// The data for route origin validation, where that is configured. /// The data for route origin validation, where that is configured.
pub rpki: Option<Rpki>, pub rpki: Option<Rpki>,
/// The machine's interfaces, where routes are installed in the kernel. /// The machine's interfaces, where routes are installed in the kernel.
@@ -106,7 +106,7 @@ pub struct PeerSettings {
/// The address families to offer the peer. /// The address families to offer the peer.
pub families: FamilySet, pub families: FamilySet,
/// The directions to offer the peer ADD-PATH for (RFC 7911). /// The directions to offer the peer ADD-PATH for (RFC 7911).
pub add_path: Option<AddPathSendRecv>, pub add_path: Option<SendReceive>,
/// Which of the peer's routes are taken. /// Which of the peer's routes are taken.
pub import: Arc<ImportPolicy>, pub import: Arc<ImportPolicy>,
/// What the peer is to us, if that is known. /// What the peer is to us, if that is known.
@@ -262,7 +262,7 @@ fn hold_down_after(session: &Session, result: &Result<(), SessionError>) {
if let Some((why, exceeded)) = over_limit { if let Some((why, exceeded)) = over_limit {
hold_downs.hold(address, session.peer.import.hold_down, why); hold_downs.hold(address, session.peer.import.hold_down, why);
exceeded exceeded
.with_label_values(&[session.refusals.label()]) .with_label_values(&[session.rejections.label()])
.inc(); .inc();
} }
if let (Some(up), Some(_)) = (&session.established, &session.peer.name) if let (Some(up), Some(_)) = (&session.established, &session.peer.name)
@@ -313,7 +313,7 @@ async fn release_routes(
} = established; } = established;
// The peer has no session to count its routes for. // The peer has no session to count its routes for.
// Fails only if the session never set it, which is nothing to do. // Fails only if the session never set it, which is nothing to do.
let _ = PEER_ROUTES.remove_label_values(&[session.refusals.label()]); let _ = PEER_ROUTES.remove_label_values(&[session.rejections.label()]);
if *session.leave.borrow() == Some(Leave::Restart) { if *session.leave.borrow() == Some(Leave::Restart) {
// The daemon is about to go, and its routes with it; the kernel // The daemon is about to go, and its routes with it; the kernel
// keeps its own. Taking them out first would only hold the // keeps its own. Taking them out first would only hold the
@@ -418,7 +418,7 @@ impl Established {
fn import<'a>( fn import<'a>(
&'a mut self, &'a mut self,
policy: &'a ImportPolicy, policy: &'a ImportPolicy,
refusals: &'a PeerRefusals, rejections: &'a PeerRejections,
) -> (routes::Import<'a>, &'a mut Held) { ) -> (routes::Import<'a>, &'a mut Held) {
let import = routes::Import { let import = routes::Import {
peer: self.rib_peer, peer: self.rib_peer,
@@ -428,7 +428,7 @@ impl Established {
.validation .validation
.as_mut() .as_mut()
.map_or(Checks::NONE, |validation| validation.validator.checks()), .map_or(Checks::NONE, |validation| validation.validator.checks()),
refusals, rejections,
}; };
(import, &mut self.held) (import, &mut self.held)
} }
@@ -652,7 +652,7 @@ struct Session {
/// What is configured for the peer. /// What is configured for the peer.
peer: PeerSettings, peer: PeerSettings,
/// Where what is refused is noted. /// Where what is refused is noted.
refusals: Arc<PeerRefusals>, rejections: Arc<PeerRejections>,
/// Says when the daemon is leaving, and how. /// Says when the daemon is leaving, and how.
leave: watch::Receiver<Option<Leave>>, leave: watch::Receiver<Option<Leave>>,
} }
@@ -690,7 +690,7 @@ impl Session {
.unwrap_or_else(|| PeerAddress::from(peer_addr).to_string()); .unwrap_or_else(|| PeerAddress::from(peer_addr).to_string());
let leave = server.registry.leaving(); let leave = server.registry.leaving();
let session = Session { let session = Session {
refusals: server.refusals.of_peer(peer_addr.into(), &label), rejections: server.rejections.of_peer(peer_addr.into(), &label),
fsm, fsm,
frames: FrameReader::new(reader), frames: FrameReader::new(reader),
writer, writer,
@@ -1018,7 +1018,7 @@ impl Session {
routes: PeerRoutes::new(Arc::clone(&self.server.rib), rib_peer), routes: PeerRoutes::new(Arc::clone(&self.server.rib), rib_peer),
from: self.route_source(negotiated), from: self.route_source(negotiated),
counters: Arc::clone(&handle.counters), counters: Arc::clone(&handle.counters),
routes_metric: PEER_ROUTES.with_label_values(&[self.refusals.label()]), routes_metric: PEER_ROUTES.with_label_values(&[self.rejections.label()]),
_registration: self.server.registry.register(handle), _registration: self.server.registry.register(handle),
}); });
if let Some(kept) = kept { if let Some(kept) = kept {
@@ -1228,7 +1228,7 @@ impl Session {
let Some(established) = &mut self.established else { let Some(established) = &mut self.established else {
return Ok(()); return Ok(());
}; };
let (import, held) = established.import(&self.peer.import, &self.refusals); let (import, held) = established.import(&self.peer.import, &self.rejections);
let routes::Applied { outcome, rejected } = let routes::Applied { outcome, rejected } =
routes::apply(&self.server.rib, &import, held, update, attributes_wire)?; routes::apply(&self.server.rib, &import, held, update, attributes_wire)?;
// One line for every UPDATE is too much to log by default: a full // One line for every UPDATE is too much to log by default: a full
@@ -1252,7 +1252,7 @@ impl Session {
let Some(established) = &mut self.established else { let Some(established) = &mut self.established else {
return vec![]; return vec![];
}; };
let (import, held) = established.import(&self.peer.import, &self.refusals); let (import, held) = established.import(&self.peer.import, &self.rejections);
let routes::Applied { outcome, rejected } = let routes::Applied { outcome, rejected } =
routes::revalidate(&self.server.rib, &import, held, suspects); routes::revalidate(&self.server.rib, &import, held, suspects);
if outcome != Outcome::default() { if outcome != Outcome::default() {
@@ -1606,7 +1606,7 @@ mod tests {
collisions: CollisionTable::new(false), collisions: CollisionTable::new(false),
hold_downs: HoldDowns::default(), hold_downs: HoldDowns::default(),
interfaces: None, interfaces: None,
refusals: RefusalLog::default(), rejections: RejectionLog::default(),
rpki: None, rpki: None,
restarts: Restarts::new(rib, true, [], 120), restarts: Restarts::new(rib, true, [], 120),
}); });
@@ -1657,7 +1657,7 @@ mod tests {
collisions: CollisionTable::new(false), collisions: CollisionTable::new(false),
hold_downs: HoldDowns::default(), hold_downs: HoldDowns::default(),
interfaces: None, interfaces: None,
refusals: RefusalLog::default(), rejections: RejectionLog::default(),
rpki: None, rpki: None,
restarts: Restarts::new(rib, true, [], 120), restarts: Restarts::new(rib, true, [], 120),
}); });
@@ -1732,7 +1732,7 @@ mod tests {
}, },
policy: &ImportPolicy::permissive(), policy: &ImportPolicy::permissive(),
checks: Checks::NONE, checks: Checks::NONE,
refusals: &RefusalLog::default().of_peer( rejections: &RejectionLog::default().of_peer(
std::net::IpAddr::from(Ipv4Addr::new(192, 0, 2, 1)).into(), std::net::IpAddr::from(Ipv4Addr::new(192, 0, 2, 1)).into(),
"test", "test",
), ),
+3 -4
View File
@@ -17,7 +17,7 @@ use std::time::Duration;
use bgp_packet::errors::{BgpError, CeaseSubcode, DecodeError, OpenMsgSubcode}; use bgp_packet::errors::{BgpError, CeaseSubcode, DecodeError, OpenMsgSubcode};
use bgp_packet::message::{AS_TRANS, BGP4_VERSION, Message, NotificationMessage, UpdateMessage}; use bgp_packet::message::{AS_TRANS, BGP4_VERSION, Message, NotificationMessage, UpdateMessage};
use bgp_packet::open::{ use bgp_packet::open::{
AddPathEntry, AddPathSendRecv, Capability, ExtendedNextHopEntry, OpenMessage, OpenOption, AddPathEntry, Capability, ExtendedNextHopEntry, OpenMessage, OpenOption, SendReceive,
}; };
use bgp_primitives::constants::{Afi, AfiSafi, Safi}; use bgp_primitives::constants::{Afi, AfiSafi, Safi};
@@ -69,7 +69,7 @@ pub struct FsmConfig {
pub families: FamilySet, pub families: FamilySet,
/// The directions to offer ADD-PATH (RFC 7911) for, on each of /// The directions to offer ADD-PATH (RFC 7911) for, on each of
/// `families`; `None` for neither. /// `families`; `None` for neither.
pub add_path: Option<AddPathSendRecv>, pub add_path: Option<SendReceive>,
/// What the peer is to us, if that is configured. Our OPEN then says /// What the peer is to us, if that is configured. Our OPEN then says
/// what we are to it, and the peer's must not say otherwise (RFC 9234). /// what we are to it, and the peer's must not say otherwise (RFC 9234).
/// Not for a peer in our own AS. /// Not for a peer in our own AS.
@@ -900,8 +900,7 @@ mod tests {
#[test] #[test]
fn add_path_is_in_use_where_one_side_sends_and_the_other_receives() { fn add_path_is_in_use_where_one_side_sends_and_the_other_receives() {
use bgp_packet::open::SendReceive::{Both, Receive, Send}; use bgp_packet::open::SendReceive::{Both, Receive, Send};
let negotiated = |ours: Option<AddPathSendRecv>, let negotiated = |ours: Option<SendReceive>, theirs: Vec<(AfiSafi, SendReceive)>| {
theirs: Vec<(AfiSafi, AddPathSendRecv)>| {
let (mut fsm, first) = Fsm::new(FsmConfig { let (mut fsm, first) = Fsm::new(FsmConfig {
add_path: ours, add_path: ours,
..config() ..config()
+1 -1
View File
@@ -14,7 +14,7 @@ pub mod metrics;
mod panic; mod panic;
pub mod policy; pub mod policy;
mod reach; mod reach;
pub mod refusals; pub mod rejections;
pub mod restart; pub mod restart;
mod routes; mod routes;
pub mod rpki; pub mod rpki;
+1 -1
View File
@@ -1325,7 +1325,7 @@ mod tests {
assert!(out.is_empty()); assert!(out.is_empty());
} }
/// The counts of refusals are kept by a reason's place in the list. /// The counts of rejections are kept by a reason's place in the list.
#[test] #[test]
fn every_reason_is_at_its_own_place_in_the_list() { fn every_reason_is_at_its_own_place_in_the_list() {
for (at, rejection) in Rejection::ALL.iter().enumerate() { for (at, rejection) in Rejection::ALL.iter().enumerate() {
+7 -7
View File
@@ -214,7 +214,7 @@ mod tests {
use super::*; use super::*;
use crate::policy::{ImportPolicy, PrefixSet, Rejection, RouteSource}; use crate::policy::{ImportPolicy, PrefixSet, Rejection, RouteSource};
use crate::refusals::RefusalLog; use crate::rejections::RejectionLog;
use crate::routes::{self, Held, Import}; use crate::routes::{self, Held, Import};
use crate::rpki::Checks; use crate::rpki::Checks;
@@ -261,7 +261,7 @@ mod tests {
struct Bed { struct Bed {
table: RibTable, table: RibTable,
policy: ImportPolicy, policy: ImportPolicy,
refusals: RefusalLog, rejections: RejectionLog,
} }
impl Bed { impl Bed {
@@ -269,7 +269,7 @@ mod tests {
Self { Self {
table: RibTable::new(4), table: RibTable::new(4),
policy: ImportPolicy::permissive(), policy: ImportPolicy::permissive(),
refusals: RefusalLog::default(), rejections: RejectionLog::default(),
} }
} }
@@ -322,7 +322,7 @@ mod tests {
}), }),
..Checks::NONE ..Checks::NONE
}, },
refusals: &self.refusals.of_peer(address.into(), "test"), rejections: &self.rejections.of_peer(address.into(), "test"),
}; };
work(&self.table, &import, &mut peer.held) work(&self.table, &import, &mut peer.held)
} }
@@ -366,7 +366,7 @@ mod tests {
bed.announce(&mut peer, &interfaces, "192.0.2.0/24", 1); bed.announce(&mut peer, &interfaces, "192.0.2.0/24", 1);
bed.announce(&mut peer, &interfaces, "192.0.2.128/25", 1); bed.announce(&mut peer, &interfaces, "192.0.2.128/25", 1);
assert_eq!((bed.table.len(), peer.held.len()), (0, 2)); assert_eq!((bed.table.len(), peer.held.len()), (0, 2));
let refused = bed.refusals.peers(); let refused = bed.rejections.peers();
assert_eq!(refused[0].1.by_reason(), [(Rejection::OwnNetwork, 2)]); assert_eq!(refused[0].1.by_reason(), [(Rejection::OwnNetwork, 2)]);
} }
@@ -387,7 +387,7 @@ mod tests {
bed.announce(&mut peer, &interfaces, "203.0.113.0/24", 1); bed.announce(&mut peer, &interfaces, "203.0.113.0/24", 1);
assert_eq!((bed.table.len(), peer.held.len()), (0, 0)); assert_eq!((bed.table.len(), peer.held.len()), (0, 0));
assert!( assert!(
bed.refusals.peers().is_empty(), bed.rejections.peers().is_empty(),
"nothing counted as refused" "nothing counted as refused"
); );
@@ -412,7 +412,7 @@ mod tests {
assert_eq!(bed.best(route), Some(near.index)); assert_eq!(bed.best(route), Some(near.index));
assert_eq!(bed.table.len(), 1, "the other is not in the table at all"); assert_eq!(bed.table.len(), 1, "the other is not in the table at all");
assert_eq!((near.held.len(), far.held.len()), (0, 1)); assert_eq!((near.held.len(), far.held.len()), (0, 1));
let refused = bed.refusals.peers(); let refused = bed.rejections.peers();
assert_eq!(refused.len(), 1); assert_eq!(refused.len(), 1);
assert_eq!( assert_eq!(
refused[0].1.by_reason(), refused[0].1.by_reason(),
@@ -2,7 +2,7 @@
//! //!
//! A refused route is not stored, so without this there would be nothing //! A refused route is not stored, so without this there would be nothing
//! to show for it but a number. For each peer the reasons are counted and //! to show for it but a number. For each peer the reasons are counted and
//! the latest refusals are kept: enough to see what a peer is doing wrong, //! the latest rejections are kept: enough to see what a peer is doing wrong,
//! or what in our policy is. They are kept by the peer's address and //! or what in our policy is. They are kept by the peer's address and
//! outlive the session, since the session a peer was cut off on is the one //! outlive the session, since the session a peer was cut off on is the one
//! worth looking at. //! worth looking at.
@@ -25,7 +25,7 @@ use crate::sync::IgnorePoison;
/// How many of a peer's latest refused routes are kept. /// How many of a peer's latest refused routes are kept.
pub const RECENT_REFUSALS: usize = 200; pub const RECENT_REFUSALS: usize = 200;
/// How many peers refusals are kept for. More than any configuration has; /// How many peers rejections are kept for. More than any configuration has;
/// it bounds what hosts that are let in without being configured (on a /// it bounds what hosts that are let in without being configured (on a
/// test network) can make us keep. /// test network) can make us keep.
const MAX_PEERS: usize = 8192; const MAX_PEERS: usize = 8192;
@@ -36,7 +36,7 @@ const LOG_INTERVAL: Duration = Duration::from_secs(60);
/// A route a peer announced that its import policy refused. /// A route a peer announced that its import policy refused.
#[derive(Debug, Clone, PartialEq, Eq)] #[derive(Debug, Clone, PartialEq, Eq)]
pub struct RefusedRoute { pub struct RejectedRoute {
pub prefix: IpPrefix, pub prefix: IpPrefix,
/// The peer's identifier for the route; zero without ADD-PATH. /// The peer's identifier for the route; zero without ADD-PATH.
pub path_id: u32, pub path_id: u32,
@@ -55,15 +55,15 @@ pub struct RefusedRoute {
pub at: SystemTime, pub at: SystemTime,
} }
/// What the routes of one UPDATE came with, for [`PeerRefusals::note`]. /// What the routes of one UPDATE came with, for [`PeerRejections::note`].
pub(crate) struct Came<'a> { pub(crate) struct Came<'a> {
pub(crate) as_path: &'a Arc<[u32]>, pub(crate) as_path: &'a Arc<[u32]>,
pub(crate) attributes: &'a Arc<[PathAttribute]>, pub(crate) attributes: &'a Arc<[PathAttribute]>,
pub(crate) next_hop: Option<&'a NlriNextHop>, pub(crate) next_hop: Option<&'a NlriNextHop>,
} }
/// The refusals of one peer. /// The rejections of one peer.
pub struct PeerRefusals { pub struct PeerRejections {
/// What the peer is called in the log and in metrics: its configured /// What the peer is called in the log and in metrics: its configured
/// name, or its address. /// name, or its address.
label: String, label: String,
@@ -74,13 +74,13 @@ pub struct PeerRefusals {
struct State { struct State {
/// The latest refused routes, oldest first. /// The latest refused routes, oldest first.
recent: VecDeque<RefusedRoute>, recent: VecDeque<RejectedRoute>,
logged: Option<Instant>, logged: Option<Instant>,
/// How many were refused since the log last said so. /// How many were refused since the log last said so.
unlogged: u64, unlogged: u64,
} }
impl PeerRefusals { impl PeerRejections {
fn new(label: &str) -> Self { fn new(label: &str) -> Self {
Self { Self {
label: label.to_string(), label: label.to_string(),
@@ -113,7 +113,7 @@ impl PeerRefusals {
if state.recent.len() == RECENT_REFUSALS { if state.recent.len() == RECENT_REFUSALS {
state.recent.pop_front(); state.recent.pop_front();
} }
state.recent.push_back(RefusedRoute { state.recent.push_back(RejectedRoute {
prefix: route.prefix, prefix: route.prefix,
path_id: route.path_id, path_id: route.path_id,
reason, reason,
@@ -139,7 +139,7 @@ impl PeerRefusals {
reason = reason.as_str(), reason = reason.as_str(),
as_path = ?&came.as_path[..], as_path = ?&came.as_path[..],
others_since_last_report = state.unlogged + count - 1, others_since_last_report = state.unlogged + count - 1,
"Refused a route" "Rejected a route"
); );
state.logged = Some(Instant::now()); state.logged = Some(Instant::now());
state.unlogged = 0; state.unlogged = 0;
@@ -172,25 +172,25 @@ impl PeerRefusals {
} }
/// The latest refused routes, newest first. /// The latest refused routes, newest first.
pub fn recent(&self) -> Vec<RefusedRoute> { pub fn recent(&self) -> Vec<RejectedRoute> {
self.state.locked().recent.iter().rev().cloned().collect() self.state.locked().recent.iter().rev().cloned().collect()
} }
} }
/// The refusals of every peer, by address. Cheap to clone; clones share /// The rejections of every peer, by address. Cheap to clone; clones share
/// one record. /// one record.
#[derive(Clone, Default)] #[derive(Clone, Default)]
pub struct RefusalLog(Arc<Mutex<HashMap<PeerAddress, Arc<PeerRefusals>>>>); pub struct RejectionLog(Arc<Mutex<HashMap<PeerAddress, Arc<PeerRejections>>>>);
impl RefusalLog { impl RejectionLog {
/// The record for the peer at `address`, made if there was none. /// The record for the peer at `address`, made if there was none.
/// `label` is what to call the peer; see [`PeerRefusals::label`]. /// `label` is what to call the peer; see [`PeerRejections::label`].
pub(crate) fn of_peer(&self, address: PeerAddress, label: &str) -> Arc<PeerRefusals> { pub(crate) fn of_peer(&self, address: PeerAddress, label: &str) -> Arc<PeerRejections> {
let mut peers = self.0.locked(); let mut peers = self.0.locked();
if let Some(existing) = peers.get(&address) { if let Some(existing) = peers.get(&address) {
return Arc::clone(existing); return Arc::clone(existing);
} }
let record = Arc::new(PeerRefusals::new(label)); let record = Arc::new(PeerRejections::new(label));
// Past the bound the record is still made, for the session to // Past the bound the record is still made, for the session to
// count in, but it is not kept after. // count in, but it is not kept after.
if peers.len() < MAX_PEERS { if peers.len() < MAX_PEERS {
@@ -200,7 +200,7 @@ impl RefusalLog {
} }
/// Every peer that has had a route refused, with its record. /// Every peer that has had a route refused, with its record.
pub fn peers(&self) -> Vec<(PeerAddress, Arc<PeerRefusals>)> { pub fn peers(&self) -> Vec<(PeerAddress, Arc<PeerRejections>)> {
self.0 self.0
.locked() .locked()
.iter() .iter()
@@ -225,9 +225,9 @@ mod tests {
#[test] #[test]
fn refusals_are_counted_by_reason_and_the_latest_are_kept() { fn refusals_are_counted_by_reason_and_the_latest_are_kept() {
let log = RefusalLog::default(); let log = RejectionLog::default();
let address = PeerAddress::from("192.0.2.1".parse::<std::net::IpAddr>().unwrap()); let address = PeerAddress::from("192.0.2.1".parse::<std::net::IpAddr>().unwrap());
let record = log.of_peer(address, "refusals-test-1"); let record = log.of_peer(address, "rejections-test-1");
assert!(log.peers().is_empty(), "nothing refused yet"); assert!(log.peers().is_empty(), "nothing refused yet");
let path: Arc<[u32]> = vec![3333, 64512].into(); let path: Arc<[u32]> = vec![3333, 64512].into();
@@ -269,7 +269,7 @@ mod tests {
// The peer's next session finds the same record. // The peer's next session finds the same record.
let again = log.of_peer(address, "another-name"); let again = log.of_peer(address, "another-name");
assert_eq!(again.total(), 3); assert_eq!(again.total(), 3);
assert_eq!(again.label(), "refusals-test-1"); assert_eq!(again.label(), "rejections-test-1");
assert_eq!(log.peers().len(), 1); assert_eq!(log.peers().len(), 1);
} }
@@ -277,7 +277,7 @@ mod tests {
/// amount of memory. /// amount of memory.
#[test] #[test]
fn only_so_many_refused_routes_are_kept() { fn only_so_many_refused_routes_are_kept() {
let record = PeerRefusals::new("refusals-test-2"); let record = PeerRejections::new("rejections-test-2");
let path: Arc<[u32]> = vec![3333].into(); let path: Arc<[u32]> = vec![3333].into();
let attributes: Arc<[PathAttribute]> = Vec::new().into(); let attributes: Arc<[PathAttribute]> = Vec::new().into();
let came = Came { let came = Came {
+4 -4
View File
@@ -14,7 +14,7 @@ use bgp_rib::table::{AttrSet, Outcome, PeerIndex, RibTable};
use bgp_rpki::{PathValidity, Validity}; use bgp_rpki::{PathValidity, Validity};
use crate::policy::{self, ImportPolicy, Rejection, RouteSource}; use crate::policy::{self, ImportPolicy, Rejection, RouteSource};
use crate::refusals::{Came, PeerRefusals}; use crate::rejections::{Came, PeerRejections};
use crate::rpki::Checks; use crate::rpki::Checks;
/// The most routes of one peer that are held aside as invalid. Far more /// The most routes of one peer that are held aside as invalid. Far more
@@ -40,7 +40,7 @@ pub(crate) struct Import<'a> {
/// RPKI. /// RPKI.
pub(crate) checks: Checks<'a>, pub(crate) checks: Checks<'a>,
/// Where what is refused is noted. /// Where what is refused is noted.
pub(crate) refusals: &'a PeerRefusals, pub(crate) rejections: &'a PeerRejections,
} }
/// A peer's routes that are not in the routing table because the RPKI's /// A peer's routes that are not in the routing table because the RPKI's
@@ -433,7 +433,7 @@ impl Applying<'_> {
.get_or_init(|| attributes_of(self.update)), .get_or_init(|| attributes_of(self.update)),
next_hop, next_hop,
}; };
self.import.refusals.note(refused, rejection, &came); self.import.rejections.note(refused, rejection, &came);
self.applied.rejected += refused.len(); self.applied.rejected += refused.len();
self.applied.outcome += self.table.withdraw(self.import.peer, refused); self.applied.outcome += self.table.withdraw(self.import.peer, refused);
} }
@@ -557,7 +557,7 @@ pub(crate) fn revalidate(
applied.rejected += 1; applied.rejected += 1;
route.attrs.as_path(&mut path); route.attrs.as_path(&mut path);
let (attributes, next_hop) = route.attrs.attributes().unwrap_or_default(); let (attributes, next_hop) = route.attrs.attributes().unwrap_or_default();
import.refusals.note( import.rejections.note(
std::slice::from_ref(suspect), std::slice::from_ref(suspect),
rejection, rejection,
&Came { &Came {
+4 -4
View File
@@ -30,7 +30,7 @@ use crate::metrics::{CONNECTIONS_REFUSED, PEER_MAX_PREFIXES};
use crate::panic; use crate::panic;
use crate::policy::ExportLimits; use crate::policy::ExportLimits;
use crate::reach; use crate::reach;
use crate::refusals::RefusalLog; use crate::rejections::RejectionLog;
use crate::restart::{Leave, Restarts}; use crate::restart::{Leave, Restarts};
use crate::rpki::spawn_revalidator; use crate::rpki::spawn_revalidator;
use crate::session::{ConfiguredPeer, DialState, SessionRegistry}; use crate::session::{ConfiguredPeer, DialState, SessionRegistry};
@@ -87,7 +87,7 @@ pub struct BgpServer {
/// Live view of established sessions. /// Live view of established sessions.
pub registry: SessionRegistry, pub registry: SessionRegistry,
/// What peers announced that was not taken. /// What peers announced that was not taken.
pub refusals: RefusalLog, pub rejections: RejectionLog,
/// The data for route origin validation, where that is configured. /// The data for route origin validation, where that is configured.
pub rpki: Option<Rpki>, pub rpki: Option<Rpki>,
/// The task that keeps that data up to date. /// The task that keeps that data up to date.
@@ -163,7 +163,7 @@ impl BgpServer {
rib, rib,
local_rib, local_rib,
registry: SessionRegistry::with_configured(configured), registry: SessionRegistry::with_configured(configured),
refusals: RefusalLog::default(), rejections: RejectionLog::default(),
config, config,
}) })
} }
@@ -189,7 +189,7 @@ impl BgpServer {
registry: self.registry.clone(), registry: self.registry.clone(),
collisions: CollisionTable::new(self.config.collision_detect_established_state), collisions: CollisionTable::new(self.config.collision_detect_established_state),
hold_downs: HoldDowns::default(), hold_downs: HoldDowns::default(),
refusals: self.refusals.clone(), rejections: self.rejections.clone(),
rpki: self.rpki.clone(), rpki: self.rpki.clone(),
interfaces: self.kernel.as_ref().map(Fib::interfaces), interfaces: self.kernel.as_ref().map(Fib::interfaces),
restarts: Arc::clone(&self.restarts), restarts: Arc::clone(&self.restarts),
+10 -10
View File
@@ -1105,7 +1105,7 @@ pub async fn kernel(State(state): State<AppState>) -> Json<KernelView> {
/// What one peer announced that was refused. /// What one peer announced that was refused.
#[derive(Debug, Clone, Serialize)] #[derive(Debug, Clone, Serialize)]
pub struct RefusalsView { pub struct RejectionsView {
/// The peer's configured name, or its address if it has none. /// The peer's configured name, or its address if it has none.
pub peer: String, pub peer: String,
pub peer_addr: String, pub peer_addr: String,
@@ -1114,11 +1114,11 @@ pub struct RefusalsView {
/// The same, by reason. /// The same, by reason.
pub by_reason: BTreeMap<&'static str, u64>, pub by_reason: BTreeMap<&'static str, u64>,
/// The latest of them, newest first. /// The latest of them, newest first.
pub recent: Vec<RefusedRouteView>, pub recent: Vec<RejectedRouteView>,
} }
#[derive(Debug, Clone, Serialize)] #[derive(Debug, Clone, Serialize)]
pub struct RefusedRouteView { pub struct RejectedRouteView {
pub prefix: String, pub prefix: String,
pub path_id: u32, pub path_id: u32,
pub reason: &'static str, pub reason: &'static str,
@@ -1133,7 +1133,7 @@ pub struct RefusedRouteView {
} }
#[derive(Debug, Default, Deserialize)] #[derive(Debug, Default, Deserialize)]
pub struct RefusalQuery { pub struct RejectionQuery {
/// Only this peer: its configured name, or its address. /// Only this peer: its configured name, or its address.
peer: Option<String>, peer: Option<String>,
} }
@@ -1142,10 +1142,10 @@ pub struct RefusalQuery {
/// each peer that had any, how many and why, and the latest of them. /// each peer that had any, how many and why, and the latest of them.
pub async fn rejected_routes( pub async fn rejected_routes(
State(state): State<AppState>, State(state): State<AppState>,
Query(query): Query<RefusalQuery>, Query(query): Query<RejectionQuery>,
) -> Json<Vec<RefusalsView>> { ) -> Json<Vec<RejectionsView>> {
let mut peers: Vec<RefusalsView> = state let mut peers: Vec<RejectionsView> = state
.refusals .rejections
.peers() .peers()
.into_iter() .into_iter()
.filter(|(address, record)| { .filter(|(address, record)| {
@@ -1154,7 +1154,7 @@ pub async fn rejected_routes(
.as_deref() .as_deref()
.is_none_or(|wanted| record.label() == wanted || address.is_called(wanted)) .is_none_or(|wanted| record.label() == wanted || address.is_called(wanted))
}) })
.map(|(address, record)| RefusalsView { .map(|(address, record)| RejectionsView {
peer: record.label().to_string(), peer: record.label().to_string(),
peer_addr: address.to_string(), peer_addr: address.to_string(),
total: record.total(), total: record.total(),
@@ -1166,7 +1166,7 @@ pub async fn rejected_routes(
recent: record recent: record
.recent() .recent()
.into_iter() .into_iter()
.map(|route| RefusedRouteView { .map(|route| RejectedRouteView {
prefix: route.prefix.to_string(), prefix: route.prefix.to_string(),
path_id: route.path_id, path_id: route.path_id,
reason: route.reason.as_str(), reason: route.reason.as_str(),
+4 -4
View File
@@ -20,15 +20,15 @@ use bgp_fib::Fib;
use bgp_rib::LocalRib; use bgp_rib::LocalRib;
use bgp_rib::table::RibTable; use bgp_rib::table::RibTable;
use bgp_rpki::Rpki; use bgp_rpki::Rpki;
use bgp_server::refusals::RefusalLog; use bgp_server::rejections::RejectionLog;
use bgp_server::restart::Restarts; use bgp_server::restart::Restarts;
use bgp_server::session::SessionRegistry; use bgp_server::session::SessionRegistry;
use tokio::net::TcpListener; use tokio::net::TcpListener;
use tracing::info; use tracing::info;
pub use api::{ pub use api::{
AttrSummary, ConfigView, KernelView, LocalRouteView, PeerView, ReceivedRouteView, RefusalsView, AttrSummary, ConfigView, KernelView, LocalRouteView, PeerView, ReceivedRouteView,
RefusedRouteView, RoutesPage, RpkiView, StatusView, RejectedRouteView, RejectionsView, RoutesPage, RpkiView, StatusView,
}; };
/// Static facts about the router shown in status output. /// Static facts about the router shown in status output.
@@ -98,7 +98,7 @@ pub struct AppState {
/// The routing table: routes learned from peers and the best of them. /// The routing table: routes learned from peers and the best of them.
pub rib: Arc<RibTable>, pub rib: Arc<RibTable>,
/// What peers announced that was not taken. /// What peers announced that was not taken.
pub refusals: RefusalLog, pub rejections: RejectionLog,
/// The data for route origin validation, where that is configured. /// The data for route origin validation, where that is configured.
pub rpki: Option<Rpki>, pub rpki: Option<Rpki>,
/// What keeps the best routes in the kernel, where that is configured. /// What keeps the best routes in the kernel, where that is configured.
+16 -16
View File
@@ -113,11 +113,11 @@ from <select id="lookup-peer"><option value="">any peer</option></select>
</table> </table>
<div class="pager" id="received-pager-below"></div> <div class="pager" id="received-pager-below"></div>
<h1 id="refused">Rejected routes (<span id="refused-count">...</span>)</h1> <h1 id="rejected">Rejected routes (<span id="rejected-count">...</span>)</h1>
<div class="dim">Routes that peers announced and their import policies did not take. The counts are of announcements since the daemon started: a route that a peer announces again, and that is rejected again, counts again. The latest rejected routes of each peer are kept.</div> <div class="dim">Routes that peers announced and their import policies did not take. The counts are of announcements since the daemon started: a route that a peer announces again, and that is rejected again, counts again. The latest rejected routes of each peer are kept.</div>
<table class="data"> <table class="data">
<thead><tr><th>peer</th><th>prefix</th><th>AS path</th><th>reason</th><th>when</th><th>next hop</th><th>origin</th><th>MED</th><th>local pref</th><th>communities</th><th>large communities</th><th>other attributes</th></tr></thead> <thead><tr><th>peer</th><th>prefix</th><th>AS path</th><th>reason</th><th>when</th><th>next hop</th><th>origin</th><th>MED</th><th>local pref</th><th>communities</th><th>large communities</th><th>other attributes</th></tr></thead>
<tbody id="refused-body"></tbody> <tbody id="rejected-body"></tbody>
</table> </table>
<h1 id="events">Event log</h1> <h1 id="events">Event log</h1>
@@ -137,7 +137,7 @@ from <select id="lookup-peer"><option value="">any peer</option></select>
const $ = id => document.getElementById(id); const $ = id => document.getElementById(id);
const esc = s => String(s).replace(/[&<>"']/g, c => ({'&':'&amp;','<':'&lt;','>':'&gt;','"':'&quot;',"'":'&#39;'}[c])); const esc = s => String(s).replace(/[&<>"']/g, c => ({'&':'&amp;','<':'&lt;','>':'&gt;','"':'&quot;',"'":'&#39;'}[c]));
const state = { status: null, peers: [], local: [], received: [], receivedPage: {}, refused: [], rpki: null, kernel: null }; const state = { status: null, peers: [], local: [], received: [], receivedPage: {}, rejected: [], rpki: null, kernel: null };
const DASH = '<span class="dim">-</span>'; const DASH = '<span class="dim">-</span>';
// ---- helpers ----------------------------------------------------------- // ---- helpers -----------------------------------------------------------
@@ -274,7 +274,7 @@ from <select id="lookup-peer"><option value="">any peer</option></select>
<td>${up ? age(p.established_at_unix) : DASH}</td><td>${up ? (p.four_octet_asn ? 'yes' : 'no') : DASH}</td> <td>${up ? age(p.established_at_unix) : DASH}</td><td>${up ? (p.four_octet_asn ? 'yes' : 'no') : DASH}</td>
<td>${up ? esc((p.address_families || []).join(', ')) + addPath(p) + (p.extended_next_hop ? ' &middot; IPv4 with IPv6 next hops' : '') + graceful : DASH}</td> <td>${up ? esc((p.address_families || []).join(', ')) + addPath(p) + (p.extended_next_hop ? ' &middot; IPv4 with IPv6 next hops' : '') + graceful : DASH}</td>
<td>${up ? p.routes + stale : DASH}</td> <td>${up ? p.routes + stale : DASH}</td>
<td>${up ? (p.rejected_routes ? `<a class="bad" href="#refused">${p.rejected_routes}</a>` : 0) + (p.held_routes ? ` &middot; <span title="Routes rejected as RPKI or ASPA invalid, or for a next hop that cannot be reached, and kept aside, to be taken if that changes">${p.held_routes} held now</span>` : '') : DASH}</td><td>${detail}</td></tr>`; <td>${up ? (p.rejected_routes ? `<a class="bad" href="#rejected">${p.rejected_routes}</a>` : 0) + (p.held_routes ? ` &middot; <span title="Routes rejected as RPKI or ASPA invalid, or for a next hop that cannot be reached, and kept aside, to be taken if that changes">${p.held_routes} held now</span>` : '') : DASH}</td><td>${detail}</td></tr>`;
}).join('')); }).join(''));
renderPeerChoices(); renderPeerChoices();
} }
@@ -387,7 +387,7 @@ from <select id="lookup-peer"><option value="">any peer</option></select>
const data = r.updated_at_unix const data = r.updated_at_unix
? `${r.vrps_ipv4} IPv4 and ${r.vrps_ipv6} IPv6 VRPs and ${r.aspas} ASPAs at serial ${r.serial ?? '?'}, confirmed ${age(r.updated_at_unix)} ago` + ? `${r.vrps_ipv4} IPv4 and ${r.vrps_ipv6} IPv6 VRPs and ${r.aspas} ASPAs at serial ${r.serial ?? '?'}, confirmed ${age(r.updated_at_unix)} ago` +
(r.expires_at_unix ? `, dropped in ${until(r.expires_at_unix)} unless confirmed again` : '') (r.expires_at_unix ? `, dropped in ${until(r.expires_at_unix)} unless confirmed again` : '')
: '<span class="warn">no data: every route is "not found" and none is refused as invalid</span>'; : '<span class="warn">no data: every route is "not found" and none is rejected as invalid</span>';
setHTML(b, `Cache <span class="mono">${esc(r.cache || '?')}</span>: <span class="${RPKI_STATE_CLASS[r.state] || ''}">${esc(r.state)}</span>` + setHTML(b, `Cache <span class="mono">${esc(r.cache || '?')}</span>: <span class="${RPKI_STATE_CLASS[r.state] || ''}">${esc(r.state)}</span>` +
` <span class="dim">(RTR version ${r.protocol_version})</span>. ${data}.` + ` <span class="dim">(RTR version ${r.protocol_version})</span>. ${data}.` +
(r.updated_at_unix && r.protocol_version < 2 ? '<br><span class="warn">This cache speaks an RTR version without ASPAs: AS paths are not verified.</span>' : '') + (r.updated_at_unix && r.protocol_version < 2 ? '<br><span class="warn">This cache speaks an RTR version without ASPAs: AS paths are not verified.</span>' : '') +
@@ -406,7 +406,7 @@ from <select id="lookup-peer"><option value="">any peer</option></select>
row('installed as', `<span class="mono">table ${k.table} metric ${k.metric} proto bgp</span>`) + row('installed as', `<span class="mono">table ${k.table} metric ${k.metric} proto bgp</span>`) +
(k.kept ? row('kept from before the restart', `<span class="warn">${fmtCount(k.kept)}</span>, not announced again yet`) : '') + (k.kept ? row('kept from before the restart', `<span class="warn">${fmtCount(k.kept)}</span>, not announced again yet`) : '') +
(unresolved ? row('not installed', `<span class="warn">${unresolved}</span>`) : '') + (unresolved ? row('not installed', `<span class="warn">${unresolved}</span>`) : '') +
(k.refused ? row('refused by the kernel', `<span class="bad">${fmtCount(k.refused)}</span>`) : '') + (k.rejected ? row('rejected by the kernel', `<span class="bad">${fmtCount(k.rejected)}</span>`) : '') +
(k.last_error ? row('last error', `<span class="bad">${esc(k.last_error)}</span> (${fmtCount(k.errors)} errors in all)`) : '') + (k.last_error ? row('last error', `<span class="bad">${esc(k.last_error)}</span> (${fmtCount(k.errors)} errors in all)`) : '') +
'</table>'; '</table>';
// Interfaces that are up or have addresses get a row each; the others // Interfaces that are up or have addresses get a row each; the others
@@ -428,19 +428,19 @@ from <select id="lookup-peer"><option value="">any peer</option></select>
} }
// For each peer a line that says how many and why, then its latest few. // For each peer a line that says how many and why, then its latest few.
const REFUSED_SHOWN = 20; const REJECTED_SHOWN = 20;
function renderRefused() { function renderRefused() {
const b = $('refused-body'); const b = $('rejected-body');
const total = state.refused.reduce((n, p) => n + p.total, 0); const total = state.rejected.reduce((n, p) => n + p.total, 0);
const announcements = n => `${n} announcement${n === 1 ? '' : 's'}`; const announcements = n => `${n} announcement${n === 1 ? '' : 's'}`;
$('refused-count').textContent = total ? `${announcements(total)} from ${state.refused.length} peer${state.refused.length === 1 ? '' : 's'}` : '0'; $('rejected-count').textContent = total ? `${announcements(total)} from ${state.rejected.length} peer${state.rejected.length === 1 ? '' : 's'}` : '0';
if (!state.refused.length) { setHTML(b, '<tr><td colspan="12" class="dim">Nothing a peer announced has been rejected.</td></tr>'); return; } if (!state.rejected.length) { setHTML(b, '<tr><td colspan="12" class="dim">Nothing a peer announced has been rejected.</td></tr>'); return; }
setHTML(b, state.refused.map(p => { setHTML(b, state.rejected.map(p => {
const why = Object.entries(p.by_reason).sort((a, c) => c[1] - a[1]).map(([reason, n]) => `${n} ${esc(reason)}`).join(', '); const why = Object.entries(p.by_reason).sort((a, c) => c[1] - a[1]).map(([reason, n]) => `${n} ${esc(reason)}`).join(', ');
const more = p.recent.length > REFUSED_SHOWN const more = p.recent.length > REJECTED_SHOWN
? ` <span class="dim">Latest ${REFUSED_SHOWN} shown; <a href="/api/routes/rejected?peer=${encodeURIComponent(p.peer)}">the latest ${p.recent.length} as json</a>.</span>` : ''; ? ` <span class="dim">Latest ${REJECTED_SHOWN} shown; <a href="/api/routes/rejected?peer=${encodeURIComponent(p.peer)}">the latest ${p.recent.length} as json</a>.</span>` : '';
return `<tr><td colspan="12"><b>${esc(p.peer)}</b> <span class="dim">${esc(p.peer_addr)}</span>: <span class="bad">${announcements(p.total)} rejected</span> (${why}).${more}</td></tr>` + return `<tr><td colspan="12"><b>${esc(p.peer)}</b> <span class="dim">${esc(p.peer_addr)}</span>: <span class="bad">${announcements(p.total)} rejected</span> (${why}).${more}</td></tr>` +
p.recent.slice(0, REFUSED_SHOWN).map(r => { const s = r.summary; return `<tr><td></td> p.recent.slice(0, REJECTED_SHOWN).map(r => { const s = r.summary; return `<tr><td></td>
<td class="mono">${esc(r.prefix)}${r.path_id ? ` <span class="dim">path ${r.path_id}</span>` : ''}</td> <td class="mono">${esc(r.prefix)}${r.path_id ? ` <span class="dim">path ${r.path_id}</span>` : ''}</td>
<td class="mono path">${asPath(r.as_path)}</td><td>${esc(r.reason)}</td><td class="nowrap">${age(r.at_unix)} ago</td> <td class="mono path">${asPath(r.as_path)}</td><td>${esc(r.reason)}</td><td class="nowrap">${age(r.at_unix)} ago</td>
<td class="mono">${s.next_hop ? esc(s.next_hop) : DASH}</td><td>${s.origin ? esc(s.origin) : DASH}</td> <td class="mono">${s.next_hop ? esc(s.next_hop) : DASH}</td><td>${s.origin ? esc(s.origin) : DASH}</td>
@@ -480,7 +480,7 @@ from <select id="lookup-peer"><option value="">any peer</option></select>
} }
renderReceived(); renderReceived();
} }
async function loadRefused() { state.refused = await getJSON('/api/routes/rejected'); renderRefused(); } async function loadRefused() { state.rejected = await getJSON('/api/routes/rejected'); renderRefused(); }
async function loadRpki() { state.rpki = await getJSON('/api/rpki'); renderRpki(); } async function loadRpki() { state.rpki = await getJSON('/api/rpki'); renderRpki(); }
async function loadKernel() { state.kernel = await getJSON('/api/kernel'); renderKernel(); } async function loadKernel() { state.kernel = await getJSON('/api/kernel'); renderKernel(); }
async function loadAll() { await Promise.all([loadStatus(), loadPeers(), loadLocal(), loadReceived(), loadRefused(), loadRpki(), loadKernel()]); } async function loadAll() { await Promise.all([loadStatus(), loadPeers(), loadLocal(), loadReceived(), loadRefused(), loadRpki(), loadKernel()]); }