Skip to content

fix(inbound): send 503 on DispatchNoRuleDrop instead of silently dropping#696

Open
simllll wants to merge 2 commits into
livekit:mainfrom
simllll:fix/dispatch-no-rule-drop-sends-503
Open

fix(inbound): send 503 on DispatchNoRuleDrop instead of silently dropping#696
simllll wants to merge 2 commits into
livekit:mainfrom
simllll:fix/dispatch-no-rule-drop-sends-503

Conversation

@simllll
Copy link
Copy Markdown

@simllll simllll commented May 22, 2026

Problem

When the dispatch layer returns DispatchNoRuleDrop (no matching dispatch rule, or a server-side DROP decision), handleInvite calls cc.Drop() which terminates the SIP transaction without sending any SIP response. However, by the time the dispatch result is evaluated, livekit-sip has already sent 100 Trying and 180 Ringing to the carrier, so the call's existence is already revealed.

The result: the caller hears indefinite ringing. The carrier never receives a final response, keeps the INVITE transaction alive, and only terminates when the caller manually hangs up and a CANCEL arrives. This wastes carrier resources and produces confusing UX.

Observed via SIP trace (Homer/HEP):

+0.000s  INVITE (carrier → kamailio → livekit-sip)
+0.005s  100 Trying
+0.015s  180 Ringing
+0.077s  [livekit-sip closes with 486/flood — no SIP response sent]
+13.1s   CANCEL (caller gave up and hung up)

livekit-sip logs showed Closing inbound call / status=486 / reason=flood with no subsequent Inbound call closed entry, confirming the SIP response was never transmitted. The close() call after Drop() calls CloseWithStatus(), which checks inviteTx != nil to send the response — but Drop() already set inviteTx = nil, so no response is sent.

Fix

Replace cc.Drop() with cc.RespondAndDrop(sip.StatusServiceUnavailable, "Service Unavailable") — the same pattern used by DispatchNoRuleReject and DispatchServiceUnavailable.

RespondAndDrop sends the final response first, then terminates the transaction, so close() / CloseWithStatus() correctly becomes a no-op.

Why 503 and not silent drop?

The intent of DispatchNoRuleDrop appears to be "don't reveal endpoint information." However, since 100 Trying and 180 Ringing are already sent before dispatch runs, the endpoint's existence is already revealed by the time Drop() is called. Sending a 503 at this point leaks no additional information, but it immediately terminates the call at the carrier side instead of leaving it hanging.

The auth-level AuthDrop (which fires before any provisional response is sent) is a different case and correctly remains a silent drop.

Testing

Reproduce by removing all dispatch rules for a DID, calling it, and observing the caller experience before/after this fix:

  • Before: caller hears ringing indefinitely until they hang up
  • After: call immediately fails with 503 Service Unavailable

…ntly dropping

When the dispatch layer returns DispatchNoRuleDrop (e.g. no matching
dispatch rule, or server-side flood decision), the current code calls
cc.Drop() which terminates the SIP transaction without sending any
response. However, by the time the dispatch result is evaluated,
livekit-sip has already sent 100 Trying and 180 Ringing to the carrier,
so the call's existence is already revealed to the caller.

The consequence of the silent drop is that the caller hears indefinite
ringing: the carrier never receives a final response, keeps the INVITE
alive, and only terminates when the caller manually hangs up and a
CANCEL arrives. This wastes carrier resources and delivers a confusing
user experience ("phone rings forever with no answer").

Since the provisional responses already confirm the endpoint to the
caller, sending a 503 Service Unavailable at this point does not leak
any additional information, but it immediately terminates the call on
the carrier side.

Fix: replace cc.Drop() with cc.RespondAndDrop(503) — the same pattern
used by DispatchNoRuleReject and DispatchServiceUnavailable.

Observed via SIP trace (Homer/HEP): INVITE → 100 → 180 → [13 second
gap, no final response] → CANCEL from carrier. livekit-sip logs showed
"Closing inbound call / status=486 / reason=flood" with no corresponding
"Inbound call closed" entry, confirming the SIP response was never sent.
@simllll simllll requested a review from a team as a code owner May 22, 2026 19:02
@codecov
Copy link
Copy Markdown

codecov Bot commented May 22, 2026

Codecov Report

❌ Patch coverage is 0% with 1 line in your changes missing coverage. Please review.
✅ Project coverage is 66.70%. Comparing base (0460b40) to head (db21d39).
⚠️ Report is 294 commits behind head on main.

Files with missing lines Patch % Lines
pkg/sip/inbound.go 0.00% 1 Missing ⚠️
Additional details and impacted files
@@            Coverage Diff             @@
##             main     #696      +/-   ##
==========================================
+ Coverage   65.25%   66.70%   +1.44%     
==========================================
  Files          51       40      -11     
  Lines        6588     7382     +794     
==========================================
+ Hits         4299     4924     +625     
- Misses       1915     2016     +101     
- Partials      374      442      +68     

☔ View full report in Codecov by Sentry.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.

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.

1 participant