Rework the speaker: sans-I/O FSM, sharded table, RFC 7606, ADD-PATH
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>
This commit is contained in:
1 parent
1ec60255ea
commit
630c175918
138 files changed
+21650
-2925
No files matched your search
@@ -0,0 +1,13 @@
|
||||
//! The configuration file. It is written by the operator and not by a peer,
|
||||
//! but a typo in it should be an error message, never a panic at startup.
|
||||
#![no_main]
|
||||
|
||||
use bgp_server::config::Config;
|
||||
use bgp_server::proto::config::BgpConfig;
|
||||
use libfuzzer_sys::fuzz_target;
|
||||
|
||||
fuzz_target!(|text: &str| {
|
||||
if let Ok(proto) = protobuf::text_format::parse_from_str::<BgpConfig>(text) {
|
||||
let _ = Config::from_proto(proto);
|
||||
}
|
||||
});
|
||||
@@ -0,0 +1,12 @@
|
||||
//! Raw bytes, as they come off a TCP connection. The first byte picks what the
|
||||
//! session settled: see `bgp_fuzz::Session`.
|
||||
#![no_main]
|
||||
|
||||
use libfuzzer_sys::fuzz_target;
|
||||
|
||||
fuzz_target!(|data: &[u8]| {
|
||||
let Some((selector, wire)) = data.split_first() else {
|
||||
return;
|
||||
};
|
||||
bgp_fuzz::decode_and_check(bgp_fuzz::Session::from_bits(*selector), wire);
|
||||
});
|
||||
@@ -0,0 +1,25 @@
|
||||
//! A message body behind a correct header, so that the fuzzer spends its
|
||||
//! time in the OPEN, UPDATE and NOTIFICATION parsers and not on guessing
|
||||
//! the marker and length. The first byte picks the message type and what
|
||||
//! the session settled: see `bgp_fuzz::Session`.
|
||||
#![no_main]
|
||||
|
||||
use libfuzzer_sys::fuzz_target;
|
||||
|
||||
/// The longest body that fits a BGP message (4096 less the 19-byte header).
|
||||
const MAX_BODY: usize = 4096 - 19;
|
||||
|
||||
fuzz_target!(|data: &[u8]| {
|
||||
let Some((selector, body)) = data.split_first() else {
|
||||
return;
|
||||
};
|
||||
let body = &body[..body.len().min(MAX_BODY)];
|
||||
// Bits 1 to 3; the others pick the session.
|
||||
let msg_type = 1 + ((selector >> 1) & 0x07) % 5;
|
||||
|
||||
let mut wire = vec![0xff; 16];
|
||||
wire.extend_from_slice(&((19 + body.len()) as u16).to_be_bytes());
|
||||
wire.push(msg_type);
|
||||
wire.extend_from_slice(body);
|
||||
bgp_fuzz::decode_and_check(bgp_fuzz::Session::from_bits(*selector), &wire);
|
||||
});
|
||||
@@ -0,0 +1,12 @@
|
||||
//! Prefixes as text, as they appear in the configuration file.
|
||||
#![no_main]
|
||||
|
||||
use bgp_primitives::ip_prefix::IpPrefix;
|
||||
use libfuzzer_sys::fuzz_target;
|
||||
|
||||
fuzz_target!(|text: &str| {
|
||||
if let Ok(prefix) = IpPrefix::try_from(text) {
|
||||
let shown = prefix.to_string();
|
||||
assert_eq!(IpPrefix::try_from(shown.as_str()), Ok(prefix));
|
||||
}
|
||||
});
|
||||
Reference in new issue
Block a user