All notable changes to rustbgpd will be documented in this file.
Format based on Keep a Changelog. This project follows Semantic Versioning.
- Dynamic prefix-based neighbors. Accept inbound sessions from any peer
matching a configured IP prefix range (
[[dynamic_neighbors]]), with peer group inheritance,remote_asn = 0(accept any ASN from OPEN), automatic peer slot management, and config validation.ListDynamicNeighborsgRPC RPC andis_dynamicflag in peer state. - RPKI/ASPA import policy validation. Import policy can now apply
origin-validation and ASPA upstream-path-verification results to filter or
tag incoming routes. Transport sessions receive the current VRP + ASPA
tables via
tokio::sync::watchchannel. - FlowSpec interop test (M22). Injection, distribution to FRR, withdrawal.
- GoBGP interop test (M23). Bidirectional route exchange against GoBGP 4.3.0.
- BMP collector interop test (M24). Python receiver validates message types.
- TCP MD5 + GTSM interop test (M25). Two FRR peers with transport security.
- Cease subcode interop test (M26). Max-prefix Cease/1 handling with FRR.
- ASPA/RTR v2 cache interop test (M27). Validates RTR v2 session setup and ASPA data delivery against a Python RTR v2 mock server.
- Dynamic neighbor interop test (M28). FRR auto-accepted via
[[dynamic_neighbors]]prefix range, auto-removed on disconnect. - Shared test library.
test-lib.shwith pre-flight checks, timestamps, common helpers — deduplicated across all interop scripts. - Duplicate BMP collector detection. Config validation rejects duplicate collector addresses.
- Policy helper deduplication. Extracted shared
policy_helpers.rsfrompolicy_service.rsandpeer_group_service.rs.
- Peer slot leak on dynamic neighbor start failure. Dynamic peers are only
inserted and counted after
handle.start()succeeds, preventing slot leaks on failed inbound starts. - Config validation hardening.
remote_asn = 0rejected for static neighbors; impossible prefix lengths rejected for dynamic ranges. - BMP duplicate detection canonicalized through
SocketAddr. Previously used string comparison, which could miss equivalent addresses. - M11 GR test flake. Replaced
sleep 10with EoR polling loop. - M22 FlowSpec test race. Fixed race condition in withdrawal rule 2 check.
- M21 RPKI interop switched from GoRTR to StayRTR.
- Best-path explain. New
RibService.ExplainBestPathRPC andrustbgpctl rib --prefix X --explainshow all candidates for a prefix with the decisive comparison reason (BestPathReasonenum) for each non-winner. Winner is excluded from the candidate list and returned in a dedicatedbestfield.--explainis rejected with a clear error when used with rib subcommands other than the default best-routes view. - Birdwatcher-compatible looking glass API. Optional axum HTTP server
exposing
/status,/protocols/bgp,/routes/protocol/{id}, and/routes/peer/{peer}. Response shapes match birdwatcher field names for looking glass frontend consumption (Alice-LG, etc.). Configured via[global.telemetry.looking_glass]. Not yet integration-tested against Alice-LG. - Optional Prometheus metrics listener.
prometheus_addris now optional. If absent, no metrics HTTP server is started; metrics are still collected for gRPC health and internal counters. - RTR/RPKI cache interop test (M21). Containerlab scenario with StayRTR serving static VRPs. 12 assertions covering RTR session establishment, VRP delivery, and origin validation (Valid/Invalid/NotFound) visible via gRPC.
- RTR v2→v1 version fallback against real caches. GoRTR and StayRTR disconnect on unsupported protocol versions instead of sending RFC 8210 error code 4. The RTR client now also falls back to v1 when the connection is closed without completing a handshake, and preserves the v1 downgrade across reconnection attempts.
- ASPA upstream path verification. Validates AS_PATH
customer-provider relationships to detect route leaks. RTR v2 support with
automatic v1 fallback, ASPA PDU type 11 codec,
AspaTablewith multi-record merge, upstream verification algorithm per draft-ietf-sidrops-aspa-verification. Best-path step 0.7 (Valid > Unknown > Invalid) between RPKI and LOCAL_PREF.match_aspa_validationin import and export policy.aspa_stateexposed in gRPC Route responses. Import validation is best-effort against the current snapshot — see validation snapshot delivery. Downstream verification deferred. (ADR-0049)
- Added
AspaValidationenum (Valid,Invalid,Unknown) withDisplay,FromStr,Default,Hash, andEqimplementations.
- Config reload dry-run (
rustbgpd --diff). Preview what a SIGHUP reload would change before sending it. Output grouped into three sections: reload-applied (neighbor add/remove/modify), restart-required (global/rpki/bmp/mrt), and informational (peer-group/policy changes not yet reconciled by current SIGHUP path). Supports--jsonfor scripting withhas_actionable_changes,has_informational_changes, andhas_any_changesflags. Exit code 1 = actionable changes found, 0 = no actionable changes. - Multi-implementation feature comparison. New
docs/COMPARISON.mdcompares rustbgpd against FRR, BIRD, GoBGP, and OpenBGPd across address families, core protocol, policy, security, observability, API, and operations.
- Wire crate version decoupled from workspace.
rustbgpd-wirenow has its own explicit version incrates/wire/Cargo.tomlinstead of inheriting from the workspace. All internal crates are markedpublish = false. Wire crate is only published whencrates/wire/actually changes. - Roadmap aligned with market research. Added "Next Up" section with ASPA verification, birdwatcher-compatible looking glass API, and best-path explain. Built-in looking glass replaced by API-first approach. Deprioritized EVPN/VPN, YANG/NETCONF.
- Reliable GR / route-refresh control delivery. Inbound
EndOfRib,RouteRefreshRequest,BeginRouteRefresh,EndRouteRefresh, and transport lifecycle updates (PeerUp,SetPeerPolicyContext,PeerDown,PeerGracefulRestart) now use reliablesend(...).awaitdelivery to the RIB instead of lossytry_send, eliminating dropped control messages when the RIB channel is full. - GR timer vs buffered
EoRrace. Before firing GR, LLGR, or refresh timer sweeps, the RIB manager now drains already-buffered main-channel updates so a bufferedEndOfRibis processed before stale routes are swept. - Injection API zero-value
local_pref/MED.AddPathRequestlocal_prefandmedare now presence-based optional fields, so valid zero values can be injected instead of being silently treated as unset. - Peer-group / policy API validation parity. Peer-group family strings and
remove_private_asvalues now reuse the dynamic-neighbor validation helpers, andPolicyService/PeerGroupServicereject invalid policy action strings at the API boundary. - AS_PATH segment encode overflow.
AS_SEQUENCEandAS_SETsegments longer than 255 ASNs are now split into multiple wire segments during encode instead of silently truncating the length viau8wraparound. - Adj-RIB-In teardown cleanup.
PeerDownnow removes empty per-peerAdjRibInentries entirely, and unicast withdraw chunks trigger intern-table garbage collection to prevent orphaned attribute intern entries from growing without bound under churn. - Route explain global export policy fallback.
ExplainAdvertisedRoutenow uses the per-peer → global export policy fallback (viaexport_policy_for()), matching the actual distribution path. Previously it only checked per-peer export policy, returning incorrect results for peers using the global default. - Drain pending route batches on PeerDown/GR.
handle_peer_down()andhandle_peer_graceful_restart()now drain any in-progress chunked route batches for the departing peer. Previously, pending chunks could be processed after the peer's RIB was cleared, re-inserting ghost routes. - FSM double-increment of connect_retry_counter on DecodeError. The
DecodeErrorhandler in Connect, Active, OpenSent, OpenConfirm, and Established states incremented the counter before callingenter_idle_with_notification()/enter_idle_silent(), which also increments — resulting in double-counting. Removed the redundant increment from all five handlers. - FlowSpec routes not stale-marked or swept during GR/LLGR. Graceful
restart and LLGR now correctly mark, sweep, promote, and clear stale
flags on FlowSpec routes alongside unicast routes. Added
promote_to_llgr_stale_flowspec(),sweep_llgr_stale_flowspec(),clear_llgr_stale_flowspec(), andsweep_stale_flowspec_family()toAdjRibIn. End-of-RIB handling now clears FlowSpec stale/LLGR-stale flags for the completed family, recomputes/distributes affected FlowSpec routes, and removes locally injectedLLGR_STALEcommunities when the route returns to fresh state. - IPv6 export next-hop rewrite locked down. Export policy
set_next_hop = "<ipv6>"is now covered by transport and explain regression tests, confirming the MP_REACH export path andExplainAdvertisedRoutereport the same effective IPv6 next hop. - LLGR_STALE stripped for non-LLGR peers. Outbound transport now removes
the
LLGR_STALEcommunity when the destination peer did not negotiate LLGR for that family, matching RFC 9494 §4.6. LLGR-capable peers still receive the community unchanged.
- Rustc-style config error diagnostics. Config validation errors now show
the offending TOML source line with column markers and underlined spans, like
rustcerror messages. Usestoml_edit::ImDocumentfor span-preserving key/value lookup on semantic errors, andtoml::de::Error::span()for parse errors. Falls back to plain text when no span is available. - Minimal export route explain. New
RibService.ExplainAdvertisedRouteandrustbgpctl rib advertised <peer> --prefix <CIDR> --explainexplain whether the current best route for one prefix would be advertised to one peer, including decisive reasons and any export modifications. This v1 scope is export-only. Import explain and named policy/statement attribution are not yet implemented. - Per-peer log level filtering. New
log_levelfield on[[neighbors]]and[peer_groups.<name>]overrides the globalRUST_LOGlevel for individual peers. Each peer session runs inside a tracing span withpeer_addr,remote_asn, andpeer_groupfields, enabling targeted filtering via config (log_level = "debug") or environment (RUST_LOG=info,peer{peer_addr=10.0.0.1}=debug). - gRPC priority query channel. Read-only gRPC queries (neighbor list, RIB queries, control RPCs) now use a dedicated channel with bounded fair scheduling (8 queries per route chunk). This prevents management API stalls during bulk route loading — previously queries could block for 60+ seconds behind thousands of queued route updates.
- Chunked RIB processing. Large
RoutesReceivedbatches are now split into 1024-prefix chunks with per-chunk recompute/distribute. Between chunks, bounded query servicing and timer checks proceed. Main channel ordering is preserved: control messages (EoR, PeerDown) cannot overtake unfinished route work. At 200k prefixes, convergence improved from 103s to 74s (28%). - AdjRibIn pre-sizing. New
AdjRibIn::with_capacity()constructor uses first-batch size hints to pre-allocate routes, prefix index, and intern table, reducing HashMap rehash stalls during bulk insert. - Outbound UPDATE construction optimization. The export hot path now uses
try_reserve-based enqueue/commit to avoid clone-before-send overhead, pointer fast-paths in route equality, hash-indexed attribute grouping, and per-call prepared-attribute caching insend_route_update(). This reduces per-route allocation and repeated attribute rewrites during large outbound batches without changing wire behavior. - AdjRibOut secondary prefix index.
AdjRibOutnow maintains aHashMap<Prefix, SmallVec<[u32; 1]>>secondary index for O(1) per-prefix path ID lookup. Previously,path_ids_for_prefix()anditer_prefix()scanned the entire route HashMap — O(N) per call, called per-prefix during outbound distribution. At 200k routes this caused a 560x cost blowup (1.4 µs/prefix early → 780 µs/prefix late). With the index, 200k-prefix convergence dropped from 71s to 12s (5.9x improvement).
- ConnectRetryTimer default reduced to 5 seconds. The BGP connect retry interval now defaults to 5s (down from 30s), reducing session establishment delay when the first outbound connection attempt fails. The exponential backoff progression is now 5→10→20→40→80→160→300s.
- Policy CRUD via gRPC. New
PolicyServiceadds named policy definition CRUD plus global/per-neighbor chain assignment at runtime. Successful mutations hot-apply to the running daemon, persist back to TOML, and reuse the existing named-policy / chain model from ADR-0036. Import-chain changes affect future inbound UPDATE processing; operators useSoftResetInwhen they want existing Adj-RIB-In state re-evaluated. Export-chain changes trigger immediate outbound recomputation. - Peer groups via gRPC. New
PeerGroupServiceadds full-replace peer-group CRUD plus neighbor membership assignment. Effective peer config is resolved through the existing config/peer-manager boundary and persisted back to TOML. - Peer-aware policy matching. Policy statements now support
match_neighbor_set,match_route_type,match_local_pref_ge/le, andmatch_med_ge/le. Neighbor sets are managed underPolicyService, persist to TOML, and evaluate against the current policy peer context on both import and export. - Exact next-hop policy matching. Policy statements now support
match_next_hopfor unicast routes on both import and export. The field is available in TOML config and the gRPCPolicyService, persists through config snapshots, and evaluates as exact IPv4/IPv6 equality. --versionand--checkflags. Bothrustbgpd --versionandrustbgpctl --versionnow print the version.rustbgpd --check config.tomlvalidates config and exits without starting the daemon.- Shell completions.
rustbgpctl completions {bash,zsh,fish}generates shell completions. Pre-generated files shipped inexamples/completions/. - Startup banner. The daemon now prints a human-friendly topology summary on startup: ASN, router-id, peer counts by type, peer groups, named policies, listener endpoints, and optional subsystems (RPKI, BMP, MRT).
- FlowSpec route-server transparency.
route_server_client = truenow skips automatic eBGP AS_PATH prepend on FlowSpec export too, matching transparent unicast behavior. FlowSpec still has noNEXT_HOPfield on the wire. - Colored CLI output.
rustbgpctlnow uses colored session states (green=Established, yellow=OpenSent/Connect/OpenConfirm, red=Idle/Active), colored best-path markers, colored health/event output, human-readable uptimes ("2d 4h 12m"), and dynamically aligned table columns. Colors auto-disable when piped. Use--no-colororNO_COLOR=1to force plain output. - Live TUI dashboard.
rustbgpctl toplaunches a terminal UI (ratatui) showing sessions, prefix counts, message rates, RPKI VRP counts, and streaming route events — all updating live. Peer table with sort (cycle withs/S), detail view (Enter), toggleable events panel (e), and help overlay (h). Configurable poll interval (-i). Thinkhtopfor BGP. - Docker Compose quick-start. New
examples/docker-compose/spins up rustbgpd peered with FRR (4 IPv4 + 3 IPv6 sample prefixes) in a singledocker compose up -d. gRPC exposed on localhost:50051 for immediaterustbgpctluse from the host. - Per-listener gRPC access mode. Each configured gRPC listener (TCP or UDS)
can independently set
access_mode = "read_write"(default) or"read_only". Read-only listeners allow query and watch RPCs but reject all mutating RPCs (neighbor add/delete, route injection, policy changes, peer-group changes, shutdown, MRT trigger) withPERMISSION_DENIED. Intended for monitoring or dashboard listeners that should not expose control-plane writes. - CLI gRPC integration tests.
rustbgpctlcommands now have mock-server integration tests over both TCP (with bearer token auth) and Unix domain sockets, covering health, global, neighbor add, and soft-reset RPC paths. - BMP transport-path tests. Session-to-BMP emission points (PeerUp, PeerDown, RouteMonitoring) are now covered by transport crate tests.
- M17-M20 interop tests. Four new containerlab test suites validated against FRR 10.3.1: Add-Path multi-path send (15 assertions), Extended Next-Hop dual-stack (9), Transparent Route Server with NH preservation and AS_PATH transparency (13), Private AS Removal in remove/all/replace modes (22). Total: 59 new interop assertions.
- CLI
NO_COLORhandling.rustbgpctlnow treatsNO_COLORas a presence-based runtime override instead of asking clap to parse it as a boolean, so environments withNO_COLOR=1no longer break argument parsing. - CLI uptime display. Zero-second uptimes now render as
00:00:00instead ofnever. - Use-case command examples.
docs/USE_CASES.mdnow uses the actualrustbgpctlcommand surface and a gRPC example for peer-group assignment.
First public alpha release. No protocol changes from v0.4.1 — this release focuses on operator experience, documentation accuracy, and release hygiene.
-
Operations guide (
docs/OPERATIONS.md). Covers configuration reload (SIGHUP), upgrade procedure, state persistence, failure modes for gRPC / RPKI / BMP / MRT, key metrics and log messages, session debugging, and common operational tasks. -
Example configs. Minimal single-peer config (
examples/minimal/) and IXP route-server config with RPKI, Add-Path, and policy chains (examples/route-server/). -
systemd unit (
examples/systemd/rustbgpd.service). Hardened withProtectSystem=strict,NoNewPrivileges,CAP_NET_BIND_SERVICE, andExecReloadfor SIGHUP. -
Release checklist (
docs/RELEASE_CHECKLIST.md). Pre-release smoke matrix covering CLI, UDS, token auth, interop, and Docker. -
Container image CI (
.github/workflows/container.yml). Publishes to GHCR with semver tags on version tag push. -
Issue and PR templates. Bug report, feature request, and pull request templates.
-
CI rustdoc gate.
cargo doc --workspace --no-depswith-D warnings. -
Project status statement in README. Explicit alpha expectations for config/API stability, supported OS, and target use case.
-
CLI examples in docs. All
rustbgpctlexamples now match actual CLI syntax (neighbor <addr> add --asn,neighbor <addr> softreset,rib received <addr>, etc.). -
Build command. README, CONTRIBUTING.md, Dockerfile, and release checklist now use
cargo build --workspace --releaseto build bothrustbgpdandrustbgpctl. -
systemd config persistence. Unit file now includes
/etc/rustbgpdinReadWritePathsso gRPCAddNeighbor/DeleteNeighborcan persist to the config file underProtectSystem=strict. -
Non-root quickstart. Minimal example uses
runtime_state_dir = "/tmp/rustbgpd"so the quickstart works without root. README documentsRUSTBGPD_ADDRenv var for matching UDS path. -
RTR reconnect docs. Operations guide now correctly says "fixed retry_interval" instead of "exponential backoff" for RTR client reconnect.
-
Dockerfile. Now includes
rustbgpctlin the image and uses a production-friendly default CMD. Interop clab topologies updated with explicitcmd: sleep infinityoverride.
-
Secure-by-default gRPC listeners. The daemon now defaults to a local Unix domain socket at
/var/lib/rustbgpd/grpc.sockinstead of loopback TCP. TCP gRPC listeners are now explicit config via[global.telemetry.grpc_tcp], local UDS can be configured via[global.telemetry.grpc_uds], and both may run concurrently. Optional per-listener bearer-token authentication is available viatoken_file.rustbgpctlnow supportsunix:///...endpoints and--token-file/RUSTBGPD_TOKEN_FILE. Security docs and the Envoy mTLS example were updated to reflect the new operator posture. -
Interop tests M13–M16. Four new containerlab test suites: policy engine with chain accumulation (M13, 15 assertions), route reflector with ORIGINATOR_ID/CLUSTER_LIST validation (M14, 14 assertions), route refresh via SoftResetIn (M15, 10 assertions), and LLGR GR→stale transition (M16, 8 assertions). All 10 interop suites now pass (130 total assertions).
-
Route reflector config wiring.
route_reflector_clientwas not copied from the neighbor config toTransportConfig, so route reflection was non-functional when configured via TOML (gRPC dynamic peers were unaffected). -
M10 IPv6 interop test. FRR static IPv6 routes via
fd00::2were unreachable in the container, preventing IPv6 prefix advertisement. Changed toNull0blackhole routes.
-
Enhanced Route Refresh (RFC 7313). Capability code 70 is now advertised alongside RFC 2918 Route Refresh. ROUTE-REFRESH message type 5 now models subtype
0/1/2(Normal/BoRR/EoRR).SoftResetIngains family-scoped replacement semantics for ERR-capable peers: inboundBoRRmarks current routes refresh-stale, refreshed announcements/withdrawals clear replaced entries, and inboundEoRRsweeps unreplaced state. Active ERR windows now also have a fixed 5-minute timeout, which performs the same unreplaced-state sweep ifEoRRnever arrives. Outbound route-refresh responses emitBoRR -> routes -> EoRRfor ERR peers while preserving existingroutes -> EndOfRibbehavior for RFC 2918-only peers. (ADR-0038) -
Extended Next Hop (RFC 8950). Capability code 5 is now advertised automatically for dual-stack unicast peers. IPv4 unicast NLRI can be received and advertised via
MP_REACH_NLRI/MP_UNREACH_NLRIwith an IPv6 next hop. Existing peers that do not negotiate RFC 8950 keep the legacy body-NLRI +NEXT_HOPencoding. Add-Path for IPv4 unicast remains compatible in both legacy and RFC 8950 MP-encoding modes. (ADR-0037) -
Policy chaining + named policies (ADR-0036). Named policy definitions in TOML with configurable
default_action(permit or deny). Policy chains reference named policies by name in ordered sequences. GoBGP-style chain semantics: permit accumulates modifications and continues, deny stops immediately, implicit permit after all policies. Backward compatible — existing inlineimport_policy/export_policyentries still work.RouteModifications::merge_from()accumulates across chain steps (scalars: later wins; lists accumulate, with later conflicting add/remove operations winning). New TOML syntax:[policy.definitions.*],import_chain/export_chainon global and per-neighbor. -
Admin shutdown communication (RFC 8203). DisableNeighbor gRPC reason field is now propagated through to the Cease/2 (Administrative Shutdown) NOTIFICATION data as a 1-byte length + UTF-8 string (max 128 bytes). Inbound Cease/2 and Cease/4 NOTIFICATIONs with shutdown communication are decoded and logged. Wire helpers:
encode_shutdown_communication()anddecode_shutdown_communication()in the notification module. -
Notification GR (RFC 8538). GR capability now advertises the N-bit (notification support). NOTIFICATION-triggered teardown now preserves routes only when both sides negotiated N-bit support. Cease/Hard Reset (subcode 9) sent or received bypasses Graceful Restart, forcing immediate route purge instead of stale preservation. Completes the GR story alongside ADR-0024 (helper), ADR-0040 (restarting speaker), and ADR-0042 (LLGR). (ADR-0046)
-
AS_PATH length matching in policy. New
match_as_path_length_geandmatch_as_path_length_lefields on policy statements for inclusive range-based AS_PATH length filtering. Fields can be used independently or together (AND logic), and work standalone or combined with existing match criteria (prefix, community, regex, RPKI).AS_SETcounts as 1 per RFC 4271. -
Private AS removal. New per-neighbor
remove_private_asconfig strips private ASNs (64512–65534, 4200000000–4294967294) from AS_PATH before eBGP advertisement. Three modes:"remove"(entire path must be private),"all"(unconditional),"replace"(substitute local ASN). Applied before local ASN prepend in both unicast and FlowSpec outbound paths. eBGP only; route-server clients skip. (ADR-0045) -
Transparent route server mode. Static neighbor config now supports
route_server_client = truefor eBGP peers. Outbound unicast advertisements to route-server clients preserve the original next hop and skip the automatic local-AS prepend normally applied on eBGP export. Explicit export-policy next-hop overrides still win. RFC 8950 IPv4 over IPv6 next-hop and IPv6 unicast both honor the same transparent behavior. FlowSpec transparency remains deferred. (ADR-0039) -
Graceful Restart restarting speaker (minimal mode). Static peers now advertise GR
restart_state = trueafter a coordinated daemon restart when a persisted marker file is present inglobal.runtime_state_dir. This is an honest helper-to-speaker bridge only:forwarding_preservedremains false for all families, and dynamic gRPC-added peers do not participate in the restart window. (ADR-0040) -
FlowSpec fuzz target. New
decode_flowspecfuzz target exercises FlowSpec NLRI decoding directly with both IPv4 and IPv6 AFIs, complementing the existingdecode_messageanddecode_updatetargets. -
BMP exporter (RFC 7854). New
crates/bmp/crate implementing the BGP Monitoring Protocol. Unidirectional streaming of BGP state to external collectors (OpenBMP, pmacct). Encodes Initiation, Peer Up, Peer Down, Route Monitoring, Stats Report, and Termination messages. Per-collector async TCP client with reconnect/backoff. Fan-out manager distributes encoded BMP messages to all configured collectors. Raw BGP PDU capture in transport layer (ReadBuffer::try_decode()returns(Message, Bytes)) enables byte-perfect Route Monitoring and Peer Up messages. TOML config:[bmp]section with[[bmp.collectors]]. Near-zero overhead when BMP is not configured (raw frame capture usesBytesrefcount clones, not data copies). (ADR-0041) -
Periodic BMP Stats Report.
PeerManagernow emits periodic per-peer BMP Statistics Report messages every 60 seconds (RFC 7854 type 7: routes in Adj-RIB-In), using currentprefix_countfrom transport session state. -
CLI tool (
rustbgpctl). Newcrates/cli/crate providing a command-line interface wrapping the gRPC API. Client-only proto codegen — no dependency on internal crates. Commands:global,neighbor(list/show/add/delete/enable/ disable/softreset),rib(best/received/advertised/add/delete),watch(streaming),flowspec(list/add/delete),health,metrics,shutdown. Global--jsonflag for structured output on all commands. Global--addrflag withRUSTBGPD_ADDRenv var support. -
Long-Lived Graceful Restart (RFC 9494). Two-phase GR timer: when the GR restart timer expires, routes for LLGR-negotiated families are promoted to LLGR-stale (with
LLGR_STALEcommunity, well-known 0xFFFF0006) instead of being purged. Routes carryingNO_LLGR(0xFFFF0007) are purged at the GR-to-LLGR transition. Effective stale time ismin(local llgr_stale_time, peer per-family minimum). Three-tier best-path ranking: fresh > GR-stale > LLGR-stale at step 0 (before LOCAL_PREF). New capability code 71 with per-family 24-bit stale time. Config:llgr_stale_timeper neighbor (0 = disabled, default). EoR during LLGR clearsis_llgr_staleand removes locally- injectedLLGR_STALEcommunities. PeerUp during LLGR moves families back to GR phase. -
Config persistence + SIGHUP reload. Neighbor add/delete mutations via gRPC are now persisted back to the TOML config file via atomic write (temp file + rename).
ConfigPersistertask accepts mutations through a bounded channel. SendingSIGHUPto the daemon triggers a config reload:diff_neighbors()computes the delta,ReconcilePeersapplies per-peer add/delete operations. Global config changes are logged as warnings but require restart. Structured per-peer failure reporting on reconciliation. -
MRT dump export (RFC 6396). New
crates/mrt/crate implementingTABLE_DUMP_V2(type 13) periodic and on-demand RIB snapshots.MrtManagerruns a configurable interval timer and accepts on-demand triggers via the newTriggerMrtDumpgRPC RPC onControlService. Snapshots query Adj-RIB-In routes fromRibManagerviaQueryMrtSnapshot(no Loc-RIB overlay to avoid duplication). Peer metadata (peer_asn,peer_bgp_id) is tracked inRibManagerand retained during GR/LLGR transitions. Codec synthesizes next-hop attributes stripped by the MP-BGP architecture:NEXT_HOPfor IPv4,MP_REACH_NLRIfor IPv6, andMP_REACH_NLRIwithAfi::Ipv4for RFC 8950 IPv4-with-IPv6-NH routes. Add-Path subtypes 8/9 per RFC 8050.EncodeErrorenum for explicit length-overflow handling (no truncation). Atomic file writes with optional gzip compression (flate2). Collision-resistant filenames (seconds + nanoseconds). TOML config:[mrt]section withoutput_dir,dump_interval,compress, andfile_prefix. CLI:mrt-dumpsubcommand. (ADR-0044)
- Policy
RouteContextstruct. Policy evaluate functions now take a borrowedRouteContext<'a>instead of 7+ individual parameters, eliminating#[expect(clippy::too_many_arguments)]from all production policy code. Public API:rustbgpd_policy::RouteContext. RibManager::handle_update()extraction. The 615-line match dispatch is now a thin dispatcher delegating to focused handler methods indistribution.rs,peer_lifecycle.rs,route_refresh.rs, andgraceful_restart.rs. Structural refactor only.- Config and peer-session module splits.
src/config.rsis now organized assrc/config/submodules (mod.rs,schema.rs,parse.rs,validation.rs,tests.rs). The transport peer runtime is likewise split fromcrates/transport/src/session.rsintocrates/transport/src/session/submodules (mod.rs,fsm.rs,io.rs,inbound.rs,outbound.rs,commands.rs,tests.rs). This is a structural refactor only; behavior and public interfaces are unchanged. - RibManager submodule split. The 8,318-line
manager.rshas been split into 7 files undercrates/rib/src/manager/:mod.rs(893 lines, struct + event loop),distribution.rs(729 lines),peer_lifecycle.rs(193 lines),route_refresh.rs(333 lines),graceful_restart.rs(170 lines),helpers.rs(100 lines), andtests.rs(5,969 lines). Zero behavior change — pure refactor for reviewability.
-
Neighbor gRPC
remove_private_asparity.AddNeighbornow validates and appliesremove_private_as("", remove, all, replace) instead of silently forcing disabled mode for dynamic peers.ListNeighborsandGetNeighborStatenow return the activeremove_private_asmode from runtime peer state. -
Neighbor gRPC mutations are now fail-fast when persistence is unavailable.
AddNeighborandDeleteNeighborreserve config-persistence queue capacity before mutating runtime state. If the persistence channel is busy/closed, the RPC fails withINTERNALinstead of applying an unpersisted runtime change. -
SIGHUP reload no longer silently accepts partial reconcile failures.
ReconcilePeersnow returns structured per-peer failures; reload logs each failed operation and keeps the previous in-memory config snapshot when reconciliation is incomplete. -
LLGR_STALE community provenance preserved. Adj-RIB-In now tracks which
LLGR_STALEcommunities were injected locally during LLGR promotion and only removes those on stale clear/EoR. Peer-originatedLLGR_STALEcommunities are preserved. -
Neighbor duplicate detection uses canonical IP identity. Config validation now detects duplicates by parsed
IpAddr(e.g.,::1and0:0:0:0:0:0:0:1are treated as the same neighbor address). -
BMP Termination on coordinated shutdown. Main runtime now sends an explicit BMP shutdown control event, then drains BMP manager/client tasks with bounded waits so connected collectors receive BMP Termination (type 5, reason 0) before daemon exit.
-
BMP client write timeout. Per-collector TCP writes now use a 5-second timeout to avoid indefinite stalls on slow or wedged collectors.
-
CLI gRPC connect timeout.
rustbgpctlnow sets a 5-secondEndpoint::connect_timeout(...)to avoid hanging indefinitely when the daemon endpoint is unreachable. -
CLI FlowSpec DSCP validation.
mark-dscp=is now bounds-checked in the CLI (0..=63) and fails fast on invalid values before RPC submission. -
CLI prefix IP validation. Prefix parsing now validates address syntax (
IpAddr) instead of only slash-length bounds. -
sendable_familiesexcluded IPv6 for route-server clients. eBGP peers without a local IPv6 next-hop had IPv6 unicast filtered fromsendable_families, silently preventing IPv6 route advertisement toroute_server_clientpeers that preserve the original next-hop. Fixed by including route-server clients in the filter condition. -
BMP collector reconnect replay.
BmpManagernow caches live Peer Up state and replays it only to the collector that just reconnected, instead of requiring fresh session transitions to rebuild collector state. -
Policy engine test modularization. Extracted the
RouteModifications::merge_fromandPolicyChaintest cluster intocrates/policy/src/engine/tests/chain.rsto reduce monolithic test sprawl inengine.rs. -
Export policy IPv6 next-hop discarded on MP path. When export policy set
NextHopAction::Specific(IpAddr::V6(addr)), the IPv6 MP_REACH send path detected the policy but usedroute.next_hopinstead of extracting the policy address. Fixed by matchingSpecific(addr)directly. -
IPv6 policy next-hop on classic IPv4 body-NLRI now warns. Setting an IPv6 next-hop via export policy for a non-RFC-8950 peer is unencodable in the classic
NEXT_HOPattribute. This now logs a warning and falls through to default next-hop selection instead of silently discarding the policy address. -
Cease subcode constants.
ADMINISTRATIVE_RESET(4) added,OUT_OF_RESOURCEScorrected from 4 to 8 per RFC 4486. Description table updated for subcode 4 ("Administrative Reset") and 8. -
FlowSpec (RFC 8955/8956). IPv4 and IPv6 unicast FlowSpec (SAFI 133) with all 13 component types: destination/source prefix, IP protocol, port, destination/source port, ICMP type/code, TCP flags, packet length, DSCP, fragment, flow label. Numeric and bitmask operator encoding per RFC 8955.
FlowSpecRule/FlowSpecRouteparallel types preservePrefix'sCopytrait. FlowSpec actions (rate-limit, redirect, DSCP mark) encoded as extended communities. Separate FlowSpec collections in AdjRibIn/LocRib/AdjRibOut. Transport decode/encode via MP_REACH/MP_UNREACH with NH length 0. gRPCAddFlowSpec/DeleteFlowSpec/ListFlowSpecRoutesRPCs. Same policy/iBGP/RR infrastructure. Config families"ipv4_flowspec"and"ipv6_flowspec". (ADR-0035) -
RPKI Origin Validation (RFC 6811). New
rustbgpd-rpkicrate with persistent RTR client (RFC 8210), per-cache-server async client,SerialNotify-triggered refreshes, enforced expiry timers, and multi-cache VRP aggregation. Routes stamped withRpkiValidation(Valid/Invalid/NotFound). Best-path step 0.5 prefers Valid > NotFound > Invalid. Policymatch_rpki_validationenables rejection of invalid routes. Config[rpki]section with[[rpki.cache_servers]]for connecting to validators (Routinator, rpki-client, FORT). Prometheus metrics for VRP counts. gRPCvalidation_stateon Route messages. (ADR-0034) -
Extended Communities (RFC 4360).
ExtendedCommunity(u64)newtype with helpers for type/sub-type extraction, route target, and route origin decoding. Full wire codec (type 16, Optional|Transitive), stored on routes, exposed via gRPCRouteandAddPath. (ADR-0025) -
Extended Community Policy Matching. Import/export policy can now match on route target (
RT:) and route origin (RO:) values viamatch_communityin prefix list entries. Encoding-agnostic matching (2-octet AS, IPv4-specific, and 4-octet AS compare equal). Prefix is now optional — entries can match community-only, prefix-only, or both (AND). Multiple communities in one entry use OR logic. (ADR-0026) -
M12 interop test — Extended communities validated against FRR 10.3.1. FRR route-map sets RT:65002:100, rustbgpd decodes/stores/exposes via gRPC. Injection round-trip verified. 14/14 tests pass.
-
Route Refresh (RFC 2918). ROUTE-REFRESH message codec (type 5), capability code 2 advertised unconditionally. Inbound: peer requests trigger Loc-RIB re-advertisement for the requested family. Outbound:
SoftResetIngRPC RPC sends ROUTE-REFRESH to peers for soft inbound reset after policy changes. (ADR-0027) -
AS_PATH loop detection (RFC 4271 §9.1.2). Routes containing the local ASN in any AS_PATH segment (AS_SEQUENCE or AS_SET) are discarded before RIB entry. Applies to all peers (eBGP and iBGP). Withdrawals in the same UPDATE are still processed. New metric:
bgp_as_path_loop_detected_total(labeled by peer, counts rejected prefixes). -
iBGP split-horizon (RFC 4271 §9.1.1). Non-route-reflector speakers no longer re-advertise iBGP-learned routes to other iBGP peers. Applies to
distribute_changes(),send_initial_table(), and route refresh responses. UsesRouteOriginenum (Ebgp/Ibgp/Local) instead of a boolean — locally originated routes pass through to all peers. -
Standard Communities Policy Matching (RFC 1997). Import/export policy can now match on standard community values via
match_communityin prefix list entries. Three formats:ASN:VALUE(e.g.,65001:100), well-known names (NO_EXPORT,NO_ADVERTISE,NO_EXPORT_SUBCONFED), and existing extended community syntax (RT:65001:100). Standard and extended community criteria use OR semantics within a single entry. (ADR-0028) -
Route Reflector (RFC 4456). Designated speakers can reflect iBGP-learned routes based on client/non-client roles, eliminating the full-mesh requirement. Config:
cluster_id(global),route_reflector_client(per-neighbor). Reflection rules: client routes go to all iBGP peers, non-client routes go to clients only. ORIGINATOR_ID (type 9) and CLUSTER_LIST (type 10) attributes with full wire codec, inbound loop detection, outbound manipulation (set on reflection, stripped on eBGP). Best-path tiebreakers: shortest CLUSTER_LIST, lowest ORIGINATOR_ID (RFC 4456 §9). New metric:bgp_rr_loop_detected_total. (ADR-0029) -
Policy actions — route modification on import/export. Policy engine redesigned from accept/reject to full match+modify+filter.
set_local_pref,set_med,set_next_hop(self or IP),set_community_add/set_community_remove(standard, extended, large),set_as_path_prepend(ASN + count). Import modifications stored on Route; export modifications clone Loc-RIB route. Policy types renamed from prefix-list terminology to engine terminology. (ADR-0030) -
AS_PATH regex matching.
match_as_pathfield in policy statements supports Cisco/Quagga-style patterns (^65100_,_65200$,_65100_)._expands to boundary anchor. ANDed with existing prefix and community conditions.AsPath::to_aspath_string()for regex-matchable format. (ADR-0030) -
Large Communities (RFC 8092). 12-byte community values for 4-byte ASN operators. Wire codec (type 32, Optional|Transitive),
Route::large_communities()accessor, gRPC API fields on Route and AddPath, policy matching (LC:global:local1:local2format inmatch_community), and set/delete in policy actions. (ADR-0031) -
Extended Messages (RFC 8654). Raises the 4096-byte BGP message limit to 65535 bytes. Capability code 6 advertised unconditionally. Negotiated per-session; dynamic buffer sizing on establishment.
max_message_lenparameter threaded through header decode, message decode, and UPDATE encode. (ADR-0032) -
Add-Path (RFC 7911) — receive + multi-path send. Accept and advertise multiple paths per prefix. Capability code 69 with
AddPathMode(Receive/Send/Both) negotiation.NlriEntryandIpv4NlriEntrystructs for path-id-aware NLRI. RIB re-keyed with composite(Prefix, path_id)keys in Adj-RIB-In and Adj-RIB-Out. Multi-path send (route server mode):distribute_multipath_prefix()collects all candidates, applies per-candidate export policy, assigns rank-based path IDs. TOML config:[neighbors.add_path] receive = true,send = true,send_max = N. gRPC API:path_idon Route, RouteEvent, AddPathRequest, DeletePathRequest. (ADR-0033)
- IPv4
set_next_hopnow reaches the wire.apply_modifications()updatesPathAttribute::NextHopdirectly forSpecific(V4)addresses. Export path carries fullNextHopAction(not a boolean) soprepare_outbound_attributes()can skip eBGP rewrite when policy explicitly sets an address. IPv6 policy next-hop override also wired through. - RT/RO extended community ASN validation.
build_rt_ec()/build_ro_ec()now reject ASN > 65535 at config load time (2-octet AS-Specific sub-type only carries u16). Previously silently truncated to u16. - RT/RO impossible match specs rejected.
parse_community_match()rejects RT/RO match patterns with local fields exceeding the encoding capacity (e.g.RT:192.0.2.1:70000where IPv4-specific only allows u16 local). - AS_PATH regex
_now matches AS_SET braces. Expanded from(?:^| |$)to(?:^| |$|[{}])so patterns like_65003_match inside{65003 65004}. - Zero-length LARGE_COMMUNITIES rejected. Wire decoder now rejects zero-length attribute value (must carry at least one 12-byte community).
- Extended community add/remove uses logical RT/RO equivalence.
set_community_removeandset_community_addnow compare RT/RO semantically, not by raw bytes. Removes work across encodings (2-octet AS, 4-octet AS, IPv4-specific) and adds avoid creating logical duplicates. - AS_PATH prepend overflow guard.
set_as_path_prependno longer creates AS_SEQUENCE segments longer than 255 ASNs (wire segment length is u8). When merging would exceed the limit, a separate leading AS_SEQUENCE is created. - Proto
large_communitiesformat documented. Added format comments ("global_admin:local_data1:local_data2") toRouteandAddPathRequestmessage fields. - Dead code removed. Deleted
prefix_list.rs(969 lines of duplicated code superseded byengine.rs). Removed 36 duplicate tests.
-
Large community duplicates preserved. Duplicate large communities in received UPDATEs are stored and re-advertised unchanged. Strict RFC 8092 normalization (dedup on receipt) is deferred as a hardening item.
-
Proto: single source of truth. Eliminated duplicate proto file;
crates/api/build.rsnow compiles fromproto/rustbgpd.protodirectly.SoftResetInRPC is now in the public proto. -
ROUTE-REFRESH: unknown AFI/SAFI no longer tears down session.
RouteRefreshMessagestores raw wire values; unknown families are logged and ignored instead of triggering a decode error. -
ROUTE-REFRESH: outbound queue no longer leaks across reconnects. Outbound channel is recreated on
SessionDownso stale updates from a dying session cannot be sent on the next one. -
ROUTE-REFRESH: negotiated family/capability checks on both paths. Inbound and outbound ROUTE-REFRESH now verify the requested family is negotiated and the peer advertised the capability.
-
SoftResetIn: accurate gRPC error codes. Peer-not-found returns
NOT_FOUND; send failures returnINTERNAL(was allNOT_FOUND). -
SoftResetIn: docs corrected. Empty families means "all configured" (not "all negotiated"); transport filters to negotiated.
-
Route refresh: backpressure observable. RIB channel full and EoR enqueue failures are now logged at
warnlevel. -
EoR retry under backpressure. Failed EoR markers are tracked in
pending_eorand retried on the next dirty-peer resync, so the protocol completion signal is no longer permanently lost. -
SoftResetIn returns actual send outcome.
SendRouteRefreshis now a request/reply command; the gRPC response reflects whether the message was sent, not just enqueued. -
AS_PATH loop fast-path: negotiated-family filter on withdrawals. The loop-detection branch now applies the same
negotiated_familiescheck toMP_UNREACH_NLRIas the normal UPDATE path, preventing withdrawals for unnegotiated address families from reaching the RIB. -
Best-path step 5 comment corrected. The comment now accurately states that only
RouteOrigin::Ebgpis preferred over iBGP;Localroutes do not receive explicit preference at this step (they win via LOCAL_PREF or shorter AS_PATH instead).
Graceful Restart (RFC 4724) — receiving speaker. Wire codec hardening. 448 tests.
- Graceful Restart — receiving speaker (RFC 4724). When a peer restarts
with GR capability, routes are preserved as stale during the restart window
instead of immediately withdrawn. End-of-RIB markers clear stale flags;
timer expiry sweeps remaining stale routes. Enabled by default.
- Wire: capability code 64 encode/decode with per-family forwarding flags
- Config:
graceful_restart(defaulttrue),gr_restart_time(default120),gr_stale_routes_time(default360) - FSM: peer GR capability negotiation
- RIB: stale route demotion in best-path (step 0, before LOCAL_PREF), timer-based stale sweep, End-of-RIB detection and sending
- Transport: GR-aware session teardown (PeerGracefulRestart vs PeerDown)
- Metrics:
bgp_gr_active_peers,bgp_gr_stale_routes,bgp_gr_timer_expired_total
rustbgpd-wire:Capability::encode()now returnsResult<(), EncodeError>— validates capability value lengths andrestart_timerange before encoding- Config:
gr_restart_time=0rejected whengraceful_restartis enabled;gr_stale_routes_timecapped at 3600 seconds; duplicate address families in config are deduplicated
-
Graceful Restart state machine corrections (RFC 4724 review).
- GR trigger now checks
peer_gr_capableinstead of the R-bit from the dying session; R-bit is only meaningful in the new OPEN after restart - All families from the peer's GR capability are retained as stale, not
just those with
forwarding_preserved=true - Routes for negotiated families NOT in the peer's GR capability are withdrawn immediately on GR start
PeerUpduring GR no longer clears stale flags — routes stay stale until End-of-RIB per family, matching RFC 4724 §4.2- Initial GR timer uses
restart_time(session window); timer resets tostale_routes_timeonPeerUp(EoR window) graceful_restart=falseconfig now gates GR in transportbgp_gr_stale_routesmetric updated during partial EoR recovery- Dead outbound channels cleaned up on GR start
- GR trigger now checks
-
rustbgpd-wire: capability decode now bounded to the enclosing optional-parameter slice — a malformed capability length can no longer consume into the next parameter or beyond the OPEN body -
rustbgpd-wire:restart_time > 4095inCapability::encode()now returns an error instead of silently masking with& 0x0FFF -
rustbgpd-rib: Adj-RIB-Out no longer diverges from wire state for eBGP peers without a valid IPv6 next-hop.sendable_familiespassed atPeerUptime filters unsendable address families before Adj-RIB-Out insertion, keepingListAdvertisedRoutes, withdraw bookkeeping, and dirty-peer resync in sync with what the transport actually sends. -
rustbgpd-wire:MP_REACH_NLRIflags corrected from optional-transitive (0xC0) to optional-non-transitive (0x80) per RFC 4760 §3. Affects encoding, decoding validation (expected_flags), andflags()accessor.MP_UNREACH_NLRIwas already correct. -
rustbgpd-wire:validate_update_attributes()now requiresNEXT_HOPfor body NLRI even whenMP_REACH_NLRIis present. Mixed UPDATEs (body NLRI + MP_REACH) no longer incorrectly waive NEXT_HOP. -
rustbgpd-wire:Ipv4Prefix::new()clamps prefix length to 32;Ipv6Prefix::new()clamps to 128. Wire decoders already rejected invalid lengths but constructors silently created invalid prefixes. -
rustbgpd-wire: IPv6 next-hops inMP_REACH_NLRIvalidated — link-local (fe80::/10), loopback, multicast, and unspecified addresses rejected with NOTIFICATION (3,8). -
rustbgpd-transport: IPv6 routes built fromMP_REACH_NLRIno longer inheritPathAttribute::NextHop(ipv4)from the same UPDATE. -
rustbgpd-transport: IPv6 eBGP next-hop resolution: useslocal_ipv6_nexthopconfig > local socket address > suppress (no longer falls back to::). -
rustbgpd-transport: IPv6 outbound batching now groups by(attributes, next_hop)instead of just attributes. Routes with different next-hops get separate UPDATEs. -
rustbgpd-transport: Negotiated address families enforced at inbound and outbound edges. Routes for non-negotiated families are ignored inbound and filtered outbound. -
rustbgpd-transport: Send-time IPv6 next-hop filter now rejects loopback, link-local, and multicast (was only rejecting::), consistent with receive-side validation. -
rustbgpd-fsm: Implicit IPv4 unicast fallback per RFC 4760 §8 — when neither side advertises MP-BGP for IPv4, IPv4 unicast is still negotiated. -
rustbgpd-api:ListReceivedRoutes,ListBestRoutes,ListAdvertisedRoutes, andWatchRoutesnow filter results by the requestedafi_safifamily (previously validated the enum but returned all routes regardless). -
rustbgpd-api:AddNeighborgRPC now acceptsfamiliesfield for address family configuration (previously hardcoded to IPv4 unicast). -
rustbgpd-api:ListNeighborsandGetNeighborStatenow return configured address families (was hardcoded to empty). -
rustbgpd-api:local_ipv6_nexthopconfig now properly wired throughPeerManagerNeighborConfigfor statically configured peers (was dead config). -
rustbgpd-rib: Metrics label changed from"ipv4_unicast"to"all"since RIB now tracks both IPv4 and IPv6 routes. -
Config:
local_ipv6_nexthopvalidation now rejects loopback, link-local, multicast, and unspecified addresses (was only checking parse-ability).
- Config:
local_ipv6_nexthopfield on[[neighbors]]— explicit IPv6 next-hop address for eBGP sessions over IPv4 transport. rustbgpd-wire: Publicis_valid_ipv6_nexthop()helper for reuse across config validation, send-time filtering, and receive-side validation.
MP-BGP (IPv6 unicast) support. rustbgpd is now a dual-stack BGP speaker —
IPv6 prefixes are exchanged via MP_REACH_NLRI / MP_UNREACH_NLRI (RFC 4760)
alongside existing IPv4 unicast. This is a cross-cutting change touching all 7
crates. 388 tests pass.
rustbgpd-wire:Ipv6Prefixtype with NLRI encode/decode (prefix-length encoding, max 128, host-bit masking).Prefixenum wrappingIpv4PrefixandIpv6Prefixfor AFI-agnostic route representation. Helper methodsaddr_string()andprefix_len()onPrefix.rustbgpd-wire:MpReachNlriandMpUnreachNlripath attribute variants (types 14 and 15). Full decode/encode per RFC 4760 §3: AFI/SAFI, variable- length next-hop (16 or 32 bytes for IPv6, take global address), NLRI.AfiandSafienums withUnknown(u16)/Unknown(u8)variants.rustbgpd-wire:MP_REACH_NLRI(14) andMP_UNREACH_NLRI(15) constants. Flag validation: type 14 = Optional (0x80), type 15 = Optional (0x80) per RFC 4760 §3/§4 (both are optional non-transitive).rustbgpd-fsm:intersect_families()computes the intersection of locally configured address families and peer-advertised MP-BGP capabilities.NegotiatedSessiongainsnegotiated_families: Vec<(Afi, Safi)>.rustbgpd-transport:process_update()extractsMpReachNlriandMpUnreachNlrifrom parsed attributes, builds routes withPrefix::V6andIpAddr::V6next-hops, combines with body NLRI for unified RIB insertion.rustbgpd-transport:send_route_update()splits outbound routes by AFI — IPv4 routes use body NLRI (existing path), IPv6 routes useMpReachNlri/MpUnreachNlriattributes. eBGP IPv6 next-hop rewritten to local socket address.rustbgpd-api:InjectionServiceaccepts IPv6 prefixes and next-hops inAddPathandDeletePath. Prefix length validated against AFI-specific maximum (32 for IPv4, 128 for IPv6).rustbgpd-api:RibServiceaccepts IPv6 unicast inafi_safifilter (previously rejected non-IPv4).WatchRoutesevents carry correct AFI based on prefix type.- Config:
familiesfield on[[neighbors]]— list of address families to negotiate (e.g.,["ipv4_unicast", "ipv6_unicast"]). Defaults to["ipv4_unicast"]for IPv4 neighbors,["ipv4_unicast", "ipv6_unicast"]for IPv6 neighbors. - Config: IPv6 neighbor addresses now accepted (previously rejected at validation).
- Config: IPv6 prefixes supported in policy prefix lists (e.g.,
prefix = "2001:db8::/32"). Prefix length validation uses AFI-specific maximum (32 for IPv4, 128 for IPv6). - Interop:
m10-frr-ipv6.clab.ymlcontainerlab topology — rustbgpd + FRR dual-stack (IPv4 session with MP-BGP IPv6 unicast). FRR advertises 2 IPv4 and 2 IPv6 prefixes. - Interop:
test-m10-frr-ipv6.shautomated test script with 6 tests: session with IPv6 capability, IPv4 backward compat, IPv6 prefix receipt, IPv6 best routes, IPv6 withdrawal, IPv6 route injection via gRPC. - ADR-0023: Prefix enum and AFI-agnostic RIB for MP-BGP.
rustbgpd-wire:UpdateMessage::build()now encodes path attributes when attributes are non-empty, even if body NLRI is empty. Required for IPv6-only UPDATEs that carry NLRI insideMpReachNlriattributes.rustbgpd-wire:validate_update_attributes()relaxes the NEXT_HOP requirement whenMP_REACH_NLRIis present (RFC 4760 §3 — next-hop is carried inside the MP attribute for non-IPv4 families).rustbgpd-rib:Route.prefixchanged fromIpv4PrefixtoPrefixenum.Route.next_hopchanged fromIpv4AddrtoIpAddr. All RIB data structures (AdjRibIn,LocRib,AdjRibOut) generalized fromHashMap<Ipv4Prefix, _>toHashMap<Prefix, _>.rustbgpd-rib:RibUpdateandOutboundRouteUpdateusePrefixfor withdrawn routes (wasIpv4Prefix).RouteEvent.prefixis nowPrefix.rustbgpd-policy:PrefixListEntrygeneralized to match both IPv4 and IPv6 prefixes.ledefaults to 32 for IPv4, 128 for IPv6.rustbgpd-transport:known_prefixeschanged fromHashSet<Ipv4Prefix>toHashSet<Prefix>.prepare_outbound_attributes()stripsMpReachNlriandMpUnreachNlrifrom cloned attributes (rebuilt per-route for outbound).- Workspace version bumped to 0.2.0.
First tagged release. Covers milestones M0–M9: a fully functional, IPv4-unicast BGP daemon with gRPC API, RFC 4271 compliance, TCP collision detection, and interop validation against FRR 10.3.1 and BIRD 2.0.12. 367 tests pass.
rustbgpd-transport:SessionNotification::OpenReceivednow readsself.fsm.negotiated()(available atOpenConfirm) instead ofself.negotiated(set later atSessionEstablished). Previously the notification never fired, bypassing TCP collision detection entirely. 1 integration test.rustbgpd-transport:QueryStatenow readsremote_router_id(andnegotiated_hold_time,four_octet_as) fromself.fsm.negotiated()with fallback toself.negotiated. Previouslyhandle_inbound()in OpenConfirm could not resolve collisions becauseremote_router_idwasNone. 1 integration test.rustbgpd-transport: Session notification channel changed from boundedmpsc::channel(64)withtry_send()tompsc::unbounded_channel()withsend(). Collision notifications are no longer silently dropped under channel pressure. Unbounded is safe here because rate is bounded by FSM state transitions (infrequent). Avoids deadlock risk thatsend().awaiton a bounded channel would introduce (PeerManager queries peer state via the same task).PeerManager:disable_peer()now clearspending_inbound.BackToIdlehandler guards against accepting pending inbound for disabled peers. Previously disabling a peer could be undone by a queued inbound connection. 1 test.src/metrics_server.rs: Semaphore permit acquired beforeaccept()for exact connection cap (was off-by-one: 65 instead of 64).docs/SECURITY.md: Corrected metrics endpoint description (no default address; common port is 9179, not 9090).README.md: Docker section now warns thatgrpc_addr = "0.0.0.0:50051"exposes unauthenticated RPCs. Links todocs/SECURITY.md.crates/fsm/src/session.rs: Doc comment onnegotiated()corrected from "available after Established" to "available afterOpenConfirm".ROADMAP.md: Corrected M8 test count from 347 to 357.
rustbgpd-wire: Cease subcode 7 (CONNECTION_COLLISION_RESOLUTION) for TCP collision detection per RFC 4271 §6.8. Human-readable description innotification::description().rustbgpd-transport:SessionNotificationenum (OpenReceived,BackToIdle) sent from peer sessions to PeerManager for collision detection coordination.CollisionDumpcommand variant onPeerCommand— sends Cease/7 NOTIFICATION, cleans up RIB if Established, closes TCP.remote_router_id: Option<Ipv4Addr>added toPeerSessionState. Session notification channel threaded throughPeerHandle::spawn()andPeerHandle::spawn_inbound(). (ADR-0021)PeerManager: TCP collision detection.pending_inboundper peer stores inbound TCP streams awaiting resolution.session_notify_rxinselect!loop handlesOpenReceived(resolve collision) andBackToIdle(accept pending).resolve_collision()compares BGP Identifiers — higher wins.replace_with_inbound()helper extracted for clean session replacement. 4 new tests. (ADR-0021)docs/SECURITY.md: new document covering gRPC security posture, authentication gaps, privileged RPCs, and deployment recommendations.docs/adr/0021-tcp-collision-detection.md: ADR for collision detection architecture.docs/adr/0022-grpc-server-supervision.md: ADR for gRPC server supervision.
src/main.rs: gRPC serverJoinHandlenow supervised — unexpected exit triggers coordinated shutdown (API-first daemon without API should not keep running). Added to shutdownselect!alongside ctrl-c and Shutdown RPC. (ADR-0022)src/main.rs: Non-loopback gRPC bind address triggers a warning at startup, informing operators that all RPCs are unauthenticated.src/metrics_server.rs: Read timeout (5s) prevents slow-client exhaustion. Request-line size limit (8192 bytes) returns 400 for oversized requests. Concurrent connection cap (64 viatokio::sync::Semaphore) provides backpressure.gather()errors return 500 Internal Server Error instead of panicking. 3 new tests.- CHANGELOG updated with versioning through M9.
- ROADMAP updated: completed summary reflects M0–M8 work, M9 marked complete, v1 scope section added, TCP collision detection moved from post-v1 into M9.
rustbgpd-rib: WatchRoutes event model now carriesprevious_peerandtimestampon allRouteEventvariants. Subscribers filtered to a specific peer now see "route moved away" events (BestChanged/Withdrawn) where the old peer matches.recompute_best()captures previous best peer before Loc-RIB mutation. 4 tests.rustbgpd-rib: Prometheus gauges (bgp_rib_prefixes,bgp_rib_adj_out_prefixes,bgp_rib_loc_prefixes) wired at all RIB mutation points — RoutesReceived, PeerDown, distribute_changes, send_initial_table, InjectRoute, WithdrawInjected, recompute_best. Zero-valued gauges initialized on PeerUp for stable dashboard series. 3 tests.rustbgpd-api:active_peersin GetHealth now counts only Established peers (was counting all configured peers).total_routesnow queries Loc-RIB count (was summing per-peer prefix counts). 1 test.rustbgpd-api:prefixes_sentin ListNeighbors and GetNeighborState now queries Adj-RIB-Out count per peer (was hardcoded to 0). ReturnsStatus::internalon RIB manager failure instead of silently returning 0. 1 test.- Config: IPv6 neighbor addresses rejected at config validation and gRPC
AddNeighborboundary. Wire crate is IPv4-only and GTSM uses IPv4-only socket options. 2 tests.
- Proto:
AddPathResponse.uuidremoved (was a fake 6-byte value derived from prefix bytes thatDeletePathignored). BothAddPathResponsefield 1 andDeletePathRequestfield 3 are now reserved for wire compatibility. - Proto:
SetGlobalRPC,SetGlobalRequest, andSetGlobalResponseannotated as reserved for future use (documentation-only; RPC still returns UNIMPLEMENTED). - Proto:
RouteEventgainsprevious_peer_address(field 7) and timestamp comment clarified as Unix epoch seconds. rustbgpd-rib:QueryLocRibCountandQueryAdvertisedCountvariants added toRibUpdatefor accurate health and neighbor counters. 2 tests.rustbgpd-api:ControlServiceandNeighborServicenow acceptrib_txfor querying RIB state.
rustbgpd-rib: Adj-RIB-Out divergence on channel-full.distribute_changes()andsend_initial_table()now stage deltas beforetry_send(). Mutations commit only on success. On failure the peer is marked dirty and a persistent resync timer (1 second, pinned across loop iterations) fires independently of both incoming mutations and non-mutating query traffic, diffing the entire Loc-RIB against AdjRibOut to recover missed updates and withdrawals. AdjRibOut is preserved (not cleared) so knowledge of the peer's on-wire state is retained. 4 tests.rustbgpd-wire: Both malformed NLRI cases — prefix length > 32 and truncated NLRI buffer — now produceInvalidNetworkFieldwith UPDATE subcode 10 (Invalid Network Field). Previously prefix_len > 32 used subcode 1 and truncation mapped to Message Header / Bad Message Length (1/2). Error data includes the offending length byte and available address bytes. 2 tests.rustbgpd-wire: PARTIAL bit on re-advertised unknown attributes narrowed to optional transitive only (both OPTIONAL and TRANSITIVE flags set). Previously set PARTIAL whenever TRANSITIVE was set, incorrectly marking well-known transitive attributes like ATOMIC_AGGREGATE. 1 test.- Config: policy prefix lengths eagerly validated in
Config::validate()at load time. Rejects prefix length > 32, ge > 32, ge < prefix length, le > 32, and ge > le. Previously deferred to first policy access, which could cause panics inPrefixListEntry::matches(). Both global and per-neighbor policies are now checked. 4 tests.
rustbgpd-rib: eBGP-over-iBGP preference in best-path selection.Routegainsorigin_type: RouteOriginfield (Ebgp/Ibgp/Local). Best-path step 5 (between MED and peer address tiebreaker) prefers eBGP routes over iBGP per RFC 4271 §9.1.2. 3 tests.
rustbgpd-wire: RFC-compliant attribute flag validation at decode time. Known attribute types are checked for correct Optional/Transitive flags, producingUpdateAttributeErrorwith subcode 4 (Attribute Flags Error) and full attribute data per RFC 4271 §6.3. Replaces deadcheck_wellknown_flagsin validator.rustbgpd-wire: Specific UPDATE error subcodes replace generic subcode 1 (Malformed Attribute List) — length errors produce subcode 5, invalid ORIGIN produces subcode 6, malformed AS_PATH produces subcode 11. All include the offending attribute as NOTIFICATION data.rustbgpd-wire:UpdateAttributeErrorvariant onDecodeErrorcarrying subcode, attribute data, and detail string.to_notification()maps it to the correct(UpdateMessage, subcode, data)tuple.rustbgpd-wire: Sharedattr_error_data()helper builds RFC 4271 §6.3 error data (flags + type + length + value), correctly setting the Extended Length flag for values > 255 bytes. Replaces buggyencode_attr_for_errorin validator.rustbgpd-wire: Partial bit (0x20) is now OR'd into flags when encoding unknown transitive attributes for re-advertisement, per RFC 4271 §5. 10 new tests.rustbgpd-api:GlobalServicegRPC implementation —GetGlobalreturns daemon ASN, router-id, and listen port;SetGlobalreturns UNIMPLEMENTED (runtime mutation deferred to post-v1). 2 tests. (ADR-0020)rustbgpd-api:ControlServicegRPC implementation —GetHealthreturns uptime, active peer count, and total route count;GetMetricsreturns Prometheus text exposition;Shutdowninitiates coordinated daemon shutdown via gRPC. 2 tests. (ADR-0020)- Coordinated shutdown: ctrl-c and
ShutdownRPC both trigger ordered teardown — PeerManager drains all peers (sending NOTIFICATIONs), then gRPC server exits gracefully viaserve_with_shutdown. Previously the runtime dropped mid-shutdown.
rustbgpd-transport: eBGP NEXT_HOP rewrite now uses the TCP session's local address instead oflocal_router_id. Router-id is often a loopback that is not reachable from the peer; the local socket address is correct.rustbgpd-api:AddPathwith emptyas_pathno longer produces a zero-length AS_SEQUENCE segment that fails our own UPDATE validator. Empty input now creates an AS_PATH with no segments (correct for locally-originated routes).rustbgpd-api:afi_safifield inListReceivedRoutes,ListBestRoutes,ListAdvertisedRoutes, andWatchRoutesis now validated. Requesting an unsupported address family (e.g., IPv6) returnsINVALID_ARGUMENTinstead of silently returning IPv4 data.rustbgpd-wire: 2-octet ASN encoding no longer silently truncates 4-byte ASNs. ASNs > 65535 are now mapped toAS_TRANS(23456) per RFC 6793.- Config: invalid policy entries (unknown action, malformed prefix) now return
ConfigError::InvalidPolicyEntryinstead of being silently filtered. 2 tests. KNOWN_ISSUES.md: removed stale entries about missing inbound listener and outbound UPDATE generation (resolved in M5 and M3 respectively).- Metrics server: inbound accept forwarding failure now logged instead of silently dropped.
rustbgpd-api: Deduplicated pagination logic inRibService— extractedparse_page_params()andbuild_response()helpers used by all 3 list RPCs.- Workspace version bumped to 0.1.0. Repository URL fixed. Added
rust-version,keywords,categoriesmetadata for crates.io publishing. Proto file copied into api crate for standalone packaging.
rustbgpd-transport: Inbound TCP listener.BgpListeneraccepts connections onlisten_portand forwards to PeerManager viaAcceptInboundcommand.PeerSession::new_inbound()starts with an already-connected stream.PeerHandle::spawn_inbound()spawns inbound sessions. (ADR-0019)rustbgpd-transport: Session counters —updates_received,updates_sent,notifications_received,notifications_sent,flap_count,uptime_secs,last_errortracked per session and exposed viaPeerSessionStateand gRPCNeighborState.rustbgpd-transport: Accurate prefix tracking viaHashSet<Ipv4Prefix>instead of add/subtract heuristic. Duplicate announcements no longer inflate count; withdrawals of unknown prefixes no longer underflow.rustbgpd-transport: NLRI batching — outbound UPDATEs with identical path attributes are grouped into a single wire UPDATE message.rustbgpd-api: Input validation forAddNeighbor(rejectremote_asn=0,hold_timeof 1 or 2) andAddPath(rejectnext_hopof0.0.0.0or multicast). 4 unit tests.rustbgpd-api:NeighborStateproto fields fully populated — uptime, update/notification counters, flap count, last error, hold_time, max_prefixes. Previously hardcoded to 0.rustbgpd-rib:RibManageracceptsBgpMetricsand recordsoutbound_route_dropscounter whentry_send()fails.rustbgpd-telemetry:outbound_route_dropsIntCounterVec metric (labeled by peer).- Config:
#[serde(deny_unknown_fields)]on all config structs — typos now cause startup errors instead of silent acceptance. 2 tests. - Metrics server: per-connection task spawn, HTTP path routing (404 for non-
/metrics), 5-second write timeout. 2 tests.
rustbgpd-wire: Typed COMMUNITIES attribute (RFC 1997).PathAttribute::Communities(Vec<u32>)variant replaces opaqueUnknownfor type code 8. Decode, encode, andRoute::communities()accessor. 6 tests.rustbgpd-rib:RouteEventtype withAdded,Withdrawn,BestChangedvariants.tokio::sync::broadcastchannel (capacity 4096) emits events after best-path recomputation.SubscribeRouteEventsvariant inRibUpdate. (ADR-0018)rustbgpd-rib: Per-peer export policy support.RibManagerstores per-peer policies viaPeerUp, resolves withexport_policy_for()(per-peer overrides global). Cleaned up onPeerDown. 2 new tests.rustbgpd-api:NeighborServicegRPC implementation with all 6 RPCs:AddNeighbor,DeleteNeighbor,ListNeighbors,GetNeighborState,EnableNeighbor,DisableNeighbor.rustbgpd-api:WatchRoutesgRPC streaming endpoint. Subscribes to RIB broadcast channel, wraps inBroadcastStream, filters by peer address, mapsRouteEventto protoRouteEvent. Lagged subscribers are logged and skipped.rustbgpd-api:peer_typesmodule with sharedPeerManagerCommand,PeerManagerNeighborConfig, andPeerInfotypes used by both binary and API crate.rustbgpd-api: Communities field populated inroute_to_proto()and accepted inAddPathinjection requests.rustbgpd-transport:PeerCommand::QueryStatevariant returnsPeerSessionState(FSM state, prefix count, negotiated hold time, four-octet-AS flag).PeerManager(src/peer_manager.rs): Channel-based single-task ownership for dynamic peer lifecycle management. Commands: AddPeer, DeletePeer, ListPeers, GetPeerState, EnablePeer, DisablePeer, Shutdown. (ADR-0017)- Config: per-neighbor
import_policyandexport_policysections in[[neighbors]]. Neighbor-specific policy overrides global; absence falls back to global. - Config: starting with zero
[[neighbors]]is now valid (peers added dynamically). - Dependencies:
tokio-stream(withsyncfeature) forBroadcastStreamwrapper. - Interop: 10-peer containerlab topology
m4-frr.clab.yml(rustbgpd + 10× FRR). 8 static peers + 2 dynamic. Automated test scripttest-m4-frr.shwith 7 test scenarios (17 pass/fail checks): static sessions, ListNeighbors, received routes, per-peer export policy, dynamic AddNeighbor/DeleteNeighbor, Enable/Disable.
- MSRV bumped from Rust 1.85 to 1.88. Required for
letchains andusize::is_multiple_of()stabilization. - Dockerfile updated from
rust:1.85-bookwormtorust:1.88-bookworm.
rustbgpd-policy:PrefixListwith ge/le range matching, first-match-wins evaluation, andcheck_prefix_list()convenience function. 9 tests.rustbgpd-wire:UpdateMessage::build()high-level constructor for creating outbound UPDATEs from structured data (announced prefixes, withdrawn prefixes, path attributes). 4 tests.rustbgpd-rib:AdjRibOutper-peer outbound route table.OutboundRouteUpdatetype for announce/withdraw batches. (ADR-0015)rustbgpd-rib:RibManagergains outbound distribution:distribute_changes()computes deltas per peer with split-horizon and export policy filtering.send_initial_table()sends full Loc-RIB dump on peer establishment.rustbgpd-rib: Route injection viaInjectRoute/WithdrawInjectedmessages. Injected routes stored under sentinel peer0.0.0.0in standard Adj-RIB-In, participating in normal best-path selection and distribution.rustbgpd-rib:QueryAdvertisedRoutesvariant for querying Adj-RIB-Out per peer. 8 new M3 tests (38 total).rustbgpd-transport: Per-peer outbound channel (mpsc, capacity 4096) receivesOutboundRouteUpdatefrom RIB manager.send_route_update()converts to wire UPDATEs.prepare_outbound_attributes()handles eBGP (ASN prepend, NEXT_HOP rewrite, LOCAL_PREF strip) and iBGP (default LOCAL_PREF 100). 5 unit tests.rustbgpd-transport: Import policy filtering — inbound UPDATEs filtered by global prefix-list before RIB insertion.rustbgpd-transport: Max-prefix enforcement — tracks accepted prefix count, sends Cease/1 (Maximum Number of Prefixes Reached) NOTIFICATION when exceeded.rustbgpd-transport: TCP MD5 authentication (RFC 2385) viasetsockopt(TCP_MD5SIG). Linux only. Configurable per-neighbor viamd5_passwordconfig field. (ADR-0016)rustbgpd-transport: GTSM / TTL security (RFC 5082) viasetsockopt(IP_MINTTL). Linux only. Configurable per-neighbor viattl_securityconfig field.rustbgpd-transport: TCP connection refactored to usesocket2::Socketfor pre-connect socket option application.rustbgpd-api:InjectionServicewithAddPath(returns UUID derived from prefix) andDeletePathgRPC endpoints.rustbgpd-api:ListAdvertisedRoutesimplemented (previously UNIMPLEMENTED stub). Queries Adj-RIB-Out for a specific peer.rustbgpd-telemetry: New metrics —rib_adj_out_prefixes(gauge),rib_loc_prefixes(gauge),max_prefix_exceeded(counter).- Config:
max_prefixes,md5_password,ttl_securityfields on[[neighbors]]. Global[policy]section withimportandexportprefix-list entries. - Interop: 3-node containerlab topology
m3-frr.clab.yml(rustbgpd + 2× FRR). Automated test scripttest-m3-frr.shwith 5 test scenarios: route redistribution, split horizon, route injection, withdrawal propagation, DeletePath.
rustbgpd-rib:Routenow carriespeer: IpAddrfor tiebreaking and gRPC reporting. Accessor helpersorigin(),as_path(),local_pref(),med()extract attributes with RFC-appropriate defaults.rustbgpd-rib: Best-path comparison functionbest_path_cmp()implementing RFC 4271 §9.1.2 decision process: LOCAL_PREF → AS_PATH length → ORIGIN → MED → peer address. Deterministic MED (always-compare). Standalone function, notOrdonRoute. (ADR-0014)rustbgpd-rib: Property tests for best-path comparison (antisymmetry, transitivity, totality) via proptest.rustbgpd-rib:LocRibstruct — stores one best route per prefix, with incrementalrecompute()that returns whether the best path changed.rustbgpd-rib:RibManagernow owns aLocRiband recomputes best paths on every announce, withdraw, and peer-down event. Only affected prefixes are recomputed.QueryBestRoutesvariant added toRibUpdate.rustbgpd-api:ListBestRoutesgRPC endpoint with offset pagination, returning routes withbest: true.route_to_proto()now usesroute.peerfor thepeer_addressfield.- Interop validation: FRR 10.3.1 — M1 automated test script (15/15 pass),
ListBestRoutesreturns correct best routes with pagination. Reuses M1 containerlab topology (m1-frr.clab.yml).
-
Interop test script: peer restart test (test 4) now relies on watchfrr to auto-restart bgpd instead of manually running
/usr/lib/frr/bgpd -dwhich failed to load FRR's integrated config. Wait timeout increased to 90s to accommodate the 30s reconnect timer. -
rustbgpd-wire: Unknown NOTIFICATION error codes are now preserved asNotificationCode::Unknown(u8)instead of being silently mapped toCease. This fixes incorrect logging and metrics for NOTIFICATIONs with future or non-standard error codes. (ADR-0011) -
rustbgpd-transport: Usecode.as_u8()instead ofcode as u8cast for NOTIFICATION metric labels — more explicit and correct with the new enum representation. -
rustbgpd-transport: Fix hot reconnect loop when peer persistently rejects OPENs (e.g., ASN mismatch). Auto-reconnect now uses a deferred timer (connect-retry interval, default 30s) instead of firingManualStartimmediately. Discovered during malformed OPEN interop testing against FRR.
rustbgpd-wire:Ipv4Prefixtype with NLRI encode/decode per RFC 4271 §4.3 prefix-length encoding. Host bit masking, 0-32 range validation, Display impl.rustbgpd-wire: Path attribute decode/encode (decode_path_attributes,encode_path_attributes) supporting ORIGIN, AS_PATH (2-byte and 4-byte), NEXT_HOP, MED, LOCAL_PREF, and unknown attribute preservation. Extended Length flag support.rustbgpd-wire: UPDATE attribute validation (validate_update_attributes) separate from structural decode. Checks: duplicate types (3,1), unrecognized well-known (3,2), missing mandatory attributes (3,3), flag mismatch (3,4), invalid NEXT_HOP (3,8), malformed AS_PATH (3,11). (ADR-0012)rustbgpd-wire:ParsedUpdatestruct andUpdateMessage::parse()for combined NLRI + attribute decoding.rustbgpd-wire: Fuzz target for UPDATE decoder (decode_update), added to nightly fuzz CI.rustbgpd-rib: Adj-RIB-In implementation withRoute,AdjRibIn, andRibManager. Single tokio task owns all state via bounded mpsc channel (4096). Queries via embedded oneshot. NoArc<RwLock>. (ADR-0013)rustbgpd-fsm:UpdateValidationErrorevent — triggers NOTIFICATION and session teardown on RFC-violating UPDATEs.UpdateReceivedis now payloadless (transport handles UPDATE content).rustbgpd-transport: UPDATE processing pipeline inprocess_update(): structural decode → semantic validation → RIB insertion → FSM event. SendsPeerDownto RIB on session teardown.rustbgpd-api: gRPC server via tonic with proto codegen.ListReceivedRoutesRPC with offset pagination (default page_size=100). Other RibService RPCs return UNIMPLEMENTED.- Config:
grpc_addrfield in[global.telemetry](default127.0.0.1:50051) with SocketAddr validation. - Daemon: gRPC server spawned alongside metrics server and RIB manager.
- CI:
protobuf-compilerinstalled in GitHub Actions workflow. - Dockerfile:
protobuf-compileradded to builder stage for tonic-build. - Containerlab topology
m1-frr.clab.yml: FRR advertising 3 prefixes (192.168.1.0/24, 192.168.2.0/24, 10.10.0.0/16) for UPDATE/RIB interop testing. - Interop test script
test-m1-frr.sh: validates routes received, path attributes, withdrawal propagation, and RIB clearing on peer restart.
- Workspace with 7 crates: wire, fsm, transport, rib, policy, api, telemetry
- gRPC proto skeleton (
rustbgpd.v1package, all 5 services) - Containerlab interop topologies for FRR 10.x and BIRD 2.x
- Design document, RFC notes, interop matrix template
- Roadmap with market context and milestone plan (M0–M4)
rustbgpd-wire: OPEN, KEEPALIVE, NOTIFICATION, UPDATE encode/decoderustbgpd-wire: Capability parsing (4-byte ASN, MP-BGP, unknown pass-through)rustbgpd-wire: Strict 4096-byte message size enforcementrustbgpd-wire:DecodeError::to_notification()mapping for protocol errorsrustbgpd-wire: Property tests (encode(decode(x)) == xroundtrip)rustbgpd-fsm: RFC 4271 §8 state machine (all 6 states, full transition table)rustbgpd-fsm: Timer management as input events / output actionsrustbgpd-fsm: OPEN validation and capability negotiationrustbgpd-fsm: Exponential backoff on connect retry (30s–300s)rustbgpd-fsm: Property tests (no panics on arbitrary event sequences)rustbgpd-telemetry: Prometheus metrics (state transitions, flaps, notifications, messages)rustbgpd-telemetry: RIB metric stubs (registered at zero for M1)rustbgpd-telemetry: Structured JSON logging via tracing-subscriber with env-filterrustbgpd-transport: Single-task-per-peer Tokio TCP session runtimerustbgpd-transport: Length-delimited framing withpeek_message_lengthrustbgpd-transport: Timer management withpoll_timerfuture forselect!compatibilityrustbgpd-transport:PeerHandle/PeerCommandAPI for spawning and controlling sessionsrustbgpd-transport: Full OPEN/KEEPALIVE handshake, reconnection, and teardownrustbgpd-transport: Telemetry integration (state transitions, messages, notifications)- Daemon entrypoint: TOML config loading, peer spawning, graceful SIGTERM shutdown
- Prometheus
/metricsHTTP endpoint served viatokio::net::TcpListener - Config module (
src/config.rs) with validation (router ID, neighbor addresses, hold time) - CI workflow (
.github/workflows/ci.yml): fmt, clippy, test on push/PR - Nightly fuzz CI (
.github/workflows/fuzz.yml): 5-minute wire decoder fuzzing rustbgpd-wire: Negative property tests — 5 corruption strategies (bit flip, truncation, insertion, overwrite, trailing garbage) verify decoder never panicsrustbgpd-wire: Fuzz harness fordecode_messagevia cargo-fuzz / libfuzzer- Malformed OPEN interop test config (
rustbgpd-frr-badopen.toml)