fix: one Closing behaviour for sm and peer

Merged
GO
13d ago
+3707-184

Part 2 of the protocol contract decided in #108. sm.StateMachine,
sm.Client and peer.Manager handled a connection in Closing (after a DPR
was sent or accepted) differently, and none of them followed RFC 6733
§5.6, whose Closing state defines only DPA, timeout and disconnect.

  • Once Closing is latched, answers to requests sent before it still
    complete. New requests and other base commands are dropped before
    validation and dispatch, with no handler, hook, reply or watchdog
    credit; the Manager's early error-answer path no longer answers them.
  • A request counts as sent before Closing if its write acquired the
    connection's write gate first. Gate acquisition registers answer
    ownership under the same lock as Closing, so a later request is
    rejected with ErrConnectionClosing and never written, and frames stay
    whole. Cancellation and request expiry remove that ownership.
  • A crossed or repeated valid DPR is answered without extending the
    original deadline, and any correlated DPA closes the transport,
    whatever its Result-Code (§5.4).
  • A DPR that arrives while this node's requests on the connection still
    await answers is answered with DIAMETER_UNABLE_TO_COMPLY (5012), as
    §5.4 recommends for messages likely in flight; the DPR's sender still
    closes.
  • sm.Client stops its watchdog when Closing starts, so no DWR follows a
    DPR, and the disconnect deadlines are armed before waiting for watchdog
    work, so a stalled DWR write can no longer hold Closing open.
  • The Manager freezes which requests may still be answered when Closing
    starts and disposes of reservations that were never written.

Refs #108

Validation

  • make check passes locally for all modules: format, test (Go 1.27.2 and
    1.26.9), race, vet, lint, vuln, windows, secrets and codeql.
  • TCP captures on main show non-DPA replies during Closing, DWRs after a DPR,
    early closure on a crossed DPR and 2001 DPAs while requests were in flight;
    here there are none of the first three and the in-flight DPAs carry 5012.
  • Three review rounds found and fixed a Closing hang behind a stalled DWR, a
    request boundary error in the Manager and in WriteToWithRetry, and a
    frame-splitting bug in an intermediate design; the final version writes every
    frame whole and mutating any guarded behaviour fails a named test.

Summary by cubic

Unifies Closing behaviour across sm.StateMachine, sm.Client, and peer.Manager so all follow RFC 6733 §5.6: once Closing is latched, new requests and other base commands are dropped before validation and dispatch, while answers to requests sent before Closing still complete.

Behaviour changes

  • A request counts as sent before Closing only if its write acquired the connection's write gate first; later writes are rejected with ErrConnectionClosing.
  • A crossed or repeated valid DPR receives a DPA without extending the original deadline, and any correlated DPA closes the transport regardless of its result code.
  • A DPR arriving while this node's requests on the connection still await answers is answered with DIAMETER_UNABLE_TO_COMPLY (5012).

Watchdog and peer.Manager

  • sm.Client stops its watchdog when Closing starts so no DWR follows a DPR, and disconnect deadlines are armed before waiting for watchdog work.
  • peer.Manager freezes which requests may still be answered when Closing starts and disposes of admission reservations that were never written.