Files
ferrix/docs/content/docs/guides/import-policy.md
T
Rayhaan JaufeerallyandClaude Fable 5.1 6d8f89e509
ci / check (push) Canceled after 0s
ci / docs (push) Canceled after 0s
ci / interop (push) Canceled after 0s
ci / fuzz (push) Canceled after 0s
policy, fib: blackhole routing for routes with the BLACKHOLE community
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>
2026-10-08 23:08:50 +02:00

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_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 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_routes is 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
}
  • prefixes lists the prefixes that the neighbor can advertise. The max_length option 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_asns lists 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 reason prefix 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_prefixes for 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_communities removes 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_communities and add_large_communities add 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_pref sets 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, if L is 65535 or lower.
  • Large communities L:n:m.
  • Standard communities 0:n, if control_communities is 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.