A review of the session driver, the server loop, the dialer, the advertiser and the HTTP API, with the fixes. Bugs: - A peer that stopped reading held its session, and our routes, for as long as TCP kept retrying: a blocked write was awaited with no timer running. Writes now give up after the Send Hold Time of RFC 9687. - Routes we originate were sent to a peer in our own AS with our AS number on the path and no LOCAL_PREF, which such a peer refuses. They now go with an empty path and a LOCAL_PREF. - Collision detection knew a peer by its BGP Identifier alone. A neighbour with one session over IPv4 and one over IPv6 had the second closed as a collision, and any host that could connect could displace a peer's connection by claiming its identifier. A peer is now its address and identifier together. - The dialer reset its backoff whenever TCP connected, so a peer that accepts the connection and refuses the session was dialled every few seconds for ever. It resets only after a session was established. - A failure on our side (routes that cannot be stored) dropped the connection without a NOTIFICATION. It goes through the state machine and the peer is sent Cease. A queued message that cannot be encoded is logged and skipped instead of ending the session. - The accept loop retried at once after an error, spinning when the process was out of file descriptors. - Attribute sets no route used any more were dropped only when a peer went away. A task now does it every minute. - The advertiser subscribed to session events inside its task, after the dialers were started, and could miss the first session. - Shutdown waited without limit on a session whose queue was full. - /api/routes/received took any limit and built the reply on a runtime thread. The limit is capped and the work is done off those threads. - The default log level was TRACE, which logs every UPDATE. - IPv6 listeners are IPv6-only: listening on 0.0.0.0 and [::] fails on systems where the second would take IPv4 too, and IPv4 peers arriving under mapped addresses matched no configured peer. - A connection closed after a NOTIFICATION is kept for a moment so that the peer can read it. - The configuration accepted two peers with one name or one address, and a router id of 0.0.0.0. - An HTTP address that cannot be bound now stops the daemon at startup. Simplifications: - SessionConfig mixed what a server's sessions share with what is configured for one peer, and was cloned and patched field by field in two places. It is ServerContext, shared, and PeerSettings, made from a PeerConfig in one place. - The attributes of an UPDATE as they were on the wire travelled from the reader to the table through a field of the session, around the state machine. They go through it, with the message. - run_session returns how the session ended and whether it had been established; Ok no longer reads as "closed by peer". - Session is not generic over its reader. - BgpServerBuilder had nothing to build: BgpServer::bind takes the configuration. - One shutdown path, close_sessions, for the signal handler and for a failed server. - The advertiser's three copies of "send every route" are one. - PeerId moved from the rib crate, which did not use it, to the server. - Unused API removed or made private to the crate. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
63 lines
1.9 KiB
TOML
63 lines
1.9 KiB
TOML
[workspace]
|
|
default-members = ["bin/bgp"]
|
|
members = ["bin/*", "crates/*"]
|
|
# Fuzz targets need a nightly toolchain; see fuzz/README.md.
|
|
exclude = ["fuzz"]
|
|
resolver = "3"
|
|
|
|
[workspace.package]
|
|
authors = ["Rayhaan Jaufeerally <rayhaan+git@rayhaan.ch>"]
|
|
description = "A Border Gateway Protocol implementation"
|
|
edition = "2024"
|
|
homepage = "https://rayhaan.ch"
|
|
license = "Apache-2.0"
|
|
repository = "https://git.rayhaan.net/rayhaan/ferrix"
|
|
version = "0.0.2"
|
|
|
|
# Every crate opts in with `[lints] workspace = true`. CI turns warnings into
|
|
# errors, so a lint added here is enforced.
|
|
[workspace.lints.rust]
|
|
unsafe_code = "forbid"
|
|
|
|
[workspace.lints.clippy]
|
|
all = { level = "warn", priority = -1 }
|
|
dbg_macro = "warn"
|
|
todo = "warn"
|
|
unimplemented = "warn"
|
|
|
|
[workspace.dependencies]
|
|
# --- Our crates ---
|
|
bgp-packet = { path = "crates/packet" }
|
|
bgp-primitives = { path = "crates/primitives" }
|
|
bgp-rib = { path = "crates/rib" }
|
|
bgp-server = { path = "crates/server" }
|
|
bgp-web = { path = "crates/web" }
|
|
bgp-integration-tests = { path = "crates/integration-tests" }
|
|
bgp-bench = { path = "crates/bench" }
|
|
|
|
# --- General ---
|
|
bitfield = "0.19.0"
|
|
bytes = "1.10.1"
|
|
eyre = "0.6.12"
|
|
nom = "8.0.0"
|
|
serde = { version = "1.0.219", features = ["derive"] }
|
|
socket2 = "0.6"
|
|
serde_json = "1.0.140"
|
|
serde_repr = "0.1.20"
|
|
thiserror = "2.0.12"
|
|
tokio = { version = "1.44.2", features = ["full"] }
|
|
strum = { version = "0.27.1", features = ["derive"] }
|
|
tracing = "0.1.44"
|
|
tracing-subscriber = { version = "0.3.22", features = ["env-filter"] }
|
|
prometheus = "0.14.0"
|
|
proptest = "1.7"
|
|
protobuf = { package = "protobuf", version = "3.7.2" }
|
|
protobuf-codegen = { package = "protobuf-codegen", version = "3.7.2" }
|
|
clap = { version = "4.5.53", features = ["derive"] }
|
|
foldhash = "0.2"
|
|
futures = "0.3"
|
|
hashbrown = "0.17"
|
|
axum = "0.8.9"
|
|
tokio-stream = { version = "0.1.19", features = ["sync"] }
|
|
reqwest = { version = "0.13.5", default-features = false, features = ["json"] }
|