What the parsers and writers of messages, attributes and capabilities
had each written for themselves is one module (wire.rs): the shape of a
parser's result and its error, reading a list to the end, checking that
nothing was left over, a non-empty list of fixed-size records (the
community attributes and CLUSTER_LIST, which now fail alike), an AS
number at the session's width, an AFI/SAFI pair, and a length written
before what it counts. open.rs loses its fail! macro and attributes.rs
its own copies of the same.
Tests for what had none: AS4_AGGREGATOR kept opaque and discarded when
malformed; IPv4 routes with IPv6 next hops (RFC 8950) in the property
tests; and what a part-level parser accepts re-encodes to what it read,
but for the fields the decoders normalise.
Nlri::wire_len had no callers; the mask of a prefix's last octet is one
function; a test's name is spelled right; a comment is on the test it
describes.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
What the code-health review found, and what a fuzz target written for it
found in its first seconds.
- A set of attributes with the same attribute twice is refused by the
routing table. Its readers (the selection key, the path, the next hop)
could each take a different one of the two. The decoder never lets one
through; the new fuzz target `attrs` found that the table would.
- bgp_peer_up{peer}, to alert on a peer going away, and bgp_build_info.
Every metric is there from the first scrape. Alerts for a peer down,
hijack attempts and kernel errors are in the README.
- `bgp --check config` validates a configuration without starting, and
`--version` says which build it is. CI runs the first on the example
configuration, which a test now also parses with its commented fields
put in; the example had to learn about the next hop check.
- Path attributes have a module of their own, packet::attributes,
re-exported from packet::message as before.
- policy::From, which shadowed the trait, is RouteSource. The server's
connection, dialer and fsm modules are private. Logging arguments live
in the binary; the server library no longer depends on clap.
- One is_unicast_link_local (the standard library's) instead of two of
our own; SelectionKey::origin(); one place where an Import is made.
- READMEs say what the daemon does today rather than what it did not do
a year ago. Documentation links are checked in CI. Unused dependencies
and bgp_web::serve are gone; the Docker build is --locked and does not
ship the fuzz corpus.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
The first part of RPKI origin validation (RFC 6811): the data and how it
gets here. Nothing uses it yet.
VrpTable holds the validated ROA payloads and says whether a route is
valid, invalid or not found. It is a binary trie in one allocation with
a short cut past the first sixteen levels; a lookup against 600,000
entries takes about 40 ns for random addresses. A table is built once
and not changed: when the data changes another takes its place.
rtr is the RPKI-to-Router protocol's PDUs (RFC 8210, and version 0 of
RFC 6810), as bytes and back, without I/O. Lengths are checked from the
header, before the rest of a PDU is waited for.
The client keeps the table in step with a validator's cache. It asks
for everything once and then for changes, when the cache notifies and
at the refresh interval. An answer takes effect only when it is complete
and consistent. Each update says which VRPs came or went, so that only
the routes they cover need looking at again. A cache that fails is
retried and the next one tried; the data is used meanwhile, and dropped
if no cache confirms it within the expire interval. A cache that speaks
only version 0 is spoken to in version 0.
Tests: the cases of RFC 6811, a property test of the trie against the
plain definition, round-trip and hostile-input properties for the
codec, a fuzz target, and the client against a scripted cache.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
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>
The work of the speaker-cleanup branch as one commit; the branch keeps
the step-by-step history.
Sessions
- The state machine is a pure function from events to actions, driven by
one task per connection. Idle, Connect and Active belong to a dialer
that keeps configured peers up with backoff.
- Capabilities are negotiated: address families, four-octet AS numbers
with AS_TRANS and AS4_PATH, route refresh, ADD-PATH per family and
direction.
- Collision detection per RFC 4271 section 6.8, by address and
identifier.
- Connections are taken only from configured peers, a few per address.
- A panic in a session is contained and answered with a Cease; a core
task that stops shuts the server down with a Cease to every peer.
- Writes that make no progress are given up (RFC 9687).
Codec
- Malformed UPDATEs are handled as RFC 7606 says: treat-as-withdraw,
attribute discard, or session reset, with the NOTIFICATION to send.
- Address families are open types; any can be named and ignored.
- Prefixes are fixed-size values, normalised, with path identifiers.
- Lengths are filled in after writing; one definition of each type code.
- Property tests and fuzz targets for every decoder.
Routing table
- Adj-RIBs-In and Loc-RIB in one sharded table that sessions write to
directly. Routes are sixteen bytes; attribute sets are interned in
wire form. Best-path selection per RFC 4271 section 9.1.2.2 with
deterministic MED.
- A journal per shard lets followers read changes at their own pace;
one that falls behind is resynchronised.
- On a RIPE RIS dump of 148 peers and 39.3M routes: loaded in 2.0 s on
12 cores, about 52 bytes a route.
Announcing
- Originated routes come from the configuration. Each session has its
own exporter with an Adj-RIB-Out, so a slow peer delays only itself.
- Routes to a peer in our own AS have an empty path and a LOCAL_PREF.
Operations
- HTTP status page, JSON API, event stream and Prometheus metrics.
- Benchmarks on synthetic and real tables.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>