Reject blackhole requests with a reason of their own
ci / check (push) Canceled after 0s
ci / docs (push) Canceled after 0s
ci / interop (push) Canceled after 0s
ci / fuzz (push) Canceled after 0s

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:
Rayhaan JaufeerallyandClaude Opus 5.5 committed 2026-10-04 01:03:16 +02:00
1 parent bd674002a2
commit efb2d81a50
3 files changed
+77 -4

No files matched your search

+49 -2
View File
@@ -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]
+20 -2
View File
@@ -56,6 +56,23 @@ disabled.
| Route from an EBGP neighbor whose AS path contains the local AS number | `own AS in path` |
| Route whose next hop is unusable: unspecified, loopback, multicast, or an address of the local router | `next hop unusable` |
| Route with the Only to Customer attribute received from the wrong direction (see [BGP Roles](@/docs/guides/roles.md)) | `route leak (Only to Customer)` |
| Route from an EBGP neighbor with the BLACKHOLE community (RFC 7999) | `blackhole request` |
### Blackhole Requests
A network under a denial-of-service attack can advertise a route for the
attacked address with the BLACKHOLE community (65535:666), to request that
other networks discard traffic to the address. At an Internet exchange, the
route server distributes such routes to its members with the exchange's
blackhole address as the next hop. These routes are usually host routes
(/32 or /128).
Ferrix does not support blackhole routing. It rejects every route with the
BLACKHOLE community from an EBGP neighbor, with the reason
`blackhole request`, regardless of the prefix length. A route with the
BLACKHOLE community that is accepted as an ordinary route directs traffic
to the blackhole next hop, which discards it, so Ferrix does not accept
these routes as ordinary routes either.
### Next Hop Check
@@ -105,8 +122,9 @@ Ferrix.
Ferrix also applies the following checks, which the guide does not list,
by default: the next hop must be the neighbor, routes for local prefixes
are rejected, AS paths with an AS_SET are rejected, and the AS path must
begin with the neighbor's AS number.
are rejected, AS paths with an AS_SET are rejected, the AS path must begin
with the neighbor's AS number, and routes with the BLACKHOLE community are
rejected.
> [!NOTE]
> Ferrix does not implement the default behavior of RFC 8212 on import: it
+8
View File
@@ -173,6 +173,14 @@ The base protocol specification.
| `LARGE_COMMUNITIES` path attribute (parse, serialise) | ✅ |
| Attach to locally originated routes | ✅ |
### RFC 7999 — BLACKHOLE Community
<https://datatracker.ietf.org/doc/html/rfc7999>
| Feature | Status |
|---------|--------|
| `BLACKHOLE` accepted as a community name in configuration | ✅ |
| Routes with BLACKHOLE from EBGP neighbors rejected on import (`blackhole request`) | ✅ |
| Blackhole routing: discarding traffic for prefixes with BLACKHOLE | ❌ |
## Other Capabilities (Wire Format Only)