Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
14 changes: 14 additions & 0 deletions .github/workflows/kernel-dataplane.yml
Original file line number Diff line number Diff line change
Expand Up @@ -193,6 +193,20 @@ jobs:
topology: tests/interop/m52-fib-ecmp-relax-frr.clab.yml
script: tests/interop/scripts/test-m52-fib-ecmp-relax-frr.sh

m58:
name: M58 — ADR-0061 FIB-table runtime CRUD (FRR 10.3.1)
runs-on: ubuntu-latest
timeout-minutes: 30
steps:
- uses: actions/checkout@v6
- uses: ./.github/actions/setup-dataplane-host
- name: Run M58 (deploy + test + destroy with retry)
uses: ./.github/actions/run-interop-test
with:
label: M58
topology: tests/interop/m58-fib-table-crud-frr.clab.yml
script: tests/interop/scripts/test-m58-fib-table-crud-frr.sh

m53:
name: M53 — ADR-0069 BGP unnumbered + scoped FIB (FRR 10.3.1)
runs-on: ubuntu-latest
Expand Down
14 changes: 14 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,20 @@ This project follows [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
Adding a range validates identically to config load (peer-group must exist
and not enable BFD, valid prefix, no duplicate effective prefix); config-load
validation now also rejects exact-duplicate effective prefixes.
- **Runtime `[[fib_tables]]` CRUD.** `SetFibTable` (create-or-replace by name,
tier `mutating`), `DeleteFibTable` (tier `mutating`), and `ListFibTables`
(tier `sensitive_read`) on `RibService`, with `rustbgpctl fib-table
{list,set,delete}`. `set` carries the full table definition (not a patch);
changing `table_id`/`metric` for an existing name is a table-key move (old
kernel rows withdraw, the new table back-fills). Edits hot-apply through the
ADR-0061 FIB reconciler and persist to the TOML config (atomic write) when
started with `--config`. The candidate is validated against the live config
before dispatch, persisted only after the reconciler acknowledges the exact
accepted set, and serialized with SIGHUP FIB reloads through one coordinator
lock — so runtime and on-disk config cannot drift. Requires the reconciler to
be running (at least one `[[fib_tables]]` entry at startup) — otherwise the
mutating RPCs return `FAILED_PRECONDITION` (enabling FIB from an empty config
is still restart-required).

### Changed

Expand Down
4 changes: 3 additions & 1 deletion ROADMAP.md
Original file line number Diff line number Diff line change
Expand Up @@ -102,7 +102,9 @@ Later for what remains.
operator-confidence pieces).* ADR-0061/0066/0068 cover configured-table
install, ECMP, per-class caps, `multipath_relax`, and Link Bandwidth
weighting; the next pain points are lifecycle and scale, not base
capability. In scope now: hot-swap `[[fib_tables]]` (operator confidence).
capability. **Done:** hot-swap `[[fib_tables]]` without a restart — SIGHUP
soft-reload and gRPC/`rustbgpctl fib-table` CRUD (`SetFibTable` /
`DeleteFibTable` / `ListFibTables`), ack-gated with no runtime/config drift.
Decide based on signal: over-cap detail APIs beyond the sampled
`route_limit_exceeded` rows. Defer unless perf-gated or demanded: incremental
equal-cost sibling index for wide full-table multipath; platform-diversity
Expand Down
24 changes: 21 additions & 3 deletions crates/api/src/authz.rs
Original file line number Diff line number Diff line change
Expand Up @@ -412,6 +412,24 @@ pub const METHODS: &[GrpcMethodAuthz] = &[
"/rustbgpd.v1.RibService/ListFibRoutes",
AuthTier::SensitiveRead,
),
method(
"rustbgpd.v1.RibService",
"SetFibTable",
"/rustbgpd.v1.RibService/SetFibTable",
AuthTier::Mutating,
),
method(
"rustbgpd.v1.RibService",
"DeleteFibTable",
"/rustbgpd.v1.RibService/DeleteFibTable",
AuthTier::Mutating,
),
method(
"rustbgpd.v1.RibService",
"ListFibTables",
"/rustbgpd.v1.RibService/ListFibTables",
AuthTier::SensitiveRead,
),
method(
"rustbgpd.v1.RibService",
"ListRouteEvents",
Expand Down Expand Up @@ -707,7 +725,7 @@ mod tests {
.collect::<BTreeSet<_>>();

assert_eq!(matrix_methods, proto_methods);
assert_eq!(METHODS.len(), 78);
assert_eq!(METHODS.len(), 81);
}

#[test]
Expand Down Expand Up @@ -748,8 +766,8 @@ mod tests {
#[test]
fn method_matrix_tier_counts_match_inventory() {
assert_eq!(method_count_by_tier(AuthTier::Read), 0);
assert_eq!(method_count_by_tier(AuthTier::SensitiveRead), 42);
assert_eq!(method_count_by_tier(AuthTier::Mutating), 17);
assert_eq!(method_count_by_tier(AuthTier::SensitiveRead), 43);
assert_eq!(method_count_by_tier(AuthTier::Mutating), 19);
assert_eq!(method_count_by_tier(AuthTier::OperatorOnly), 19);
}

Expand Down
2 changes: 1 addition & 1 deletion crates/api/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ mod peer_group_service;
pub mod peer_types;
mod policy_helpers;
mod policy_service;
mod rib_service;
pub mod rib_service;
pub mod server;

pub use evpn_service::EvpnService;
Expand Down
62 changes: 62 additions & 0 deletions crates/api/src/peer_types.rs
Original file line number Diff line number Diff line change
Expand Up @@ -298,6 +298,34 @@ pub enum PeerManagerCommand {
/// Reply channel returning redacted diff output only.
reply: oneshot::Sender<Result<RuntimeConfigDiff, String>>,
},
/// Atomically validate a candidate `[[fib_tables]]` set against the live
/// runtime config (peer-group references, reserved/duplicate table ids,
/// families, ECMP caps) and, on success, stage it into
/// `current_config.fib_tables`. Used by the gRPC FIB-table CRUD control
/// path before it reaches the FIB reconciler. Validating and staging in one
/// command (the peer manager processes commands serially) closes the TOCTOU
/// against a concurrent peer-group deletion that would otherwise check a
/// snapshot that doesn't yet reflect the in-flight table's references.
StageFibTables {
/// The full candidate table set (already merged with the upsert/delete).
tables: Vec<FibTableSnapshot>,
/// Reply: `Ok(())` if it validated and was staged, else `Err(msg)`
/// (nothing staged).
reply: oneshot::Sender<Result<(), String>>,
},
/// Refresh the peer manager's runtime config snapshot with the accepted
/// `[[fib_tables]]` set after a successful gRPC CRUD mutation, so the
/// snapshot the live `DiffRuntimeConfig` compares against doesn't report
/// the just-applied set as a pending change. The control path awaits the
/// ack (while holding the FIB coordinator lock) so the snapshot is applied
/// before the mutation returns and before a concurrent SIGHUP reload can
/// run — keeping the two snapshot writers serialized.
SetFibTablesSnapshot {
/// The full accepted table set the FIB reconciler acknowledged.
tables: Vec<FibTableSnapshot>,
/// Acknowledgement, sent after `current_config.fib_tables` is assigned.
reply: oneshot::Sender<()>,
},
/// Query a single peer's state by address.
GetPeerState {
/// Peer identity to query.
Expand Down Expand Up @@ -886,11 +914,45 @@ pub struct PeerManagerNeighborConfig {
pub export_policy: Option<PolicyChain>,
}

/// Crate-boundary mirror of a binary `FibTableConfig` (`[[fib_tables]]`).
///
/// `ConfigEvent` lives in this API crate but the binary owns the real config
/// types, so FIB-table persistence events carry this plain snapshot instead of
/// the binary struct. The binary's `apply_config_event` converts it back to a
/// `FibTableConfig`, validating the candidate config before assigning.
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct FibTableSnapshot {
/// Operator-facing handle, unique across the table set.
pub name: String,
/// Linux route table id.
pub table_id: u32,
/// Kernel route metric / priority for daemon-owned rows.
pub metric: u32,
/// Address families eligible for install (empty = both unicast families).
pub families: Vec<String>,
/// Optional peer-group allow-list (empty = all peer groups).
pub allowed_peer_groups: Vec<String>,
/// Optional neighbor-address allow-list (empty = all neighbors).
pub allowed_neighbors: Vec<String>,
/// Optional hard row cap (None = no cap).
pub max_routes: Option<u32>,
/// Optional ECMP caps (ADR-0066); None = single next-hop / fallback.
pub maximum_paths: Option<u32>,
/// Per-class eBGP ECMP cap; None falls back to `maximum_paths`.
pub maximum_paths_ebgp: Option<u32>,
/// Per-class iBGP ECMP cap; None falls back to `maximum_paths`.
pub maximum_paths_ibgp: Option<u32>,
}

/// A config persistence event sent after successful peer add/delete.
///
/// The binary crate converts these into config file mutations.
/// Kept simple — only the data the neighbor service already has.
pub enum ConfigEvent {
/// The `[[fib_tables]]` set was replaced at runtime (gRPC FIB-table CRUD).
/// Carries the full accepted table set the FIB reconciler acknowledged, so
/// persistence writes exactly what the runtime applied.
FibTablesReplaced(Vec<FibTableSnapshot>),
/// A neighbor was successfully added at runtime.
NeighborAdded(PeerManagerNeighborConfig),
/// A neighbor was successfully deleted at runtime.
Expand Down
Loading