Preparing to run ferrix next to BIRD on the production router.
- build: the config proto is now parsed in pure Rust, so building needs no
`protoc` binary. It previously worked only where one happened to be
installed; the Dockerfile in this repo could not have built.
- peers gain `local_address`, the source IP for connections we open (and
therefore the next hop we advertise). When both speakers share a host
the OS would otherwise pick an arbitrary local address and the peer,
which identifies us by address, would refuse the session. Address-family
mismatches are rejected at config load; an address the host lacks shows
up as the peer's backoff reason.
- docs/lab/prod/on-router: static musl cross-build instructions, config,
hardened systemd unit (unprivileged, SIGTERM gives BIRD a Cease),
loopback addresses for networkd, the BIRD stanza for a same-host
multihop session, and reverse-proxy examples with TLS and a password,
since the API has no authentication.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
When both ends dial each other, exactly one TCP connection must survive
and both ends must pick the same one. Previously ferrix relied on the
other router to sort this out and could briefly hold two sessions.
- collision: a shared map keyed by the peer's BGP Identifier holds the one
connection currently preferred for that peer plus a kill signal. Each
connection calls admit() on receiving the peer's OPEN. The connection
initiated by the higher BGP Identifier wins; the loser gets Cease /
Connection Collision Resolution, whether it is the new connection or an
existing one in OpenConfirm running in another task.
- A collision with an Established session closes the new connection (RFC
default). The optional CollisionDetectEstablishedState is available as
collision_detect_established_state: a restarted peer's new connection
then replaces the stale session at once instead of being refused until
the hold timer expires. Enabled in the lab config.
- Two connections opened by the same side: the newer wins.
- OPEN validation rejects a peer using our own BGP Identifier or 0.0.0.0
with Bad BGP Identifier; collision resolution needs distinct identifiers.
- OpenConfirm no longer times out instantly when the negotiated hold time
is zero.
- Sessions record which side opened them; shown as "opened by" on the
status page and as `direction` in /api/peers. New counter
bgp_connection_collisions_total{closed}.
- Tests: unit tests for the rule (including that both ends agree), and
integration tests for simultaneous open in both identifier orders and
both arrival orders, Established default, restart replacement with route
flush, distinct identifiers, slot release, and Bad BGP Identifier.
Existing fan-out test now gives its two peers distinct identifiers.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>