A configured peer could announce any prefix with any path, in any number, and all of it was stored and could be chosen as best. Each peer now has an import policy, and every route goes through it before it reaches the routing table. Checks that hold for any peer unless turned off for it: - no prefixes that are not routed on the Internet (private, loopback, link-local, documentation, multicast, reserved; for IPv6, anything outside global unicast); - nothing longer than /24 or /48, no default route, nothing shorter than /8 or /16; - no reserved, private or documentation AS number in the path; - a path that begins with the peer's own AS (turned off for a route server), and of at most 64 AS numbers. Always: a route with our own AS number in its path is refused. RFC 4271 requires this loop check and it was missing. Per peer, if configured: the prefixes it may announce, each with an optional longest more-specific, and the AS numbers its routes may originate from. This is the list generated from the routing registries. max_prefixes limits how many routes a peer may have. One more ends the session with Cease / Maximum Number of Prefixes Reached, and the peer is kept down for hold_down_seconds (ten minutes unless set): its connections are not taken and it is not dialled. Refused routes do not count towards the limit. A refused route is withdrawn rather than ignored, so that it replaces whatever the peer had announced for it before. On a session with two-octet AS numbers the path is judged with its AS4_PATH merged in, so that the real AS numbers are what is checked. Peers that are not configured (accept_unconfigured_peers, for test networks) get a policy that takes everything but loops. Refusals are counted in bgp_routes_rejected_total by reason. The status page and /api/peers show, for each session, how many routes the peer has and how many were refused. The server warns at startup about peers with no limit. The prefix check costs about 4 ns a route with the default policy; the end-to-end benchmark is unchanged. Also: the benchmark's server AS is one that is in none of its generated paths, and a table test that depended on the hasher's seed is fixed. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
BGP
This crate provides the bgp binary, which runs the Border Gateway Protocol
daemon together with its read-only HTTP status interface.
cargo run -- path/to/config.textpb
The configuration format is defined in crates/server/proto/config.proto;
testdata/server.textpb is a worked example. The daemon is configured
entirely through that file: peers, and the routes it originates
(originate { prefix: ... }). There is no way to change either at runtime.
When http_endpoint is set the binary also serves:
| Path | Purpose |
|---|---|
GET /statusz |
Status page: configuration, peers and session state, originated and received routes, live event log |
GET /api/status |
Router ID, ASN, uptime and table sizes |
GET /api/config |
The configuration file as read at startup |
GET /api/peers |
Configured peers and established sessions |
GET /api/routes/local |
Originated routes |
GET /api/routes/received |
Adj-RIB-In contents |
GET /api/events |
Server-sent events for session and route changes |
GET /metricz |
Prometheus metrics |
Everything under it is read-only and unauthenticated. Bind it to loopback or put it behind a reverse proxy.
What it does today
It establishes sessions (both dialling configured peers and accepting connections, with RFC 4271 collision detection), announces its originated routes to every peer with itself as next hop, and records what peers send in an Adj-RIB-In. It does not select best paths, re-advertise received routes or program a forwarding table, and it has no import or export policy.
Who may connect
Connections are taken only from the addresses of configured peers, and from
each of those only a few at a time. Anything else is closed unread and
counted in bgp_connections_refused_total. accept_unconfigured_peers: true
lets any host open a session as any AS; it exists for tests on a closed
network.
What is taken from a peer
Every route a peer announces is judged before it goes into the routing
table (import in a peer's configuration; see config.proto):
- Refused for any peer unless turned off: prefixes that are not routed on the Internet (private, documentation, multicast, reserved), prefixes longer than /24 or /48, a default route, paths with a private or reserved AS number, and paths that do not begin with the peer's own AS.
- Always refused: a path with our own AS number in it.
- If configured: a list of the prefixes the peer may announce and of the AS numbers its routes may originate from.
max_prefixes: one route more ends the session with Cease / Maximum Number of Prefixes Reached and keeps the peer down for ten minutes.
A refused route is dropped, along with the route it would have replaced.
bgp_routes_rejected_total counts them by reason, and the status page shows
how many routes each peer has and how many were refused.
Trying it without a router
fake_peer is a minimal BGP speaker that connects to the daemon, announces a
prefix, prints every UPDATE it receives and, with --echo, re-announces each
received prefix back with its own ASN prepended (dropping NO_EXPORT routes).
The prefix it announces shows up in the received table. The ones it echoes
have been through our AS already, so they are refused as loops and counted
in the peer's refused routes:
cargo run -- testdata/server.textpb
cargo run -p bgp-integration-tests --example fake_peer -- \
--server 127.0.0.1:179 --asn 65001 --announce 203.0.113.0/24 --echo
Please refer to the other implementation crates in this repository where most of the actual implementation lives.
Warning: This is still very much a work in progress and is not yet ready for production use.