policy, fib: blackhole routing for routes with the BLACKHOLE community
ci / check (push) Canceled after 0s
ci / docs (push) Canceled after 0s
ci / interop (push) Canceled after 0s
ci / fuzz (push) Canceled after 0s

Before this change, a route carrying the BLACKHOLE community (65535:666,
RFC 7999) from an EBGP neighbor was always rejected with the reason
"blackhole request", and nothing discarded traffic. A customer under a
denial-of-service attack had no way to ask this router to drop traffic
to an address.

This change adds `accept_blackholes` to the import policy. When it is
set, a route with the BLACKHOLE community is accepted if:

* its prefix is a single address, a /32 or a /128 (otherwise it is
  rejected with the new reason "blackhole for more than one address");
  the `max_ipv4_prefix_length` and `max_ipv6_prefix_length` limits do
  not apply, as RFC 7999 section 3.2 recommends;
* the address is inside a prefix the neighbor may announce, if
  `prefixes` is configured, regardless of the entry's `max_length`
  (new `PrefixSet::covers`); without a prefix list, an address inside
  the local AS's own prefixes is rejected as before ("prefix is our
  own");
* the AS path, origin and RPKI checks that apply to every route pass.

The next hop of an accepted blackhole route is not checked for
third-party addresses and is not resolved, because traffic is never
forwarded to it. The route takes part in best path selection as usual;
a host route is normally the active route for its address.

The kernel task installs the active route as a kernel blackhole route
(`RTN_BLACKHOLE`, shown by `ip route` as `blackhole PREFIX proto bgp`).
Blackhole routes left by an earlier run are adopted at startup like
other routes of ours. The route is advertised to other neighbors as the
export policy and export limits permit, with the community intact, so a
provider that honors BLACKHOLE can discard the traffic upstream.

Implementation:

* `plan::Target` becomes an enum: `Via { gateway, interface }` or
  `Discard`. `plan::resolve` takes a `blackhole` flag and returns
  `Discard` for such routes without looking at the next hop (the
  own-network check still applies). No nexthop object is made for a
  discard target.
* `AttrSet::is_blackhole` reads the community off the stored attribute
  bytes, like `next_hop` and `as_path`, so the kernel task, the
  revalidator and the RPKI revalidation can ask it of every route.
* `ImportPolicy::is_blackhole_request` and `check_blackhole_prefix`
  carry the import side; the import flow (`routes.rs`) computes once per
  UPDATE whether its routes are an accepted blackhole request and skips
  the next hop checks for them.
* `Status::blackholes` and the `blackholes` field of `/api/kernel` count
  the installed blackhole routes; the status page shows the count next
  to the installed routes.

Not covered: a prefix this router originates with the BLACKHOLE
community is advertised but not installed as a blackhole route. The
documentation says to add a kernel blackhole route by hand for that
case.

Documentation: the "Blackhole Routes" section of the import policy
guide replaces "Blackhole Requests", the kernel guide gains a "Blackhole
Routes" section, the configuration reference describes
`accept_blackholes`, the HTTP API reference describes `blackholes`, and
the RFC 7999 rows of the standards reference are updated.

Testing: unit tests for the attribute check, the prefix checks and the
plan; integration tests that a blackhole request is refused by default
and, with `accept_blackholes`, taken for one address inside the peer's
space and refused otherwise, with the recorded reasons; a kernel test
that a blackhole request becomes a kernel blackhole route whatever its
next hop, is removed on withdrawal and is adopted after a restart. The
existing kernel tests, the interoperability lab and the workspace tests
pass.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
This commit is contained in:
Rayhaan JaufeerallyandClaude Fable 5.1 committed 2026-10-08 23:08:50 +02:00
1 parent 01ac3e2cd8
commit 6d8f89e509
21 files changed
+682 -96

No files matched your search

