feat(sip): M7 CP1 — wire-correct digest challenge handling on origination (M-f) - #157
Merged
Merged
Conversation
…tion (M-f) Today an origination INVITE that gets a 401/407 was never answered with credentials: `OutboundAuthenticator::create_authenticated_request` had no production caller, and the challenge was treated as a plain call failure. Fix all of M-f so rustisk can originate through a challenging carrier, proven RECEIVER-SIDE against an offline carrier UAS. Changes: - authenticator: the credentialed retry is now a NEW client transaction — the top Via branch is rewritten to a fresh branch (was reused: the M-f defect), alongside the existing CSeq increment + Authorization. `replace_via_branch` preserves rport/transport/sent-by. - session: `build_auth_retry_invite` answers a 401/407 and adopts the retry as the live INVITE; `build_ack` (2xx) and `build_cancel` now carry the INVITE's REAL CSeq (were hardcoded 1); `build_ack`/`build_bye` target the dialog route set (Record-Route -> Route headers, Request-URI = refreshed Contact) via loose/strict routing; `in_dialog_next_hop` resolves the first route hop. - event_handler: on a 401/407 to our INVITE, bounded credentialed retry (`MAX_OUTBOUND_AUTH_ATTEMPTS`) as a new INVITE transaction; the 2xx ACK and in-dialog BYE are sent to the route-set/Contact target, not the response source. The non-2xx ACK itself is already emitted by the transaction layer. - pjsip_config / channel_driver: new `outbound_auth` endpoint field; the resolved credential rides on the outbound session. Proofs: session cp1_tests (RED-capable: revert to CSeq 1 -> "1 ACK"!="2 ACK"); authenticator branch test; and tests/cp1-origination-challenge — an isolated offline-carrier harness that verifies, receiver-side, (a) the 401 is ACKed (same branch), (b) the retry carries a new branch + CSeq++ + a valid digest, (c) the 2xx ACK and BYE land on the route-set/Contact target (a SEPARATE address) not the core, and (d) the in-dialog CSeq is real (ACK=2, BYE=3).
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.
M7 CP1 — wire-correct digest challenge handling on origination (M-f)
Origination could not authenticate through a challenging carrier: on a 401/407
to our INVITE rustisk sent no credentials (the outbound authenticator had no
production caller) and failed the call. This lands all of M-f.
What changed
authenticator.rs): the credentialedretry now rewrites the top Via to a fresh branch (was reused — the M-f
defect), plus the existing CSeq increment + Authorization.
session/mod.rs):build_ack(2xx) and
build_cancelcarry the INVITE's actual CSeq (were hardcoded 1);build_ack/build_byeroute through the dialog route set (Record-Route →Route, Request-URI = refreshed Contact);
in_dialog_next_hopresolves thefirst route hop.
build_auth_retry_inviteanswers the challenge and adopts theretry as the live INVITE.
event_handler.rs): 401/407 → boundedcredentialed retry (
MAX_OUTBOUND_AUTH_ATTEMPTS = 2) as a new INVITEtransaction; the 2xx ACK and in-dialog BYE go to the route-set/Contact target,
not the response source. (The non-2xx ACK is already emitted by the M4
transaction layer — verified receiver-side, not duplicated.)
outbound_authendpoint credential (pjsip_config.rs,channel_driver.rs).Receiver-side proof (isolated offline-carrier harness)
tests/cp1-origination-challenge/run.sh— 3 containers on an--internalnetwork: rustisk, a carrier core (challenges + answers), and a separate
route target T (Contact/Record-Route target). All asserts are on the carrier's
captured datagrams, never a rustisk TX log. GREEN verdict:
Core capture shows
ACK-CHALLENGE(branch = INVITE branch), thenRETRY-INVITE … branch=<new> cseq=2 auth=yes valid=yes prev_branch=<old>. Target capture showsACK-2XX-AT-TARGET … cseq=2 ACKandBYE-AT-TARGET … cseq=3 BYE— both at T,none at core.
Decisive REDs (each captured by reverting the specific fix)
retry INVITE branch: z9hG4bKc417… == first INVITE branch z9hG4bKc417…→ (b) FAIL.the route hop → harness:
route target never saw the 2xx ACK→ (c) FAIL(ACK/BYE land back on the core).
"1 ACK"/"1 CANCEL"→ unit tests:left: "1 ACK", right: "2 ACK"andleft: "1 CANCEL", right: "2 CANCEL"→FAIL.
Tests
cargo test --workspace --exclude pjsip-shimandcargo clippy --workspace --exclude pjsip-shim -- -D warningsgreen. Rust 1.97.0.Note: the harness dialplan uses
Wait(2)to hold the leg through thechallenge→retry→200 exchange (the app-runs-before-answer timing is CP3's
concern; this CP proves signaling wire-correctness only).