Skip to content

chore(release): prepare v0.23.1 - #350

Merged
lance0 merged 2 commits into
mainfrom
release/v0.23.1
Oct 26, 2026
Merged

lance0 merged 2 commits into
mainfrom
release/v0.23.1

Conversation

@lance0

@lance0 lance0 commented Oct 26, 2026 •

Copy link
Copy Markdown
Owner

Prepares the v0.23.1 patch release from main at 1b04148. It covers the two changes since v0.23.0: #348 (the selection_deferred unexported reason) and #349 (Graceful Restart off on the example rustbgpd neighbor).

This PR changes:

  • Cargo.toml, Cargo.lock and fuzz/Cargo.lock: the version is 0.23.1. Only the prefixd entry changed in each lockfile.
  • CHANGELOG.md: the [0.23.1] - 2026-10-26 section. It adds a summary paragraph, Upgrade notes that link the new guide section with absolute v0.23.1 URLs, and Known limitations. The existing Changed and Fixed entries list each of the two changes once.
  • docs/how-to/upgrading.md: a new v0.23.0 → 0.23.1 section in the 0.22.1 layout, and an updated intro. It links the existing Graceful Restart note under rustbgpd v0.80 rather than repeating it. Existing anchors are unchanged.
  • README.md: the Status line names v0.23.1 and still says it needs rustbgpd v0.80.0 or later.
  • docs/reference/cli.md: the version example is 0.23.1.

Checked from source and history:

  • No migration: git diff --stat v0.23.0 origin/main -- migrations/ is empty.
  • grafana/prometheus-alerts.yml changed: PrefixdBlackholeNotExported gains the selection_deferred hint and a comment, and its window stays at 5m. The guide tells operators to copy it.
  • prefixd.yaml is unchanged. The selection_deferred warning lives in the in-memory export snapshot, not the database, so a downgrade to 0.23.0 needs no data step.
  • prefixd_blackhole_unexported{reason} is pre-registered for every reason in UNEXPORTED_REASONS at startup, so the new label is exported at zero before any blackhole exists.

Preconditions

just gate

flock <lock> taskset -c 24-31,56-63 env CARGO_BUILD_JOBS=16 PREFIXD_TEST_CPUSET=24-31,56-63 VITEST_MAX_WORKERS=8 just gate

It exited 0 on the first run. No stage was rerun, and no Docker create or stop timed out. It ran on the release tree before two CHANGELOG wording changes: one to the summary paragraph, and a follow-up commit that limits Known limitations to released rustbgpd behaviour. Both are prose only; git diff --check, the anchor check and scripts/release-notes.py 0.23.1 passed after each. Results by stage:

  • fmt: passed.
  • python: ruff 0.16.0 passed.
  • alerts: promtool v3.15.0 found 5 rules, and the unit tests, including the new selection_deferred cases, reported SUCCESS.
  • proto: passed.
  • Clippy: both feature sets passed with -D warnings.
  • Tests: no failures. The GoBGP-only set passed 952 tests and the all-features set passed 1000.
  • MSRV: cargo +1.89.0 check passed for all three feature sets.
  • Release build: passed.
  • Frontend: lint, typecheck, 598 tests in 48 files, and the build all passed.
  • cargo audit passed on both lockfiles. It ignores only RUSTSEC-2023-0071, as CI does.
  • bun audit found no vulnerabilities.
  • cargo deny: advisories, bans, licenses and sources ok.
  • Speakers:
    • GoBGP: integration_e2e 9/9 and integration_gobgp 11/11.
    • rustbgpd 0.80.0: integration_rustbgpd 15/15 and integration_rustbgpd_peer 4/4.
    • rustbgpd 0.80.1: integration_rustbgpd 15/15 and integration_rustbgpd_peer 4/4.