+60 -12
View File
@@ -62,23 +62,71 @@ 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` |
| Route from an EBGP neighbor with the BLACKHOLE community (RFC 7999), unless `accept_blackholes` is set | `blackhole request` |
| Route with the BLACKHOLE community that is not a host route (/32 or /128), with `accept_blackholes` set | `blackhole for more than one address` |
### Blackhole Requests
### Blackhole Routes
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).
other networks discard traffic to the address (RFC 7999). 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 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.
By default, Ferrix rejects every route with the BLACKHOLE community from
an EBGP neighbor, with the reason `blackhole request`, regardless of the
prefix length. Ferrix does not accept such a route as an ordinary route
either, because that would direct traffic to the blackhole next hop,
which discards it.
To honor blackhole requests from a neighbor, set `accept_blackholes` in
its import policy:
```textproto
peers {
name: "customer-a"
endpoint { address: "192.0.2.10" }
remote_asn: 64501
role: CUSTOMER
import {
prefixes { prefix: "203.0.113.0/24" }
accept_blackholes: true
}
}
```
With `accept_blackholes` set, Ferrix accepts a route with the BLACKHOLE
community if all of the following conditions are met:
- The prefix is a single address: a /32 or a /128. The
`max_ipv4_prefix_length` and `max_ipv6_prefix_length` limits do not
apply to blackhole routes. A blackhole route for a longer prefix is
rejected with the reason `blackhole for more than one address`.
- The address is within a prefix that the neighbor may announce, if
`prefixes` is configured. The `max_length` of the entry does not apply.
- The route passes the AS path, origin, and RPKI checks that apply to
every route.
The next hop of a blackhole route is not checked, and need not be
reachable: Ferrix never forwards traffic to it. If the route is the active
route for its prefix, which a host route is unless a longer-matching
route exists, Ferrix installs a blackhole route in the kernel, and the
router discards traffic to the address. See [Blackhole
Routes](@/docs/guides/kernel.md#blackhole-routes) in the kernel guide.
Ferrix advertises an accepted blackhole route like any other route, with
the BLACKHOLE community, to the neighbors that the export policy and the
export limits permit. A provider that honors the community then discards
the traffic before it reaches the local network. To keep a blackhole
route from being advertised, the originator adds the NO_EXPORT or
NO_ADVERTISE community, as RFC 7999 recommends.
> [!NOTE]
> Ferrix does not install a blackhole route for a prefix that it
> originates with the BLACKHOLE community. To discard traffic locally for
> such a prefix, add a kernel blackhole route, for example
> `ip route add blackhole 203.0.113.9/32`.
### Next Hop Check
+12
View File
@@ -112,6 +112,18 @@ next hop becomes unresolvable is also moved to the held state.
If the `kernel` statement is not configured, Ferrix has no information about
next hop reachability and accepts all routes.
## Blackhole Routes
Ferrix installs the active route for a prefix as a kernel blackhole route
(`ip route` displays it as `blackhole PREFIX proto bgp`) if the route
carries the BLACKHOLE community (RFC 7999) and the import policy accepted
it with `accept_blackholes`. The kernel discards traffic to the prefix.
The next hop of the route is not resolved, and the route is installed
even if the next hop is unreachable. The kernel section of the status
page and `/api/kernel` report the number of installed blackhole routes.
See [Blackhole Routes](@/docs/guides/import-policy.md#blackhole-routes)
in the import policy guide.
## Connected Networks
Ferrix never installs a route for a subnet of a local interface, or for a
@@ -103,6 +103,7 @@ accepted. See [Import Policy](@/docs/guides/import-policy.md).
| `max_prefixes` <span class="type">uint32</span> | 0 (no limit) | Specifies the maximum number of routes that the neighbor can have in the routing table. If the limit is exceeded, Ferrix tears down the session and places the neighbor in hold-down. |
| `hold_down_seconds` <span class="type">uint32</span> | 600 | Specifies the hold-down time, in seconds, after the neighbor exceeds `max_prefixes`, or after the routes to be advertised to it exceed `export_limits.max_prefixes`. |
| `accept_bogons` <span class="type">bool</span> | `false` | Accepts bogon prefixes, and AS paths that contain private, reserved, or documentation AS numbers. |
| `accept_blackholes` <span class="type">bool</span> | `false` | Accepts routes with the BLACKHOLE community (RFC 7999) for single addresses, and installs them as blackhole routes. See [Blackhole Routes](@/docs/guides/import-policy.md#blackhole-routes). |
| `accept_default_route` <span class="type">bool</span> | `false` | Accepts the default routes `0.0.0.0/0` and `::/0`. |
| `max_ipv4_prefix_length` <span class="type">uint32</span> | 24 | Specifies the longest IPv4 prefix accepted. |
| `max_ipv6_prefix_length` <span class="type">uint32</span> | 48 | Specifies the longest IPv6 prefix accepted. |
+2
View File
@@ -206,6 +206,7 @@ routes. To return the records for one neighbor, add `?peer=NAME`.
"synced": true,
"installed": 0,
"kept": 0,
"blackholes": 0,
"unresolved": {},
"refused": 0,
"errors": 0,
@@ -222,6 +223,7 @@ routes. To return the records for one neighbor, add `?peer=NAME`.
| `synced` | Whether the kernel routing table has been synchronized with the routing table since the daemon started. |
| `installed` | Number of routes installed. |
| `kept` | Number of routes from a previous run that are retained during startup. |
| `blackholes` | Number of installed routes that are blackhole routes, which discard traffic for their prefix (RFC 7999). |
| `unresolved` | Number of prefixes whose active route is not installed, by reason. |
| `refused` | Number of prefixes whose active route the kernel rejected. |
| `errors` | Number of requests that the kernel rejected. |
+5 -2
View File
@@ -203,8 +203,11 @@ The base protocol specification.
| 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 | ❌ |
| Routes with BLACKHOLE from EBGP neighbors rejected on import by default (`blackhole request`) | ✅ |
| Blackhole routes for single addresses accepted with `accept_blackholes` (§3.2: prefix length limits relaxed, next hop not checked) | ✅ |
| Blackhole routing: a kernel blackhole route discards traffic for the prefix | ✅ |
| Blackhole routes advertised with the community, for providers to honor (§3.3) | ✅ |
| A blackhole route for a prefix this router originates | ❌ (add a kernel blackhole route by hand) |
## Other Capabilities (Wire Format Only)