Reject blackhole requests with a reason of their own
A route with the BLACKHOLE community (RFC 7999) asks for the traffic to its prefix to be dropped. Exchange route servers pass these on as host routes with the exchange's blackhole address as next hop, and they were rejected as "prefix too specific", which hid what they are. Routes from EBGP neighbors with BLACKHOLE are now rejected as "blackhole request", whatever their length. One short enough to pass would have been taken as an ordinary route, sending the traffic to the blackhole next hop: the request honoured without anyone having chosen to. Acting on blackhole requests is not supported; the import policy page and the standards list say so. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
1 parent
bd674002a2
commit
efb2d81a50
3 files changed
+77
-4
No files matched your search
@@ -218,6 +218,11 @@ pub enum Rejection {
|
||||
Leak,
|
||||
/// The path has an AS_SET in it (RFC 9774).
|
||||
AsSet,
|
||||
/// The route asks for the traffic to its prefix to be dropped: it has
|
||||
/// the BLACKHOLE community (RFC 7999). Such requests are not acted on,
|
||||
/// and the route is not taken as an ordinary one either, which would
|
||||
/// send the traffic to wherever its next hop drops it.
|
||||
Blackhole,
|
||||
/// The next hop is no address packets could be sent to, or is our
|
||||
/// own, or is an IPv6 address for an IPv4 route on a session that did
|
||||
/// not agree to that (RFC 8950).
|
||||
@@ -242,7 +247,7 @@ impl Rejection {
|
||||
)
|
||||
}
|
||||
|
||||
pub const ALL: [Self; 17] = [
|
||||
pub const ALL: [Self; 18] = [
|
||||
Self::BogonPrefix,
|
||||
Self::TooShort,
|
||||
Self::TooSpecific,
|
||||
@@ -256,6 +261,7 @@ impl Rejection {
|
||||
Self::AspaInvalid,
|
||||
Self::Leak,
|
||||
Self::AsSet,
|
||||
Self::Blackhole,
|
||||
Self::NextHopUnusable,
|
||||
Self::NextHopNotPeer,
|
||||
Self::OwnPrefix,
|
||||
@@ -282,6 +288,7 @@ impl Rejection {
|
||||
Self::AspaInvalid => "ASPA invalid",
|
||||
Self::Leak => "route leak (Only to Customer)",
|
||||
Self::AsSet => "AS_SET in path",
|
||||
Self::Blackhole => "blackhole request",
|
||||
Self::NextHopUnusable => "next hop unusable",
|
||||
Self::NextHopNotPeer => "next hop is not the peer",
|
||||
Self::OwnPrefix => "prefix is our own",
|
||||
@@ -290,6 +297,10 @@ impl Rejection {
|
||||
}
|
||||
}
|
||||
|
||||
/// The community that asks for the traffic to a prefix to be dropped
|
||||
/// (RFC 7999).
|
||||
const BLACKHOLE: (u16, u16) = (0xFFFF, 0x029A);
|
||||
|
||||
/// What a peer is to us, in the business between networks. It decides
|
||||
/// what its routes may look like: a customer's routes have come up to us
|
||||
/// through its own customers only, while a provider's may have been
|
||||
@@ -419,6 +430,13 @@ impl ImportPolicy {
|
||||
if facts.has_local_asn {
|
||||
return Err(Rejection::OwnAs);
|
||||
}
|
||||
let blackhole = attributes.iter().any(|attribute| {
|
||||
matches!(attribute, PathAttribute::Communities(communities)
|
||||
if communities.0.contains(&BLACKHOLE))
|
||||
});
|
||||
if blackhole {
|
||||
return Err(Rejection::Blackhole);
|
||||
}
|
||||
// RFC 9234 §5, ingress, 1 and 2. A route that is only to go to
|
||||
// customers has been down or across once already. From a customer
|
||||
// it is coming up again; from a peer it is coming across again,
|
||||
@@ -852,7 +870,7 @@ impl HoldDowns {
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use bgp_packet::message::AsPathSegment;
|
||||
use bgp_packet::message::{AsPathSegment, CommunitiesPathAttribute};
|
||||
use proptest::prelude::*;
|
||||
|
||||
use super::*;
|
||||
@@ -1229,6 +1247,35 @@ mod tests {
|
||||
assert_eq!(lenient.check_path(&with_set, &EXTERNAL), Ok(None));
|
||||
}
|
||||
|
||||
/// A request to drop traffic is neither acted on nor taken as an
|
||||
/// ordinary route, whose next hop would drop the traffic all the same.
|
||||
/// It is told apart from a prefix that is merely too long, which is
|
||||
/// what the request usually comes as.
|
||||
#[test]
|
||||
fn a_blackhole_request_is_refused_as_one() {
|
||||
let mut attributes = path(&[3333, 2121]);
|
||||
attributes.push(PathAttribute::Communities(CommunitiesPathAttribute(vec![
|
||||
(3333, 100),
|
||||
(0xFFFF, 0x029A),
|
||||
(0xFFFF, 0xFF01),
|
||||
])));
|
||||
assert_eq!(
|
||||
ImportPolicy::default().check_path(&attributes, &EXTERNAL),
|
||||
Err(Rejection::Blackhole)
|
||||
);
|
||||
assert_eq!(Rejection::Blackhole.as_str(), "blackhole request");
|
||||
// Other communities are nothing to the policy.
|
||||
attributes.pop();
|
||||
attributes.push(PathAttribute::Communities(CommunitiesPathAttribute(vec![
|
||||
(3333, 666),
|
||||
(0xFFFF, 0xFF01),
|
||||
])));
|
||||
assert_eq!(
|
||||
ImportPolicy::default().check_path(&attributes, &EXTERNAL),
|
||||
Ok(Some(2121))
|
||||
);
|
||||
}
|
||||
|
||||
/// What we originate is ours: a more specific route for part of it
|
||||
/// from another network would draw our own traffic away.
|
||||
#[test]
|
||||
|
||||
Reference in new issue
Block a user