Fixture pins

  • rustbgpd ghcr.io/lance0/rustbgpd:0.80.0@sha256:9945c2f8cc6fabf73d408018a4aa6cb50fe5095bd7d8bbf28d1bad847ff0e9d8

  • rustbgpd ghcr.io/lance0/rustbgpd:0.80.1@sha256:a7a6c4533ba0976f73525ef4d932ad04ef1b034115dc684511fd2679c02513f1

    These two match ci.yml's rustbgpd matrix, ["0.80.0", "0.80.1"]. Compose pins the 0.80.1 digest.

  • GoBGP jauderho/gobgp:latest@sha256:42b8d0017237e388c55936efe1f23632365b24a31751f6e8815f93e12c4d18c2 (gobgpd 4.10.0)

Upgrade proof: 0.23.0 → 0.23.1

I followed the new guide section's Compose steps as written.

The proof ran as a dedicated Compose project with the rustbgpd profile. Every container was pinned to cpuset 24-31,56-63 and had a unique name. The project started from the v0.23.0 tag's Compose file and configuration, in enforced mode with bearer auth and longer TTLs. It ran:

  • ghcr.io/lance0/prefixd:0.23.0@sha256:98b7ec61432411290b92eb02de67d8586b7b416e5d6ac84f061570831bd1e24f
  • postgres:16-alpine
  • rustbgpd 0.80.1, the Compose pin
  1. Seed on 0.23.0.

    • Manual mitigations: police UDP/53 on .10, discard TCP/80,443 on .20, and discard UDP/123 on .30.
    • FastNetMon detector events: an ICMP flood on .50 and a SYN flood on 443 on .51.
    • The .30 mitigation was then withdrawn.

    The result was 4 active mitigations (2 manual, 2 detector), 1 withdrawn, 0 pending withdrawals and 4 FlowSpec routes in rustbgpd. Health was healthy at 0.23.0. Blackholes are off in the shipped configuration, so none was seeded.

  2. Baseline. I captured:

    • the mitigation rows: ID, status, action, victim, origin mode, match, action parameters and expiry;
    • the pending withdrawals;
    • the migration list, 1–18;
    • the normalized local FlowSpec RIB from rbgp flowspec received.

    I also took a pg_dump and a /metrics snapshot.

  3. Negative control. 0.23.0's /metrics exported 11 prefixd_blackhole_unexported reasons, from exact_export_rejected to pending, and no selection_deferred series.

  4. Replace the controller (guide steps 2–4). I switched the checkout to the candidate tree, keeping the operator configuration edits. Then I ran up -d --no-deps --no-build prefixd-rustbgpd with the 0.23.1 build. That image is the repository Dockerfile's runtime stage around the gate's locked release binaries, which report prefixd 0.23.1 and prefixdctl 0.23.1. rustbgpd kept running.

  5. Results with 0.23.1.

    • New label: about 15 s after start, after one reconciliation pass, /metrics showed prefixd_blackhole_unexported{reason="selection_deferred"} 0.
    • Migrations: none applied. Startup logged versions: [1, ..., 18], and prefixdctl migrations listed the same 18 rows, all with their original 0.23.0 timestamps.
    • Fence: acquired.
    • Health: authenticated GET /v1/health/detail was healthy with version 0.23.1, and unexported was empty. prefixdctl status showed daemon 0.80.1 ready and peer observation connected.
    • Versions: /openapi.json reported info.version 0.23.1, and prefixd --version printed prefixd 0.23.1.
    • First reconciliation: no repairs. After three successful passes, reconciliation_repairs_total was 0/0 and unknown_local_routes{kind="flowspec"} was 0. The controller log had no repair, WARN, ERROR or failure lines.
    • State: the mitigation rows, pending withdrawals, migration list and normalized RIB were byte-identical to the 0.23.0 baseline. All five IDs were present with the same statuses.
    • Local routes: prefixdctl speaker list-routes listed the same 4 rules as the active mitigations.
  6. Speaker restart with the new example config. This step goes beyond the checklist. I recreated rustbgpd from the candidate tree, which now reads graceful_restart = false on the placeholder neighbor. About 3 s later, 0.23.1 logged repairing local announcement exactly once for each of the 4 active IDs. reconciliation_repairs_total{status="success"} went to 4, with no errors. A few seconds after the replay, the normalized RIB, including selected: true on all 4 routes, matched the baseline byte for byte. In the 0.23.0 proof, the same restart with the old example config left the replayed routes selected: false for at least the first minute. Afterwards, health was healthy and selection_deferred was 0.

  7. Teardown: I removed every container, volume and network of the proof project, and the candidate image.

