Export limits, and a role for every peer where routes are passed on

What other networks were told of rested on one thing: the Only to
Customer mark that routes from providers, peers and route servers get as
they come in. A route without it, from a session without a role or by a
mistake anywhere along the way, went to every peer, from every router of
ours, and nothing of ours stood in the way.

export_limits are a second check, independent of the roles, the marks
and the export policy. For peers above or beside us (PROVIDER, PEER,
ROUTE_SERVER, and any in another AS without a role):

- prefixes: only routes for these go out, learned or our own. With our
  and our customers' space listed, a leak of anyone else's routes goes
  nowhere above us.
- max_prefixes: if more routes were to go out than this, none of them
  does. The session ends with a Cease / Administrative Shutdown whose
  Shutdown Communication says why (RFC 9003), and the peer is held down
  for its import.hold_down_seconds, so that it is told nothing rather
  than too much. The whole table is counted before any of it is sent.

They are set at the top and hold for every such peer that has none of its
own; unlike `export`, a peer's own export policy does not drop them. A
peer's own export_limits take their place, and an empty one lifts them.
Customers, route server clients and our own routers are not limited.

A configuration in which learned routes are passed on and a peer in
another AS has no role is now refused, as is accept_unconfigured_peers
with learned routes: without a role, a peer's routes go to everyone and
it is told of everything. This used to be a warning.

At startup, a warning for each peer the limits apply to that has none,
and for each originated route the limits leave out. A metric,
bgp_export_limit_exceeded_total, counts the sessions ended for it.

The interop lab has a phase in which a customer leaks: a route outside
its space never reaches BIRD, and when more were to go than the limit,
BIRD's session ends and its log has our reason.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
Rayhaan JaufeerallyandClaude Opus 5.5 committed 2026-10-05 23:10:43 +02:00
1 parent 17045a3634
commit 9cfd3f67f0
27 files changed
+1074 -19

No files matched your search

