Skip to content

fix: route the ACK of a 2xx to the INVITE with the same CSeq (rebase of #167) - #170

Merged
shenjinti merged 2 commits into
mainfrom
fix/ack-routes-to-its-cseq-rebased
Oct 6, 2026
Merged

shenjinti merged 2 commits into
mainfrom
fix/ack-routes-to-its-cseq-rebased

Conversation

@shenjinti

Copy link
Copy Markdown
Contributor

Rebase of #167 by @tgeorge06, closes #165.

Original change: a second ACK index waiting_ack_cseq: (DialogId, INVITE CSeq) → TransactionKey so the ACK of re-INVITE N reaches its own transaction even after re-INVITE N+1 was answered; forget_waiting_ack removes only a transaction's own routes.

Adaptation for the merged RFC 6026 state machine (#164/#169): the PR was cut against pre-#164 main where a server 2xx entered Completed; here the waiting_ack_cseq insert moved to the Accepted entry (its answered_2xx-gated copy in the Completed entry would have been dead code — Accepted only carries 2xx, so it is unconditional there). The Accepted ACK arm already ended the transaction (#169), so confirmed calls clean both ACK routes synchronously.

Local verification: 378 lib + 65 doc tests green across repeated runs, fmt clean. The PR's #165 reproduction test (re-INVITE 2 → re-INVITE 3 → ACK 2 → ACK 3) passes.

tgeorge06 and others added 2 commits October 6, 2026 22:28
ACKs for 2xx are routed to their server INVITE transaction by dialog
through waiting_ack, which holds one transaction per dialog. Over UDP
the ACK of a re-INVITE can arrive after the next re-INVITE on the same
dialog: the UAC sends the next re-INVITE once its ACK is out, and the
two can be reordered. By then the newer transaction owns the entry, so
the late ACK is routed to it and ignored there (its CSeq does not
match), while the older transaction never sees its ACK, retransmits its
2xx until 64*T1 and then ends the call with a BYE.

An ACK carries the CSeq number of the INVITE it acknowledges (RFC 3261
§13.2.2.4) and stops that 2xx's retransmissions (§13.3.1.4). Keep the
waiting transactions by dialog and CSeq number and route an ACK to the
one with its CSeq. waiting_ack is kept as before, and a transaction now
removes only its own entries, so an older transaction ending no longer
drops a newer one's route.

Only a 2xx gets a CSeq route: the ACK of a non-2xx is part of the
INVITE transaction and matches it by branch (§17.2.3), so it is no
longer routed by dialog.
@shenjinti
shenjinti merged commit adbaae1 into main Oct 6, 2026
6 checks passed
shenjinti added a commit that referenced this pull request Oct 6, 2026
Ships this round on top of 0.7.0: proxy-mode auto_ack_2xx (#146/#172),
ACK CSeq routing (#155/#170/#172), digest auth_username (#153), dead
stream retirement (#161), remote-ack getter (#157), UAS ACK timeout →
BYE (#149/#169), RFC 6026 Accepted state + Timer L/M with documented
deviations (#164/#169), CANCEL/2xx race handling (#162/#171), in-dialog
Via transport (#163), flow-reuse and ACK hardening tests.
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.

A late ACK of a re-INVITE 2xx is routed to the next re-INVITE, and the call is ended with a BYE

2 participants