Release-notes preview (scripts/release-notes.py 0.23.1)

prefixd 0.23.1 makes planned rustbgpd restarts clearer and shorter. A blackhole that the speaker holds from selection after its restart now reports the unexported reason `selection_deferred`, and the example rustbgpd configuration disables Graceful Restart on its placeholder neighbor, so in the Compose quickstart replayed rules are selected as soon as reconciliation re-adds them instead of after the 120-second restart window. There is no migration and no configuration change; copy the updated alert rules.

### Upgrade notes

- Follow [v0.23.0 → 0.23.1](https://github.com/lance0/prefixd/blob/v0.23.1/docs/how-to/upgrading.md#v0230--0231) in the upgrade guide: replace the controller and dashboard, then copy `grafana/prometheus-alerts.yml`. No migration, adoption step or configuration change is required, and rustbgpd stays at 0.80.1.

- `selection_deferred` is a new value of the `reason` label on `prefixd_blackhole_unexported` and of the blackhole export warning. Check your own queries and silences that match `reason="pending"`.

- If you based your rustbgpd configuration on the shipped example, set `graceful_restart = false` on any neighbor that never comes up, or remove that neighbor. See [speaker upgrade notes](https://github.com/lance0/prefixd/blob/v0.23.1/docs/how-to/upgrading.md#rustbgpd-v080).

### Known limitations

- After a planned speaker restart, rustbgpd v0.80.x holds injected routes, blackholes included, from selection while any Graceful Restart peer is down, until that peer sends End-of-RIB or `gr_restart_time` expires (120 seconds by default). `MarkReplayComplete` does not end the hold. Disable Graceful Restart on peers that never come up. The example configuration change removes only its placeholder neighbor's hold. prefixd reports a held blackhole as `selection_deferred` and cannot end the hold.
- The 0.23.0 known limitations otherwise still apply.

### Changed

- A blackhole that every Established peer reports as pending with rustbgpd's `selection_deferred` reason now carries the unexported reason `selection_deferred` instead of `pending`, in detail, health and `prefixd_blackhole_unexported{reason}`. The speaker accepted the route but holds route selection after its restart until Graceful Restart peers send End-of-RIB or the restart window expires. Health stays `degraded` while the route is exported to no peer. A mix of pending reasons stays `pending`. `PrefixdBlackholeNotExported` adds a hint for the new reason and keeps its five-minute window, which outlasts the default 120-second hold.

### Fixed

- The example `configs/rustbgpd.toml` disables Graceful Restart on its placeholder neighbor. That neighbor never connects, so after each planned rustbgpd restart in the Compose quickstart, replayed FlowSpec rules were held from selection until the restart window (`gr_restart_time`, 120 seconds by default) expired. They are now selected as soon as reconciliation re-adds them. Enable Graceful Restart on real peers that support it.

Known limitations

The limitations are as stated in the notes above. New in this release:

  • the Graceful Restart selection hold after a planned rustbgpd restart. On rustbgpd v0.80.x, injected routes, blackholes included, are held from selection while any Graceful Restart peer is down, until that peer sends End-of-RIB or gr_restart_time expires (120 s by default). MarkReplayComplete does not end the hold. Disable Graceful Restart on peers that never come up.

The 0.23.0 list otherwise still applies.

The CHANGELOG date is 2026-10-26 UTC. Correct it if this merges on a later UTC day.

@lance0
lance0 marked this pull request as ready for review October 26, 2026 16:26
@lance0
lance0 merged commit 2592a8b into main Oct 26, 2026
20 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant