feat(bfd): gRPC GetBfdSessions + rustbgpctl bfd (ADR-0067 PR3 — operator surface)#252
Merged
Conversation
Operator inspection surface for BFD sessions — observability before the RFC
5882 BGP coupling slice.
- proto: BfdService.GetBfdSessions, BfdSession + BfdSessionState enum, with an
optional peer-address filter. Also lands the event contract for the next
step (EVENT_CATEGORY_BFD, BGP_EVENT_TYPE_BFD_SESSION_{UP,DOWN,STATE_CHANGED},
BfdSessionEvent, BgpEvent.bfd oneof) so the proto is stable now.
- crates/api: bfd_service.rs (snapshot-provider, mirrors rib_service's FIB
snapshot), registered on both TCP + UDS listeners; ADR-0064 authz tier
(SensitiveRead) + method-inventory/count tests updated; event_service
filters accept the BFD category/types.
- daemon: BfdService snapshot wired from the actor's status watch (the
receiver kept from PR2), with BFD state/diagnostic → proto conversions.
- CLI: `rustbgpctl bfd [list|show <peer>]`, JSON + table output.
Tests: bfd_service unit tests (list/filter/empty), authz matrix coverage +
tier counts. Next on this branch: actor-emitted BFD events into the unified
WatchEvents stream.
… next Per review: make the ADR-0067 staged plan and CHANGELOG explicit that the operator surface slice ships read-only inspection (GetBfdSessions + rustbgpctl bfd) and the BFD event proto contract, but does NOT yet stream live BFD events over WatchEvents — actor event emission is a separate follow-up. Splits the ADR's step 3 into 3a (inspection, shipped) and 3b (emission, next).
There was a problem hiding this comment.
Pull request overview
Adds an operator-facing inspection surface for single-hop BFD (ADR-0067) by exposing session state over gRPC and surfacing it in rustbgpctl, while also landing the (future) BFD event proto contract and expanding EventService filter support.
Changes:
- Introduce
BfdService.GetBfdSessionsand related BFD proto types (sessions, states, event contract). - Wire the daemon’s BFD status watch channel into the gRPC API server as a live snapshot source.
- Add
rustbgpctl bfd (list|show <peer>)with table/JSON output, and update authz + method inventory.
Reviewed changes
Copilot reviewed 14 out of 14 changed files in this pull request and generated 3 comments.
Show a summary per file
| File | Description |
|---|---|
| src/main.rs | Wires BFD actor status into API snapshot provider; adds BFD state/diagnostic conversions. |
| proto/rustbgpd.proto | Adds BFD service/messages/enums and BFD event contract fields/types. |
| docs/grpc-method-inventory.json | Updates method inventory counts and adds BfdService.GetBfdSessions. |
| crates/cli/src/main.rs | Adds rustbgpctl bfd command routing and subcommands. |
| crates/cli/src/commands/mod.rs | Exposes the new bfd command module. |
| crates/cli/src/commands/bfd.rs | Implements BFD list/show output (table + JSON) via gRPC. |
| crates/api/src/server.rs | Registers BfdService on TCP + UDS listeners; threads snapshot through serve config. |
| crates/api/src/lib.rs | Exports the new bfd_service module. |
| crates/api/src/event_service/filters.rs | Extends EventService filter parsing to accept BFD category/event types. |
| crates/api/src/event_service/convert.rs | Extends stream-lag category string mapping to include BFD. |
| crates/api/src/bfd_service.rs | Implements BfdService backed by a daemon-provided snapshot closure + unit tests. |
| crates/api/src/authz.rs | Adds authz tier entry for BfdService.GetBfdSessions and updates method counts/tests. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
Comment on lines
+43
to
+47
| let filter = request.into_inner().peer_address; | ||
| let mut sessions = (self.snapshot)(); | ||
| if !filter.is_empty() { | ||
| sessions.retain(|s| s.peer_address == filter); | ||
| } |
Comment on lines
+608
to
+611
| // stable snake_case name: "none", "control_detection_time_expired", | ||
| // "echo_function_failed", "neighbor_signaled_session_down", | ||
| // "forwarding_plane_reset", "path_down", "concatenated_path_down", | ||
| // "administratively_down", "reverse_concatenated_path_down". |
Comment on lines
391
to
394
| | proto::EventCategory::Evpn | ||
| | proto::EventCategory::Bfd => { | ||
| parsed.insert(category as i32); | ||
| } |
…s, doc reserved diag Three findings on the surface PR: - GetBfdSessions parses peer_address to IpAddr (invalid_argument on bad input) and compares canonical forms, so equivalent IPv6 textual representations match — mirrors NeighborService/RibService filter handling. - WatchEvents now *rejects* EVENT_CATEGORY_BFD and the BFD event types with "BFD event streaming is not yet available" instead of accepting a filter that yields an empty/immediately-closed stream. The proto contract stays defined; PR3b flips these to accept + wires the actual stream. - proto BfdSession.diagnostic documents "reserved" as a possible value (the daemon maps unassigned RFC 5880 diagnostic codes to it). Tests: invalid-peer rejection + IPv6 canonical-match in bfd_service.
| // peer addresses are already `IpAddr::to_string()` (canonical). | ||
| let wanted = filter | ||
| .parse::<std::net::IpAddr>() | ||
| .map_err(|_| Status::invalid_argument(format!("invalid peer_address {filter:?}")))? |
Comment on lines
+394
to
+401
| // The BFD event proto contract exists, but the actor does not yet | ||
| // emit into WatchEvents (ADR-0067 step 3b). Reject the filter rather | ||
| // than hand back an empty/immediately-closed stream. | ||
| proto::EventCategory::Bfd => { | ||
| return Err(Status::invalid_argument( | ||
| "BFD event streaming is not yet available", | ||
| )); | ||
| } |
Use "invalid peer_address: {e}" to match RibService/NeighborService so
clients get consistent InvalidArgument text across services.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Operator inspection surface for single-hop BFD (ADR-0067), building on the
merged actor (PR2, #251). Observability before the RFC 5882 BGP coupling slice.
In this PR
BfdService.GetBfdSessions→BfdSession(peer, state,diagnostic, strict) +
BfdSessionStateenum, with an optional peer-addressfilter. Also lands the event contract so the proto is stable now and the
emission wiring (PR3b) won't churn it:
EVENT_CATEGORY_BFD,BGP_EVENT_TYPE_BFD_SESSION_{UP,DOWN,STATE_CHANGED},BfdSessionEvent,BgpEvent.bfdoneof.bfd_service.rs(snapshot-provider, mirrorsrib_service'sFIB snapshot; parses the peer filter to
IpAddrand matches canonicalforms), registered on both TCP + UDS listeners; ADR-0064 authz tier
(
SensitiveRead) + method-inventory JSON + count tests updated.BfdServicesnapshot is wired from the actor's statuswatchchannel (the receiver retained in PR2), with BFD state/diagnostic →proto conversions.
rustbgpctl bfd [list | show <peer>], JSON + table output.Scope note — inspection only; events do not stream yet
The plan's PR3 groups surface (gRPC + CLI) with event emission (actor →
WatchEvents). This PR is the surface half: a complete, mergeable,read-only inspection unit. BFD events do not yet stream —
WatchEventsexplicitly rejects
EVENT_CATEGORY_BFDand the BFD event types with a"not yet available" error rather than handing back an empty stream. The event
proto contract is landed here so the emission half (PR3b) is purely wiring:
the actor emits an internal
BfdRuntimeEvent→ bridge →EventServiceBFDstream, and the filter reject flips to accept. PR3b changes the merged actor's
spawnsignature and adds a stream toevent_service, so it is a cleanerseparate review.
Testing
cargo fmt/clippy --workspace -D warnings/test --workspacegreen.bfd_serviceunit tests (list / peer-filter / invalid-input / IPv6 canonicalmatch / empty), ADR-0064 authz matrix coverage + tier-count + inventory tests.
Next: PR3b (BFD event emission into
WatchEvents), then PR4 (RFC 5882 coupling)and PR5 (M51 interop).