Skip to content

Client INVITE transaction auto-ACKs 2xx final responses — incorrect for proxy use (RFC 3261 section 17.1.1.2), and not suppressable from outside #145

Description

@nbasil

When a client INVITE transaction receives a 2xx final response,
Transaction::on_received_response transitions to Completed and
unconditionally calls send_ack (src/transaction/transaction.rs, the
is_completed_client_invite branch).

Per RFC 3261 section 17.1.1.2, a 2xx final response is treated differently
from non-2xx: the client INVITE transaction terminates immediately and
the ACK for a 2xx is generated by the TU (UAC core), end-to-end —
not by the transaction layer. The transaction-layer ACK is correct
only for non-2xx finals (hop-by-hop, section 17.1.1.3).

For UA applications built on the dialog layer this auto-ACK is
convenient (and the dialog layer relies on it — nothing outside the
transaction module sends ACKs). But for a proxy built on the
transaction layer (RFC 3261 section 16), it is a protocol violation
with interop consequence:

  • The proxy forwards an INVITE (or in-dialog re-INVITE) downstream
    via a client transaction.
  • The UAS answers 200.
  • rsipstack immediately emits an ACK the proxy must not send.
  • The genuine end-to-end ACK from the caller then arrives and is
    relayed, so the UAS receives two ACKs per 2xx.

Some stacks ignore the duplicate as a retransmission, but causes
issues with servers that mishandle the duplicate ACK after a re-INVITE,
breaking the call. There is no way to suppress the ACK from outside the
crate: send_ack is unconditional, and the MessageInspector hook
can transform a message but not cancel thesend.

Two secondary effects of the current behavior also matter for proxies:

  • While the transaction sits in Completed, retransmitted 2xx are
    swallowed as duplicates instead of being passed to the TU — but a
    proxy must forward 2xx retransmissions end-to-end (RFC 3261 section 16.7);
    the 200-retransmit/ACK cycle is the end-to-end reliability loop.
  • After termination, cleanup() stores the ACK in
    finished_transactions, so the endpoint re-sends the stray ACK on
    every retransmitted 200 for the cleanup window.

Proposed fix: an opt-out flag
EndpointOption::auto_ack_2xx, default true so existing UA/dialog
behavior is fully preserved. With the flag false, a 2xx moves the client
INVITE transaction directly to Terminated with no transaction-layer ACK
and no stored ACK, so the 2xx (and its retransmissions) are the TU's to
handle — the RFC-correct proxy semantics. Non-2xx handling is
unchanged in both modes.

This patch has been tested internally and can confirm it fixes the
duplicate-ACK interop failure.

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions