Skip to content

fix(dialog): a server in-dialog request with no route and no dial-back fails at once - #187

Merged
shenjinti merged 1 commit into
restsend:mainfrom
tgeorge06:fix/in-dialog-request-without-route-fails-fast
Oct 8, 2026
Merged

shenjinti merged 1 commit into
restsend:mainfrom
tgeorge06:fix/in-dialog-request-without-route-fails-fast

Conversation

@tgeorge06

Copy link
Copy Markdown
Contributor

Fixes #186.

Problem

In send_dialog_request (dialog.rs:1118), when tx.send() fails for a server dialog, the error is kept for the dial-back retry (line 1152). In practice send() fails there only on a TargetLocator error (transaction.rs:274), which returns before the transaction enters Calling (line 339): nothing is sent and no timer is armed. With no dial-back address the code then waits in tx.receive() (line 1239) on a transaction nothing will ever end, so bye(), reinvite(), info() and every other in-dialog request on that dialog never return.

Fix

Remember the first send's error, and return it when there is no dial-back address (the "giving up after first send" branch). +7 lines in src/dialog/dialog.rs.

Unchanged on purpose:

  • A client dialog and an ACK still return the send error at once, as before (line 1162).
  • With a dial-back address, the retry is exactly as before, whether the first send failed or succeeded without a connection.
  • A first send that succeeded without a connection and has no dial-back address still waits for the transaction's timeout: that transaction is in Calling with its timers armed.
  • The dial-back send's own error is still only logged. It cannot fail the same way: the dial-back sets destination, so the locator is not consulted and lookup failures are left to the transaction's timers.

Contract / coverage

What an in-dialog request on a server dialog does when its send fails:

locator dial-back address before after
Err none never returns Err(<locator error>) at once
Err Via received / rport, or recorded from the connection dialed back same
Ok / none (send succeeds), no connection either dialed back, or waits for the timeout same

The locator error is returned as is, the same error a client dialog gets. For bye(), send_bye then transitions to Terminated as for any other BYE error. The failure happens before any transport is chosen, so it is the same for UDP, TCP, TLS and WS; a server dialog with a live reliable affinity connection never consults the locator and is not affected.

Tests

src/dialog/tests/test_connection_affinity.rs: test_server_request_without_route_fails_unless_dialed_back. A UDP endpoint with a locator that always fails, and a confirmed server dialog with no recorded connection:

  • initial Via without received / rport: info(), reinvite() and bye() each return Err containing "no route" within 5 s;
  • initial Via with received / rport pointing at a UDP socket: the BYE reaches that socket (the dial-back after a failed first send, which no existing test covers).

On main (92cec74) it fails at the first request:

---- dialog::tests::test_connection_affinity::test_server_request_without_route_fails_unless_dialed_back stdout ----
panicked at src/dialog/tests/test_connection_affinity.rs:573:45:
INFO must fail at once, not hang

Row by row (assertions turned into prints), main then this PR:

INFO: Err(Elapsed(()))                  INFO: Ok(Err(Error("no route")))
re-INVITE: Err(Elapsed(()))             re-INVITE: Ok(Err(Error("no route")))
BYE: Err(Elapsed(()))                   BYE: Ok(Err(Error("no route")))
dial-back: Some("BYE sip:alice@alice.invalid:5060 SIP/2.0")   (same on both)

Checks

  • cargo test --features bench: 407 lib tests passed, 0 failed (406 on main plus the new one), and 65 doc tests passed. Plain cargo test passes too. The new test passed 20 runs in a row.
  • cargo check --no-default-features --features platform-embassy: the same output as on main.
  • cargo clippy --features bench --all-targets: the same output as on main, none in the changed code. (On main it stops at a clippy::never_loop error in src/dialog/tests/test_refer_notify.rs:98, unrelated to this PR; with -A clippy::never_loop the warnings are the same as on main.)
  • rustfmt --check on the changed files: clean.
  • Merges cleanly with fix(dialog): end the session on a never-ACKed re-INVITE 2xx in ClientInviteDialog #185; with both, all tests pass (409 lib tests).

…k fails at once

When the target locator fails, Transaction::send returns before the
transaction enters Calling, so no timer is armed and nothing is ever
sent. A server dialog then looks for a dial-back address; with none it
went on to wait in tx.receive() for a response that cannot come, and
BYE, re-INVITE, INFO and every other in-dialog request never returned.

Return the send error in that case, as a client dialog already does.
The dial-back retry is unchanged.
@shenjinti
shenjinti merged commit b45bb53 into restsend:main Oct 8, 2026
3 checks passed
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.

In-dialog request from a server dialog never returns when the target locator fails and there is no dial-back address

2 participants