Fix re-INVITE dialog matching using RFC 3261 compliant To tag lookup#582
Open
briankwest wants to merge 8 commits intolivekit:mainfrom
Open
Fix re-INVITE dialog matching using RFC 3261 compliant To tag lookup#582briankwest wants to merge 8 commits intolivekit:mainfrom
briankwest wants to merge 8 commits intolivekit:mainfrom
Conversation
Fixes a critical bug where mid-dialog re-INVITEs from the remote party were incorrectly processed as new inbound calls instead of being recognized as part of an existing dialog. ## Problem When a SIP provider (e.g., SignalWire) sends a re-INVITE during an active call, LiveKit was treating it as a new call, causing: - Authentication challenges (401/407) - "No trunk found" errors (404) - Call failures and disconnections This primarily affected outbound calls where From/To headers are "swapped" from LiveKit's perspective (remote is UAC for the re-INVITE). ## Root Cause The existing dialog matching happened too late (after processInvite) and only checked byCallID, which is unreliable. The code missed the definitive proof: the To tag in the request matches our local tag. Per RFC 3261 Section 12.2, a dialog is identified by: - Call-ID - Local tag (in To header for UAS) - Remote tag (in From header for UAS) ## Solution Add early dialog matching in onInvite() before processing as new call: 1. Extract To tag from incoming INVITE 2. Check if tag exists in byLocalTag map 3. If match found, delegate to existing call's handleReinvite() 4. Otherwise, proceed with normal new call processing New handleReinvite() method on inboundCall: - Validates CSeq (detect retransmissions vs new re-INVITEs) - Responds with current SDP for session refresh - Handles edge cases (old CSeq, missing headers) ## Impact ✅ Fixes outbound call re-INVITEs (broken → working) ✅ Improves inbound call re-INVITEs (performance + reliability) ✅ RFC 3261 compliant dialog identification ✅ Reduced CPU usage (early exit avoids processInvite) ✅ Better logging for debugging ## Testing Tested with SignalWire re-INVITEs on both inbound and outbound calls. Verified proper 200 OK responses and continued call operation.
Author
Re-INVITE Flow: Before vs After Fix🔴 BEFORE FIX (Broken)* AFTER FIX (Correct)🔑 Key DifferencesDetection Point
Matching Strategy
Performance
Correctness
📊 Dialog State Diagram🎭 From/To Direction ExamplesOutbound Call Initial INVITE (LiveKit → Provider)Provider's 200 OK ResponseProvider's Mid-Dialog re-INVITE🧩 Why byLocalTag Works🎯 SummaryThe fix is simple but profound: Instead of asking: "Does this Call-ID exist?" (ambiguous) The To tag is like a session ID that we generated. If we see our own session ID in an incoming request, we KNOW it's for an existing session, not a new one. This is faster, simpler, and RFC-compliant. * |
The nonce was using only time.Now().UnixMicro() which could produce identical values for calls arriving in the same microsecond, causing the TestDigestAuthSimultaneousCalls test to fail. Now includes the Call-ID in the nonce to ensure uniqueness even when multiple authentication challenges are generated simultaneously. This is a defensive fix to prevent race conditions in digest auth.
Codecov Report❌ Patch coverage is
Additional details and impacted files@@ Coverage Diff @@
## main #582 +/- ##
==========================================
- Coverage 65.25% 64.22% -1.03%
==========================================
Files 51 34 -17
Lines 6588 6617 +29
==========================================
- Hits 4299 4250 -49
- Misses 1915 1935 +20
- Partials 374 432 +58 ☔ View full report in Codecov by Sentry. 🚀 New features to boost your workflow:
|
Adds comprehensive test coverage for the new handleReinvite() method: - Missing CSeq header (400 Bad Request) - Retransmission detection (same CSeq → resend 200 OK) - Out-of-order INVITE (lower CSeq → ignore) - New re-INVITE (higher CSeq → accept with current SDP) - No SDP available error handling (500 Internal Server Error) - Nonce uniqueness verification These tests cover the main code paths added by the re-INVITE fix, improving code coverage for the new functionality.
5547a81 to
1f3bbee
Compare
dennwc
reviewed
Feb 7, 2026
Contributor
dennwc
left a comment
There was a problem hiding this comment.
Thank you @briankwest! The change look good, just a few minor comments:
- Extract nonce generation into generateNonce() helper function - Add nil check and logging when SDP unavailable during retransmission - Use AcceptAsKeepAlive() helper to ensure proper SIP headers - Remove redundant Call-ID based re-INVITE fallback in processInvite() - Improve TestNonceUniqueness to test actual helper function Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
- Remove unused tr variable - Extract To tag from re-INVITE request as local tag for newInbound - Fixes type mismatch (RemoteTag vs LocalTag) Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
Instead of creating a temporary sipInbound (which requires server infrastructure and fails in unit tests), directly create the response following the same pattern as AcceptAsKeepAlive/respondWithData: - Add Content-Type header - Add Allow header with supported methods - Add Contact header - Add extra headers via addExtraHeaders This satisfies the intent of using proper SIP headers while working in both production and test scenarios. Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
Add defensive nil checks before accessing contact and calling addExtraHeaders to prevent panics in test scenarios where the full server infrastructure isn't initialized. - Check c.cc.contact != nil before appending Contact header - Check c.s != nil && c.s.conf != nil before calling addExtraHeaders This allows unit tests to work with minimal inboundCall setup while maintaining full functionality in production. Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
7f0452e to
12f7b49
Compare
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Fixes a critical bug where mid-dialog re-INVITEs from the remote party were incorrectly processed as new inbound calls instead of being recognized as part of an existing dialog.
Problem
When a SIP provider (e.g., SignalWire) sends a re-INVITE during an active call, LiveKit was treating it as a new call, causing:
This primarily affected outbound calls where From/To headers are "swapped" from LiveKit's perspective (remote is UAC for the re-INVITE).
Root Cause
The existing dialog matching happened too late (after processInvite) and only checked byCallID, which is unreliable. The code missed the definitive proof: the To tag in the request matches our local tag.
Per RFC 3261 Section 12.2, a dialog is identified by:
Solution
Add early dialog matching in onInvite() before processing as new call:
New handleReinvite() method on inboundCall:
Impact
✅ Fixes outbound call re-INVITEs (broken → working) ✅ Improves inbound call re-INVITEs (performance + reliability) ✅ RFC 3261 compliant dialog identification
✅ Reduced CPU usage (early exit avoids processInvite) ✅ Better logging for debugging
Testing
Tested with SignalWire re-INVITEs on both inbound and outbound calls. Verified proper 200 OK responses and continued call operation.