Document class: REFERENCE.
This reference documents rustbgpd's gRPC API.
rustbgpd exposes twelve native rustbgpd.v1 gRPC services (Global, Config,
Neighbor, Policy, PeerGroup, Rib, BFD, RPKI, Event, Injection, Control, Evpn) plus the
gnmi.gNMI OpenConfig service over one or more configured listeners. The
gNMI surface is read-only telemetry (Capabilities / Get / Subscribe) plus an
operator-tier Set subset — transaction-backed create/update/delete for static
numbered BGP neighbors, peer-group catalog entries, dynamic-neighbor prefixes,
and the commit-confirmed extension through ADR-0076. Unsupported Set paths
return UNIMPLEMENTED. The default listener is a local Unix domain socket at
/var/lib/rustbgpd/grpc.sock.
For same-host administration, prefer UDS:
grpcurl -plaintext -unix /var/lib/rustbgpd/grpc.sock \
-import-path . -proto proto/rustbgpd.proto \
rustbgpd.v1.GlobalService/GetGlobalThe remaining examples below use
grpcurl against an explicit local
TCP listener for readability. Those examples require grpc_tcp to be
enabled, and under the default tier authorization a TCP listener must
authenticate — bearer token plus a role-mapped principal, or native mTLS:
[global.telemetry.grpc_tcp]
address = "127.0.0.1:50051"
token_file = "/etc/rustbgpd/grpc.token"
principal = "automation.example"
[security.grpc.roles]
"automation.example" = "operator"For readability the examples omit the accompanying
-H "authorization: bearer <token>" metadata header.
The proto definition lives at proto/rustbgpd.proto.
For a worked client in a general-purpose language — stub generation, mTLS with
composed bearer-token call credentials, per-call deadlines, and the
authorization tiers each call needs — see
examples/python-client/.
The project-wide API remains alpha outside the explicit native-gRPC methods listed in the narrow v1 route-server / route-reflector contract. That machine inventory pins method names, streaming modes, and top-level request/response messages; a service or RPC existing in this reference does not by itself make it v1-stable.
| Service | RPCs | Purpose |
|---|---|---|
GlobalService |
GetGlobal |
Daemon identity |
ConfigService |
DiffRuntimeConfig, PlanConfigTransaction, StreamPlanConfigTransaction, StreamApplyConfigTransaction, ApplyConfigTransaction, ConfirmConfigTransaction, AbortConfigTransaction, GetConfigTransactionStatus, GetEffectiveConfig, ListConfigHistory, RollbackConfigTransaction |
Candidate-vs-live config diff, effective running config with defaults resolved (selected default-empty policy lists omitted) and secrets redacted, plus the v1 config-transaction lifecycle: validate/plan, commit/apply (incl. commit-confirmed), confirm, abort, status, and the bounded recorded config history with Junos-style rollback N through the same transaction executor; the outside-v1 streams provide bounded large-candidate plan and apply ingress |
NeighborService |
AddNeighbor, DeleteNeighbor, ListNeighbors, GetNeighborState, EnableNeighbor, DisableNeighbor, SoftResetIn, RefreshOutbound, ReplayOutbound, ResetNeighbor, SetGracefulShutdown, AddDynamicNeighbor, DeleteDynamicNeighbor, ListDynamicNeighbors |
Peer lifecycle, inbound soft reset, single-peer outbound re-advertisement, outside-v1 administrative session reset, RFC 8326 graceful-shutdown toggle, and dynamic-neighbor CRUD — AddDynamicNeighbor / DeleteDynamicNeighbor add and remove [[dynamic_neighbors]] prefix ranges at runtime (queued to the config file), ListDynamicNeighbors for visibility |
PolicyService |
ListPolicies, GetPolicy, SetPolicy, DeletePolicy, ListNeighborSets, GetNeighborSet, SetNeighborSet, DeleteNeighborSet, GetGlobalPolicyChains, GetNeighborPolicyChains, SetGlobalImportChain, SetGlobalExportChain, ClearGlobalImportChain, ClearGlobalExportChain, SetNeighborImportChain, SetNeighborExportChain, ClearNeighborImportChain, ClearNeighborExportChain, ExplainImportPolicy, ListRejectedRoutes, TestPolicy, GetPolicyStats, GetValidationPolicyPosture |
Named policy CRUD, neighbor sets, global/per-neighbor chain attachment, import-policy diagnostics, read-only candidate-policy dry runs, live counters, and bounded invalid-validation disposition posture |
PeerGroupService |
ListPeerGroups, GetPeerGroup, SetPeerGroup, DeletePeerGroup, SetNeighborPeerGroup, ClearNeighborPeerGroup |
Peer-group CRUD and neighbor membership assignment |
RibService |
ListReceivedRoutes, ListBestRoutes, ListAdvertisedRoutes, ExplainAdvertisedRoute, ExplainBestPath, LookupBestPath, ListFlowSpecRoutes, ListEvpnRoutes, ListReceivedEvpnRoutes, ListAdvertisedEvpnRoutes, ExplainEvpnRoute, ListBgpLsRoutes, ListTopologyNodes, ListTopologyLinks, ListOrrStatus, ListVpnRoutes, ListRtcRoutes, ListLabeledRoutes, ListBlackholeDiscards, ListFibRoutes, ListFibTables, SetFibTable, DeleteFibTable, ListRouteEvents |
Query-only RIB route surfaces (incl. EVPN, BGP-LS, VPNv4/v6, RT-Constrain, and labeled-unicast), the RFC 9107 ORR / BGP-LS topology read surface (ListTopologyNodes / ListTopologyLinks / ListOrrStatus), BLACKHOLE discard status, paginated FIB status, runtime FIB-table CRUD, exact explain plus outside-v1 global LPM, and recent route-event history; live route streaming is owned by EventService.WatchEvents / SubscribeFromEvent |
BfdService |
GetBfdSessions |
BFD session inspection (single-hop and multihop) for configured static neighbors |
RpkiService |
ValidateRouteOrigin, ListCaches, LookupAspa, VerifyAsPath |
Bounded point validation and configured RTR-cache accepted-epoch inventory, plus the outside-v1 ASPA diagnostics: a bounded merged-provider lookup for one customer ASN and a literal AS_PATH verification with an explicit local role and neighbor ASN |
EventService |
WatchEvents, SubscribeFromEvent, ListEvpnEvents, ListSessionEvents, ListPolicyEvents |
Unified live stream for route, session lifecycle, BGP NOTIFICATION metadata, policy mutation, EVPN route events, BFD session events, and FIB / BLACKHOLE dataplane status-row summary events, with stream_lagged warnings for bounded-source backpressure; durable cursor replay via SubscribeFromEvent when [event_history].enabled = true; plus bounded after-the-fact EVPN, session-lifecycle, and policy-mutation history. Per-MAC EVPN dataplane categories remain follow-up work |
InjectionService |
AddPath, DeletePath, AddFlowSpec, DeleteFlowSpec, AddEvpnRoute, DeleteEvpnRoute |
Programmatic route, FlowSpec, and EVPN injection |
ControlService |
CheckLiveness, GetHealth, GetMetrics, Shutdown, TriggerMrtDump |
Health, metrics, lifecycle, MRT dumps |
EvpnService |
GetEvpnRuntime, ListEvpnInstances, ListEvpnNexthops, ListEthernetSegments, ListIpVrfs, ListManagedNetdevs, GetIpVrf, ListDuplicateMacQuarantines, ClearDuplicateMacQuarantine, SetEthernetSegmentDrain, ApplyEvpnRuntime |
Local EVPN VTEP instance state, ADR-0059 FDB-nexthop ownership, ADR-0083/0085 Ethernet Segment multi-homing diagnose state, symmetric IRB (Type-5 / L3VNI) IP-VRF readiness / route counters, ADR-0091 managed-netdev lifecycle/status, duplicate-MAC quarantine status and clear, ADR-0084 Ethernet Segment drain for access-circuit maintenance, and ADR-0063 runtime model status / apply |
gnmi.gNMI |
Capabilities, Get, Set, Subscribe |
OpenConfig BGP telemetry subset (Get / Subscribe) plus a transaction-backed Set subset (static numbered-neighbor create/update/delete, peer-group config, and dynamic-neighbor-prefix entries, with commit-confirmed via ADR-0076; unsupported paths UNIMPLEMENTED; see gNMI); served on UDS and mTLS TCP listeners |
The daemon supports three deployment patterns for the gRPC surface:
| Pattern | Config | Auth |
|---|---|---|
| Unix domain socket | [global.telemetry.grpc_uds] with path + mode |
File-system permissions on the socket path |
| Plaintext TCP + bearer token | [global.telemetry.grpc_tcp] with address and optional token_file |
Bearer token in the authorization: bearer <value> metadata header (when token_file is set) |
| mTLS TCP | [global.telemetry.grpc_tcp] with tls_cert_file + tls_key_file + tls_client_ca_file |
Client certificate signed by the configured CA |
The mTLS path is the recommended default for any non-loopback gRPC
listener. All three TLS fields are required together; partial
configuration is rejected at Config::load. There is no
"TLS-without-mTLS" half-mode by design — when TLS is enabled the
daemon presents the server certificate, requires every client to
present a certificate signed by tls_client_ca_file, and rejects
unverified clients at the TLS layer before any gRPC handler runs.
Config loading checks file readability and PEM framing. Both --check and
--check --strict also run startup credential staging: they parse the server
certificate, private key, and client CA bundle and check the cert/key match
without binding listeners. After staging replacement files at the configured
paths, run rustbgpd --check --strict /etc/rustbgpd/config.toml before SIGHUP
or restart. This checks the files at that moment; it does not prove client
trust or hostname matching, or cover subsequent file changes. A positive
tls_expiry_warning_seconds adds expiry warnings from that staged generation;
the default 0 disables those warnings. See
native gRPC certificate expiry
for metrics, successful-client metadata logs, and bundle-date limits.
Native mTLS accepts TLS 1.2 and TLS 1.3 using the rustls/ring default cipher
suites; protocol versions and cipher suites have no configuration overrides.
A client whose certificate is trusted can complete TLS without a mapped
principal, but its RPCs receive PERMISSION_DENIED from role authorization.
Valid credential bytes behind unchanged TLS paths rotate on SIGHUP. Adding or removing TLS, changing a configured TLS path, or changing TLS/auth mode remains restart-required; runtime config pins that drift to the live values until the daemon is restarted.
Native gNMI is available on the local UDS listener and on TCP listeners only
when mTLS is configured. Plaintext or bearer-token-only TCP listeners serve the
native rustbgpd.v1 API but do not register gnmi.gNMI. See
GNMI.md for the operator-facing OpenConfig path list and gnmic
examples.
# mTLS client example with grpcurl
grpcurl \
-cacert /etc/rustbgpd/server-ca.pem \
-cert /etc/operator/client.pem -key /etc/operator/client.key \
-import-path . -proto proto/rustbgpd.proto \
rustbgpd.example.net:50051 \
rustbgpd.v1.GlobalService/GetGlobal# mTLS gNMI client example with gnmic
gnmic \
--address rustbgpd.example.net:50051 \
--tls-ca /etc/rustbgpd/server-ca.pem \
--tls-cert /etc/operator/client.pem \
--tls-key /etc/operator/client.key \
--tls-server-name rustbgpd.example.net \
capabilitiesPer-listener access_mode = "read_only" rejects mutating RPCs
(neighbor add/delete, route injection, policy changes, peer-group
changes, shutdown, MRT trigger) with PERMISSION_DENIED. Use this
on a dedicated monitoring listener that exposes the read surface
without the mutating control plane.
Each configured listener can independently set access_mode = "read_write" or
"read_only". Read-only listeners allow query and watch RPCs but reject all
mutating RPCs with PERMISSION_DENIED.
read_only is a listener-level authorization boundary. It does not create
per-user roles: every client accepted by that listener gets the same read-only
surface. Use a separate read_write listener for automation that needs to
mutate daemon state.
docs/reference/grpc-method-inventory.md, docs/reference/grpc-method-inventory.json, and
crates/api/src/authz.rs classify every RPC into read, sensitive_read,
mutating, or operator_only for ADR-0064. The JSON file is the
machine-readable export for auditors and generated clients; the Rust authz
tests verify it against the source-of-truth matrix. The runtime records tier
decisions for every RPC via structured grpc_authz logs and
bgp_grpc_authz_decisions_total{tier,result,authn,access_mode}. Listener
max_tier caps are always enforced. The same runtime layer also enforces the
authenticated principal's configured role ceiling before the handler runs —
unconditionally: "tier" has been the default since v0.24.0 and is the only
mode since v0.63.0. The former "legacy" migration mode (roles recorded as
audit context but never enforced) was removed; a config that still sets it is
rejected at boot, --check, and reload with a migration message.
Forwarded calls emit result-aware labels such as result="handler_ok" or
result="handler_invalid_argument" after the handler returns. Rejected calls use
bounded pre-handler labels: result="listener_tier_denied" means the method was
rejected before the handler ran, and result="authn_failed" means an over-cap
bearer-token request failed authentication before tier details were disclosed.
Tier-mode denials use result="principal_unmapped" when the authenticated
principal has no role entry and result="role_tier_denied" when the principal's
role is below the method tier.
Credential-bearing request summaries are masked before entering grpc_authz
logs; DiffRuntimeConfigRequest.candidate_toml,
PlanConfigTransactionRequest.candidate_toml, and
ApplyConfigTransactionRequest.candidate_toml are always summarized as
redacted metadata. StreamPlanConfigTransaction logs only framing version,
bounded counts, token presence, and outcome; StreamApplyConfigTransaction
adds only expected-token and confirm-handle presence — never candidate bytes, digest,
plan token, path, or spool name. Transaction apply comments are not logged verbatim, and
SetPeerGroup logs MD5 state without the MD5 value.
Operators declare [security.grpc.roles] and set explicit listener
principal labels for bearer-token TCP and explicitly named UDS listeners;
those labels are the principal strings looked up in [security.grpc.roles].
An owner-only UDS without a principal retains its implicit local-operator
role even when a bearer token is required. Token-protected UDS listeners use
authn="bearer_token"; permissions-only UDS listeners use uds for an explicit
principal or uds_owner for the implicit identity. Their parent directories
must meet the UDS path requirements.
Native mTLS listeners derive the audit principal from the client certificate
using ADR-0064 precedence: rustbgpd: URI SAN, then email SAN, then Subject
CN. Extracted principal values must fit the bounded
audit label form and must not contain embedded control characters; unsupported
values fall back to mtls-unresolved, which cannot be mapped in
[security.grpc.roles] and is therefore denied.
Tier enforcement is the default since v0.24.0 and mandatory since v0.63.0.
When upgrading from an older release, stage [security.grpc.roles] plus
explicit listener principals and validate the candidate config with
rustbgpd --check. The implicit default UDS listener needs no staging: it is
owner-only, so its clients are authorized as the implicit
local-operator principal at operator tier without a roles entry — a config
whose only management surface is a local socket needs no [security.grpc]
block at all. Only group/world-accessible UDS sockets require an explicit
principal mapped in [security.grpc.roles].
enforcement = "legacy" was removed in v0.63.0 and from the typed schema in
v0.65; the exact retired value still receives a migration diagnostic. Earlier
editions of this document projected a two-minor/90-day
compatibility floor (approximately 2026-10-09) as the earliest eligibility
for removal; that guidance is superseded. The removal landed earlier as an
explicit owner decision under the project's pre-1.0 alpha stability posture
(see README/ROADMAP on version stability), taken once the implicit
local-operator identity removed the migration burden for local-only
deployments: for them the migration is deleting the [security.grpc] block.
A stuck operator keeps a named-principal setup working with three lines —
enforcement = "tier" plus a [security.grpc.roles] entry mapping the
listener's principal; the rejection message carries the exact paste block.
docs/reference/security.md covers deployment hardening for this surface,
and ADR-0064 records the tier model itself and
which of its slices remain open.
Operational collection, retention, query examples, and resource-abuse guardrails
for grpc_authz logs and the related Prometheus metrics live in
docs/reference/operations.md.
| Service | Read-only RPCs | Mutating RPCs rejected on read_only |
|---|---|---|
GlobalService |
GetGlobal |
— |
ConfigService |
DiffRuntimeConfig, PlanConfigTransaction, StreamPlanConfigTransaction, GetConfigTransactionStatus, GetEffectiveConfig, ListConfigHistory |
StreamApplyConfigTransaction, ApplyConfigTransaction (pure [[fib_tables]], pure [[dynamic_neighbors]], static [[neighbors]] add/delete/modify, catalog-only policy/neighbor-set/peer-group/global-chain changes, pure live policy-chain impact for static neighbors and accepted dynamic peers, or peer-group/session reshape impact for static members and live dynamic sessions; mixed or unsupported candidates rejected without mutation), ConfirmConfigTransaction, AbortConfigTransaction, RollbackConfigTransaction |
NeighborService |
ListNeighbors, GetNeighborState, ListDynamicNeighbors |
AddNeighbor, DeleteNeighbor, EnableNeighbor, DisableNeighbor, SoftResetIn, RefreshOutbound, ReplayOutbound, ResetNeighbor, AddDynamicNeighbor, DeleteDynamicNeighbor, SetGracefulShutdown |
PolicyService |
ListPolicies, GetPolicy, ListNeighborSets, GetNeighborSet, GetGlobalPolicyChains, GetNeighborPolicyChains, ExplainImportPolicy, ListRejectedRoutes, TestPolicy, GetPolicyStats, GetValidationPolicyPosture |
SetPolicy, DeletePolicy, SetNeighborSet, DeleteNeighborSet, SetGlobalImportChain, SetGlobalExportChain, ClearGlobalImportChain, ClearGlobalExportChain, SetNeighborImportChain, SetNeighborExportChain, ClearNeighborImportChain, ClearNeighborExportChain |
PeerGroupService |
ListPeerGroups, GetPeerGroup |
SetPeerGroup, DeletePeerGroup, SetNeighborPeerGroup, ClearNeighborPeerGroup |
RibService |
All read/list/explain RPCs (incl. ListFibTables) |
SetFibTable, DeleteFibTable |
EventService |
All RPCs | None |
EvpnService |
GetEvpnRuntime, ListEvpnInstances, ListEvpnNexthops, ListEthernetSegments, ListIpVrfs, ListManagedNetdevs, GetIpVrf, ListDuplicateMacQuarantines |
ClearDuplicateMacQuarantine, SetEthernetSegmentDrain, ApplyEvpnRuntime |
BfdService |
GetBfdSessions |
None |
RpkiService |
ValidateRouteOrigin, ListCaches, LookupAspa, VerifyAsPath |
None |
gnmi.gNMI |
Capabilities, Get, Subscribe |
Set (operator-only; transaction-backed OpenConfig subset — static numbered-neighbor neighbor-address/peer-as/description/peer-group create/update/delete, peer-group catalog entries, dynamic-neighbor prefixes, and the commit-confirmed extension via ADR-0076; unsupported paths return UNIMPLEMENTED) |
InjectionService |
None | AddPath, DeletePath, AddFlowSpec, DeleteFlowSpec, AddEvpnRoute, DeleteEvpnRoute |
ControlService |
CheckLiveness, GetHealth, GetMetrics |
Shutdown, TriggerMrtDump |
The API uses gRPC status codes consistently across services:
| Code | Meaning |
|---|---|
UNAUTHENTICATED |
Listener authentication failed: missing bearer token, non-ASCII authorization metadata, or a token mismatch |
PERMISSION_DENIED |
The request reached a read-only listener but called a mutating RPC, the RPC method tier exceeds the listener max_tier ceiling, the principal is unmapped in tier mode, or the principal's role is below the method tier |
INVALID_ARGUMENT |
Client-supplied request data is malformed, missing, out of range, uses an unsupported enum value, or combines incompatible filters |
NOT_FOUND |
A named or targeted resource does not exist: peer, policy, neighbor set, peer group, IP-VRF, route-event target, or injected route |
ALREADY_EXISTS |
A create request targets an existing resource, such as duplicate neighbor creation or an exact-duplicate dynamic-neighbor range |
FAILED_PRECONDITION |
The request is valid but the daemon is not in a state where it can complete it, such as MRT export being disabled or a policy object still being referenced |
DEADLINE_EXCEEDED |
A bounded actor or session read did not complete before its operation deadline; this does not mean the targeted runtime state is absent |
UNAVAILABLE |
A required actor or session task exited, or a required actor, command channel, or persistence queue is unavailable, closed, or back-pressured |
UNIMPLEMENTED |
The RPC is reserved in the protobuf but runtime support has not shipped yet, the connected daemon predates the RPC, or a gNMI path or extension is outside the supported subset |
INTERNAL |
An internal daemon actor, metrics encoder, or RIB boundary failed unexpectedly after the request passed validation |
Runtime-config mutations that fail after transient runtime changes, but whose
forward effects are then fully compensated, preserve the original gRPC status
code and append the original message after this exact prefix:
runtime effects were fully compensated; retry may repeat transient runtime changes:.
When an original message is present, exactly one ASCII space separates it from
that prefix; an empty original message adds no trailing space.
Their trailing metadata also includes
rustbgpd-runtime-config-outcome: fully-compensated. The staged persistent
candidate is discarded, so the runtime and TOML remain at their prior state.
The marker is a retry warning: repeating the request can repeat those transient
runtime changes even though the reported attempt left no final-state change.
Read budgets depend on the operation; there is no server-wide read timeout.
| Read path | Existing backend wait budget |
|---|---|
| Peer-manager operator reads | 2 s per request to the peer manager, including channel admission and reply |
ListNeighbors / GetNeighborState RIB summaries |
A separate 2 s for the RIB summary admission and reply, after the peer-manager read; this is not one 2 s end-to-end RPC budget |
GetPolicyStats |
One absolute 2 s deadline shared by peer validation, export, import and dataset backend waits; admission consumes that budget and no stage resets it |
GetHealth |
One 200 ms internal core-probe budget shared by peer-manager and RIB snapshot queries; failures return INTERNAL |
| General RIB listing and RIB explain reads | No fixed server-side timeout in the shared RIB read helper; callers set their own deadlines and cancel abandoned reads |
General RIB reads can wait behind policy-transition consistency fences. A raw gRPC caller with no deadline can remain waiting for that fence to clear. The CLI bounds ordinary unary reads, including these RIB reads, at 30 s; methods with separate documented budgets retain those allowances. Custom clients should supply their own deadline. Cancellation releases the waiting reply and is checked by actor work; it does not preempt a synchronous work unit already executing. Route work retains priority. These backend budgets do not promise end-to-end response latency, including synchronous response construction, transport and client scheduling. The separate measured end-to-end soak acceptance gates remain unchanged.
OpenConfig BGP telemetry surface (ADR-0070). The supported subset is
deliberately narrow: Capabilities, Get, and Subscribe (ONCE, POLL,
STREAM SAMPLE, and STREAM ON_CHANGE — the last is scoped to the neighbor
session-state leaf, requires [event_history] enabled, and returns
FAILED_PRECONDITION otherwise) for global and neighbor state under the
default network instance. Set is operator-only and supports the first durable
config subset: static, numbered BGP neighbor create/update/delete for
neighbor-address, peer-as, description, and peer-group, peer-group
catalog entries, and dynamic-neighbor prefixes, plus graceful-restart config
leaves. Supported Set edits are
translated into full candidate TOML and fed through PlanConfigTransaction /
ApplyConfigTransaction; the standard gNMI commit-confirmed extension maps to
the same confirm / abort lifecycle as native config transactions. Unsupported
paths return UNIMPLEMENTED instead of bypassing the transaction model. See
GNMI.md for the full ON_CHANGE v1 scope (initial sync,
reconnect-no-replay, lag → DATA_LOSS) and Set path matrix.
Network gNMI is served only on mTLS TCP listeners. The UDS listener also exposes the service as a local-only extension.
For the full supported path list, setup guidance, troubleshooting table, and
tested gnmic commands, see GNMI.md.
gnmic \
--address rustbgpd.example.net:50051 \
--tls-ca /etc/rustbgpd/server-ca.pem \
--tls-cert /etc/operator/client.pem \
--tls-key /etc/operator/client.key \
--tls-server-name rustbgpd.example.net \
get \
--encoding json_ietf \
--type STATE \
--path '/network-instances/network-instance[name=DEFAULT]/protocols/protocol[identifier=BGP][name=BGP]/bgp/global/state'Daemon identity and configuration.
| RPC | Description |
|---|---|
GetGlobal |
Returns ASN, router ID, listen port, host TCP-AO capability probe status and detail, and the Unix-epoch-seconds timestamp of the last successfully accepted full policy generation |
# Get daemon identity
grpcurl -plaintext -import-path . -proto proto/rustbgpd.proto \
localhost:50051 rustbgpd.v1.GlobalService/GetGlobalpolicy_generation_loaded_timestamp_seconds is the Unix epoch second at which
the daemon last accepted a complete policy generation — initial load, SIGHUP, or
a config transaction. It is 0 until the first generation is accepted, and a
rejected load never advances it (the prior generation stays live and the
timestamp freezes), so a caller can prove a reload was accepted rather than
merely attempted. It is the same value the
bgp_policy_generation_loaded_timestamp_seconds Prometheus gauge exports; see
"Policy artifact freshness" in OPERATIONS.md for the staleness
alert expressions.
tcp_ao_support is a read-only Linux capability probe for RFC 5925 TCP-AO. It
reports whether the host kernel accepts the TCP-AO socket primitive that
rustbgpd uses for static-neighbor and dynamic-prefix startup key installation.
If a tcp_ao key is configured on a host where the primitive fails,
listener setup aborts startup, and active-open setup rejects that connect
attempt without falling back to unauthenticated TCP. Dynamic-range keys are
config-file-only: runtime range CRUD rejects protected ranges and overlaps.
SIGHUP can append a non-preferred successor generation; selection,
deprecation, deletion, and protected-owner CRUD are not exposed.
tcp_ao_detail carries human-readable probe detail alongside it, populated only
when tcp_ao_support is TCP_AO_SUPPORT_UNSUPPORTED or
TCP_AO_SUPPORT_PROBE_FAILED, and empty otherwise.
NeighborState.authentication reports the effective protected transport as
PLAINTEXT, MD5, or TCP_AO. For direct dynamic-prefix TCP-AO sessions,
that identity comes from the validated accepted socket rather than a synthesized
per-neighbor key configuration. When socket inspection succeeds for a connected
TCP-AO session, NeighborState.tcp_ao contains current/RNext KeyIDs,
Linux verification/error counters, and an ordered keys inventory. Each key
row is deliberately redacted: it contains only peer/prefix, directional IDs,
algorithm, current/RNext and local rollover flags, optional Linux VRF
L3-master ifindex, and per-key counters. The VRF ifindex is not an IPv6
link-local scope ID; absence means the MKT is VRF-unbound, not default-VRF
bound. The inventory never contains key material, key length, a hash, or a
fingerprint. The daemon
refreshes TCP_AO_INFO and TCP_AO_GET_KEYS from the live socket for each
neighbor state query, publishes them only as one internally consistent result,
and clears it on disconnect or inspection failure; it never serves an older healthy snapshot as
a fallback. The counters are cumulative for the lifetime of that TCP socket,
so any non-zero error counter keeps the socket DEGRADED until reconnect.
NeighborState.tcp_ao_health is NOT_APPLICABLE for plaintext and MD5 peers,
UNAVAILABLE when TCP-AO protects the session but there is no socket snapshot
(including disconnect, inspection failure, or persistent INFO/inventory
inconsistency), HEALTHY when the published snapshot has both current/RNext
key-validity flags mapped to a nonempty, internally consistent live inventory,
neither active key is deprecated, and no error counters, and DEGRADED when
either key-validity flag is absent, an active key is deprecated, or any bad,
key-not-found, unsigned-required, or dropped-ICMP counter is non-zero. An
inconsistent INFO/inventory pair is never published as degraded state.
For TCP-AO peers, NeighborState.tcp_ao_desired_generation,
tcp_ao_applied_generation, tcp_ao_rotation_phase, and
tcp_ao_rotation_error expose the secret-free ordered-rotation state. A phase
of add_only_failed retains the prior selectable keys and is retryable with
another SIGHUP. During add_only, protected accepts may carry either the
globally applied generation or the exactly proved desired generation. During
add_only_failed, only the applied generation may enter the peer manager;
fully installed but uncommitted children remain fail-closed. A partial listener
mutation may reject affected passive accepts until retry or restart.
During selecting and awaiting_peer, the applied or exactly adjacent desired
generation may enter; selection_failed admits only the applied generation.
awaiting_peer is a one-shot result, not actor polling: the next identical
SIGHUP re-observes the same generation. Selection sets only local RNext, never
Linux Current, and predecessor deprecation metadata commits only after the
whole affected session cohort has observed verified successor traffic beyond its
per-socket pre-selection baseline.
During deleting, the applied or exactly adjacent desired generation may
enter; a desired-generation accept is projected from the coordinator's retained
immutable survivor inventory. delete_failed admits only the applied
generation. Deletion removes only deprecated MKTs that are neither Current nor
RNext; an ambiguous partial session mutation discards the complete changed
cohort before the failure is exposed.
NeighborState.effective_distribution_mode reports the live RIB selection
surface. When multiple mechanisms apply, its primary-label precedence is
ADD_PATH > PER_CLIENT_BEST > ORR > SINGLE_BEST. It is UNKNOWN when the
peer has no active outbound registration; UNSPECIFIED remains the
backward-compatible value returned by older servers. This field is independent
of the diagnostic update_group label. A peer eligible for both per-client-best
and ORR reports PER_CLIENT_BEST because it is the primary label; that value
does not mean its configured ORR vantage is inactive.
NeighborState.selection_deferral is empty on a cold start. During a planned,
marker-backed RFC 4724 restart it reports one row per frozen address-family
gate: whether the gate is active, this peer's waiter state and stamped session,
the process-wide blocking-waiter count, and remaining time. Released rows are
retained for the daemon lifetime with reason all_eor, all_excluded, or
timer, so an operator can distinguish complete convergence from timer-driven
release.
NeighborState.paths_limits is sorted by numeric AFI then SAFI. Optional
effective_send_limit is presence-aware: presence means active, with zero
unlimited and non-zero finite; absence means inactive. The superseded raw
sentinel field number and name (effective_send_max, field 5) are reserved.
NeighborState.slow_peer is true while the peer is flagged slow: the
session is Established and alive but its outbound queue has stayed above
the configured backlog threshold for the configured duration
(slow_peer_threshold_pct / slow_peer_duration in the neighbor
config). It clears when the queue drains and on session teardown, and
mirrors the bgp_peer_slow{peer} gauge. See the "Slow peers" section
in OPERATIONS.md for interpretation.
NeighborState.rejected_routes_retained is the actor-authoritative size of
that exact peer session's bounded retained-reject store. Presence distinguishes
a current fresh snapshot, including an explicit zero, from an older daemon or a
stale peer-session query; clients must not turn absence into zero. Inventory
consumers can therefore obtain every peer's count in one ListNeighbors
request without scanning routes or issuing ListRejectedRoutes per peer.
NeighborState.reconnect_in_seconds is the whole-second wait, rounded up,
until this session's next automatic reconnect attempt while it sits in Idle
after an unplanned teardown; 0 means no reconnect is pending. Each response
recomputes it from the live timer, so it is a sample rather than a countdown
a client can decrement. Consecutive NOTIFICATION-driven returns to Idle
lengthen the wait from the peer's connect-retry base up to a fixed ceiling,
so a rising value distinguishes a peer that keeps failing the OPEN exchange
from one waiting out an ordinary retry. Presence is explicit: a current
daemon always sends the field, including the zero, so absence means only that
the daemon predates it and must never be read as "no reconnect pending".
rbgp neighbor omits the field rather than rendering the zero.
NeighborState.negotiation_available and negotiated_session expose the
actor-authoritative capability outcome of the current Established session:
hold time, cached local IP address, negotiated keepalive cadence, remote
router ID, four-octet AS, negotiated families,
graceful-restart coverage, the peer's Route Refresh / Enhanced Route
Refresh / Extended Message capabilities, and the resulting outbound
message-size ceiling. Presence of negotiation_available distinguishes an
older daemon that does not expose negotiation state from a current daemon
reporting no Established session; negotiated_session is absent while the
session is down, during OpenConfirm, or when the actor query is stale. The
scalars inside it are optional so genuinely negotiated false/zero values
stay distinct from fields an older daemon never sent during a rolling
upgrade. local_address is copied from the Established session's cached
export profile; it is never substituted with the remote peer address or
queried from the socket during an API request. keepalive_interval_seconds
is the negotiated send cadence and remains present as zero when negotiated
hold time is zero. Both fields and the enclosing negotiated state are absent
for down or stale responses; older daemons may send the enclosing state while
omitting these two additive fields. Neither field is a live countdown.
NeighborState.rfc8212_import_policy and rfc8212_export_policy report
the RFC 8212 explicit-policy status of each direction (ADR-0112); a
one-sided configuration is never collapsed into a single "policy present"
answer. PRESENT means enforcement applies and explicit operator policy is
installed. MISSING means enforcement applies and the reserved internal
deny is installed on that direction of this peer — no route crosses it, in
any negotiated family. NOT_REQUIRED covers process-wide disabled
enforcement and iBGP sessions. Clients must render UNSPECIFIED (a daemon
that predates the field) and any unrecognized future value as "unknown",
never as NOT_REQUIRED.
NeighborState.inbound_prefix_limits reports ADR-0108 inbound max-prefix
capacity, one InboundPrefixLimitState per finite configured bound in
enforcement order: the scope name (aggregate, ipv4_unicast,
ipv6_unicast, ipv4_unicast_received, or ipv6_unicast_received), the
session actor's usage for that scope, the configured limit, the
saturating headroom, a blocking flag for an open
max_prefix_action = "block" episode, and the stable reason
inbound_prefix_limit_reached while blocking. Unlike the outbound sibling,
a scope with no configured bound has no row at all, so the list is empty for
a peer with no finite inbound bound — and equally empty from a daemon that
does not expose the field and from a stale peer-session snapshot. Absence is
therefore never proof that no bound is configured. Counts come from the
session actor's O(1) enforcement accounting, never from the RIB, whose rows
carry different Add-Path and lifecycle semantics.
NeighborState.max_prefix_action is the effective current disposition of a
crossed inbound bound, not a straight echo of the max_prefix_action
configuration key. block and warning mirror the configured value.
Configured shutdown reports restart whenever a timed
max_prefix_restart_seconds is configured, and while a peer latched by a
max-prefix violation still has a hold-down deadline pending; it reports
shutdown otherwise, including once a latch has become indefinite. The
companion max_prefix_restart_remaining_millis is present only while a
hold-down is counting down. The field is a plain string: an empty value is a
daemon that predates it, and clients must render an unrecognized value as
unknown rather than mapping it back onto a configuration key.
NeighborState.outbound_prefix_limits reports ADR-0113 outbound unicast
capacity, one OutboundPrefixLimitState per family: the family name
(ipv4_unicast / ipv6_unicast), usage — distinct prefixes currently
admitted into this peer's advertised state, post-policy, post-OTC, and
post-exact-export, agreeing with advertised-route queries and never the
shared update-group table's count — the optional configured limit
(absence means unlimited, never zero), the saturating headroom (absent
while the family is unlimited), a blocking flag for an open blocking
episode, and the stable reason outbound_prefix_limit_reached while
blocking. The list is empty from a daemon that does not expose it and from
a peer with no outbound registration; a registered peer always reports
both unicast families.
NeighborState.effective_posture is a read-only snapshot of the resolved
running neighbor posture: NEXT_HOP ownership enforcement, RFC 1997
interpretation, route-server control-community handling, and the optional ORR
vantage. The containing message is always present from a current daemon, so
explicit false remains distinguishable from an older daemon that omitted the
field. Clients must render an absent message, UNSPECIFIED, and unrecognized
future ownership modes as unknown rather than disabled.
Live runtime config diagnostics and transaction planning. Diff / plan
callers submit candidate TOML and receive only redacted diff / plan
output; GetEffectiveConfig is the one deliberate full-document export
— it returns the effective running config as normalized TOML with
defaults resolved (selected default-empty policy lists may be omitted) and
secret material replaced with <redacted> before it leaves the daemon
(rbgp config effective). The two shipped
full-document consumers (rbgp config effective and rbgp doctor) accept
at most 384 MiB of TOML plus its protobuf envelope; other config RPC clients
retain tonic's 4 MiB decode default.
| RPC | Description |
|---|---|
DiffRuntimeConfig |
Validate candidate TOML and compare it against the daemon's live runtime config snapshot |
PlanConfigTransaction |
Validate candidate TOML, return a runtime snapshot token, and classify v1 transaction support without mutating daemon state |
StreamPlanConfigTransaction |
Outside-v1 authenticated client stream for plan-only candidates larger than the unary ceiling; returns the unchanged plan plus an optional ephemeral plan token |
StreamApplyConfigTransaction |
Outside-v1 authenticated operator stream that consumes an exact streamed-plan binding and invokes the existing transaction executor |
ApplyConfigTransaction |
Operator-tier commit entry point for ADR-0076 config transactions; currently commits one pure runtime family at a time: full-set [[fib_tables]], full-set [[dynamic_neighbors]], static [[neighbors]] add/delete/modify changes, catalog-only policy/neighbor-set/peer-group/global-chain changes, pure live policy-chain impact for static neighbors and accepted dynamic peers, or peer-group/session reshape impact for static members with post-persist best-effort reset of live dynamic sessions |
ConfirmConfigTransaction |
Confirm a pending confirmed transaction before its timer expires |
AbortConfigTransaction |
Abort a pending confirmed transaction and roll back immediately |
GetConfigTransactionStatus |
Return redacted confirmed-transaction lifecycle state |
GetEffectiveConfig |
Return the effective running config as normalized TOML — defaults resolved, selected default-empty policy lists omitted, secrets redacted (rbgp config effective) |
ListConfigHistory |
List up to twenty mixed v2/v3 config history rows — newest row at index 0, then older entries; per-entry timestamp, normalized-TOML SHA-256, config-source SHA-256 over that TOML digest plus the canonical accepted rpol/dataset source roster, provenance status, and one-line summary; never config documents (rbgp config history). Payload-bearing v2 rows are RECORDED; accepted normalized TOML above 10 MiB produces a rollback-ineligible METADATA_ONLY v3 row with normalized_toml_bytes and metadata_only_reason. Corrupt or duplicate-sequence rows are UNREADABLE with both digests empty. Retired TOML files are ignored and retained. |
RollbackConfigTransaction |
Restore a provenance-verified v2 row through the same transaction executor as apply — same plan/impact classification and receipts (rbgp config rollback N). Metadata-only rows return FAILED_PRECONDITION before payload/source access, planning, confirm authority, or mutation; index zero remains INVALID_ARGUMENT. Unreadable rows and provenance mismatches fail closed before planning or mutation. |
History recording is best effort after an accepted durable config. Exactly
10 MiB of normalized TOML remains v2; larger accepted snapshots retain only a
canonical metadata envelope of at most 64 KiB, including a redacted summary of
at most 4 KiB. Neither normalized TOML nor the external-source roster is retained
in v3. Both formats share one newest-first, twenty-row sequence. Listing rejects
a twenty-first recognized final before decoding any row (INTERNAL, storage
unavailable or unsafe); rollback maps the same unsafe roster to
FAILED_PRECONDITION. Hashes identify accepted history; they cannot recover
omitted bytes or authorize use of commit-confirm cleanup residue.
For a supported transaction with an independent mutation, the plan carries one canonical committed candidate
through token, runtime stage, persistence, and accepted history. It may materialize omitted RFC 8212 presence
(epoch-1 omission as epoch 1 plus explicit false; epoch-2 omission as epoch 2 plus explicit true)
without changing effective policy. Representation-only or posture-changing candidates are rejected before
mutation. FIB transactions stage the full tables-plus-posture snapshot;
determinate failures attempt to restore both. Lost persistence acknowledgement remains ambiguous after attempted restore; rollback or post-durable finalization failure is also ambiguous.
Index 0 is not necessarily the running or currently persisted config. V2 rows
hash the accepted external-source identity, but do not archive the
.rpol or dataset bytes. Rollback performs one detached load and requires the
normalized TOML, complete manifest, and source digest to match exactly.
DiffRuntimeConfigResponse contains boolean summary fields, a
plain-text human_text rendering, and diff_json using the
rustbgpd --diff --json schema. Secret-bearing fields such as
neighbor md5_password and tcp_ao.key material are redacted in renderings.
The corresponding grpc_authz request summaries never log candidate_toml
content; they record only redacted metadata such as request body size and token
presence.
rbgp config plan and apply stream candidates by default. Plan prints its
ephemeral plan token only when present and emits a JSON string or null;
Apply accepts --plan-token, or obtains one by streaming a fresh plan for the
same candidate and expected runtime snapshot. Plan and automatically planned
Apply receive one unary compatibility attempt only when the streaming call
returns the generated missing-method fingerprint (UNIMPLEMENTED with empty
message and details). Explicit --plan-token Apply fails closed instead,
because the unary RPC cannot preserve the reviewed candidate binding.
Before that fallback, a fully encoded request above the 4,194,304-byte unary
limit fails locally without a unary RPC or secret-bearing diagnostic. The
proto and unary server behavior are unchanged. Both streaming methods use
metadata version 1, which
must appear first and once, followed by zero or more non-empty chunks no larger
than 1 MiB, then one end frame carrying the exact aggregate length and raw
32-byte SHA-256, immediately followed by EOF. Aggregate input is capped at
384 MiB, idle input at 60 seconds, and the whole admission through planner
reply at 30 minutes. One stream is active process-wide across all listeners.
When it is busy, exactly one operator Plan or Apply may wait without preemption
under that request's original 30-minute deadline. Observer/automation Plan,
a second operator waiter, and lower-role calls while the waiter is registered
return RESOURCE_EXHAUSTED; this is one bounded priority waiter, not a general
queue. The listener must authenticate clients (mTLS, bearer token, or the
owner-only UDS identity).
The owner-only descriptor-relative spool file is unlinked immediately after
open, before any candidate bytes are accepted, so candidate bytes never survive
at a pathname. A crash between exclusive creation and unlink can leave an empty
owner-only stub; unlink failure rejects the request before ingress.
A COMMITTABLE streamed plan additionally carries plan_token, a UUIDv4
bound in memory to the runtime token, candidate digest, and length for 30
minutes. At most 256 bindings are live. Lower-role issuance evicts the oldest
lower-role binding and fails with RESOURCE_EXHAUSTED if every binding is
operator-issued; operator issuance evicts the oldest lower-role binding first,
then the oldest operator binding. Consumption is role-agnostic, so an operator
may apply an exact binding issued by observer/automation Plan. NOOP,
REJECTED, and failed plans never issue one. Streamed Apply
validates all framing and Apply metadata before atomically consuming the exact
token/runtime/digest/length binding. A mismatch preserves the token; an
expired, evicted, or successfully consumed token requires a new plan. After
consume, client cancellation or deadline never cancels the existing
state-changing apply future or restores the token.
ConfigTransactionPlanResponse wraps the same redacted diff with:
runtime_snapshot_token: an optimistic-concurrency token for the live runtime snapshot used during planning. It is an opaque, process-local change detector, not a cryptographic commitment or authorization credential; re-plan after a daemon restart.supported_sections: sections the v1 transaction model can commit ([[fib_tables]],[[dynamic_neighbors]], static neighbor add/delete/modify, catalog-only[policy]/[peer_groups]changes with no effective impact on static neighbors or dynamic ranges, pure live policy-chain impact for static neighbors or accepted dynamic peers, and peer-group/session reshape impact — static members and live dynamic sessions).unsupported_sections: hot-reloadable sections v1 refuses until an atomic executor exists. Mixed policy/session effective impact and dynamic-range peer-group reassignments continue to reporteffective neighbor inheritance impact.restart_required_sections: sections that still require daemon restart.
redacted_diff.reload_applied.effective_neighbor_impact[] includes a
machine-readable kind: policy_chain for a pure resolved import/export chain
move that the live-policy executor can commit, or session_reshape when
inherited peer-group/session state changes — committed by the session reshape
executor (static members reconfigured in place; live dynamic sessions
gracefully reset post-persist).
The planner is intentionally stricter than SIGHUP: "reload-applied" does not
mean "transaction-committable" unless the section appears in
supported_sections. ApplyConfigTransaction commits one pure runtime family
at a time. Cross-family candidates, mixed policy/session effective impacts,
and other valid-but-unsupported sections return REJECTED without mutation
until their section executor lands.
Native plan/apply/rollback admit a full-snapshot family with external
.rpol main/import files or [policy.datasets] snapshots declared only when
the planner's fresh capture of those files is byte-identical to the accepted
snapshot's recorded identity (ADR-0130) — the bytes are not covered by the
transaction token and cannot be rolled back atomically with the TOML, so any
drift rejects the transaction without mutation. Deploy changed TOML, .rpol
graphs, and dataset snapshots together, then send SIGHUP. gNMI Set never
verifies external inputs: a gNMI full-snapshot candidate remains rejected
whenever they are present. A true no-op remains NOOP; a pure
[[fib_tables]] transaction with unchanged external inputs remains
committable because that executor substitutes only the targeted table set
rather than adopting the full candidate config.
diff_json.reload_applied.datasets_changed reports dataset binding/path edits.
diff_json.summary.declared_datasets_count and
diff_json.reload_applied.declared_datasets_count report how many datasets
the candidate declares; the diff never compares their file contents, so a
non-zero count means a reload re-reads files the preview did not evaluate.
grpcurl -plaintext -import-path . -proto proto/rustbgpd.proto \
-d @ localhost:50051 rustbgpd.v1.ConfigService/DiffRuntimeConfig <<'JSON'
{
"candidate_toml": "[global]\nasn = 65001\nrouter_id = \"10.0.0.1\"\nlisten_port = 179\n\n[global.telemetry]\nlog_format = \"json\"\n"
}
JSONPlan a transaction without mutation:
grpcurl -plaintext -import-path . -proto proto/rustbgpd.proto \
-d @ localhost:50051 rustbgpd.v1.ConfigService/PlanConfigTransaction <<'JSON'
{
"candidate_toml": "[global]\nasn = 65001\nrouter_id = \"10.0.0.1\"\nlisten_port = 179\n\n[[fib_tables]]\nname = \"edge\"\ntable_id = 1000\nmetric = 200\n"
}
JSONUse the returned runtime_snapshot_token on the apply request. The daemon
re-plans under the shared runtime-config coordinator before committing; a stale
token fails without mutation. client_request_id and comment are audit
metadata only and are not logged verbatim.
To use a commit-confirmed workflow, include confirm_id on
ApplyConfigTransaction; confirm_timeout_seconds defaults to 600 when omitted
and is capped at 86400. While a confirmed transaction is applying or awaiting
confirmation, other persisted runtime config mutators fail with
FAILED_PRECONDITION. Call ConfirmConfigTransaction with the same
confirm_id to make the change permanent, or AbortConfigTransaction to roll
back immediately. If the timer expires first, the daemon re-applies the
pre-commit runtime snapshot through the same transaction executor and persists
the rollback. The confirm window is durable. Before commit, the v3 writer
publishes the exact accepted normalized prior to
<runtime_state_dir>/commit-confirm-v3-prior.toml, its provenance and
file-identity metadata to commit-confirm-v3-metadata.json, then the sole boot
authority to <absolute lexical config path>.commit-confirm-locator.json.
Startup checks that locator before candidate contents and verifies the complete
chain before restoring; unsafe, torn, or mismatched state fails closed.
Confirm and successful rollback become terminal only after locator unlink and
parent-directory fsync. Subsequent verified exact metadata/raw cleanup and
pending-directory fsync are warning-only; locator-free residue cannot re-arm
the transaction.
Production reads and writes v3 authority only. A v2 locator or locator-free
v1/v2 journal makes v0.65.0 and every later release refuse untouched. Recover
with rustbgpd v0.64.0, or delete only after proving it terminal and intended.
Status reports pending/terminal state.
A failed abort/auto-revert rollback is not terminal: the transaction stays
pending with an ABORT_FAILED/AUTO_REVERT_FAILED status and the mutation
fence stays closed until the abort is retried successfully, the candidate is
confirmed, or a restart boot-reverts from the retained journal.
Abort and timer rollback do not depend on the runtime snapshot token. The
token is a Plan→Apply change detector for callers: it covers the runtime config
and the live update-group membership, so a session going up or down changes
it, and a caller's Plan, Apply, or RollbackConfigTransaction holding the older
token still fails with FAILED_PRECONDITION and must re-plan. The rollback of a
pending confirmed transaction instead restores the prior snapshot the
transaction recorded, under the runtime-config coordinator and behind the
mutation fence, whatever sessions did during the window. It depends on what
any apply depends on: the peer manager and config persistence being available,
and the prior snapshot still planning as committable.
V3 commit-confirm caps the current accepted normalized prior at 384 MiB. An
oversized prior makes Apply return FAILED_PRECONDITION, including actual and
limit byte counts, before authority publication or peer, persistence, and
runtime mutation. Ordinary unconfirmed Apply remains available.
GetConfigTransactionStatus (and the apply response) carries a
ConfigTransactionConfirmation:
| Field | Type | Meaning |
|---|---|---|
status |
ConfigTransactionConfirmationStatus |
Lifecycle state (see below). |
confirm_id |
string | The operator-supplied handle for the transaction. |
timeout_seconds |
uint32 | Effective confirm timeout applied (echoed). |
deadline_unix_seconds |
uint64 | Absolute live auto-revert deadline, minted after Apply commits and retained in the last terminal lifecycle record. Pre-commit durable authority carries a separate informational deadline derived at publication because boot recovery is unconditional. |
committed_sections |
repeated string | Sections the confirmed apply committed. |
runtime_snapshot_token |
string | Post-commit token for the pending change. |
human_text |
string | Redacted human-readable summary. |
ConfigTransactionConfirmationStatus values:
| Value | Meaning |
|---|---|
..._UNSPECIFIED |
Default zero value; not emitted in normal responses. |
..._NONE |
No confirmed transaction is currently tracked. |
..._PENDING |
Applied and awaiting confirmation before the timer expires. Also reported while an expired timer's rollback is still waiting for the runtime-config coordinator; human_text then names the missed deadline and the wait. |
..._CONFIRMED |
Made permanent by ConfirmConfigTransaction. |
..._ABORTED |
Rolled back by AbortConfigTransaction. |
..._AUTO_REVERTED |
Timer expired; the pre-commit snapshot was re-applied. |
..._AUTO_REVERT_FAILED |
Timer expired but the rollback re-apply failed; manual correction required. |
..._ABORT_FAILED |
Abort requested but the rollback re-apply failed; manual correction required. |
(Each value is prefixed CONFIG_TRANSACTION_CONFIRMATION_STATUS_ in the proto.)
Confirmed lifecycle transitions are also exposed through the bounded Prometheus
counter bgp_config_transaction_lifecycle_total{operation, outcome}:
operation is confirm, abort, or auto_revert; outcome is success or
failure. The metric deliberately does not include confirm_id, candidate
TOML, peer labels, or error strings.
Apply a pure full-set [[fib_tables]] transaction:
grpcurl -plaintext -import-path . -proto proto/rustbgpd.proto \
-d @ localhost:50051 rustbgpd.v1.ConfigService/ApplyConfigTransaction <<'JSON'
{
"candidate_toml": "[global]\nasn = 65001\nrouter_id = \"10.0.0.1\"\nlisten_port = 179\n\n[[fib_tables]]\nname = \"edge\"\ntable_id = 1000\nmetric = 200\n",
"expected_runtime_snapshot_token": "<runtime-snapshot-token-from-plan>",
"client_request_id": "deploy-2026-06-03-001",
"comment": "roll edge FIB table definition"
}
JSONApply a pure static-neighbor modify transaction:
grpcurl -plaintext -import-path . -proto proto/rustbgpd.proto \
-d @ localhost:50051 rustbgpd.v1.ConfigService/ApplyConfigTransaction <<'JSON'
{
"candidate_toml": "[global]\nasn = 65001\nrouter_id = \"10.0.0.1\"\nlisten_port = 179\n\n[[neighbors]]\naddress = \"192.0.2.10\"\nremote_asn = 65010\nhold_time = 45\n",
"expected_runtime_snapshot_token": "<runtime-snapshot-token-from-plan>",
"client_request_id": "deploy-2026-06-03-002",
"comment": "adjust neighbor hold timer"
}
JSONApply a transaction with a confirm timer:
grpcurl -plaintext -import-path . -proto proto/rustbgpd.proto \
-d @ localhost:50051 rustbgpd.v1.ConfigService/ApplyConfigTransaction <<'JSON'
{
"candidate_toml": "[global]\nasn = 65001\nrouter_id = \"10.0.0.1\"\nlisten_port = 179\n\n[[neighbors]]\naddress = \"192.0.2.10\"\nremote_asn = 65010\nhold_time = 45\n",
"expected_runtime_snapshot_token": "<runtime-snapshot-token-from-plan>",
"client_request_id": "deploy-2026-06-03-003",
"comment": "safe neighbor timer deploy",
"confirm_id": "deploy-2026-06-03-003",
"confirm_timeout_seconds": 120
}
JSONInspect and confirm the pending transaction:
grpcurl -plaintext -import-path . -proto proto/rustbgpd.proto \
-d '{}' localhost:50051 rustbgpd.v1.ConfigService/GetConfigTransactionStatus
grpcurl -plaintext -import-path . -proto proto/rustbgpd.proto \
-d '{"confirm_id":"deploy-2026-06-03-003"}' \
localhost:50051 rustbgpd.v1.ConfigService/ConfirmConfigTransactionAbort instead of waiting for the timer:
grpcurl -plaintext -import-path . -proto proto/rustbgpd.proto \
-d '{"confirm_id":"deploy-2026-06-03-003"}' \
localhost:50051 rustbgpd.v1.ConfigService/AbortConfigTransactionThe transaction executors share the same coordinator and persistence ordering
as the targeted runtime CRUD paths: re-plan under the runtime snapshot token,
durably stage the exact accepted candidate next to the config file, stage the
live config snapshot, apply the live runtime change, publish the staged
candidate with an acknowledgement, roll back on a publication failure, and
only then release the coordinator lock. A stage the persister refuses — an
unwritable or read-only config directory, a full filesystem, or a candidate
the daemon cannot derive from its accepted config — fails the transaction with
FAILED_PRECONDITION before any session, catalog, or policy state changes; a
rename that fails after the stage is compensated, and an ambiguous publication
or lost acknowledgement fences as described under RecoveryRequired. FIB
transactions still require the FIB
reconciler to already be running, so adding the first [[fib_tables]] entry to
a daemon that started without any tables requires a restart.
Dynamic-neighbor transactions replace the complete [[dynamic_neighbors]] set
from the candidate TOML. Static-neighbor transactions support add/delete/modify:
modifies use the same delete/re-add session-reconfigure semantics as SIGHUP,
then roll back on apply or persistence failure.
Catalog-only transactions can stage named policy definitions, policy
neighbor_sets, peer groups, and global named policy-chain assignments before a
static neighbor or dynamic range depends on them. They are full-snapshot commits:
the daemon updates the live runtime config snapshot and persists the accepted
TOML, but does not run SetPolicy / SetPeerGroup live mutation commands.
Policy/neighbor-set/peer-group/global-chain transactions that move an existing
static neighbor's or accepted dynamic peer's resolved import/export
PolicyChain are also committable when the impact is purely a chain move. The
executor stages the candidate on disk and the snapshot, re-applies the resolved
chains to affected live sessions, captures prior chains for rollback, publishes
the staged candidate with an acknowledgement, and restores both live policy
chains and the snapshot on a publication failure. Dynamic peers
are selected by the canonical [[dynamic_neighbors]] range that accepted them,
not by a public API field. Re-evaluating already-received routes under a new
import chain requires Route Refresh, so every impacted Established peer must
have negotiated the Route Refresh capability; otherwise the apply is rejected
and rolled back without committing the candidate.
Peer-group/session reshape transactions commit peer-group field edits or
static-neighbor peer-group reassignments that rebuild existing sessions. The
executor stages the candidate on disk and the snapshot, reconfigures affected
static peers with the same delete/re-add semantics as SIGHUP, captures prior
peer configs for rollback, publishes the staged candidate with an
acknowledgement, and restores both live peers and the snapshot on a publication
failure. Live dynamic sessions accepted by an affected
[[dynamic_neighbors]] range cannot be delete/re-added (they exist only
because the remote dialed in), so after a successful persist the executor
gracefully resets them with a Cease NOTIFICATION carrying an RFC 9003 shutdown
communication; each remote's reconnect is re-accepted under the committed
config, and the dynamic_neighbor_limit slot accounting stays owned by the
normal session-idle reaping. The dynamic reset is post-persist and best-effort
by contract: a failed transaction never flaps a dynamic peer, and a session
that could not be signaled keeps its running config until it reconnects
(reported in the apply response, never silently swallowed). Reassigning a
range to a different peer group remains a [[dynamic_neighbors]] record edit
outside the reshape family (ADR-0086).
CLI equivalent:
rbgp config diff /tmp/new-config.toml
rbgp --json config diff /tmp/new-config.toml
# The runtime snapshot token printed by plan is opaque — capture it and pass
# it back verbatim:
RUNTIME_SNAPSHOT_TOKEN="$(rbgp --json config plan /tmp/new-config.toml \
| jq -r .runtime_snapshot_token)"
rbgp config apply /tmp/new-config.toml \
--expected-runtime-snapshot-token "$RUNTIME_SNAPSHOT_TOKEN"
rbgp config apply /tmp/new-config.toml \
--expected-runtime-snapshot-token "$RUNTIME_SNAPSHOT_TOKEN" \
--client-request-id deploy-2026-06-03-003 \
--confirm-id deploy-2026-06-03-003 \
--confirm-timeout 120
rbgp config status
rbgp config confirm deploy-2026-06-03-003
rbgp config abort deploy-2026-06-03-003Peer lifecycle management. Supports static peers from config and dynamic peers added at runtime.
| RPC | Description |
|---|---|
AddNeighbor |
Add a peer dynamically through the presence-aware intent (starts session immediately); waits for the atomic config-file update and leaves no runtime change on persistence failure |
DeleteNeighbor |
Remove a peer and tear down its session; waits for the atomic config-file update and rolls runtime back on persistence failure |
ListNeighbors |
List all peers with session state and counters |
GetNeighborState |
Get detailed state for a single peer |
EnableNeighbor |
Re-enable a previously disabled peer |
DisableNeighbor |
Administratively disable a peer (sends NOTIFICATION) |
SoftResetIn |
Request inbound route refresh (RFC 2918/7313) for one or more families |
RefreshOutbound |
Re-emit one peer's current exportable outbound inventory across all negotiated families, without resetting the session |
ReplayOutbound |
Schedule one Established peer's negotiated IPv4/IPv6 unicast replay with terminal UPDATE EoRs; outside the v1 contract |
ResetNeighbor |
Administratively reset one enabled peer's session (Cease/Administrative Reset with an optional shutdown communication); outside v1 |
AddDynamicNeighbor |
Add a [[dynamic_neighbors]] prefix range at runtime; persists the accepted change atomically before returning and rolls back runtime on persistence failure |
DeleteDynamicNeighbor |
Remove a dynamic-neighbor range at runtime (stops future accepts; established peers drain on Idle); waits for the atomic config-file update and rolls runtime back on persistence failure |
ListDynamicNeighbors |
List configured dynamic-neighbor ranges (prefix, peer group, remote ASN, description) |
SetGracefulShutdown |
RFC 8326 initiator toggle — attach the GRACEFUL_SHUTDOWN community to outbound updates for one peer (or all peers when address is empty) and clear with clear = true |
ListNeighbors and GetNeighborState expose stale=true when a peer's
session-state observation is unavailable. The row remains in the inventory;
its placeholder Idle state is not evidence that the session is down.
Check stale before interpreting state. An observed Idle with
stale=false is a different result.
ListNeighbors and GetNeighborState set is_dynamic and, for a dynamic
peer, include accepted_dynamic_range with the canonical prefix and peer group
captured when the connection was accepted. This is session provenance, not a
query against the current matcher: deleting or editing that range does not
rewrite the answer for an already-live session. The nested message is absent
for static peers and from older daemons.
AddNeighborRequest.intent is field 2 and carries an inner NeighborConfig
plus a required google.protobuf.FieldMask override_mask. Field 1 and the
name config are reserved. A missing intent, inner config, or mask returns
INVALID_ARGUMENT before persistence or runtime mutation; custom clients
must migrate the former top-level config object under intent and send a
mask naming each selected supported override (empty only when none are selected).
The bundled rbgp neighbor add command always sends intent with a present
mask, even when no overrides are selected. It does not retry another payload.
The create override mask has a closed top-level path set:
families, required_families, route_server_client, per_client_best,
strict_role, add_path_receive, add_path_send, add_path_send_max, and
paths_limit_receive_max. Wildcards, nested, duplicate, unknown, or partial
Add-Path paths are rejected. The four Add-Path paths are one atomic block.
Masked booleans preserve explicit false; masked family lists replace
inherited lists and must be non-empty. Values outside the mask must retain
their protobuf defaults.
CLI positive/negative pairs are mutually exclusive:
--[no-]route-server-client, --[no-]per-client-best, and
--[no-]strict-role. Any Add-Path option selects the quartet; explicit numeric
zero remains an override, and --no-add-path emits the all-disabled tuple.
Effective inherited prerequisites are validated by the server.
Unmasked fields stay absent in the persisted neighbor and inherit through the normal config resolver. This includes peer-group TTL security, Graceful Restart, route-reflector mode, prefix ORF, IPv6-only behavior, policies, and the complete Add-Path block. With no group and no family override, IPv4 neighbors resolve to IPv4 unicast while IPv6 neighbors resolve to IPv4 and IPv6 unicast.
NeighborConfig.required_families must be a subset of families; otherwise
AddNeighbor returns INVALID_ARGUMENT. Empty inherits a non-empty peer-group
list; when both are empty, partial negotiation is preserved.
NeighborState.config.required_families reports the effective inherited list.
Both fields accept the same twelve canonical labels as the configuration file:
ipv4_unicast, ipv6_unicast, ipv4_flowspec, ipv6_flowspec,
l2vpn_evpn, linkstate, linkstate_vpn, l3vpn_ipv4_unicast,
l3vpn_ipv6_unicast, ipv4_labeled_unicast, ipv6_labeled_unicast, and
rtc. Read responses return those same labels; the bgpls spellings remain
metric labels rather than configuration aliases.
grpcurl -plaintext -import-path . -proto proto/rustbgpd.proto \
-d '{"intent": {"config": {"address": "10.0.0.2", "remote_asn": 65002, "description": "peer-2"}, "overrideMask": {"paths": []}}}' \
localhost:50051 rustbgpd.v1.NeighborService/AddNeighborFor an IPv6 link-local / unnumbered peer, include interface in
NeighborConfig. Follow-up operations that address a scoped peer use the same
address + interface pair.
grpcurl -plaintext -import-path . -proto proto/rustbgpd.proto \
-d '{"intent": {"config": {"address": "fe80::5054:ff:fe00:1", "interface": "eth1", "remote_asn": 65101}, "overrideMask": {"paths": []}}}' \
localhost:50051 rustbgpd.v1.NeighborService/AddNeighborgrpcurl -plaintext -import-path . -proto proto/rustbgpd.proto \
localhost:50051 rustbgpd.v1.NeighborService/ListNeighborsgrpcurl -plaintext -import-path . -proto proto/rustbgpd.proto \
-d '{"address": "10.0.0.2"}' \
localhost:50051 rustbgpd.v1.NeighborService/GetNeighborStategrpcurl -plaintext -import-path . -proto proto/rustbgpd.proto \
-d '{"address": "10.0.0.2", "reason": "maintenance"}' \
localhost:50051 rustbgpd.v1.NeighborService/DisableNeighborgrpcurl -plaintext -import-path . -proto proto/rustbgpd.proto \
-d '{"address": "10.0.0.2"}' \
localhost:50051 rustbgpd.v1.NeighborService/EnableNeighborrbgp neighbor 10.0.0.2 reset --reason "maintenance"
grpcurl -plaintext -import-path . -proto proto/rustbgpd.proto \
-d '{"address": "10.0.0.2", "reason": "maintenance"}' \
localhost:50051 rustbgpd.v1.NeighborService/ResetNeighborResetNeighbor is the one-shot session bounce (bgpctl neighbor <peer> clear, clear bgp <peer>): the session sends Cease / Administrative Reset
(RFC 4486 subcode 4) carrying reason as an RFC 9003 shutdown communication
(deliberately capped at 128 bytes for interoperability, truncating at a UTF-8
boundary like DisableNeighbor) and closes the TCP connection. The peer's
enable/disable state is untouched, so no
EnableNeighbor follow-up is needed. A static active-open peer reconnects on
its normal schedule; if it is already Idle, reset clears any NOTIFICATION
backoff and starts the connection immediately. An accepted dynamic peer follows
the normal Idle reap path and must dial in again; its next inbound connection
is resolved from the current dynamic-range configuration. Scoped link-local
peers pass interface as for the sibling RPCs.
- Unknown peers return
NOT_FOUND; administratively disabled peers returnFAILED_PRECONDITION(enable the peer instead of resetting it). - A session that is not
Establishedis still accepted:Connect/Activedrop back toIdlewithout a NOTIFICATION and follow the static or dynamic lifecycle above. For an enabled static peer already inIdle, reset clears the NOTIFICATION reconnect backoff and starts a connection immediately; no teardown event or session-down sample is emitted because no session exists. - When the peer negotiated RFC 8538 Notification Graceful Restart, the reset is sent as Cease / Hard Reset wrapping the Administrative Reset and its communication, so neither side retains stale routes across the bounce.
- An Established active-primary teardown increments
bgp_session_down_total{reason="local_notification"}. Pre-established resets do not create an Established-session down sample. - The sent NOTIFICATION appears in
EventServicewith its on-wire form: code 6/subcode 4 normally, or code 6/subcode 9 when Notification GR wraps the Administrative Reset as a Hard Reset. The decoded inner communication is still exposed asshutdown_reason; no new event type is introduced.
grpcurl -plaintext -import-path . -proto proto/rustbgpd.proto \
-d '{"address": "10.0.0.2"}' \
localhost:50051 rustbgpd.v1.NeighborService/DeleteNeighbor# Refresh all configured families (empty families list)
grpcurl -plaintext -import-path . -proto proto/rustbgpd.proto \
-d '{"address": "10.0.0.2"}' \
localhost:50051 rustbgpd.v1.NeighborService/SoftResetIn
# Refresh only IPv4 unicast
grpcurl -plaintext -import-path . -proto proto/rustbgpd.proto \
-d '{"address": "10.0.0.2", "families": ["ipv4_unicast"]}' \
localhost:50051 rustbgpd.v1.NeighborService/SoftResetInrbgp neighbor 10.0.0.2 refresh-out
grpcurl -plaintext -import-path . -proto proto/rustbgpd.proto \
-d '{"address": "10.0.0.2"}' \
localhost:50051 rustbgpd.v1.NeighborService/RefreshOutboundscheduled: true means the RIB accepted the refresh pass; resulting updates
were enqueued or retained for the ordinary dirty-retry path. It does not
confirm writer drain or remote receipt.
Unknown peers return NOT_FOUND; managed peers without an active outbound RIB
registration return FAILED_PRECONDITION.
RIB queue admission and acknowledgement share a five-second budget. Readiness and operator reads remain admitted during that wait. A read already in progress finishes under its own deadline before the scheduling call returns; later mutations stay queued until both settle. A timeout does not prove that an admitted refresh had no effect.
This is an O(table) operation for the selected peer and can create a full-table UPDATE burst on a production session. Serialize operational use; the API intentionally has no all-peer or batch form.
rbgp neighbor 10.0.0.2 replay-out
grpcurl -plaintext -import-path . -proto proto/rustbgpd.proto \
-d '{"address": "10.0.0.2"}' \
localhost:50051 rustbgpd.v1.NeighborService/ReplayOutboundReplayOutbound is an experimental, mutating RPC outside the v1 contract.
It schedules wire reannouncement of one Established peer's current exportable
IPv4/IPv6 unicast routes through the canonical export and transport paths.
Every negotiated unicast family receives a terminal UPDATE End-of-RIB (EoR),
including empty families. The request has address and optional interface;
there is no family filter or all-peer form. Existing RefreshOutbound behavior
is unchanged. An older daemon returns UNIMPLEMENTED for the new RPC.
The session must negotiate only IPv4/IPv6 unicast families, with at least one family, and its IP address must be unique among managed peers. A single scoped IPv6 peer is supported; repeated link-local addresses on different interfaces are refused. These prerequisites are checked before monitoring reset or replay traffic, because the reset clears the entire cached peer inventory.
Eligible collectors have a monitor list that includes rib_out_post and
omits rib_in_pre (for example monitor = ["rib_out_post"]; the default
["rib_in_pre"] is not eligible).
Before replay, each receives a monitoring-only Peer Down (reason 5) followed
by the current Peer Up, clearing its previous peer inventory. The BGP session
stays established. Mixed inbound/outbound collectors are excluded because
this operation cannot rebuild their inbound inventory. After enrollment,
ordinary initial-table and peer-refresh unicast EoRs are not mirrored for the
rest of this BGP writer generation, including for collectors excluded from
enrollment. The actual BGP EoRs are still sent. Further complete BMP boundaries require
another explicit replay; a new BGP session restores ordinary EoR mirroring.
The response contains only scheduled. A true value confirms admission for
scheduling; it does not confirm completion, collector receipt, or remote BGP
processing. Terminal BMP EoRs mark replay completion only after the session
writer completes the preceding replay bytes and terminal wire EoRs. A caller
disconnecting after scheduling does not cancel traffic already admitted.
While scheduling waits, readiness and operator reads remain admitted. If the five-second scheduling budget expires or its caller disconnects, an already admitted read finishes under its own deadline before the peer manager moves on to later mutations. These reads report live state; a session that cannot answer still has the read's normal timeout and failure behavior.
Unknown peers return NOT_FOUND. Unavailable sessions, another active replay,
an empty or mixed-family session, a duplicate managed peer IP address,
no eligible connected rib_out_post collector,
or an export gate that prevents complete replay return FAILED_PRECONDITION.
Dispatch, timeout, or lost acknowledgement failures return an error rather
than a successful scheduling response. Later writer, peer, or collector
generation failures suppress terminal BMP completion for affected streams.
Absence of terminal BMP EoRs is incomplete evidence, even if scheduling
succeeded.
This operation reannounces the selected peer's table on its live BGP session. Serialize full-table use and collect the complete BMP stream through its terminal EoRs when using the result as replay evidence.
Named policy definition CRUD plus global and per-neighbor chain assignment.
Chain changes apply immediately for future route processing. Import-policy
changes do not retroactively re-evaluate existing Adj-RIB-In state; use
SoftResetIn if you need a full inbound refresh.
| RPC | Description |
|---|---|
ListPolicies |
List all named policy definitions |
GetPolicy |
Return one named policy definition |
SetPolicy |
Create or replace a named policy definition |
DeletePolicy |
Delete a named policy definition (rejected while referenced) |
ListNeighborSets / GetNeighborSet |
List or fetch a named neighbor set |
SetNeighborSet / DeleteNeighborSet |
Create/replace or delete a named neighbor set |
GetGlobalPolicyChains |
Return global import/export chain assignments |
SetGlobalImportChain / SetGlobalExportChain |
Replace global chain assignment. Sessions positively down (Idle/Connect/Active) accept the new chain in memory and install it (plus RIB outbound registration) at PeerUp; retained Adj-RIB-In under an active GR/LLGR window remains evaluated under the prior chain until re-sync/EOR. Ambiguous sessions (SessionGone / state-query timeout) fail closed with INTERNAL and full compensation. IPv4/IPv6-unicast Route Refresh still requires an Established session with Route Refresh negotiated |
ClearGlobalImportChain / ClearGlobalExportChain |
Remove the global chain assignment |
GetNeighborPolicyChains |
Return one neighbor's import/export chain assignments |
SetNeighborImportChain / SetNeighborExportChain |
Replace one neighbor's chain assignment |
ClearNeighborImportChain / ClearNeighborExportChain |
Remove one neighbor's chain assignment |
ExplainImportPolicy |
Explain why a prefix was permitted / denied / withdrawn / evicted / stale / not-seen on import for a given neighbor, reading the per-session import-decision cache (ADR-0073). For .rpol chain members the statement trace names the deciding term and carries per-term trace lines (ADR-0096). A bounded read timeout returns DEADLINE_EXCEEDED, not synthetic NO_SESSION. Side-effect-free; IPv4/IPv6 unicast only. SensitiveRead tier. |
ListRejectedRoutes |
List every rejected inbound route a peer's session has retained, each tagged with its canonical reject-reason token (policy_reject, otc_route_leak, next_hop_ownership, as_path_loop, rr_loop, treat_as_withdraw), a bounded sub-reason detail, and a best-effort attribute summary. A named .rpol deny identifies its deciding policy:term; named TOML/default denies retain policy-only detail. The enumeration complement to ExplainImportPolicy's point lookup. Retention is a bounded per-peer LRU ([policy.reject_retention]); the response reports retention_enabled, capacity, and optional evictions_since_reset (absent from older daemons) so completeness is explicit. A bounded read timeout returns DEADLINE_EXCEEDED, not NOT_FOUND. Side-effect-free; IPv4/IPv6 unicast only. CLI: rbgp rib received <peer> --rejected. SensitiveRead tier. |
TestPolicy |
Dry-run a candidate .rpol policy (source sent in the request, compiled before RIB access) read-only over a version-fenced walk of the retained post-policy Adj-RIB-In (import) or Loc-RIB best routes (export). Import sees routes admitted by import policy when they were received or last re-evaluated: retained Adj-RIB-In under an active GR/LLGR window, or not yet re-evaluated after an import-policy change, can hold routes the installed chain would now reject, and routes a candidate would newly admit are not visible (ListRejectedRoutes lists recent rejections). Routes are evaluated in canonical (prefix, peer, path_id) order in pages capped at 1,000; family, limit, show_changes, counts, and term hits apply globally. A conservative mutation of the selected Received/Best table returns ABORTED with no partial response: retry the whole RPC from the beginning. Paging-generation exhaustion or an unavailable RIB backend returns UNAVAILABLE. No route, session, or counter impact; IPv4/IPv6 unicast (ADR-0096). CLI: rbgp policy test. SensitiveRead tier. |
GetPolicyStats |
Read live per-term hit counters for installed policy chains (since chain install; direction import, export, or both — import chains also report their install generation). Explicit-peer validation plus export, import, and dataset reads share one absolute 2 s deadline. Fleet import reads use bounded concurrency, cancel publication collection on caller disconnect, and return no partial rows on deadline or unavailable errors. Chainless sessions contribute no row; unknown peers return NOT_FOUND. CLI: rbgp policy stats. SensitiveRead tier. |
GetValidationPolicyPosture |
Conservatively classifies RPKI-invalid and ASPA-invalid routes as ENFORCED, UNENFORCED, or UNKNOWN for installed static/dynamic peers and one prospective row per accepted dynamic range. The bounded response reports complete and omitted; an incomplete aggregate is never ENFORCED. This proves policy disposition only, not validator readiness, connectivity, configured intent, FIB state, or runtime enforcement. SensitiveRead tier; outside the narrow v1-stable surface. |
Import rows use the selected session's actual installed counters, labels and
generation. Numeric fields are sampled during collection; neither a row nor
the fleet response is one atomic snapshot, and derived ratios are not exact
instantaneous fractions. Import error count and error detail are acquired
together. A session that has not initialized its observation remains pending
within the deadline; a closed session or invalid counter state returns
UNAVAILABLE, without partial rows. Counter availability does not establish
that the session command loop is responsive or replace the live readiness checks.
A backend result observed at or after the shared absolute deadline returns
DEADLINE_EXCEEDED, including an otherwise successful final dataset reply. See
ADR-0133.
Sessions continue to service direct transport import-counter queries and
neighbor-state queries during grouped unicast output, including waits for
shared encoded chunks. Earlier queued session commands retain their ordering
for those reads. GetPolicyStats bypasses that session command queue for its
import stage; peer-manager admission and other backend stages still consume
the shared two-second deadline.
GetPolicyStats adds bounded stage timing to the existing grpc_authz
request_summary: peer_validation (targeted requests only), export,
import, and datasets, in execution order. Each completed stage records
elapsed_ms, its remaining shared budget_ms at entry, cumulative
rpc_elapsed_ms, and the gRPC code. A waiting stage records state=waiting
so cancellation retains the active API stage. A failed stage is the
last completed entry; no later stages run. These additive diagnostic fields
do not change RPC responses or the two-second deadline. They identify the
API wait, not its underlying actor or session cause. RUST_LOG=info,policy_stats=debug
also emits structured stage-completion events; no per-peer records are emitted
for a fleet request.
The daemon installs each peer's complete effective export chain, including
global inheritance. GetPolicyStats and rbgp policy stats report those
installed peer chains; they do not include a separate global export row.
The protobuf's global owner denotes a RIB fallback chain instance, not an
aggregate of peer counters. Embedders may still install that fallback through
RibManager::new; the daemon does not retain a startup fallback.
Policy statements support the same match surface as TOML config:
prefix, ge, le, match_community, match_as_path,
match_neighbor_set, match_route_type, match_as_path_length_ge/le,
match_local_pref_ge/le, match_med_ge/le, match_next_hop,
match_rpki_validation, match_aspa_validation, and
match_evpn_route_type.
Answer "why didn't this prefix come in?" (or "what did the chain do to it?")
from the per-session import-decision cache (ADR-0073). Side-effect-free —
no RIB touch, no counter movement. Omit path_id to return every matching
path. The cache is opt-in: without [policy.explain] enabled = true
on the daemon the outcome is CACHE_DISABLED, which is deliberately
distinct from NOT_SEEN. If the peer has no current session actor, the
outcome is NO_SESSION; this is also distinct from a session that answered
without a cached decision. CACHE_DISABLED and NO_SESSION are normal
explain outcomes, not transport errors.
grpcurl -plaintext -import-path . -proto proto/rustbgpd.proto \
-d '{
"peer_address": "10.0.0.2",
"afi_safi": "IPV4_UNICAST",
"prefix": "192.0.2.0",
"prefix_length": 24
}' \
localhost:50051 rustbgpd.v1.PolicyService/ExplainImportPolicyEach matches entry carries an outcome — PERMIT / DENY / WITHDRAWN /
EVICTED / STALE / NOT_SEEN — plus decision attribution and the
modifications applied. A Deny names its configured denying member; a Permit
after a nonempty chain uses chain_default_permit, while an absent or empty
chain stays inline. Compare a match's
policy_generation to the response's current_policy_generation to spot a
STALE decision recorded before a policy reload.
Answer "show me everything of mine you filtered, and why" without knowing a
prefix in advance — the looking-glass filtered-route surface. Side-effect-free.
Returns NOT_FOUND when the peer has no live session (the session-local
retention store is gone, which is honestly distinct from "nothing rejected").
grpcurl -plaintext -import-path . -proto proto/rustbgpd.proto \
-d '{"peer_address": "10.0.0.2"}' \
localhost:50051 rustbgpd.v1.PolicyService/ListRejectedRoutesEach retained rejection carries the prefix identity, the canonical reason
token with its bounded sub-reason detail (policy:term for a named .rpol
deciding term, otherwise the matched policy name for policy_reject), the
announcement's next hop, AS_PATH, communities, RPKI/ASPA
validation states, and the rejection timestamp. retention_enabled: false
means the empty listing is a configuration fact. Present
evictions_since_reset: 0 proves no retained row was displaced this session;
a positive count means the listing may be incomplete, while absent means the
older serving daemon cannot answer. CLI:
rbgp rib received <peer> --rejected.
grpcurl -plaintext -import-path . -proto proto/rustbgpd.proto \
-d '{
"name": "tag-internal",
"definition": {
"default_action": "permit",
"statements": [
{
"action": "permit",
"prefix": "10.0.0.0/8",
"le": 16,
"set_community_add": ["65001:100"]
}
]
}
}' \
localhost:50051 rustbgpd.v1.PolicyService/SetPolicygrpcurl -plaintext -import-path . -proto proto/rustbgpd.proto \
-d '{"policy_names": ["reject-bogons", "tag-internal"]}' \
localhost:50051 rustbgpd.v1.PolicyService/SetGlobalImportChaingrpcurl -plaintext -import-path . -proto proto/rustbgpd.proto \
-d '{"address": "10.0.0.2", "policy_names": ["tag-ixp"]}' \
localhost:50051 rustbgpd.v1.PolicyService/SetNeighborExportChaingrpcurl -plaintext -import-path . -proto proto/rustbgpd.proto \
-d '{
"name": "ix-clients",
"definition": {
"addresses": ["10.0.0.2", "10.0.0.3"],
"remote_asns": [65002, 65003],
"peer_groups": ["rs-clients"]
}
}' \
localhost:50051 rustbgpd.v1.PolicyService/SetNeighborSetgrpcurl -plaintext -import-path . -proto proto/rustbgpd.proto \
-d '{"name": "tag-internal"}' \
localhost:50051 rustbgpd.v1.PolicyService/DeletePolicyPeer-group CRUD plus neighbor membership assignment. Group definitions are
full-replace and persist back to TOML. When an inherited setting changes, the
daemon recomputes effective per-neighbor config and reconciles only the peers
that reference that group. Read responses redact md5_password and expose
only the non-secret has_md5_password presence flag. SetPeerGroup preserves
the existing stored MD5 password when the field is omitted or set to true
without a new md5_password; set has_md5_password = false with no
md5_password to clear it explicitly. Use the configuration file or write-side
source of truth to inspect credential material.
PeerGroupDefinition.required_families is the raw group list returned by
peer-group CRUD. An empty neighbor list inherits it and cannot clear it; a
non-empty neighbor list overrides it. Every effective requirement must be a
subset of the member's effective configured families.
| RPC | Description |
|---|---|
ListPeerGroups |
List all peer-group definitions |
GetPeerGroup |
Return one peer-group definition |
SetPeerGroup |
Create or replace a peer-group definition |
DeletePeerGroup |
Delete a peer-group definition (rejected while referenced) |
SetNeighborPeerGroup |
Assign one neighbor to a peer group |
ClearNeighborPeerGroup |
Remove a neighbor's peer-group reference |
grpcurl -plaintext -import-path . -proto proto/rustbgpd.proto \
-d '{
"name": "rs-clients",
"definition": {
"families": ["ipv4_unicast", "ipv6_unicast"],
"required_families": ["ipv6_unicast"],
"hold_time": 90,
"route_server_client": true,
"export_policy_chain": ["tag-ixp", "suppress-leaks"]
}
}' \
localhost:50051 rustbgpd.v1.PeerGroupService/SetPeerGroupgrpcurl -plaintext -import-path . -proto proto/rustbgpd.proto \
-d '{"address": "10.0.0.2", "peer_group": "rs-clients"}' \
localhost:50051 rustbgpd.v1.PeerGroupService/SetNeighborPeerGroupQuery the routing information base. Its route surface is query-only; stream live
route changes through EventService.WatchEvents or EventService.SubscribeFromEvent.
| RPC | Description |
|---|---|
ListReceivedRoutes |
Adj-RIB-In: all routes received from peers |
ListBestRoutes |
Loc-RIB: best route per prefix after path selection |
ListAdvertisedRoutes |
Adj-RIB-Out: routes advertised to a specific peer |
ExplainAdvertisedRoute |
Dry-run export decision for one prefix (or, with rd, one VPN identity) to one peer: the full gate ladder in live evaluation order (split horizon, RFC 4456 reflection, family, RFC 9494 LLGR, RFC 5291 ORF, RFC 4684 RT membership, export-policy rejection with per-term attribution or nonempty Permit with chain_default_permit, Adj-RIB-Out diff), produced by a dry run of the live staging body. For negotiated unicast Add-Path send, optional source { peer_address, path_id } selects one exact Adj-RIB-In candidate. |
ExplainBestPath |
Show all candidates for a prefix with decisive comparison reasons; optional peer_address field scopes to that peer's Add-Path send view |
LookupBestPath |
Outside-v1 global-only LPM: bounded ancestor probes return the closest installed Loc-RIB winner plus every alternative for that one matched prefix from one actor turn; old daemons fail with UNIMPLEMENTED |
ListFlowSpecRoutes |
FlowSpec routes in Adj-RIB-In / Loc-RIB view |
ListEvpnRoutes |
EVPN routes (RFC 7432 / RFC 9136) in Loc-RIB view, filterable by route type / source peer / RD |
ListReceivedEvpnRoutes |
Bounded accepted post-policy EVPN Adj-RIB-In for one source neighbor, filterable by type / RD |
ListAdvertisedEvpnRoutes |
Bounded committed EVPN Adj-RIB-Out for one destination neighbor, filterable by type / RD |
ExplainEvpnRoute |
Exact typed EVPN candidate selection and optional destination export trace, with installed and committed state kept distinct |
ListBgpLsRoutes |
BGP-LS / BGP-LS VPN routes (RFC 9552) in Loc-RIB view, exposed as opaque NLRI/TLV bytes and filterable by family, peer, and NLRI type |
ListVpnRoutes |
RFC 4364/4659 VPNv4/VPNv6 routes — RD-scoped customer prefixes, Route Targets, and MPLS labels |
ListLabeledRoutes |
RFC 8277 labeled-unicast (SAFI 4) routes in Loc-RIB view — MPLS label stack plus prefix reachability |
ListRtcRoutes |
RFC 4684 RT-Constrain membership NLRI — which Route Targets each peer imports |
ListTopologyNodes |
RFC 9107 ORR topology nodes built from the BGP-LS Adj-RIB-In union |
ListTopologyLinks |
RFC 9107 ORR topology links — IGP adjacencies, link addresses, and metrics (the SPF input) |
ListOrrStatus |
RFC 9107 ORR per-vantage status — configured vantages, their resolved topology nodes, and the peers bound to them |
ListBlackholeDiscards |
RFC 7999 BLACKHOLE kernel-discard install status when [global] honor_blackhole = true and [global] install_blackhole_discard = true |
ListFibRoutes |
ADR-0061 general unicast Linux FIB route status for configured [[fib_tables]] |
ListFibTables |
List the committed [[fib_tables]] and whether the FIB reconciler was started with an open command channel; UNAVAILABLE if that channel has closed. Served without querying the reconciler, so runtime_available = true is not a responsiveness guarantee (sensitive_read) |
SetFibTable |
Create-or-replace a [[fib_tables]] entry by name (upsert; full definition, not a patch) at runtime; hot-applies through the reconciler and persists. Requires the reconciler running (≥1 table at startup) else FAILED_PRECONDITION (mutating) |
DeleteFibTable |
Remove a [[fib_tables]] entry by name at runtime; NOT_FOUND if absent (mutating) |
ListRouteEvents |
Recent unicast route add / withdraw / best-change / export-policy-filtered event history from the bounded in-memory RIB ring |
Limit-blocked ListBlackholeDiscards rows reuse REJECTED with stable reason
text active_limit_exceeded or install_rate_limited; both are retryable.
The three unicast route-listing RPCs return raw ordered
extended_communities, the ASPA verification string in aspa_state, and
received_at_epoch_seconds on every Route (0 means unknown). Native
rbgp --json rib preserves the raw extended-community values, omits an empty
aspa_state from an older daemon but retains a genuine "unknown" state, and
retains the zero receive-time sentinel. The same native route serializer is
used by best, received, advertised, and embedded ExplainBestPath routes.
A peer-scoped view that finds no rows returns NOT_FOUND with the message
neighbor <address> not found when the address names no known peer. This
covers ListReceivedRoutes with neighbor_address set, ListAdvertisedRoutes,
received-mode ListFlowSpecRoutes, ListReceivedEvpnRoutes,
ListAdvertisedEvpnRoutes, ExplainEvpnRoute with received_from or
advertised_to when that side is absent, and BfdService.GetBfdSessions
with peer_address set. A known peer is a configured neighbor, an accepted
dynamic peer, or an address whose Adj-RIB-In still retains Graceful Restart or
LLGR stale routes after its session ended; the first two clauses are the same
managed-peer answer GetPolicyStats uses. The synthetic peer 0.0.0.0 that
owns routes added through InjectionService is always known. A known peer that
is down or has sent nothing still returns OK with an empty result. The daemon checks only when a
view is empty, so a view with rows never pays for it. The whole check is bounded
by one peer-manager read deadline, and exceeding it returns DEADLINE_EXCEEDED.
At startup the gRPC listeners serve before the configured-peer roster is
installed, so for that brief window a configured neighbor is not yet known and
these views, like GetNeighborState and GetPolicyStats, return NOT_FOUND
for it. /readyz and systemd READY=1 are reported only after the roster is
installed.
A continuation token does not outlive its peer. Removing a peer mutates the
route table, so the next continuation returns ABORTED. Restarting from an
empty token returns NOT_FOUND only once the address is neither managed nor
retaining stale routes; while its Adj-RIB-In still retains Graceful Restart or
LLGR stale routes, the restart returns OK with those rows.
VpnRouteEntry.prefix_sid (field 14) and EvpnRouteEntry.prefix_sid
(field 17) are optional views of the retained Prefix-SID path attribute.
VPN Loc-RIB listings and all EVPN route views, explain snapshots, and current
or previous event snapshots use the same conversion. An absent attribute
leaves the field absent; older daemons and stored event records decode with
no view.
The view carries the complete attribute value in raw_value and the original
path-attribute flags. services contains the first L3 (type 5) and first
L2 (type 6) Service TLV, preserving service, SID Information, and SID Structure
order. Each SID exposes its advertised IPv6 sid_value, numeric
endpoint_behavior (including unknown codes), separate SID flags, and all
six structure fields: locator block, locator node, function, argument,
transposition length, and transposition offset. Reserved bytes, unknown TLVs,
and ignored duplicate services remain available in raw_value.
rbgp --json rib vpn, EVPN route lists and explain output, and EVPN event
JSON expose an optional prefix_sid object with lowercase hex raw_value.
Text output adds an advertised SID summary; structure lists the six fields
in the order above. JSON includes a nonempty decode_error and no decoded
services if stored raw data fails structural inspection; the raw bytes remain
available. Routes without Prefix-SID keep their existing JSON shape.
Each SID can also carry optional reconstructed_sid (field 5), restoring a
Function from the high-order bits of the route's label: a single 20-bit VPN
label or the corresponding 24-bit EVPN service field. MAC/IP L2 and L3
services use label 1 and label 2 respectively; L3 requires an IP address.
Ethernet A-D per EVI uses its L2 label, IP Prefix uses its L3 label, and IMET
uses a single ingress-replication PMSI label. The raw sid_value stays unchanged.
Text labels the derived value reconstructed-sid; JSON omits the field when
it is unavailable, including when reading an older daemon or event record.
Reconstruction requires exactly one SID Structure, a nonzero transposition wholly inside its Function, valid bounds, and zero advertised bits in the vacated slice. Missing or ambiguous labels/structures, no transposition, and nonzero Argument lengths leave it absent. Argument composition involving another route (such as Ethernet A-D per ES plus IMET) is outside this view.
This is attribute inspection. It does not validate endpoint behavior against the route family, select a service, originate SRv6 routes, or program forwarding. EVPN remains alpha.
Runtime visibility is intentionally split by access pattern rather than forced through one RPC shape.
| Need | Surface | Shape | Retention / loss behavior |
|---|---|---|---|
| Live unicast route deltas | EventService.WatchEvents with EVENT_CATEGORY_ROUTE |
Streaming event feed | Live-only; WatchEvents emits stream_lagged when slow subscribers miss events |
| Recent unicast route timeline | ListRouteEvents / rbgp events |
Bounded history query | In-memory 4096-event ring, process-local, oldest entries evicted |
| Live session lifecycle | EventService.WatchEvents with EVENT_CATEGORY_SESSION |
Streaming event feed | Live-only; no replay after reconnect |
| Recent session lifecycle | EventService.ListSessionEvents / rbgp events sessions |
Bounded history query | In-memory 4096-event ring, process-local, oldest entries evicted |
| Live policy mutation summaries | EventService.WatchEvents with EVENT_CATEGORY_POLICY |
Streaming event feed | Live-only; slow subscribers can lag and miss events |
| Recent policy mutation summaries | EventService.ListPolicyEvents / rbgp events policy |
Bounded history query | In-memory 4096-event ring, process-local, oldest entries evicted |
| Live EVPN route best-path deltas | EventService.WatchEvents with EVENT_CATEGORY_EVPN |
Streaming event feed | Live-only; slow subscribers can lag and miss events |
| Recent EVPN route timeline | EventService.ListEvpnEvents / rbgp events evpn |
Bounded history query | In-memory 4096-event ring, process-local, oldest entries evicted |
| RFC 7999 discard programming | ListBlackholeDiscards / rbgp rib blackholes |
Snapshot | Current reconcile snapshot only; retryable limit blocks are REJECTED with active_limit_exceeded or install_rate_limited |
| ADR-0061 general Linux FIB programming | ListFibRoutes / rbgp rib fib |
Snapshot | Current reconcile snapshot plus persisted owned-state semantics |
| ADR-0061 FIB route apply outcomes | EventService.WatchEvents with EVENT_CATEGORY_DATAPLANE and BGP_EVENT_TYPE_DATAPLANE_ROUTE_* / rbgp events watch --category dataplane |
Streaming event feed | Live via WatchEvents; durable replay via SubscribeFromEvent when [event_history].enabled = true; no bounded List* history API |
| EVPN L2/L3 dataplane readiness and managed-netdev ownership status | EvpnService (ListEvpnInstances, ListEvpnNexthops, ListEthernetSegments, ListIpVrfs, ListManagedNetdevs) / rbgp evpn ... |
Snapshot | Latest daemon or dataplane report snapshot |
| BGP-LS topology objects | ListBgpLsRoutes / rbgp rib bgpls |
Snapshot | Current Loc-RIB only; raw BGP-LS NLRI/TLV bytes preserved |
| ADR-0067 BFD session state | BfdService.GetBfdSessions / rbgp bfd |
Snapshot | Current BFD actor snapshot |
| Live BFD session state changes | EventService.WatchEvents with EVENT_CATEGORY_BFD and BGP_EVENT_TYPE_BFD_SESSION_* / rbgp events watch --category bfd |
Streaming event feed | Live-only; opt-in (not in the default route+session set); slow subscribers can lag |
| Alerting / counters | Prometheus /metrics |
Cumulative counters and gauges | Process lifetime, scrape-dependent |
Use a live stream when you need a tail, ListRouteEvents when you need recent
route context after the fact, and status RPCs for current ownership/readiness
state. WatchEvents does not replay ListRouteEvents; clients that need both
context and a live tail should query history first, then subscribe.
# All received routes
grpcurl -plaintext -import-path . -proto proto/rustbgpd.proto \
localhost:50051 rustbgpd.v1.RibService/ListReceivedRoutes
# From a specific peer
grpcurl -plaintext -import-path . -proto proto/rustbgpd.proto \
-d '{"neighbor_address": "10.0.0.2"}' \
localhost:50051 rustbgpd.v1.RibService/ListReceivedRoutesgrpcurl -plaintext -import-path . -proto proto/rustbgpd.proto \
localhost:50051 rustbgpd.v1.RibService/ListBestRoutesgrpcurl -plaintext -import-path . -proto proto/rustbgpd.proto \
-d '{"neighbor_address": "10.0.0.2"}' \
localhost:50051 rustbgpd.v1.RibService/ListAdvertisedRoutesgrpcurl -plaintext -import-path . -proto proto/rustbgpd.proto \
-d '{"peer_address": "10.0.0.2", "prefix": "203.0.113.0", "prefix_length": 24}' \
localhost:50051 rustbgpd.v1.RibService/ExplainAdvertisedRouteThis dry-runs the current export decision for a single prefix and peer. The response includes the final decision, decisive reasons, selected best-route identity, and any export modifications that would be applied.
For an IPv4/IPv6 unicast peer with negotiated Add-Path send, set the optional
presence-bearing source message to explain one exact Adj-RIB-In candidate:
grpcurl -plaintext -import-path . -proto proto/rustbgpd.proto \
-d '{"peer_address":"10.0.0.2","prefix":"203.0.113.0","prefix_length":24,"source":{"peer_address":"198.51.100.7","path_id":0}}' \
localhost:50051 rustbgpd.v1.RibService/ExplainAdvertisedRouteThe echoed source.path_id remains the inbound Add-Path identity. The
top-level response path_id is the independent, compact outbound rank
rustbgpd would assign after eligibility and export policy. That outbound rank
is 0 for a pre-selection/policy denial or a candidate
beyond add_path_send_max. A later OTC or exact-wire denial retains the
attempted non-zero rank, and the adj_rib_out rung compares that exact rank.
Inbound ID 0 remains selectable because message presence, not a sentinel,
distinguishes selection from the legacy winner-oriented query. Unknown source
identity returns NOT_FOUND; a source selector without negotiated unicast
Add-Path send returns FAILED_PRECONDITION. source is mutually exclusive
with rd and labeled.
Best-path explain is also available via ExplainBestPath RPC — it returns all
candidates for a prefix with the decisive comparison reason for each. Set
peer_address on the request to scope the response to that peer's Add-Path
send view: candidates that the peer would actually receive (export-policy
permitted + sendable-family + not suppressed by split-horizon or iBGP /
RFC 4456 route-reflector rules + within the peer's effective
add_path_send_max) get a non-zero advertised_path_id reflecting the rank
they would carry on the wire; everything else stays at advertised_path_id = 0. The response echoes peer_address and the effective add_path_send_max
so the operator can read advertisement intent without cross-referencing the
peer config. Empty peer_address returns the v0.7.0 global Loc-RIB view
unchanged. Unknown peer_address → NOT_FOUND. Import explain is available via
PolicyService.ExplainImportPolicy (ADR-0073), including structured
statement/term traces for matched policies.
SRv6 semantic ineligibility is distinct from an import-policy rejection.
ExplainBestPath retains these candidates with vs_best_reason = "srv6_sid_invalid", multipath = "none", and advertised_path_id = 0.
If no eligible candidate remains, the RPC succeeds with an absent best_route
and empty best_reason; the CLI prints the retained candidates. A prefix with
no retained candidates still returns NOT_FOUND. only_path means one
eligible candidate, even if other retained candidates are ineligible. EVPN
received views and exact explain likewise retain the received route and its
eligibility reason. ListVpnRoutes exposes selected VPN routes only; no
received-VPN query is available.
rbgp rib lookup 203.0.113.99
rbgp --json rib lookup 2001:db8::7/128
grpcurl -plaintext -import-path . -proto proto/rustbgpd.proto \
-d '{"prefix":"203.0.113.99","prefix_length":32}' \
localhost:50051 rustbgpd.v1.RibService/LookupBestPathrbgp rib lookup <IP|CIDR> issues exactly one LookupBestPath request. Bare
IPv4 and IPv6 addresses use /32 and /128; an explicit CIDR keeps its mask.
The daemon performs bounded ancestor probes and returns the closest installed
global Loc-RIB winner plus every alternative for that matched prefix from one
actor turn. The response therefore uses the existing best-path explanation
shape, and its prefix may be less specific than the query. There is no
peer-scoped mode and no client-side listing fallback. A syntactically invalid
target is rejected locally, no covering route returns NOT_FOUND, and a
daemon without the outside-v1 method returns UNIMPLEMENTED (not supported by this daemon in the CLI).
Route-listing RPCs and the route-event surfaces (ListReceivedRoutes,
ListBestRoutes, ListAdvertisedRoutes, ListRouteEvents, and
EventService.WatchEvents) accept an
afi_safi field to filter by address family. Supported values are
IPV4_UNICAST (1), IPV6_UNICAST (2), or unspecified (0, returns both
unicast families). Route watch events include the address family of each route
change. FlowSpec routes use ListFlowSpecRoutes with
IPV4_FLOWSPEC (3), IPV6_FLOWSPEC (4), or unspecified (0). EVPN routes
use ListEvpnRoutes and its EVPN-specific filters. BGP-LS routes use
ListBgpLsRoutes with ADDRESS_FAMILY_BGP_LS (6),
ADDRESS_FAMILY_BGP_LS_VPN (7), or unspecified (0).
The unicast route-listing RPCs ListReceivedRoutes, ListBestRoutes, and
ListAdvertisedRoutes support pagination via page_size and page_token.
ListFibRoutes also supports optional pagination; unlike the route-listing
RPCs, page_size = 0 preserves the legacy behavior and returns the full
filtered FIB status snapshot. ListBlackholeDiscards, ListFlowSpecRoutes,
and ListBgpLsRoutes do not support pagination.
Their shared ListRoutesRequest also accepts optional recorded-route
predicates: typed rpki_validation (VALID, INVALID, or NOT_FOUND), typed
aspa_validation (VALID, INVALID, or UNKNOWN), and presence-aware
as_path_contains for one exact nonzero ASN. UNSPECIFIED means no verdict
filter; unknown numeric enum values and ASN 0 return INVALID_ARGUMENT.
Predicates are identical across Received, Best, and Advertised, compose AND-wise
with the existing prefix/origin/community filters, apply to both total_count
and bounded pages, and are part of the opaque continuation-token identity. AS-path
membership covers exact numeric members of represented AS_SEQUENCE and
AS_SET segments only; it is not a regex or policy evaluation. RFC 9774
rejection of newly received AS_SET and AS_CONFED_SET forms remains
unchanged.
# First page (2 routes)
grpcurl -plaintext -import-path . -proto proto/rustbgpd.proto \
-d '{"page_size": 2}' \
localhost:50051 rustbgpd.v1.RibService/ListBestRoutes
# Next page (pass the previous response's next_page_token verbatim)
grpcurl -plaintext -import-path . -proto proto/rustbgpd.proto \
-d '{"page_size": 2, "page_token": "<next_page_token>"}' \
localhost:50051 rustbgpd.v1.RibService/ListBestRoutespage_token is opaque: always pass the next_page_token from the previous
response (empty = listing complete), and only reuse it with the same RPC,
neighbor, route scope, and semantically equivalent route filters. Changing a
scope or filter returns gRPC INVALID_ARGUMENT; changing only page_size is
safe. Tokens are process-local and mutation-fenced: Received and Best share one
conservative table generation, while Advertised has an independent generation.
Any mutation in that class makes the next request fail with gRPC ABORTED; a peer-specific
listing can therefore restart after an unrelated peer in the same class
changes. The server retains no route snapshot or cursor registry. page_size
is capped server-side at 1000 rows per page (0 = default of 100).
Every ListRoutesResponse, including an empty or terminal page, carries a
page_version message with an epoch and generation. The pair exposes the
same process-local consistency fence used by continuation tokens. A client may
compare the complete pair across Received and Best pages in one logical
capture; a changed value means the entire capture must be discarded. The
values are opaque, may repeat after daemon restart, and are not a RIB snapshot
generation. In particular, page_version.generation must never be compared
with or substituted for a producer-local rbgp-ribsnap/1 header generation.
Older daemons omit this additive message field.
Every response also carries total_count: the exact filtered count for the
whole selected view, regardless of page size. This is the contract behind
rbgp rib --count and
rbgp rib received|advertised <PEER> --count, which request a single-row
page and read only total_count. For an address that names no known peer the
count fails with NOT_FOUND rather than reporting zero
(unknown peers).
The same contract backs rbgp rib --limit N,
rbgp rib received PEER --limit N, and
rbgp rib advertised PEER --limit N for N from 1 through 1000. A limited
query issues one RPC and exposes the exact total_count plus whether the
returned page is complete; it never follows a continuation into a potentially
changing table. Unbounded CLI listings retain the version-fenced full-walk
behavior described above.
Route.validation_state is the route's recorded RPKI origin-validation
verdict, not a configuration/readiness sentinel. not_found means the
validation table applied to that route had no covering VRP. It is therefore
the natural value for every route when no [rpki] sources are configured, but
on a configured and ready deployment it is a real per-route uncovered-origin
result. Consumers must use the RPKI cache/readiness surfaces when they need to
distinguish those deployment states; this route field cannot do so by itself.
The rpki_validation request filter selects that recorded fact and therefore
inherits the same distinction. aspa_validation likewise selects the recorded
per-route ASPA fact rather than cache readiness.
Live route deltas stream through EventService.WatchEvents with
EVENT_CATEGORY_ROUTE (see Watch unified events);
durable cursor replay goes through EventService.SubscribeFromEvent.
# Live route stream (streams until interrupted)
grpcurl -plaintext -import-path . -proto proto/rustbgpd.proto \
-d '{"categories": ["EVENT_CATEGORY_ROUTE"]}' \
localhost:50051 rustbgpd.v1.EventService/WatchEvents
# Watch changes for a specific peer and family
grpcurl -plaintext -import-path . -proto proto/rustbgpd.proto \
-d '{"categories": ["EVENT_CATEGORY_ROUTE"], "neighbor_address": "10.0.0.2", "afi_safi": "IPV4_UNICAST"}' \
localhost:50051 rustbgpd.v1.EventService/WatchEventsEach route event arrives as a BgpEvent whose route payload is the same
RouteEvent message that ListRouteEvents returns. Event types: ROUTE_EVENT_TYPE_ADDED, ROUTE_EVENT_TYPE_WITHDRAWN,
ROUTE_EVENT_TYPE_BEST_CHANGED, and ROUTE_EVENT_TYPE_POLICY_FILTERED.
Policy-filtered events are route-level export-policy denials: peer_address
is the source route peer, target_peer_address is the outbound peer whose
export policy denied the route, and reason is currently policy_denied.
Each RouteEvent carries an event_id, a monotonic process-local cursor that
is assigned before the event is written to history and broadcast to live
subscribers. The cursor resets on daemon restart and is not reused within one
process.
WatchEvents does not backfill recent events for new subscribers. Clients
that need both context and a live tail should call ListRouteEvents first,
then open a live stream for subsequent deltas (rbgp events watch --backfill
does exactly this), or use SubscribeFromEvent when event history is enabled.
WatchEvents emits BGP_EVENT_TYPE_STREAM_LAGGED with a StreamLagEvent
payload when a subscriber falls behind the bounded route broadcast.
# Return the recent route-event timeline (oldest-to-newest within the window)
grpcurl -plaintext -import-path . -proto proto/rustbgpd.proto \
-d '{"limit": 100}' \
localhost:50051 rustbgpd.v1.RibService/ListRouteEvents
# Filter by peer and IPv4 unicast
grpcurl -plaintext -import-path . -proto proto/rustbgpd.proto \
-d '{"neighbor_address": "10.0.0.2", "afi_safi": "IPV4_UNICAST", "limit": 50}' \
localhost:50051 rustbgpd.v1.RibService/ListRouteEvents
# Drill into one exact prefix
grpcurl -plaintext -import-path . -proto proto/rustbgpd.proto \
-d '{"prefix": "203.0.113.0", "prefix_length": 24, "limit": 20}' \
localhost:50051 rustbgpd.v1.RibService/ListRouteEventsListRouteEvents reads the same unicast route events that feed
WatchEvents, but from a bounded 4096-event in-memory ring. Peer filters
match peer_address, previous_peer_address, and target_peer_address, so a
peer-scoped query includes withdraws, best-path moves away from that peer, and
routes filtered by that peer's export policy. Prefix filters are exact-match
only and can be combined with peer, family, and limit filters. The
filter does not do containment or longest-prefix matching, so a query for
203.0.113.0/16 will not return an event recorded for 203.0.113.0/24.
event_id values are monotonic within the running daemon and can be used by
clients to de-duplicate a history window against a live stream. They are not
persisted or reused within one process. The history is process-local and
resets on daemon restart.
# List all FlowSpec routes
grpcurl -plaintext -import-path . -proto proto/rustbgpd.proto \
localhost:50051 rustbgpd.v1.RibService/ListFlowSpecRoutes
# List only IPv6 FlowSpec routes
grpcurl -plaintext -import-path . -proto proto/rustbgpd.proto \
-d '{"afi_safi": "ADDRESS_FAMILY_IPV6_FLOWSPEC"}' \
localhost:50051 rustbgpd.v1.RibService/ListFlowSpecRoutesrbgp --json flowspec retains the legacy formatted components array and
also emits ordered component_details records with the API component type,
prefix, value, and offset. The explicit offset preserves the RFC 8956
IPv6 prefix-match semantics, including zero and nonzero values.
The ordinary ListFlowSpecRoutes response remains the selected Loc-RIB view
in routes. Set received_peer_address to an IP address to inspect that
peer's retained, post-import-policy candidates instead, including nonselected
and infeasible rules. afi_safi narrows either view. Invalid peer addresses
are rejected before the query reaches the RIB.
Received mode returns received_routes and sets received_view: true, even
when empty; ordinary mode leaves both unset. An empty received view for an
address that names no known peer returns NOT_FOUND instead
(unknown peers). Clients must check this
acknowledgement because older servers ignore the additive request field.
rbgp flowspec received PEER [-a ipv4_flowspec|ipv6_flowspec] performs that
check and reports an unsupported operation instead of displaying an older
server's selected routes as received candidates.
Each received row contains the existing route projection plus path_id,
selected, validation, reason, and pending. Validation is disabled,
local, feasible, or infeasible; pending is the validation value only
when no result has completed for this candidate. Otherwise a pending
revalidation preserves the last completed value and reason, with
pending: true. Selection is reported independently: a completed feasible
result does not mean the candidate currently owns the selected entry.
Infeasibility reasons are missing_destination, nonzero_destination_offset,
no_covering_unicast, local_covering_unicast, missing_as_path,
unsupported_as_path, originator_mismatch, leftmost_as_mismatch,
unknown_neighbor_as, and conflicting_more_specific. An empty reason means
no completed failure. local_covering_unicast names an eBGP rule whose
best-match cover is a locally injected unicast route without an AS_PATH, which
cannot supply the leftmost AS that RFC 9117 section 4.2 requires.
Received means retained after import policy, not a historical record of every
UPDATE. See the feasibility decision
for opt-in validation and convergence semantics.
ListEvpnRoutes retains its existing unpaginated best-route view. The additive
ListReceivedEvpnRoutes and ListAdvertisedEvpnRoutes methods require
neighbor_address and accept route_type_filter (0 or 1–5), rd_filter,
page_size, and page_token. They are outside the narrow v1 contract; older
servers return UNIMPLEMENTED without falling back to a unicast table.
Received means accepted post-policy Adj-RIB-In, including candidates that
are not best. An absent route does not prove the neighbor never sent it;
historical EVPN import rejection details are not retained. Advertised means
committed Adj-RIB-Out for the requested destination, after export gates and
successful outbound enqueue. It does not confirm remote receipt or installation.
Each row's peer_address remains its original source peer, which may differ
from the requested destination. Use exact EVPN explain
for candidate-selection and destination export diagnostics.
These peer methods return one page with routes, total_count,
next_page_token, and page_version. The default page size is 100; the maximum
is 1000, and larger requests return INVALID_ARGUMENT. Rows are ordered by typed
EVPN route identity and source peer. Use the returned token with the same
neighbor, direction, and filters to continue. Changed scope or filters, malformed
or modified tokens return INVALID_ARGUMENT; table mutations return ABORTED,
requiring a restart from an empty token. An empty page for an address that names
no known peer returns NOT_FOUND
(unknown peers). Tokens expire on daemon restart.
Version counters are conservative across peers and families in the same table
class, so an unrelated mutation can also invalidate a walk. Types 3/4, attribute
changes, retained-route lifecycle changes, and peer teardown participate.
Each page scans the selected table: O(table size × log page size) work and O(page size) retained route references/copies. Pagination bounds response memory; it does not provide an index or a snapshot retained across mutations.
rbgp evpn received 192.0.2.1 --route-type 2 --rd 65000:100 --page-size 100
rbgp evpn advertised 192.0.2.2 --rd 65000:100 --page-size 100
grpcurl -plaintext -import-path . -proto proto/rustbgpd.proto \
-d '{"neighbor_address":"192.0.2.2","route_type_filter":2,"rd_filter":"65000:100","page_size":100}' \
localhost:50051 rustbgpd.v1.RibService/ListAdvertisedEvpnRoutesThe CLI prints one page and its continuation metadata; pass --page-token '<token>'
to resume, quoting the opaque token for the shell. --json returns a page object for these new views. The existing commands
below retain their best-route output and syntax.
# All EVPN routes in Loc-RIB
grpcurl -plaintext -import-path . -proto proto/rustbgpd.proto \
localhost:50051 rustbgpd.v1.RibService/ListEvpnRoutes
# Only Type 2 (MAC/IP) routes
grpcurl -plaintext -import-path . -proto proto/rustbgpd.proto \
-d '{"route_type_filter": 2}' \
localhost:50051 rustbgpd.v1.RibService/ListEvpnRoutes
# Filter by Route Distinguisher
grpcurl -plaintext -import-path . -proto proto/rustbgpd.proto \
-d '{"rd_filter": "65000:100"}' \
localhost:50051 rustbgpd.v1.RibService/ListEvpnRoutesroute_type_filter accepts 0 (no filter) or 1..=5 matching the RFC 7432
route type numbers. peer_filter is an optional typed IP-address match (for
example, expanded and compressed IPv6 spellings are equivalent); rd_filter
is an optional typed route-distinguisher match (for example, "65000:100",
"10.0.0.1:100", or "4200000000:100" per RFC 4364 RD types 0/1/2, plus
the displayed 0x/16-hex-digit fallback for unknown types). Empty strings
disable each filter. Invalid filters fail with INVALID_ARGUMENT before the
RIB actor is queried.
RibService.ExplainEvpnRoute takes a required EvpnRouteSelector in key:
its rd and exactly one route-specific selector identify a single NLRI.
Optional received_from looks up one retained source, and advertised_to
evaluates one destination. Both are neighbor IP addresses. This additive,
sensitive_read RPC is outside the narrow v1 contract; older servers return
UNIMPLEMENTED. Existing unicast explain requests keep their current meaning.
The CLI uses rbgp evpn explain <selector> --rd <RD> with optional
--received-from <PEER> and --advertised-to <PEER>. Put these options after
the selector. Each selector is exact, with no wildcard or longest-prefix match:
| CLI selector | Proto selector | Required key fields beyond RD | Ethernet Tag |
|---|---|---|---|
ead-per-es |
ead_per_es |
--esi |
CLI fixes it to MAX_ET; RPC requires 4294967295 |
ead-per-evi |
ead_per_evi |
--esi, --ethernet-tag |
Required; MAX_ET is rejected |
mac-ip |
mac_ip |
--mac; optional --ip |
--ethernet-tag defaults to 0 |
imet |
imet |
--originator-ip |
--ethernet-tag defaults to 0 |
es |
es |
--esi, --originator-ip |
Not part of the Type 4 key |
ip-prefix |
ip_prefix |
--prefix in canonical CIDR form |
--ethernet-tag defaults to 0; nonzero tags are supported |
For Type 2, omitting --ip (an empty RPC ip) selects the MAC-only key;
it does not match every host IP attached to that MAC. Type 5 prefixes must
have zero host bits. ESI uses ten colon-separated hex octets; MAC uses six.
Labels, next hop, and gateway are route payload, not selector fields. Invalid
selectors return INVALID_ARGUMENT before querying the RIB.
# Exact MAC-only route, with source and destination diagnostics
rbgp evpn explain mac-ip --rd 65000:100 --mac 02:00:00:00:00:11 \
--received-from 10.0.0.1 --advertised-to 10.0.0.2
# The same MAC with a host IP is a distinct key
rbgp evpn explain mac-ip --rd 65000:100 --mac 02:00:00:00:00:11 \
--ip 192.0.2.11 --advertised-to 10.0.0.2
# Type 5 supports an exact IPv6 prefix and a nonzero Ethernet Tag
rbgp evpn explain ip-prefix --rd 65000:100 --ethernet-tag 100 \
--prefix 2001:db8:100::/64 --advertised-to 10.0.0.2
grpcurl -plaintext -import-path . -proto proto/rustbgpd.proto \
-d '{"key":{"rd":"65000:100","imet":{"ethernet_tag":0,"originator_ip":"10.0.0.1"}},"received_from":"10.0.0.1","advertised_to":"10.0.0.2"}' \
localhost:50051 rustbgpd.v1.RibService/ExplainEvpnRouteThe response is a read-only snapshot for that exact key. It walks the peer
roster using exact lookups and returns a bounded number of routes, rather than
copying every candidate or scanning the full EVPN table. --json exposes the
same distinctions as the text output:
| Field | Meaning |
|---|---|
received |
Accepted post-policy Adj-RIB-In route from received_from, if retained. Omitted when no source is requested or no accepted route is retained. |
candidate_count |
Number of currently retained accepted candidates across sources. |
best |
Installed Loc-RIB route. |
selection_best |
Winner of a fresh comparison over the retained candidates, without installing it. |
compared |
Requested retained source when it loses; otherwise the runner-up when the source wins or none was requested. Absent when the requested source is not retained or there is no comparison. |
selection_reason |
Decisive reason why selection_best beats compared; absent without a pair. |
selection_deferred |
EVPN selection is currently deferred; the installed best may differ from fresh selection or be absent. |
export |
Present only when advertised_to was requested. |
Selection uses the live EVPN comparator: freshness ranks precede Type 2 sticky and MAC Mobility sequence preferences, followed by the remaining BGP criteria. A fresh comparison can still select a stale route if that is the best retained candidate. Missing input is not an import rejection explanation: historical EVPN import rejection details are not retained, and absence does not prove that a peer never sent this key.
export.decision, reasons, gates, and modifications describe a current
dry run through the shared export staging path, using installed best.
export.staged is the post-policy candidate that clears that path, while
export.advertised is the committed local Adj-RIB-Out route for the
requested destination. Each route's peer_address remains its source;
export.peer_address names the destination. A missing outbound session is
reported as a stopping destination_unavailable gate.
already_advertised means staging found an identical committed route and
suppressed a duplicate advertisement. outbound_dirty is a destination-wide
resynchronization flag, not proof that this particular route is pending.
A retained exact-encoder rejection can stop an otherwise eligible candidate.
The query neither sends nor freshly encodes an UPDATE, and committed state
is not proof of remote receipt, acceptance, or installation.
# All BGP-LS and BGP-LS VPN routes in Loc-RIB
grpcurl -plaintext -import-path . -proto proto/rustbgpd.proto \
localhost:50051 rustbgpd.v1.RibService/ListBgpLsRoutes
# BGP-LS only, from one peer, for Node NLRI
grpcurl -plaintext -import-path . -proto proto/rustbgpd.proto \
-d '{"afi_safi": "ADDRESS_FAMILY_BGP_LS", "peer_filter": "10.0.0.2", "nlri_type_filter": 1}' \
localhost:50051 rustbgpd.v1.RibService/ListBgpLsRoutesListBgpLsRoutes is the ADR-0077 controller-facing BGP-LS surface. It
preserves BGP-LS routes as opaque RFC 9552 objects: raw Route Distinguisher
bytes for BGP-LS VPN, raw NLRI descriptor/payload bytes, and raw BGP-LS
Attribute (type 29) bytes when present. Negotiated BGP-LS routes can also be
reflected to eligible peers through the normal route-reflector pipeline and feed
the RFC 9107 ORR topology used for per-vantage best-path selection. The daemon
does not synthesize BGP-LS from a local LSDB or negotiate BGP-LS Add-Path. GR /
LLGR stale preservation for BGP-LS and BGP-LS VPN is implemented as part of the
RR-family stale pipeline.
Its afi_safi filter accepts only unspecified, BGP-LS, or BGP-LS VPN.
As with EVPN, VPN, labeled-unicast, and RT-Constrain route listings, a
non-empty peer_filter must parse as an IP address and matches by address
identity rather than display spelling.
grpcurl -plaintext -import-path . -proto proto/rustbgpd.proto \
localhost:50051 rustbgpd.v1.RibService/ListBlackholeDiscardsReturns one row per currently observed best route carrying the RFC 7999
BLACKHOLE community when the opt-in FIB reconciler is active. state is a
BlackholeDiscardState enum (BLACKHOLE_DISCARD_STATE_INSTALLED,
BLACKHOLE_DISCARD_STATE_REJECTED, or BLACKHOLE_DISCARD_STATE_FAILED);
reason carries values such as installed, owned, broad_prefix,
not_ebgp, active_limit_exceeded, install_rate_limited,
foreign_route_exists, lookup_failed, remove_failed, or the kernel
install error string. Both limit reasons are retryable and may transition to
installed when active capacity or a token becomes available.
An empty list means either the reconciler is disabled or no BLACKHOLE-marked
best routes are currently visible.
grpcurl -plaintext -import-path . -proto proto/rustbgpd.proto \
localhost:50051 rustbgpd.v1.RibService/ListFibRoutes
rbgp rib fib # human table
rbgp rib fib --json # JSON array for scripts
rbgp rib fib --table edge --state rejected --reason route_limit_exceeded
rbgp rib fib --prefix 203.0.113.0/24 --neighbor 198.51.100.2
rbgp rib fib --page-size 100Returns one row per desired route, daemon-owned route, or one-pass
reconciliation outcome in the ADR-0061 general unicast Linux FIB runtime.
The runtime is default-off and only starts when at least one [[fib_tables]]
block is configured. state is a FibRouteState enum (FIB_ROUTE_STATE_INSTALLED,
FIB_ROUTE_STATE_REJECTED, FIB_ROUTE_STATE_FAILED, or
FIB_ROUTE_STATE_UNRESOLVED); reason
carries values such as owned, foreign_route_exists,
next_hop_family_unsupported, peer_not_allowed,
route_limit_exceeded, owned_route_drifted, next_hop_unresolved, dump_failed:DETAIL,
or a kernel apply error such as install_failed:DETAIL. When a pre-kernel
planning query fails, this RPC preserves the last successful status snapshot;
use bgp_dataplane_reconcile_planning_failures_total and the matching
structured warning for the current failure.
ListFibRoutesRequest supports optional filters for table_name, state,
reason, exact prefix + prefix_length, and peer_address; filters compose
with AND semantics. The prefix filter is an exact route-key match, not
longest-prefix or containment matching, so 203.0.113.0/24 does not match
203.0.113.128/25. Empty strings and FIB_ROUTE_STATE_UNSPECIFIED mean "no
filter"; for direct gRPC callers, prefix_length must be 0 when prefix is
empty. rbgp rib fib exposes the same filters as --table, --state,
--reason, --prefix, and --neighbor. page_size and page_token enable
optional pagination over the filtered status rows; page_size = 0 keeps the
legacy full-snapshot response, and page_token is valid only when
page_size > 0. The response includes next_page_token and total_count;
CLI JSON output remains a route array unless --page-size is greater than
0, in which case it emits an object with routes,
next_page_token, total_count, and optional sampling metadata.
table_id, metric, prefix, prefix_length, next_hop, and next_hops
describe the route identity and forwarding value. next_hop is the selected
best route's representative gateway; next_hops is the canonical installed
set for ECMP / multipath rows. The CLI human table renders Table, Metric,
Prefix, Next hop, State, and Reason; JSON output uses table_name,
table_id, metric, prefix, next_hop, next_hops, peer_address,
state, reason, and optional sampling. Sampling is present
on high-cardinality rows such as route_limit_exceeded; it reports the number
of surfaced sample rows, suppressed rows, total rows in that table/metric/reason
set, the configured max_routes, the status sample cap, and whether the sample
is complete. Pagination still applies only to surfaced rows: total_count does
not include suppressed rows. A pre-existing kernel row in a
configured table is reported as foreign_route_exists; RTPROT_BGP is not
ownership proof by itself because another daemon can use the same protocol
marker. A row rustbgpd previously owned but later finds changed by another
writer is reported as owned_route_drifted; the daemon releases ownership and
does not delete that replacement on a later withdraw.
Unified typed live event stream. Current categories are route events, session events (peer lifecycle plus BGP NOTIFICATION sent/received metadata), policy mutation summary events, EVPN route best-path events, dataplane status-row summary changes for the daemon-owned FIB / BLACKHOLE discard reconcilers, live ADR-0061 per-route FIB apply outcomes, and BFD session state changes.
| RPC | Description |
|---|---|
WatchEvents |
Server-streaming: unified typed event stream sourced from structured daemon events |
SubscribeFromEvent |
Server-streaming: durable event-history cursor replay from the local outbox, then live |
ListSessionEvents |
Unary: recent session lifecycle events from the peer manager's bounded in-memory history |
ListPolicyEvents |
Unary: recent policy / neighbor-set / peer-group / chain mutation events from the peer manager's bounded in-memory history |
ListEvpnEvents |
Unary: recent EVPN route add / withdraw / best-change events from the RIB's bounded in-memory history |
# Watch the default live route + session events
grpcurl -plaintext -import-path . -proto proto/rustbgpd.proto \
localhost:50051 rustbgpd.v1.EventService/WatchEvents
# Watch only route adds for one exact prefix
grpcurl -plaintext -import-path . -proto proto/rustbgpd.proto \
-d '{"categories": ["EVENT_CATEGORY_ROUTE"], "event_types": ["BGP_EVENT_TYPE_ROUTE_ADDED"], "prefix": "203.0.113.0", "prefix_length": 24}' \
localhost:50051 rustbgpd.v1.EventService/WatchEvents
# Watch session establishment/loss for one peer
grpcurl -plaintext -import-path . -proto proto/rustbgpd.proto \
-d '{"categories": ["EVENT_CATEGORY_SESSION"], "event_types": ["BGP_EVENT_TYPE_SESSION_ESTABLISHED", "BGP_EVENT_TYPE_SESSION_LOST"], "neighbor_address": "10.0.0.2"}' \
localhost:50051 rustbgpd.v1.EventService/WatchEvents
# Watch BGP NOTIFICATIONs for one peer
grpcurl -plaintext -import-path . -proto proto/rustbgpd.proto \
-d '{"categories": ["EVENT_CATEGORY_SESSION"], "event_types": ["BGP_EVENT_TYPE_NOTIFICATION_SENT", "BGP_EVENT_TYPE_NOTIFICATION_RECEIVED"], "neighbor_address": "10.0.0.2"}' \
localhost:50051 rustbgpd.v1.EventService/WatchEvents
# Watch policy / peer-group / chain mutation summaries
grpcurl -plaintext -import-path . -proto proto/rustbgpd.proto \
-d '{"categories": ["EVENT_CATEGORY_POLICY"], "event_types": ["BGP_EVENT_TYPE_POLICY_CHANGED"]}' \
localhost:50051 rustbgpd.v1.EventService/WatchEvents
# Query recent session establishment/loss history for one peer
grpcurl -plaintext -import-path . -proto proto/rustbgpd.proto \
-d '{"event_types": ["BGP_EVENT_TYPE_SESSION_ESTABLISHED", "BGP_EVENT_TYPE_SESSION_LOST"], "neighbor_address": "10.0.0.2", "limit": 20}' \
localhost:50051 rustbgpd.v1.EventService/ListSessionEvents
# Query recent peer-scoped policy mutation history
grpcurl -plaintext -import-path . -proto proto/rustbgpd.proto \
-d '{"event_types": ["BGP_EVENT_TYPE_POLICY_CHANGED"], "neighbor_address": "10.0.0.2", "limit": 20}' \
localhost:50051 rustbgpd.v1.EventService/ListPolicyEvents
# Watch FIB / BLACKHOLE dataplane status-row summary changes
grpcurl -plaintext -import-path . -proto proto/rustbgpd.proto \
-d '{"categories": ["EVENT_CATEGORY_DATAPLANE"], "event_types": ["BGP_EVENT_TYPE_DATAPLANE_STATUS_CHANGED"]}' \
localhost:50051 rustbgpd.v1.EventService/WatchEvents
# Watch per-route FIB install failures for one prefix
grpcurl -plaintext -import-path . -proto proto/rustbgpd.proto \
-d '{"categories": ["EVENT_CATEGORY_DATAPLANE"], "event_types": ["BGP_EVENT_TYPE_DATAPLANE_ROUTE_FAILED"], "prefix": "203.0.113.0", "prefix_length": 24}' \
localhost:50051 rustbgpd.v1.EventService/WatchEvents
# Watch EVPN route best-path changes for one peer
grpcurl -plaintext -import-path . -proto proto/rustbgpd.proto \
-d '{"categories": ["EVENT_CATEGORY_EVPN"], "event_types": ["BGP_EVENT_TYPE_EVPN_ROUTE_ADDED", "BGP_EVENT_TYPE_EVPN_ROUTE_WITHDRAWN", "BGP_EVENT_TYPE_EVPN_ROUTE_BEST_CHANGED"], "neighbor_address": "10.0.0.2"}' \
localhost:50051 rustbgpd.v1.EventService/WatchEvents
# Replay all retained durable events, then continue live
grpcurl -plaintext -import-path . -proto proto/rustbgpd.proto \
-d '{"from_event_id": 0}' \
localhost:50051 rustbgpd.v1.EventService/SubscribeFromEvent
# Query recent Type 2 EVPN route events for one RD
grpcurl -plaintext -import-path . -proto proto/rustbgpd.proto \
-d '{"route_type_filter": 2, "rd_filter": "65000:100", "limit": 20}' \
localhost:50051 rustbgpd.v1.EventService/ListEvpnEventsWatchEvents is a live stream only: it does not replay the bounded
ListRouteEvents history and it does not persist events. The rbgp events watch --backfill N command composes both RPCs client-side for route
events by subscribing live first, printing recent ListRouteEvents results
through the same BgpEvent renderer used by the live stream, and suppressing
live route events whose event_id was already printed. The command prints a
history block followed by the live tail; it does not server-side merge the two
streams by wall-clock timestamp.
SubscribeFromEvent is the restart-safe durable cursor surface backed by
[event_history]'s local SQLite outbox. Its from_event_id field uses explicit
presence: absent means live-only, 0 replays every retained event before
joining live, and N > 0 replays events with event_id > N before joining
live. If N is older than the retention floor, the first response is a
BGP_EVENT_TYPE_STREAM_LAGGED event whose missed_count describes the global
outbox gap; the stream then continues from the earliest retained event. If
retention evicts events ahead of a replay still in progress, another such event
precedes the next replayed event and counts exactly the evicted ids, so a
collector must accept this event at any position in the stream. Empty
category and type filters select every EHM-fed category on this RPC: route,
EVPN, session lifecycle, session notifications, policy, dataplane, and BFD.
Dataplane summary and per-route FIB apply events remain live on WatchEvents
and are also replayable through SubscribeFromEvent when event history is
enabled. When event history is disabled or startup fell back to live-only
operation, the RPC returns FAILED_PRECONDITION at admission; legacy live and
List*Events RPCs are unaffected. That admission result takes precedence over
filter validation.
Pre-admission loss is the baseline; later loss wins delivery races and ends with
DATA_LOSS. Cursors cannot replay pre-commit loss, so reconcile state first.
Filters compose AND-wise across category, type, peer, family, and exact prefix.
Repeated categories are ORed, repeated types are ORed, and the two dimensions
are ANDed. A request is accepted when any selected category/type pair is real;
no intersection returns INVALID_ARGUMENT. An empty category or type dimension
is a wildcard for compatibility validation (the live default/opt-in selection
rules below still apply).
Synthetic BGP_EVENT_TYPE_STREAM_LAGGED control frames bypass ordinary
category, type, peer, family, and prefix filters after compatibility validation.
Live WatchEvents compatibility:
| Category | Compatible event types |
|---|---|
| Route | BGP_EVENT_TYPE_ROUTE_ADDED, BGP_EVENT_TYPE_ROUTE_WITHDRAWN, BGP_EVENT_TYPE_ROUTE_BEST_CHANGED, BGP_EVENT_TYPE_ROUTE_POLICY_FILTERED, BGP_EVENT_TYPE_STREAM_LAGGED |
| Session | BGP_EVENT_TYPE_SESSION_STATE_CHANGED, BGP_EVENT_TYPE_SESSION_ESTABLISHED, BGP_EVENT_TYPE_SESSION_LOST, BGP_EVENT_TYPE_PEER_ADDED, BGP_EVENT_TYPE_PEER_REMOVED, BGP_EVENT_TYPE_PEER_ENABLED, BGP_EVENT_TYPE_PEER_DISABLED, BGP_EVENT_TYPE_MAX_PREFIX_WARNING, BGP_EVENT_TYPE_NOTIFICATION_SENT, BGP_EVENT_TYPE_NOTIFICATION_RECEIVED, BGP_EVENT_TYPE_STREAM_LAGGED |
| Policy | BGP_EVENT_TYPE_POLICY_CHANGED (never BGP_EVENT_TYPE_STREAM_LAGGED) |
| Dataplane | BGP_EVENT_TYPE_DATAPLANE_STATUS_CHANGED, BGP_EVENT_TYPE_DATAPLANE_ROUTE_INSTALLED, BGP_EVENT_TYPE_DATAPLANE_ROUTE_WITHDRAWN, BGP_EVENT_TYPE_DATAPLANE_ROUTE_FAILED, plus BGP_EVENT_TYPE_STREAM_LAGGED only for the per-route source (not the peerless summary source) |
| EVPN | BGP_EVENT_TYPE_EVPN_ROUTE_ADDED, BGP_EVENT_TYPE_EVPN_ROUTE_WITHDRAWN, BGP_EVENT_TYPE_EVPN_ROUTE_BEST_CHANGED, BGP_EVENT_TYPE_STREAM_LAGGED |
| BFD | BGP_EVENT_TYPE_BFD_SESSION_UP, BGP_EVENT_TYPE_BFD_SESSION_DOWN, BGP_EVENT_TYPE_BFD_SESSION_STATE_CHANGED, BGP_EVENT_TYPE_STREAM_LAGGED |
Durable SubscribeFromEvent compatibility:
If the storage actor becomes unavailable after admission and no producer loss
has been observed, the stream ends with gRPC UNAVAILABLE. Resume from the
last received top-level event_id after restoring the daemon. Once the
storage thread has stopped, admission itself returns UNAVAILABLE, and open
streams end with DATA_LOSS because producer events are refused until restart. Allocator
pass-through remains FAILED_PRECONDITION; post-admission producer loss retains
DATA_LOSS precedence.
| Category | Compatible event types |
|---|---|
| Route | BGP_EVENT_TYPE_ROUTE_ADDED, BGP_EVENT_TYPE_ROUTE_WITHDRAWN, BGP_EVENT_TYPE_ROUTE_BEST_CHANGED, BGP_EVENT_TYPE_ROUTE_POLICY_FILTERED, global BGP_EVENT_TYPE_STREAM_LAGGED |
| Session | BGP_EVENT_TYPE_SESSION_STATE_CHANGED, BGP_EVENT_TYPE_SESSION_ESTABLISHED, BGP_EVENT_TYPE_SESSION_LOST, BGP_EVENT_TYPE_PEER_ADDED, BGP_EVENT_TYPE_PEER_REMOVED, BGP_EVENT_TYPE_PEER_ENABLED, BGP_EVENT_TYPE_PEER_DISABLED, BGP_EVENT_TYPE_MAX_PREFIX_WARNING, BGP_EVENT_TYPE_NOTIFICATION_SENT, BGP_EVENT_TYPE_NOTIFICATION_RECEIVED, global BGP_EVENT_TYPE_STREAM_LAGGED |
| Policy | BGP_EVENT_TYPE_POLICY_CHANGED, BGP_EVENT_TYPE_OTC_ROUTE_BLOCKED, global BGP_EVENT_TYPE_STREAM_LAGGED |
| Dataplane | BGP_EVENT_TYPE_DATAPLANE_STATUS_CHANGED, BGP_EVENT_TYPE_DATAPLANE_ROUTE_INSTALLED, BGP_EVENT_TYPE_DATAPLANE_ROUTE_WITHDRAWN, BGP_EVENT_TYPE_DATAPLANE_ROUTE_FAILED, global BGP_EVENT_TYPE_STREAM_LAGGED |
| EVPN | BGP_EVENT_TYPE_EVPN_ROUTE_ADDED, BGP_EVENT_TYPE_EVPN_ROUTE_WITHDRAWN, BGP_EVENT_TYPE_EVPN_ROUTE_BEST_CHANGED, global BGP_EVENT_TYPE_STREAM_LAGGED |
| BFD | BGP_EVENT_TYPE_BFD_SESSION_UP, BGP_EVENT_TYPE_BFD_SESSION_DOWN, BGP_EVENT_TYPE_BFD_SESSION_STATE_CHANGED, global BGP_EVENT_TYPE_STREAM_LAGGED |
OTC_ROUTE_BLOCKED is durable Policy only. Durable STREAM_LAGGED describes a
global committed-stream gap and is therefore compatible with every category.
Route events are sourced from the structured RIB broadcast,
including export-policy denial events (route_policy_filtered) for unicast
routes present in Loc-RIB but filtered from an outbound peer;
session events are sourced from the peer manager's session broadcast and cover
both lifecycle transitions and metadata-only BGP NOTIFICATION sent/received
events; policy events are sourced from the peer manager after a runtime policy
/ neighbor-set / peer-group / chain mutation is accepted and are also retained
in the bounded ListPolicyEvents process-local history ring; dataplane summary
events are status-row count changes from the existing ListFibRoutes and
ListBlackholeDiscards snapshots. ADR-0061 per-route FIB dataplane events are
emitted directly by the FIB runtime when a route is installed, withdrawn, or
fails to apply, and are replayable through SubscribeFromEvent when
[event_history].enabled = true (they are not replayed by ListFibRoutes).
EVPN events are sourced from the RIB's EVPN best-path broadcast and are also
retained in a bounded ListEvpnEvents process-local history ring.
The FIB rejected count reflects surfaced status rows; high-cardinality
route_limit_exceeded rows carry ListFibRoutes sampling metadata with
suppressed-row totals, so the event count itself is not a global
suppressed-route total. Empty category and type filters subscribe to
the default route + session live stream. A non-empty type filter narrows the
stream; BGP_EVENT_TYPE_POLICY_CHANGED,
BGP_EVENT_TYPE_DATAPLANE_STATUS_CHANGED, an ADR-0061 per-route dataplane
event type, an EVPN route event type, or a BGP_EVENT_TYPE_BFD_SESSION_* type
with empty categories selects the corresponding opt-in stream, and
EVENT_CATEGORY_POLICY, EVENT_CATEGORY_DATAPLANE, EVENT_CATEGORY_EVPN, or
EVENT_CATEGORY_BFD selects those streams explicitly. Transport sessions send ordinary
state-change lifecycle events over
a bounded channel that is separate from the lossless TCP collision-coordination
path, so high churn can drop observability events without risking collision
handling. Live lag warnings emit for route, session, per-route dataplane, EVPN,
and BFD sources, but not policy or peerless dataplane; durable lag is global.
Prefix and family
filters match unicast route events and per-route FIB dataplane events; session,
policy, EVPN, BFD, and peerless dataplane summary events do not match requests
that set prefix or afi_safi. Peer filters match route, session, EVPN, BFD,
and per-route FIB dataplane events; route events also match
target_peer_address for route_policy_filtered. Peer filters do not match
peerless dataplane summary events. BgpEvent repeats common fields such as peer, target peer,
prefix, type, and severity at the top level even when the payload also carries
them so category-agnostic clients can render or filter events without unpacking
the oneof.
ListSessionEvents accepts neighbor_address, lifecycle-only event_types,
and limit filters. Valid event_types are the eight session lifecycle types:
BGP_EVENT_TYPE_SESSION_STATE_CHANGED, BGP_EVENT_TYPE_SESSION_ESTABLISHED,
BGP_EVENT_TYPE_SESSION_LOST, BGP_EVENT_TYPE_PEER_ADDED,
BGP_EVENT_TYPE_PEER_REMOVED, BGP_EVENT_TYPE_PEER_ENABLED,
BGP_EVENT_TYPE_PEER_DISABLED, and BGP_EVENT_TYPE_MAX_PREFIX_WARNING. Empty
event_types means all eight lifecycle types.
BGP_EVENT_TYPE_MAX_PREFIX_WARNING is one crossing of a configured
max-prefix warning threshold (max_prefix_warning_percent, or the bound
itself under max_prefix_action = "warning"): it carries
EVENT_SEVERITY_WARNING, empty old and new states because nothing
transitioned, and a reason naming the scope, usage, bound, and threshold
percentage. It is never a teardown.
NOTIFICATION sent/received types are not retained in the history ring
and are rejected here with INVALID_ARGUMENT; subscribe to WatchEvents with
BGP_EVENT_TYPE_NOTIFICATION_SENT / BGP_EVENT_TYPE_NOTIFICATION_RECEIVED
for live NOTIFICATION metadata. Route event types are likewise rejected; use
RibService.ListRouteEvents for route history. The history ring holds at most
4096 events; limit = 0 requests that full bounded daemon window, and larger
values are clamped to the same ceiling. Responses contain the most recent
matching events, ordered oldest-to-newest within that selected recent window.
ListPolicyEvents accepts neighbor_address,
BGP_EVENT_TYPE_POLICY_CHANGED, and limit filters. Empty event_types
means all policy event types, which is currently equivalent to
BGP_EVENT_TYPE_POLICY_CHANGED; route, session, dataplane, and
stream_lagged event types are rejected with INVALID_ARGUMENT. A peer
filter matches only peer-scoped policy mutations such as neighbor import/export
chain changes; global policy, neighbor-set, peer-group, and global-chain
mutations are peerless and do not match a neighbor_address filter. The
history ring holds at most 4096 events, is process-local, and resets on daemon
restart.
ListEvpnEvents accepts neighbor_address, EVPN route-event event_types,
route_type_filter, rd_filter, and limit. Valid event types are
BGP_EVENT_TYPE_EVPN_ROUTE_ADDED,
BGP_EVENT_TYPE_EVPN_ROUTE_WITHDRAWN, and
BGP_EVENT_TYPE_EVPN_ROUTE_BEST_CHANGED; empty event_types means all three.
The peer filter matches both the current and previous best-path peer so
withdrawals and best-path moves away from a peer remain visible to
peer-scoped dashboards. route_type_filter accepts RFC 7432 / RFC 9136 route
types 1 through 5, and rd_filter uses the same display format as
ListEvpnRoutes. The history ring holds at most 4096 events, is process-local,
and resets on daemon restart.
Slow live-stream consumers do not block the daemon. If a WatchEvents
subscriber falls behind the bounded broadcast channel, missed events are
skipped and bgp_event_stream_lagged_total{service,source} records the missed
count. WatchEvents emits an in-band stream_lagged warning for route,
session, per-route dataplane, EVPN, and BFD sources.
Policy and peerless dataplane lag is metric-only and discarded without an
in-band warning. Use
bgp_event_stream_subscribers{service,source} to see active stream readers and
bgp_route_event_history_depth /
bgp_route_event_history_capacity to understand how much recent unicast route
history is available through ListRouteEvents. See
docs/reference/operations.md
for alerting guidance that combines these stream metrics with ADR-0064
authorization decision volume.
Unified event types:
| Type | Meaning |
|---|---|
BGP_EVENT_TYPE_ROUTE_ADDED |
Best path for a prefix was added |
BGP_EVENT_TYPE_ROUTE_WITHDRAWN |
Best path for a prefix was withdrawn |
BGP_EVENT_TYPE_ROUTE_BEST_CHANGED |
Best path for a prefix changed |
BGP_EVENT_TYPE_SESSION_STATE_CHANGED |
BGP FSM state changed; payload carries old/new state and session role |
BGP_EVENT_TYPE_SESSION_ESTABLISHED |
FSM reached Established |
BGP_EVENT_TYPE_SESSION_LOST |
FSM left Established; severity is WARNING |
BGP_EVENT_TYPE_PEER_ADDED |
Peer entered the authoritative managed set; payload carries new state idle and the exact scoped peer label |
BGP_EVENT_TYPE_PEER_REMOVED |
Peer left the authoritative managed set after session retirement; payload carries no FSM state or role and preserves the exact scoped peer label |
BGP_EVENT_TYPE_PEER_ENABLED |
Operator enabled a configured peer |
BGP_EVENT_TYPE_PEER_DISABLED |
Operator disabled a configured peer |
BGP_EVENT_TYPE_NOTIFICATION_SENT |
rustbgpd sent a BGP NOTIFICATION; payload carries direction, code, subcode, description, session role, and optional RFC 9003 shutdown reason |
BGP_EVENT_TYPE_NOTIFICATION_RECEIVED |
rustbgpd received a BGP NOTIFICATION from the peer; payload carries the same metadata |
BGP_EVENT_TYPE_BFD_SESSION_UP |
BFD session transitioned to Up |
BGP_EVENT_TYPE_BFD_SESSION_DOWN |
BFD session transitioned to Down |
BGP_EVENT_TYPE_BFD_SESSION_STATE_CHANGED |
BFD session changed state; payload carries peer, state, diagnostic, and strict flag |
NOTIFICATION events are metadata-only. Raw NOTIFICATION packet data remains
limited to BMP peer-down handling; WatchEvents does not retain or replay it.
NotificationEvent.shutdown_reason (RFC 9003) is peer-supplied free text
delivered verbatim over the API — it can legally contain ANSI escape sequences
as valid UTF-8, so consumers rendering it to a terminal must sanitize it;
rustbgpd's own CLI and log paths already escape it.
Stream health event types:
| Type | Meaning |
|---|---|
BGP_EVENT_TYPE_STREAM_LAGGED |
Live: source-scoped route, session, per-route dataplane, EVPN, or BFD loss. Durable: a global outbox gap compatible with every category. |
Policy event types:
| Type | Meaning |
|---|---|
BGP_EVENT_TYPE_POLICY_CHANGED |
Runtime policy, neighbor-set, peer-group, or chain mutation accepted by the peer manager. Payload carries operation, target type, target, optional peer address, and affected peer count. This is a runtime-applied audit signal; config-file persistence is a separate path. |
Dataplane event types:
| Type | Meaning |
|---|---|
BGP_EVENT_TYPE_DATAPLANE_STATUS_CHANGED |
FIB / BLACKHOLE installed, rejected, or failed status-row count changed; the legacy three-bucket summary intentionally ignores unresolved FIB rows, which are exposed by ListFibRoutes and bgp_fib_routes_unresolved |
BGP_EVENT_TYPE_DATAPLANE_ROUTE_INSTALLED |
ADR-0061 FIB runtime successfully installed or replaced one route |
BGP_EVENT_TYPE_DATAPLANE_ROUTE_WITHDRAWN |
ADR-0061 FIB runtime successfully removed one owned route |
BGP_EVENT_TYPE_DATAPLANE_ROUTE_FAILED |
ADR-0061 FIB runtime failed to apply one route operation; severity is WARNING |
Programmatic route injection and withdrawal. Injected routes appear as locally
originated (peer address 0.0.0.0) and are advertised to all peers (subject to
export policy).
| RPC | Description |
|---|---|
AddPath |
Inject a route with specified attributes |
DeletePath |
Withdraw a previously injected route |
AddFlowSpec |
Inject a FlowSpec rule with actions |
DeleteFlowSpec |
Withdraw a previously injected FlowSpec rule |
AddEvpnRoute |
Inject an EVPN Type 2 (MAC/IP), Type 3 (IMET), or Type 5 (IP Prefix) route; Type 5 may be interface-less or carry an overlay-index gateway |
DeleteEvpnRoute |
Withdraw a previously injected EVPN route by its EVPN route key |
grpcurl -plaintext -import-path . -proto proto/rustbgpd.proto \
-d '{
"prefix": "10.99.0.0",
"prefix_length": 24,
"next_hop": "10.0.0.1",
"communities": [4259905793]
}' \
localhost:50051 rustbgpd.v1.InjectionService/AddPathgrpcurl -plaintext -import-path . -proto proto/rustbgpd.proto \
-d '{
"prefix": "2001:db8:ff::",
"prefix_length": 48,
"next_hop": "fd00::1",
"origin": 0,
"as_path": [65001],
"local_pref": 100
}' \
localhost:50051 rustbgpd.v1.InjectionService/AddPathOptional fields: as_path, origin, local_pref, med, communities, extended_communities, large_communities, path_id.
The prefix and next_hop fields accept both IPv4 and IPv6 addresses. Prefix
length is validated against the address family (max 32 for IPv4, 128 for IPv6).
path_id defaults to 0 (default path) when omitted.
# IPv4
grpcurl -plaintext -import-path . -proto proto/rustbgpd.proto \
-d '{"prefix": "10.99.0.0", "prefix_length": 24}' \
localhost:50051 rustbgpd.v1.InjectionService/DeletePath
# IPv6
grpcurl -plaintext -import-path . -proto proto/rustbgpd.proto \
-d '{"prefix": "2001:db8:ff::", "prefix_length": 48}' \
localhost:50051 rustbgpd.v1.InjectionService/DeletePathgrpcurl -plaintext -import-path . -proto proto/rustbgpd.proto \
-d '{
"afi_safi": "ADDRESS_FAMILY_IPV4_FLOWSPEC",
"components": [
{ "type": 1, "prefix": "203.0.113.0/24" },
{ "type": 4, "value": "=80" }
],
"actions": [
{ "traffic_rate": { "rate": 0.0 } }
]
}' \
localhost:50051 rustbgpd.v1.InjectionService/AddFlowSpecgrpcurl -plaintext -import-path . -proto proto/rustbgpd.proto \
-d '{
"afi_safi": "ADDRESS_FAMILY_IPV4_FLOWSPEC",
"components": [
{ "type": 1, "prefix": "203.0.113.0/24" },
{ "type": 4, "value": "=80" }
]
}' \
localhost:50051 rustbgpd.v1.InjectionService/DeleteFlowSpecFlowSpec component lists must be non-empty, in ascending type-code order,
and limited to the supported RFC 8955 / RFC 8956 unicast component set.
Each injected or withdrawn rule must also encode to at most 4095 NLRI
payload bytes; larger rules are rejected with InvalidArgument.
grpcurl -plaintext -import-path . -proto proto/rustbgpd.proto \
-d '{
"route_type": 2,
"rd": "65000:100",
"ethernet_tag": 0,
"mac": "02:00:00:aa:bb:cc",
"ip": "10.0.0.5",
"label": 100,
"next_hop": "10.0.0.2",
"route_targets": ["65000:100"]
}' \
localhost:50051 rustbgpd.v1.InjectionService/AddEvpnRoutedisable_vxlan_encap defaults to false — the RFC 8365 §5.1.2 VXLAN
Encapsulation extended community (tunnel-type=8) is attached
automatically. Set disable_vxlan_encap: true for MPLS-over-GRE
deployments. The injection API supports route_type 2 (MAC/IP), 3
(IMET), and 5 (IP Prefix). Type 5 injection can use the default
interface-less gateway-zero shape or an explicit overlay-index
Gateway Address. Native Gate 9 Type 5
origination from [[evpn_ip_vrfs]] shipped in v0.18.0 (slice 6 PR A
#77): the daemon dumps kernel routes per
IP-VRF table_id, classifies them (connected/static/manual only —
routes installed by other routing daemons or whose output device is
the L3 VXLAN are filtered), and originates a Type 5 per surviving
prefix when the IP-VRF's readiness probe says Ready. Remote
Type 5 import + L3 FIB programming (kernel route + neighbor +
L3VXLAN FDB) shipped in v0.18.0 (slice 6 PR B #78) through the
transactional L3OwnedState model with four-phase apply ordering,
Router MAC conflict detection, and foreign-state preservation;
RTNLGRP_IPV4_ROUTE / RTNLGRP_IPV6_ROUTE multicast (#79) drives
sub-second withdraw on tenant ip addr del.
Native Type 1/4 multi-homing origination is driven by
[[ethernet_segments]]; the injection API does not expose those route
types yet (the RR still reflects them when received from peers).
grpcurl -plaintext -import-path . -proto proto/rustbgpd.proto \
-d '{
"route_type": 3,
"rd": "65000:100",
"ethernet_tag": 0,
"ip": "10.0.0.2",
"next_hop": "10.0.0.2"
}' \
localhost:50051 rustbgpd.v1.InjectionService/AddEvpnRoutegrpcurl -plaintext -import-path . -proto proto/rustbgpd.proto \
-d '{
"route_type": 5,
"rd": "65000:5000",
"ethernet_tag": 0,
"prefix": "10.50.0.0",
"prefix_length": 24,
"label": 5000,
"next_hop": "192.0.2.10",
"router_mac": "02:00:00:00:50:00",
"route_targets": ["65000:5000"]
}' \
localhost:50051 rustbgpd.v1.InjectionService/AddEvpnRouteBy default, Type 5 injection is interface-less: ESI and Gateway IP are
encoded as zero, label carries the L3VNI in the RFC 8365 VXLAN label
slot, ethernet_tag must be 0, and next_hop is the VTEP loopback.
Set optional gateway to inject a controller-supplied overlay-index
Type 5 route with a non-zero unicast Gateway Address; the prefix,
gateway, and next-hop must use the same IP family. Non-zero ESI
overlay-index injection is not exposed. router_mac is required when VXLAN
encapsulation is enabled (the default) and is advertised as the RFC
9135 Router MAC extended community. Omit it when
disable_vxlan_encap is true. At least one route_targets entry is
required for Type 5 injection.
grpcurl -plaintext -import-path . -proto proto/rustbgpd.proto \
-d '{
"route_type": 2,
"rd": "65000:100",
"ethernet_tag": 0,
"mac": "02:00:00:aa:bb:cc",
"ip": "10.0.0.5"
}' \
localhost:50051 rustbgpd.v1.InjectionService/DeleteEvpnRouteThe withdrawal key (route type + RD + ethernet tag + MAC + optional IP
for Type 2; route type + RD + ethernet tag + originator IP for Type 3;
route type + RD + ethernet_tag=0 + prefix/prefix length for Type 5)
matches the EVPN route identity used by rustbgpd. Type 5 gateway is
payload, not part of the local route key.
Omit ip when withdrawing a MAC-only Type 2 route or the key will not
match. Requests that include key fields from another route type are
rejected with INVALID_ARGUMENT. Returns NOT_FOUND if no such route
was previously injected.
grpcurl -plaintext -import-path . -proto proto/rustbgpd.proto \
-d '{
"route_type": 5,
"rd": "65000:5000",
"ethernet_tag": 0,
"prefix": "10.50.0.0",
"prefix_length": 24
}' \
localhost:50051 rustbgpd.v1.InjectionService/DeleteEvpnRouteDaemon lifecycle, health checks, and metrics.
| RPC | Description |
|---|---|
CheckLiveness |
Empty authenticated request/response; Read tier. A response means only the gRPC handler answered, without actor readiness or topology. Outside the narrow v1 contract; older servers return UNIMPLEMENTED. |
GetHealth |
Returns health status, uptime, active peers, total routes; core actor probes are bounded by the same 200 ms deadline as HTTP /readyz |
GetMetrics |
Returns Prometheus metrics as text |
Shutdown |
Initiates graceful shutdown |
TriggerMrtDump |
Triggers an on-demand MRT TABLE_DUMP_V2 dump |
GetHealth.healthy=true means the core peer-manager and RIB snapshot was
obtained. It does not assert that every BGP session or the dataplane is healthy.
Failure to obtain the snapshot returns an RPC error, rather than a successful
healthy=false response. active_peers counts only non-stale peers observed
Established; unavailable observations are excluded even if their last known
state was Established. The count can therefore omit live sessions. Use
ListNeighbors and its stale flag to distinguish unavailable state from an
observed session state.
grpcurl -plaintext -import-path . -proto proto/rustbgpd.proto \
localhost:50051 rustbgpd.v1.ControlService/GetHealthFor a handler-liveness probe that needs only the Read tier, call
CheckLiveness (an empty response) or run rbgp health --liveness, which
prints alive. It does not check actor readiness.
grpcurl -plaintext -import-path . -proto proto/rustbgpd.proto \
localhost:50051 rustbgpd.v1.ControlService/CheckLiveness
rbgp health --livenessIf [global.telemetry] prometheus_addr is configured, the same HTTP listener
also exposes unauthenticated probe endpoints:
curl -fsS http://127.0.0.1:9179/livez
curl -fsS http://127.0.0.1:9179/readyz/livez only proves the process is accepting HTTP connections. /readyz
returns 200 ready when PeerManager and RIB respond within 200 ms total, or
503 not ready: <reason> when either core actor is unavailable, drops its
reply, or times out. During an actor-owned export-policy transition the
dedicated read-only RIB lane stays healthy for bounded progress, then returns
503 not ready: RIB export-policy transition stalled if ownership reaches 30
seconds; commit or cleaned-up fallback restores readiness immediately. A
selection-deferral release shares that bound and returns
503 not ready: RIB selection-deferral release stalled once it runs for 30
seconds; readiness returns when the release finishes. It does not require
peers or routes to exist.
Authoritative export-policy replacement and rollback service this same RIB readiness lane between construction, per-peer work, and cleanup steps. These operations preserve Loc-RIB cardinality, so readiness replies report its exact count while ordinary route queries and mutations remain queued. Nested replacements retain the original command's 30-second transition-age limit.
grpcurl -plaintext -import-path . -proto proto/rustbgpd.proto \
localhost:50051 rustbgpd.v1.ControlService/GetMetricsgrpcurl -plaintext -import-path . -proto proto/rustbgpd.proto \
-d '{"reason": "maintenance window"}' \
localhost:50051 rustbgpd.v1.ControlService/Shutdowngrpcurl -plaintext -import-path . -proto proto/rustbgpd.proto \
localhost:50051 rustbgpd.v1.ControlService/TriggerMrtDumpLocal EVPN state and bounded controls for this VTEP. Empty read
responses are normal when the daemon is acting purely as an EVPN
route reflector — RR mode does not declare local instances. The same
[[evpn_instances]] table that this service exposes is the input to the Linux kernel
reconciler (Gate 7b, ADR-0054 — programs remote-MAC FDB entries
downward), the local-MAC originator + Type 3 IMET emitter (Gate 7b+1,
ADR-0055 — emits Type 2 / Type 3 routes upward from kernel-learned
state), the Gate 8 segment/DF orchestrator, and the Gate 9 Type 5 /
IP-VRF path. The originators and dataplane actors bypass this gRPC
surface; they translate kernel/RIB events directly into reconcile
inputs and RibUpdate::InjectEvpn / WithdrawEvpn against the RIB.
See ADR-0052 for the original boundary, ADR-0054/ADR-0055 for the
dataplane + origination boundaries, and ADR-0063 for the runtime mutation
semantics used by both ApplyEvpnRuntime and SIGHUP reload.
| RPC | Description |
|---|---|
GetEvpnRuntime |
Return the committed EVPN runtime generation, lifecycle, mutation state, configured EVI/IP-VRF/ES counts, and a concise status message |
ListEvpnInstances |
List configured local EVPN instances sorted by VNI (vni, rd, resolved route_targets including any auto-derived RT, local_vtep_ip, optional bridge, optional local bridge_vlan, advertise_svi_mac flag, originated_local_macs_count, L2 dataplane readiness_state, and not_ready_reason when NotReady) |
ListEvpnNexthops |
List Linux dataplane reconciler-owned ADR-0059 FDB nexthop groups (per-VNI groups with ESI / Ethernet Tag / kernel group ID, per-VTEP member nexthop IDs + gateways, MAC refs) plus top-level L2 and L3 orphan-NH and pending-delete counts and the drift_recovery_disabled latch — read-only operator visibility |
ListEthernetSegments |
List configured Ethernet Segments sorted by ESI, joined with live multi-homing state: composed drain reasons, per-member DF role and BUM forwarding action, same-ESI local-bias eligibility, whole-port AC-gate state/interface, and matching FDB-NHG group / MAC-ref counts — read-only ADR-0083/0085 diagnose visibility |
ListIpVrfs |
List configured IP-VRFs / L3VNI tenants (name, l3vni, rd, resolved route_targets including any auto-derived RT, local_vtep_ip, router_mac, optional evpn_instance link, readiness state, originated_routes_count, installed_routes_count, remote_prefix_drop_counts) — Gate 9 / ADR-0058 |
ListManagedNetdevs |
List configured ADR-0091 managed EVPN bridge, fixed-VNI VXLAN, SVD / collect-metadata VXLAN, VLAN upper, VRF, and L3VXLAN rows joined with the latest Linux link snapshot, plus rustbgpd-stamped orphan/unsafe rows for the configured owner. Reports class, name, desired flag, ownership stamp, state (desired-absent, owned-safe, foreign-present, owned-unsafe, orphaned, or unknown), observed ifindex, bridge vlan_filtering, VXLAN/SVD/L3VXLAN vni / local / dstport / learning / collect-metadata / vnifilter / master attributes, VLAN upper vlan, VRF table_id, L3VXLAN router_mac, observed rustbgpd ownership stamps, and reason text. Bridge, fixed-VNI VXLAN, SVD VXLAN, VLAN upper, VRF, and L3VXLAN lifecycle execution is active in the dataplane actor; this RPC remains read-only status. |
GetIpVrf |
Detail view of a single IP-VRF including the seven readiness predicates (not_ready_reasons) when readiness_state != Ready and scoped remote Type 5 projection-drop counts |
ListDuplicateMacQuarantines |
List at most 4096 active duplicate-MAC local-origin quarantine keys from one actor-published snapshot, ordered by VNI and raw MAC. omitted is exact and complete is true exactly when no rows were omitted. |
ClearDuplicateMacQuarantine |
Clear one RFC 7432 §15.1 duplicate-MAC local-origin quarantine by (vni, mac). Returns cleared=false when no active quarantine exists; read-only listeners reject it. |
ApplyEvpnRuntime |
Validate or apply a full candidate EVPN runtime model through the ADR-0063 coordinator. validate_only=true returns the plan without mutation; no-op applies succeed. Supported live changes include single L2VNI/IP-VRF/Ethernet-Segment add/delete/redefine, additive build-up, atomic tenant teardown, ip_vrf relink, and decomposable mixed edits ordered as deletes -> redefines -> ip_vrf relinks -> adds. When a segment actor already exists, L2VNI add/delete republishes the current instance table so later ES add/redefine can bind a VNI added at runtime; ES-member L2VNI redefine rebuilds the segment actor's Type 1/4 routes from the candidate instance snapshot. L3VNI/device/table IP-VRF identity changes remain restart-required by design. Unsupported dependency cycles fail closed before commit; residual mid-sequence convergence failures fail-stop after already committed generations and are surfaced by evpn_runtime_decomposed_fail_stops_total. |
Instance mutation (AddEvpnInstance / DeleteEvpnInstance) remains out
of scope. GetEvpnRuntime now reports the daemon-owned ADR-0063
coordinator generation. At startup this is generation 1, lifecycle
active, and mutation state idle when the coordinator is available.
ApplyEvpnRuntime accepts a full candidate rustbgpd TOML document so the
daemon can reuse the normal config parser, validator, and EVPN table
resolution. Because that TOML may contain unrelated credentials, audit
logs record only its byte length and mode. Runtime mutation is not a
shared-table swap: it drives the live IMET controller, the
MAC-only/MAC+IP/SVI Type 2 originators, the Type 5/IP-VRF originator, and
the Linux dataplane supervisor through ordered convergence commands with
rollback on partial failure.
Supported non-noop shapes converge live and commit the next generation:
single L2VNI/IP-VRF/Ethernet-Segment add/delete/redefine, additive build-up,
atomic tenant teardown, ip_vrf relink, and decomposable mixed edits. The
mixed-edit decomposer applies primitive commits in a fixed order — deletes,
redefines, ip_vrf relinks, then adds — so operators may observe multiple
runtime generations for one SIGHUP or ApplyEvpnRuntime request. ES
add/redefine can reference a member VNI added by an earlier live L2VNI add when
the segment actor is already running; ES-member L2VNI redefine rebuilds Type 4 /
EAD-per-ES / EAD-per-EVI routes from the candidate instance snapshot while
retaining the stable ESI label. L3VNI/device/table IP-VRF identity changes
remain restart-required by design. Unsupported dependency cycles fail closed
before commit. If a later primitive step or actor command fails after earlier
steps committed, the sequence fail-stops there: the committed generations stay
visible, the coordinator pins mutation_state=Failed, an ERROR log names the
step, and evpn_runtime_decomposed_fail_stops_total increments for the
mid-sequence stop.
Operators configure instances via the [[evpn_instances]] TOML block.
SIGHUP reload submits EVPN table edits through the same coordinator for
supported ADR-0063 shapes. Unsupported dependency cycles, L3VNI/device/table
IP-VRF identity changes, or missing EVPN actors fail closed before commit.
Actor convergence failures inside a decomposed sequence fail-stop on the last
committed generation and keep the drift visible instead of silently advancing
the config snapshot.
grpcurl -plaintext -import-path . -proto proto/rustbgpd.proto \
localhost:50051 rustbgpd.v1.EvpnService/GetEvpnRuntimeOr via CLI:
rbgp evpn runtime # human format
rbgp evpn runtime --json # JSON outputgrpcurl -plaintext -import-path . -proto proto/rustbgpd.proto \
localhost:50051 rustbgpd.v1.EvpnService/ListEvpnInstancesOr via CLI:
rbgp evpn instances # human format
rbgp evpn instances --json # JSON output
rbgp evpn diagnose # instance / Type 2 / Type 3 / metric summaryThe human CLI includes readiness=ready|not-ready|unbound|unknown,
originated-local-macs=N, and bridge-vlan=N when the instance has a
local bridge VLAN binding; not-ready rows include the single L2
readiness probe reason. JSON exposes the same fields as readiness,
originated_local_macs_count, not_ready_reason, and bridge_vlan
(null when absent); gRPC exposes the enum as readiness_state and the
local VLAN binding as optional bridge_vlan. Unbound means no
bridge is configured for that L2VNI. Unknown means the instance is
bridge-bound but no dataplane verdict is on file yet, usually cold start
before the first reconcile report or an RR-only / dataplane-disabled
deployment. originated_local_macs_count counts MAC-only Type 2 routes
currently originated by this daemon for the instance and accepted by the
RIB. bridge_vlan is local Linux attribution only: EVPN Type 2 / Type 3 /
EAD-per-EVI routes still use Ethernet Tag ID 0. When present, it
selects ADR-0089's traditional VNI-per-broadcast-domain VLAN-aware path:
the probe requires vlan_filtering=1, exactly one VXLAN member for the
instance VNI, and the configured VLAN on both the bridge and that VXLAN
member; remote-MAC FDB rows are then programmed with NDA_VLAN. A
vlan_filtering=1 bridge without bridge_vlan remains NotReady.
ADR-0059 operator-visibility surface. Returns the Linux dataplane
reconciler's owned FDB nexthop-group state: one row per group with
VNI, ESI, Ethernet Tag, kernel group ID, per-VTEP member nexthop IDs,
and MAC refs. The response also includes orphan tagged nexthop count,
pending-delete count, and whether periodic drift recovery latched off
after a permanent dump failure. orphan_nexthops_count and
pending_delete_count cover L2 FDB-NHG nexthops;
l3_orphan_nexthops_count and l3_pending_delete_count report the same
state for L3 (all-active Type 5) FDB nexthops, which share the retry
queue.
grpcurl -plaintext -import-path . -proto proto/rustbgpd.proto \
localhost:50051 rustbgpd.v1.EvpnService/ListEvpnNexthopsOr via CLI:
rbgp evpn nexthops # human format
rbgp evpn nexthops --json # JSON outputAn empty groups list is normal on RR-only deployments, single-homed
VTEPs, or multi-homed VNIs with apply_aliasing_ecmp = false — the
top-level count fields and drift_recovery_disabled are always
populated regardless.
ADR-0083 / ADR-0085 diagnose surface. Returns one row per configured
[[ethernet_segments]] entry, optionally filtered by ESI. Each row
joins the committed ES config with the latest segment/dataplane
snapshots: drain reasons (operator / link), DF role and BUM action
per member VNI, same-ESI local-bias eligibility, the single-active
whole-port AC-gate state/interface, and matching owned FDB-NHG group /
MAC-ref counts. Empty runtime fields mean the segment actor or
dataplane has not published that snapshot yet; the RPC itself is
read-only.
grpcurl -plaintext -import-path . -proto proto/rustbgpd.proto \
-d '{"esi":"00:11:22:33:44:55:66:77:88:99"}' \
localhost:50051 rustbgpd.v1.EvpnService/ListEthernetSegmentsOr via CLI:
rbgp evpn es list
rbgp evpn es list 00:11:22:33:44:55:66:77:88:99 --jsonGate 9 / ADR-0058 surface. Returns one row per [[evpn_ip_vrfs]]
entry with the readiness verdict the EVPN reconcile actor most
recently published, plus the Type 5 origination / install counters and
current scoped remote Type 5 projection-drop counts. The drop counts
reuse the bounded reason labels from
evpn_ip_vrf_remote_prefix_drops{vrf,reason} and omit per-route
prefixes, gateways, next-hops, MACs, RDs, and RTs.
grpcurl -plaintext -import-path . -proto proto/rustbgpd.proto \
localhost:50051 rustbgpd.v1.EvpnService/ListIpVrfsOr via CLI:
rbgp evpn vrfs # human format
rbgp evpn vrfs --json # JSON output
rbgp evpn vrfs vrf1 # single-VRF detail (matches GetIpVrf)ADR-0091 managed-netdev lifecycle/status surface. Returns configured
[managed_netdevs] bridge, fixed-VNI VXLAN, SVD / collect-metadata VXLAN,
VLAN upper, VRF, and L3VXLAN rows joined with the latest Linux link snapshot,
plus rustbgpd-stamped orphan links observed by the dataplane actor. This is
read-only status: a row can be desired-absent, foreign-present,
owned-unsafe, owned-safe, orphaned, or unknown. Bridge, fixed-VNI
VXLAN, SVD VXLAN, VLAN upper, VRF, and L3VXLAN rows are active lifecycle
intent inside the dataplane reconciler. The RPC itself never mutates links.
Bridge rows expose observed vlan_filtering. Fixed-VNI and SVD VXLAN rows
expose observed vni, local, dstport, learning-disabled,
collect-metadata, vnifilter, and bridge-master fields when the Linux link
snapshot reports them. VRF rows expose observed table_id and up. L3VXLAN
rows expose observed vni, local, dstport, learning-disabled,
collect-metadata, vnifilter, vrf master, up, and router_mac. VLAN
upper rows expose observed parent bridge, VLAN id, and link-up state.
grpcurl -plaintext -import-path . -proto proto/rustbgpd.proto \
localhost:50051 rustbgpd.v1.EvpnService/ListManagedNetdevsOr via CLI:
rbgp evpn managed-netdevs
rbgp evpn managed-netdevs --jsonReturns the same row as ListIpVrfs plus, when readiness_state is
not Ready, the not_ready_reasons list — one entry per failing
ADR-0058 §3 predicate (e.g., vrf_table_id_mismatch,
l3vxlan_router_mac_mismatch). remote_prefix_drop_counts reports the
current bounded receive-side Type 5 projection drops for this IP-VRF.
grpcurl -plaintext -import-path . -proto proto/rustbgpd.proto \
-d '{"name": "vrf1"}' \
localhost:50051 rustbgpd.v1.EvpnService/GetIpVrfClears local-origin suppression for one (VNI, MAC) after an operator
has confirmed the loop condition is gone. The RPC does not clear every
quarantine and does not remove Loc-RIB/RR visibility; if the MAC is
still locally present, the originator immediately replays the live
MAC-only or MAC+IP state through the normal recovery path.
grpcurl -plaintext -import-path . -proto proto/rustbgpd.proto \
-d '{"vni": 100, "mac": "aa:bb:cc:dd:ee:ff"}' \
localhost:50051 rustbgpd.v1.EvpnService/ClearDuplicateMacQuarantineOr via CLI:
rbgp evpn clear-duplicate-mac --vni 100 --mac aa:bb:cc:dd:ee:ff
rbgp evpn clear-duplicate-mac --vni 100 --mac aa:bb:cc:dd:ee:ff --json
rbgp evpn duplicate-mac-quarantines
rbgp evpn duplicate-mac-quarantines --jsonValidates a full candidate rustbgpd TOML document against the committed
EVPN runtime model and returns a plan summary. Use validate_only=true
to inspect added/deleted/redefined/unchanged EVPN instances, IP-VRFs,
and Ethernet Segments — plus the ip_vrf_references_changed flag, which is
the only non-empty plan signal for a pure ip_vrf relink — without changing
the committed generation. A dry-run rejects exactly what a commit of the
identical candidate would reject, including the actor-availability
preconditions: an L2VNI add validated on an RR-only daemon (which spawns no
EVPN dataplane actors) fails with the same FAILED_PRECONDITION the commit
would return.
grpcurl -plaintext -import-path . -proto proto/rustbgpd.proto \
-d @ localhost:50051 rustbgpd.v1.EvpnService/ApplyEvpnRuntime <<'JSON'
{
"candidate_toml": "[global]\nasn = 65000\nrouter_id = \"10.0.0.1\"\nlisten_port = 179\n\n[global.telemetry]\nlog_format = \"json\"\n\n[security.grpc]\nenforcement = \"tier\"\n\n[[evpn_instances]]\nvni = 100\nrd = \"65000:100\"\nroute_targets = [\"65000:100\"]\nlocal_vtep_ip = \"10.0.0.1\"\n",
"validate_only": true
}
JSONA non-validate_only request commits when the candidate is a no-op, a
single L2VNI add, a single L2VNI delete that is not an Ethernet Segment member,
a single L2VNI redefine with unchanged ip_vrf link metadata, a single IP-VRF
add, a single standalone
IP-VRF delete with no L2VNI links, a single IP-VRF redefine with unchanged
L3VNI/device/table identity, a single Ethernet Segment add/delete/redefine,
additive build-up, an atomic tenant teardown, or an ip_vrf relink
against the committed model; the response
carries the committed generation and outcome. L3VNI/device/table IP-VRF identity changes remain restart-required by design.
Unsupported dependency cycles, an ES referencing an unknown member VNI, or an ES
apply with no running segment actor are rejected with FAILED_PRECONDITION
before commit; decomposable mixed edits may commit multiple generations and
fail-stop on a later primitive convergence failure as described above.
Read-only inspection of single-hop and multihop BFD sessions (ADR-0067,
RFC 5880/5881/5883).
Sessions themselves are configured via [[bfd_profiles]] + [neighbors.bfd]
(see CONFIGURATION.md). Member attachments apply on SIGHUP;
profile definitions require a restart. There is no mutating BFD RPC, and config
transactions do not apply BFD attachment changes.
| RPC | Description |
|---|---|
GetBfdSessions |
List BFD sessions (peer address, state, last diagnostic, strict flag, remote-AdminDown cause, multihop mode flag), optionally filtered to one peer_address; a filter that matches no session and names no known peer returns NOT_FOUND (unknown peers) |
# All BFD sessions
grpcurl -plaintext -import-path . -proto proto/rustbgpd.proto \
localhost:50051 rustbgpd.v1.BfdService/GetBfdSessions
# One peer
grpcurl -plaintext -import-path . -proto proto/rustbgpd.proto \
-d '{"peer_address": "10.0.0.2"}' \
localhost:50051 rustbgpd.v1.BfdService/GetBfdSessionsstate is a BfdSessionState (BFD_SESSION_STATE_{ADMIN_DOWN,DOWN,INIT,UP}).
remote_administrative_down is an explicit-presence boolean: true explains
that the peer disabled BFD and RFC 5882 section 4.1 permits BGP even though the
local BFD state remains DOWN; false means a current daemon observed no
such cause. An absent field means the serving daemon predates this visibility
and the cause is unknown—clients must not default absence to false.
Live state-change events are available on EventService.WatchEvents with
EVENT_CATEGORY_BFD (opt-in). RFC 5882 BGP coupling — strict (withhold BGP
until BFD Up) and non-strict (tear BGP down on BFD-down before the hold timer) —
is driven by PeerManager from these sessions; it is not exposed as a separate
RPC.
ValidateRouteOrigin is a sensitive_read, outside-v1 diagnostic over the
latest authoritative VrpTable. It accepts a bare IP address, an explicitly
present prefix length, and a nonzero origin ASN. Host bits are normalized in
the response. CIDR text in prefix, omitted or out-of-family prefix lengths,
malformed addresses, and AS0 queries return INVALID_ARGUMENT.
rbgp rpki validate 192.0.2.129/24 64496
grpcurl -plaintext -import-path . -proto proto/rustbgpd.proto \
-d '{"prefix":"192.0.2.129","prefix_length":24,"origin_asn":64496}' \
localhost:50051 rustbgpd.v1.RpkiService/ValidateRouteOriginThe valid, invalid, or not_found verdict is computed from the complete
table before diagnostic truncation. covering_vrps contains at most 256
effective duplicate-collapsed rows, authorizers first, followed by deterministic
non-authorizers. complete and exact omitted describe that listing. AS0 VRPs
remain visible but always have authorizes: false. Before the first
authoritative snapshot the RPC returns FAILED_PRECONDITION; a received empty
snapshot is authoritative and returns not_found with a complete empty list.
This surface does not expose cache provenance, readiness, raw RTR data, or cache
management.
ListCaches exposes configured RTR endpoints in canonical address order,
current transport connectivity, and the latest atomically accepted End of Data
epoch. accepted is present for an accepted empty table and absent before the
first accepted epoch or after expiry/fatal flush. Protocol version, session ID,
and serial are optional for compatibility with legacy embedded update
producers. Counts are derived from the retained per-cache contribution, and
age_seconds is monotonic whole seconds. Results are capped at 256 rows with
exact complete/omitted; an unconfigured daemon returns a complete empty
list. A closed actor returns UNAVAILABLE, while the two-second whole-query
budget returns DEADLINE_EXCEEDED.
rbgp rpki caches
grpcurl -plaintext -import-path . -proto proto/rustbgpd.proto \
-d '{}' localhost:50051 rustbgpd.v1.RpkiService/ListCachesLookupAspa and VerifyAsPath are sensitive_read methods outside the narrow
v1 contract. Each call clones the current authoritative validation snapshot
once, releases the watch borrow, and uses that immutable ASPA table throughout.
No snapshot epoch, per-cache provenance, or freshness claim is attached: those
metadata are not present in the merged table.
rbgp rpki aspa 64497
rbgp --json rpki verify-path --role peer --neighbor-asn 64496 "64496 64497"LookupAspa takes a nonzero customer_asn. It returns found plus at most
256 sorted, deduplicated provider_asns from the effective merged table;
complete and exact omitted describe truncation. A missing customer has
found: false; a present empty provider set has found: true. AS0 providers
remain visible. An authoritative empty table yields a complete missing result.
VerifyAsPath takes segments of kind SEQUENCE or SET, an explicit nonzero
neighbor_asn, and local_role. The role describes the receiving speaker:
CUSTOMER selects downstream verification; PROVIDER, PEER, ROUTE_SERVER,
RS_CLIENT, and explicit NONE select upstream verification. Only RS_CLIENT
exempts the first-AS neighbor comparison. UNSPECIFIED and unknown roles or
segment kinds are invalid arguments. There is no caller-controlled exemption.
The request is limited to 4096 nonzero ASNs across at most 4096 nonempty
segments. An empty segment list represents an empty path. Empty paths, any
AS_SET, and nonexempt first-AS mismatches produce INVALID without an
invalid_hop. The validation result otherwise comes directly from the shared
ASPA verifier; invalid_hop, when present, is its first proven
NotProviderPlus customer/provider pair. Consecutive prepends retain the
verifier's existing compression semantics. The verdict uses the complete table,
independently of the provider-lookup display limit.
This is hypothetical eBGP IPv4/IPv6-unicast verification of an effective
four-octet AS_PATH, after AS4 reconstruction. It does not evaluate a complete
UPDATE, peer admission, other address families, or import policy. Live ingress
may treat an AS_SET or first-AS mismatch as a withdrawal before reaching ASPA.
An absent authoritative ASPA table returns FAILED_PRECONDITION from both
methods; an authoritative empty table still runs the verifier, so structural
invalidity is not hidden as UNKNOWN. Malformed or oversized requests return
INVALID_ARGUMENT before table lookup. Successful diagnostic verdicts,
including invalid, produce CLI exit 0; malformed CLI input exits 2 before
connecting, and operational RPC failures exit 1.
The CLI accepts whitespace-separated decimal ASNs and {ASN ASN} sets in a
quoted literal, with a 65536-byte text limit. Both calls use its complete
30-second unary response deadline. Add --json for machine-readable results.
The full proto definition is at proto/rustbgpd.proto.
You can generate typed clients for Python, Go, Rust, Node.js, or any language
with protobuf/gRPC support.