A review of the session driver, the server loop, the dialer, the advertiser and the HTTP API, with the fixes. Bugs: - A peer that stopped reading held its session, and our routes, for as long as TCP kept retrying: a blocked write was awaited with no timer running. Writes now give up after the Send Hold Time of RFC 9687. - Routes we originate were sent to a peer in our own AS with our AS number on the path and no LOCAL_PREF, which such a peer refuses. They now go with an empty path and a LOCAL_PREF. - Collision detection knew a peer by its BGP Identifier alone. A neighbour with one session over IPv4 and one over IPv6 had the second closed as a collision, and any host that could connect could displace a peer's connection by claiming its identifier. A peer is now its address and identifier together. - The dialer reset its backoff whenever TCP connected, so a peer that accepts the connection and refuses the session was dialled every few seconds for ever. It resets only after a session was established. - A failure on our side (routes that cannot be stored) dropped the connection without a NOTIFICATION. It goes through the state machine and the peer is sent Cease. A queued message that cannot be encoded is logged and skipped instead of ending the session. - The accept loop retried at once after an error, spinning when the process was out of file descriptors. - Attribute sets no route used any more were dropped only when a peer went away. A task now does it every minute. - The advertiser subscribed to session events inside its task, after the dialers were started, and could miss the first session. - Shutdown waited without limit on a session whose queue was full. - /api/routes/received took any limit and built the reply on a runtime thread. The limit is capped and the work is done off those threads. - The default log level was TRACE, which logs every UPDATE. - IPv6 listeners are IPv6-only: listening on 0.0.0.0 and [::] fails on systems where the second would take IPv4 too, and IPv4 peers arriving under mapped addresses matched no configured peer. - A connection closed after a NOTIFICATION is kept for a moment so that the peer can read it. - The configuration accepted two peers with one name or one address, and a router id of 0.0.0.0. - An HTTP address that cannot be bound now stops the daemon at startup. Simplifications: - SessionConfig mixed what a server's sessions share with what is configured for one peer, and was cloned and patched field by field in two places. It is ServerContext, shared, and PeerSettings, made from a PeerConfig in one place. - The attributes of an UPDATE as they were on the wire travelled from the reader to the table through a field of the session, around the state machine. They go through it, with the message. - run_session returns how the session ended and whether it had been established; Ok no longer reads as "closed by peer". - Session is not generic over its reader. - BgpServerBuilder had nothing to build: BgpServer::bind takes the configuration. - One shutdown path, close_sessions, for the signal handler and for a failed server. - The advertiser's three copies of "send every route" are one. - PeerId moved from the rib crate, which did not use it, to the server. - Unused API removed or made private to the crate. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>