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>
19 KiB
+++ title = "Import Policy" description = "Describes how Ferrix validates routes received from neighbors: the default checks, per-neighbor prefix and origin lists, prefix limits, and attribute modification." weight = 2 +++
The import policy determines which routes received from a neighbor are
accepted into the routing table. Ferrix evaluates every route in an UPDATE
message against the import policy of the neighbor that advertised it. You
configure the import policy with the import statement of each neighbor.
Understanding Import Policy
A neighbor can advertise any prefix with any attributes. If a route that a neighbor is not authorized to advertise is accepted, the local router uses it for forwarding and can advertise it to other neighbors. The import policy rejects such routes before they enter the routing table.
When Ferrix rejects a route, it also removes any route previously accepted from the same neighbor for the same prefix. Ferrix counts rejected routes by reason and retains the most recent rejections for inspection. See Rejected Routes.
Default Checks
Ferrix applies the checks in Table 1 to routes from every neighbor, without configuration. Each check can be disabled for a neighbor that requires it, such as a test network or a transit provider that advertises a default route.
| Rejected route | Statement to disable | Rejection reason |
|---|---|---|
| Prefix that is not routed on the Internet: private, loopback, link-local, documentation, multicast, or reserved | accept_bogons |
bogon prefix |
| Private, reserved, or documentation AS number in the AS path | accept_bogons |
bogon AS in path |
| IPv4 prefix longer than /24, or IPv6 prefix longer than /48 | max_ipv4_prefix_length, max_ipv6_prefix_length |
prefix too specific |
Default route, 0.0.0.0/0 or ::/0 |
accept_default_route |
prefix too short |
| AS path that does not begin with the neighbor's AS number | accept_any_first_as |
path does not begin with the peer's AS |
| AS path longer than 64 AS numbers | max_as_path_length |
path too long |
| AS path that contains an AS_SET (RFC 9774) | accept_as_sets |
AS_SET in path |
| Next hop other than the neighbor's address | accept_third_party_next_hop |
next hop is not the peer |
| Prefix originated by the local router, or a more specific prefix within it | Listing the prefix in the neighbor's prefixes |
prefix is our own |
For an Internet exchange route server, configure accept_any_first_as,
because a route server does not prepend its AS number to the AS path. The
next hop check does not apply to a neighbor with the role ROUTE_SERVER.
For IBGP neighbors (where remote_asn equals local_asn), Ferrix applies
only the checks on the prefix. The AS path checks do not apply, and the
next hop can be any usable address.
An IBGP neighbor can advertise a prefix that the local router also originates, for example an anycast prefix that both routers originate. Ferrix ignores such a route: it does not accept the route, and does not count it as rejected. This applies only to the originated prefix itself, not to more specific prefixes within it.
Ferrix always rejects the routes in Table 2. These checks cannot be disabled.
| Rejected route | Rejection reason |
|---|---|
| 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) | route leak (Only to Customer) |
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 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 (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).
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:
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_lengthandmax_ipv6_prefix_lengthlimits do not apply to blackhole routes. A blackhole route for a longer prefix is rejected with the reasonblackhole for more than one address. - The address is within a prefix that the neighbor may announce, if
prefixesis configured. Themax_lengthof 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 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
The next hop of a route is the address to which the local router forwards traffic for the prefix. By default, Ferrix requires the next hop of a route from an EBGP neighbor to be the neighbor's own address. This prevents a neighbor from directing traffic to a third party, such as another member of an Internet exchange or a host in another network of the local operator.
An Internet exchange route server advertises routes with the addresses of
the originating members as next hops. For this reason, the check does not
apply to a neighbor with the role ROUTE_SERVER.
Local Prefix Check
By default, Ferrix rejects routes from EBGP neighbors for prefixes that it originates, and for more specific prefixes within them. Accepting such a route would direct traffic for the local network to an external network.
If a customer is authorized to advertise part of the local address space,
include the prefix in the customer's prefixes list. When a neighbor has a
prefixes list, the local prefix check does not apply to it.
Comparison with the NLNOG BGP Filter Guide
The NLNOG BGP Filter Guide lists the inbound filters that operators commonly configure on EBGP sessions. Many BGP implementations, including FRR, BIRD, and Cisco IOS XR, reject all routes from an EBGP neighbor until a policy is configured (RFC 8212), and the operator writes these filters in the implementation's policy language. Ferrix applies most of them by default. Table 3 compares the guide with Ferrix.
| Filter in the guide | Ferrix |
|---|---|
| Reject RPKI invalid routes | One statement: rpki |
| Attest trust anchor prefixes (SLURM) | Configured in the RPKI validator |
| Bogon ASN filtering | Default |
| Bogon prefix filtering | Default |
| No small prefixes | Default: longer than /24 or /48 |
| No IXP peering LAN leaks | Partial: such routes are never installed in the kernel, but they are not rejected |
| No long AS paths | Default: longer than 64 AS numbers |
| No known transit ASNs in the AS path | Not supported. BGP roles and ASPA verification detect many of the same route leaks. |
| Strip high community counts | Not supported |
| Remainder accept term with communities and local preference | add_communities, add_large_communities, local_pref |
| Graceful shutdown support | Not supported |
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, 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 accepts routes from an EBGP neighbor that pass the default checks. On export, Ferrix advertises only originated routes unless
learned_routesis enabled, so that learned routes are not advertised without explicit configuration.
Configuring Prefix and Origin Lists
To restrict the routes that a neighbor can advertise, configure a prefix list, an origin AS list, or both:
import {
prefixes { prefix: "193.0.0.0/21" max_length: 24 }
prefixes { prefix: "2001:67c:2e8::/48" }
origin_asns: 3333
}
prefixeslists the prefixes that the neighbor can advertise. Themax_lengthoption also permits more specific prefixes up to the specified length. If the list is empty, all prefixes are permitted. Rejection reason:prefix not allowed.origin_asnslists the AS numbers from which the neighbor's routes can originate. The origin AS is the last AS number in the AS path. If the list is empty, all origin AS numbers are permitted. Rejection reason:origin AS not allowed.
Generating Lists with bgpq4
You can generate prefix and origin AS lists from Internet Routing Registry
(IRR) data with bgpq4. The following
commands query the registry for an AS-SET and convert the output to Ferrix
configuration statements with jq:
bgpq4 -4 -R 24 -j -l prefixes AS-EXAMPLE > v4.json
bgpq4 -6 -R 48 -j -l prefixes AS-EXAMPLE > v6.json
bgpq4 -t -j -l asns AS-EXAMPLE > asns.json
jq -r '.prefixes[] | "prefixes { prefix: \"\(.prefix)\""
+ (if .exact then "" else " max_length: \(."less-equal")" end) + " }"' v4.json v6.json
jq -r '.asns[] | "origin_asns: \(.)"' asns.json
The commands produce statements such as the following, for the neighbor's
import statement:
prefixes { prefix: "193.0.0.0/21" max_length: 24 }
prefixes { prefix: "193.0.10.0/23" max_length: 24 }
origin_asns: 3333
Tip
Regenerate the lists periodically, validate the new configuration with
bgp --check, and restart the daemon. An outdated list rejects the neighbor's new prefixes with the reasonprefix not allowed.
Prefix Limits
The max_prefixes statement specifies the maximum number of routes that a
neighbor can have in the routing table. If the neighbor exceeds the limit,
Ferrix sends a Cease NOTIFICATION message with the subcode Maximum Number
of Prefixes Reached, tears down the session, and places the neighbor in
hold-down for hold_down_seconds. During hold-down, Ferrix neither
initiates nor accepts connections for the neighbor.
import {
max_prefixes: 1500000
hold_down_seconds: 600
}
By default, the number of prefixes is not limited. The default hold-down is 600 seconds.
Important
Configure
max_prefixesfor every neighbor, with a value somewhat above the number of routes that the neighbor is expected to advertise. Without a limit, a neighbor that leaks a full routing table is accepted.
The metrics bgp_peer_routes and bgp_peer_max_prefixes report the
current number of routes and the limit for each neighbor. See
Alerting Rules.
RPKI and ASPA Validation
If RPKI validation is configured, Ferrix also rejects routes whose origin is RPKI invalid and, for neighbors with a role, routes whose AS path is ASPA invalid. See RPKI.
Modifying Route Attributes
The import policy can add and remove communities on accepted routes, and set their local preference:
peers {
name: "ixp-a-rs"
import {
remove_communities: "64496:*"
add_large_communities: "64500:2:1001"
local_pref: 150
}
}
The statements have the following effect:
remove_communitiesremoves matching communities. Ferrix applies it after it removes the communities of the local AS (see Communities of the Local AS), and before adding communities. A pattern has two fields for a standard community and three for a large community; each field is a number or*. Examples:64500:100,64500:*,64500:2:*,*:*.add_communitiesandadd_large_communitiesadd communities to every accepted route. Use them to record where a route was learned, for other routers in the local AS and for export policy.local_prefsets the local preference of every accepted route. Best path selection compares local preference first; the route with the higher value is preferred. Default: 100.
Tip
A common local preference scheme assigns 200 to customer routes, 150 to peer and route server routes, and 100 to transit routes.
The examples in this documentation use large communities 64500:2:x to
record where a route was learned and 64500:3:r to record the router that
advertised it. These values do not overlap the
control communities,
or the standard communities 64500:n, which control communities interpret
as "advertise to AS n".
Recording Where Routes Are Learned
Informational communities record, on each route, where the local AS learned it. Other routers in the local AS, neighbors, and looking glasses can then see it, and import rules and control communities can act on it. A consistent scheme uses large communities with a function number of 100 or higher, for example:
| Community | Meaning |
|---|---|
64500:100:R |
Learned by router R. |
64500:101:V |
Learned at venue V: an Internet exchange, a transit provider, or customers. |
64500:102:S |
Learned over interconnection S. |
Each router adds them with add_large_communities on every EBGP session:
peers {
name: "exchange-a-rs1-v4"
import {
add_large_communities: "64500:100:1"
add_large_communities: "64500:101:1"
add_large_communities: "64500:102:1001"
}
}
The communities are retained on IBGP sessions, so that each router sees where the other routers learned their routes. Because Ferrix removes the local AS's communities from routes from EBGP neighbors (see Communities of the Local AS), a neighbor cannot set them: every such community on a route was added by a router of the local AS.
Import Rules
Import rules apply local preference and communities to some routes only: routes with certain communities, routes for certain prefixes, or both. Ferrix evaluates the rules of a session in order, and applies the first rule that matches a route. The communities that the rules match are those of the route after the session's other import statements are applied, so rules can match the session's own informational communities.
The following example, on an IBGP session, prefers routes that the other router learned at exchange A over routes that it learned at exchange B, and marks the routes of one prefix:
peers {
name: "router-2"
remote_asn: 64500
import {
rules { match_communities: "64500:101:1" local_pref: 200 }
rules { match_communities: "64500:101:2" local_pref: 130 }
rules {
match_prefixes { prefix: "192.0.2.0/24" }
add_large_communities: "64500:31:10"
}
}
}
Routes that the rules do not match keep the local preference that they were advertised with. Routes for one prefix that arrive in one UPDATE message can match different rules; Ferrix stores each group separately.
Tip
Choose local preferences so that the local AS's outbound traffic leaves through the same venue through which other networks send their traffic in. If a route is preferred at venue A for outbound traffic, use control communities to prepend the local AS number at the venues that are less preferred. Otherwise traffic to and from a network uses different venues, which makes troubleshooting harder and can overload a link that was meant as a fallback.
Communities of the Local AS
By default, Ferrix removes the local AS's communities from every route that
it receives from an EBGP neighbor, before add_communities and
add_large_communities are applied. The following communities are
removed, where L is the local AS number:
- Standard communities
L:n, ifLis 65535 or lower. - Large communities
L:n:m. - Standard communities
0:n, ifcontrol_communitiesis enabled.
These communities are instructions and records of the local AS: control communities decide where Ferrix advertises a route, and informational communities record where a route was learned. A neighbor at an Internet exchange must not be able to set them. Ferrix retains them on routes from IBGP neighbors.
A customer that sets control communities on its own routes requires the following statement:
peers {
name: "customer-a"
role: CUSTOMER
import { accept_control_communities: true }
}
Ferrix then retains all communities of the local AS on the customer's
routes. The customer can affect only its own routes. Use
remove_communities to remove informational communities that the
customer must not set, for example 64500:2:*.
Ferrix examines the communities of every UPDATE message from an EBGP neighbor. A message without communities of the local AS is accepted unchanged.
Performance
Ferrix evaluates each route once, when the session's task processes the UPDATE message. Each check is a comparison or a table lookup. Attribute modifications are applied once for each UPDATE message, because all routes in a message share the same attributes. A full routing table from one neighbor is accepted into the routing table in less than 0.5 seconds.