+78 -5
View File
@@ -84,11 +84,84 @@ peers { name: "transit-a" role: PROVIDER ... }
peers { name: "ixp-a-rs" role: ROUTE_SERVER ... }
```
> [!CAUTION]
> A neighbor without a role is not subject to this restriction. If
> `learned_routes` is enabled for such a neighbor, Ferrix advertises all
> learned routes to it, including routes from providers and peers. Ferrix
> logs a warning at startup for each such neighbor.
If any `export` statement enables `learned_routes`, every EBGP neighbor
must have a role. Ferrix does not start with a configuration in which an
EBGP neighbor has no role, because the routes received from that neighbor
would be advertised to all other neighbors, and all learned routes would
be advertised to it. For the same reason, `accept_unconfigured_peers` is
not permitted with a top-level `export` statement that enables
`learned_routes`.
A route collector, which receives routes and advertises none, is
configured with the role `CUSTOMER`.
## Export Limits
Export limits restrict the routes that Ferrix advertises to providers,
peers, and route servers, regardless of the export policy, the BGP roles,
and the communities of the routes. They protect against route leaks that
the roles do not prevent, for example a route that a customer advertises
for address space that is not its own, or an error in the configuration
of a role.
Export limits apply to the following neighbors:
- Neighbors with the role `PROVIDER`, `PEER`, or `ROUTE_SERVER`.
- EBGP neighbors without a role.
They do not apply to customers, route server clients, or IBGP neighbors,
which receive the full routing table where their export policy permits it.
### Configuring Export Limits
The top-level `export_limits` statement applies to every neighbor that
does not have its own `export_limits` statement:
```textproto
export_limits {
prefixes { prefix: "198.51.100.0/22" max_length: 24 }
prefixes { prefix: "2001:db8::/32" max_length: 48 }
max_prefixes: 50
}
```
| Statement | Description |
|---|---|
| `prefixes` | Specifies the prefixes that can be advertised: typically the address space of the local AS and of its customers. Ferrix does not advertise learned or originated routes for other prefixes to the neighbor. Default: all prefixes. |
| `max_prefixes` | Specifies the maximum number of routes advertised to the neighbor, counting learned and originated routes of both address families. Default: no limit. |
A neighbor's `export_limits` statement replaces the top-level statement.
An empty statement, `export_limits {}`, removes the limits for the
neighbor. Unlike the `export` statement, the limits are not lost when a
neighbor has its own export policy.
If more routes are to be advertised to a neighbor than `max_prefixes`
permits, Ferrix advertises none of them. It tears down the session with a
Cease NOTIFICATION message, subcode Administrative Shutdown, that contains
the reason as a shutdown communication (RFC 9003), and places the neighbor
in hold-down for the neighbor's `import.hold_down_seconds` (default: 600
seconds). Set `max_prefixes` somewhat above the number of routes that the
local AS and its customers originate.
At startup, Ferrix logs a warning for each neighbor to which export limits
apply but none are configured, and for each originated route that the
`prefixes` of the export limits do not permit.
### Verifying Export Limits
When the limit is exceeded, Ferrix logs an error that states the number of
routes and the limit, and increments the metric
`bgp_export_limit_exceeded_total{peer}`. The status page and `/api/peers`
show the reason in the neighbor's last error, for example:
```text
6 routes were to be advertised to the peer, over its export limit of 5; none of them was
```
While the neighbor is in hold-down, its last error is `held down after more
routes were to be advertised to it than its export limit allows`. To restore
the session before the hold-down expires, correct the cause and restart
Ferrix.
## Configuring Export Policy
@@ -143,6 +143,13 @@ groups:
expr: increase(bgp_prefix_limit_exceeded_total[1h]) > 0
labels: { severity: page }
# A session was torn down because more routes were to be advertised
# to the neighbor than its export limit permits: a probable route
# leak, which Ferrix did not advertise.
- alert: BgpExportLimitExceeded
expr: increase(bgp_export_limit_exceeded_total[1h]) > 0
labels: { severity: page }
# A configured neighbor has no established session.
- alert: BgpPeerDown
expr: bgp_peer_up == 0
+15 -2
View File
@@ -57,6 +57,7 @@ Top-level statements.
| `control_communities` <span class="type">bool</span> | `false` | Enables control communities, which restrict the neighbors to which a route is advertised and prepend the local AS number on its way to them. See [Control Communities](@/docs/guides/passing-routes-on.md#control-communities). |
| `cluster_id` <span class="type">string</span> | `router_id` | Specifies the cluster ID that the router uses as a route reflector, as a dotted-quad IPv4 address. See [Route Reflection](@/docs/guides/passing-routes-on.md#route-reflection). |
| `graceful_restart` <span class="type">[GracefulRestart](#gracefulrestart)</span> | Enabled | Specifies graceful restart for neighbors that have no `graceful_restart` statement. Its `restart_time_seconds` also limits how long Ferrix waits at startup for neighbors to send their routes. |
| `export_limits` <span class="type">[ExportLimits](#exportlimits)</span> | None | Specifies limits on the routes advertised to neighbors whose role is `PROVIDER`, `PEER`, or `ROUTE_SERVER`, and to EBGP neighbors without a role, for neighbors that have no `export_limits` statement. See [Export Limits](@/docs/guides/passing-routes-on.md#export-limits). |
## Peer
@@ -72,11 +73,12 @@ Statements for a BGP neighbor.
| `address_families` <span class="type">[AddressFamily](#addressfamily), repeated</span> | All | Specifies the address families proposed to the neighbor. A family is enabled only if the neighbor also proposes it (RFC 4760). |
| `add_path` <span class="type">[AddPath](#addpath)</span> | `ADD_PATH_OFF` | Specifies the ADD-PATH directions proposed to the neighbor (RFC 7911). |
| `import` <span class="type">[ImportPolicy](#importpolicy)</span> | Default checks | Specifies the import policy for routes received from the neighbor. |
| `role` <span class="type">[Role](#role)</span> | None | Specifies the BGP role of the neighbor (RFC 9234). Not permitted for IBGP neighbors. |
| `role` <span class="type">[Role](#role)</span> | None | Specifies the BGP role of the neighbor (RFC 9234). Not permitted for IBGP neighbors. Required for every EBGP neighbor if any `export` statement enables `learned_routes`. |
| `require_role` <span class="type">bool</span> | `false` | Rejects the session if the neighbor does not advertise a BGP role. Requires `role`. |
| `export` <span class="type">[ExportPolicy](#exportpolicy)</span> | Top-level `export` | Specifies the export policy for the neighbor. Replaces the top-level `export` statement. |
| `graceful_restart` <span class="type">[GracefulRestart](#gracefulrestart)</span> | Top-level `graceful_restart` | Specifies graceful restart for the neighbor. Each statement that is set replaces the corresponding top-level statement. |
| `route_reflector_client` <span class="type">bool</span> | `false` | Makes the IBGP neighbor a route reflector client: Ferrix advertises to it the routes learned from other IBGP neighbors, and advertises its routes to them. Permitted only for IBGP neighbors. See [Route Reflection](@/docs/guides/passing-routes-on.md#route-reflection). |
| `export_limits` <span class="type">[ExportLimits](#exportlimits)</span> | Top-level `export_limits` | Specifies limits on the routes advertised to the neighbor. Replaces the top-level `export_limits` statement. An empty statement removes the limits for the neighbor. Applies only if the neighbor's role is `PROVIDER`, `PEER`, or `ROUTE_SERVER`, or if it is an EBGP neighbor without a role. |
| `groups` <span class="type">uint32, repeated</span> | None | Specifies the groups of the session, by number, for control communities that name sessions by group. Use the same numbers on all routers in the local AS. Range: 1 through 4,294,967,295. See [Session Groups](@/docs/guides/passing-routes-on.md#session-groups). |
## TcpEndpoint
@@ -96,7 +98,7 @@ accepted. See [Import Policy](@/docs/guides/import-policy.md).
| Statement | Default | Description |
|---|---|---|
| `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`. |
| `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_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. |
@@ -153,6 +155,17 @@ Statements that determine which routes are advertised to a neighbor. See
| `remove_communities` <span class="type">string, repeated</span> | None | Specifies patterns of communities removed from advertised routes. Applied before communities are added. |
| `as_path_prepend` <span class="type">uint32</span> | 0 | Specifies the number of additional times the local AS number is prepended to the AS path. Range: 0 through 16. |
## ExportLimits
Limits on the routes advertised to a neighbor. The limits apply regardless
of the export policy, BGP roles, and communities of the routes. See
[Export Limits](@/docs/guides/passing-routes-on.md#export-limits).
| Statement | Default | Description |
|---|---|---|
| `prefixes` <span class="type">[PrefixRange](#prefixrange), repeated</span> | All prefixes | Specifies the prefixes that can be advertised to the neighbor. Ferrix does not advertise learned or originated routes for other prefixes. |
| `max_prefixes` <span class="type">uint32</span> | 0 (no limit) | Specifies the maximum number of routes advertised to the neighbor. If more routes are to be advertised, Ferrix advertises none of them, tears down the session with a Cease NOTIFICATION message (subcode Administrative Shutdown) that states the reason (RFC 9003), and places the neighbor in hold-down for its `import.hold_down_seconds`. |
## OriginatedRoute
A route that Ferrix originates.
+1
View File
@@ -31,6 +31,7 @@ alerting rules based on these metrics, see
| `bgp_peer_routes{peer}` | Gauge | Number of the neighbor's routes in the routing table. |
| `bgp_peer_max_prefixes{peer}` | Gauge | Prefix limit of the neighbor (`import.max_prefixes`), if configured. |
| `bgp_prefix_limit_exceeded_total{peer}` | Counter | Number of sessions torn down because the neighbor exceeded its prefix limit. |
| `bgp_export_limit_exceeded_total{peer}` | Counter | Number of sessions torn down because the routes to be advertised to the neighbor exceeded its export limit (`export_limits.max_prefixes`). |
| `bgp_routes_rejected_total{peer,reason}` | Counter | Number of routes rejected by the import policy, by neighbor and reason. For the reasons, see [Import Policy](@/docs/guides/import-policy.md#default-checks). |
| `bgp_routes_exported_total{change}` | Counter | Number of learned routes advertised (`announced`) to or withdrawn (`withdrawn`) from neighbors. |
| `bgp_local_routes` | Gauge | Number of originated routes. |
+11
View File
@@ -7,6 +7,17 @@ local_asn: 64500
# Advertise learned routes to all neighbors, as BGP roles permit.
export { learned_routes: true }
# Advertise to the providers only routes for the address space of the local
# AS and of the customer, and at most 20 routes. If more routes are to be
# advertised, Ferrix advertises none of them and tears down the session.
export_limits {
prefixes { prefix: "198.51.100.0/24" }
prefixes { prefix: "2001:db8:100::/40" max_length: 48 }
prefixes { prefix: "192.0.2.0/24" }
prefixes { prefix: "2001:db8:a00::/40" max_length: 48 }
max_prefixes: 20
}
# Customers can restrict the advertisement of their routes with the large
# communities 64500:0:P (not to AS P), 64500:1:P (only to AS P), and
# 64500:0:0 (to no neighbor), or the standard communities 0:P, 64500:P, and