test(sip): container-restart REGISTER acceptance harness + full FS regression (M6 CP5, B4) - #155
Merged
Conversation
added 2 commits
July 17, 2026 05:31
…ess (M6 CP5, B4) On-demand harness (tests/cp5-register-restart/) proving rustisk's dynamic authenticated REGISTER end-to-end with a REAL bridge container restart on an isolated --internal Docker network — the literal CP5/B4 acceptance that PR #154 landed only as an in-process mechanism proof. A sip-bridge container digest-REGISTERs (401->digest->200) advertising its Docker-assigned IP A; Dial(PJSIP/bridge) lands an INVITE at A (captured receiver-side on the bridge). The bridge container is destroyed, a sentinel seizes the vacated A, and a fresh bridge comes up with a new IP B (B!=A, enforced) and re-REGISTERs. A subsequent Dial FOLLOWS to B receiver-side and NOT to stale A (sentinel silent). Both stale-A and fresh-B bindings stay live, so best_contact's newest-wins is genuinely load-bearing. A/B are read at runtime, never hardcoded. RED negative control (captured): routing is defeated by a static contact pinned to A (bridge_pinned); the Dial misroutes to stale A, the sentinel catches it, and the follow-to-B assertion goes RED — proving the A-detection is real. Reaps every container/network on exit (host-PID kill for tron's userns-remapped docker); generates a throwaway test PIN, shredded on exit; never touches the live stack. Not wired into CI (on-demand).
- sip_agent.py: a 200 to the UNAUTHENTICATED REGISTER is now a hard failure (exit 3), not success — a regression that bound an unauthenticated contact would otherwise green-light the digest-auth proof. - run.sh: validate CP5_CASE up front (all|green|red) so a typo cannot skip the case-gated assertions and still exit 0. - pjsip.conf/run.sh: give bridge_pinned a distinct contact user (sip:pinned@A) and correlate the RED capture on that Request-URI, so no stray 'bridge' datagram can false-pass RED. - run.sh: harden reap_container (retry host-PID acquisition, timeout-wrap every docker op) and raise a loud CLEANUP WARNING on any leaked container/network. - ami_originate.py: require the Originate-specific 'successfully queued'; a bare 'Success' (also emitted by Login) no longer counts as Originate success. - README: correct 'shred' wording and document the distinct pinned user.
Owner
Author
|
External adversarial review: codex (gpt-5.5, xhigh). It confirmed the receiver-side A/B captures are genuinely distinct,
Harness re-run green after the fixes (GREEN-A/GREEN-B pass, RED captured); Docker reaped. |
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.
M6 CP5 (B4) — container-restart REGISTER acceptance + full FS regression
Two parts: (1) build the outstanding CP5/B4 acceptance — an on-demand,
isolated-Docker harness that proves dynamic authenticated REGISTER routing
follows a real container restart to a new IP, receiver-side; (2) run the full
isolated FreeSWITCH harness to confirm M6 regressed nothing.
PR #154 landed only the in-process mechanism proof
(
crates/asterisk-integration-tests/tests/e2e_dynamic_register.rs). Its literalplan acceptance — a bridge container restarted so it comes back with a new
container IP on an isolated Docker network, then a call routing to the new IP —
was not built. This adds it as
tests/cp5-register-restart/.Part 1 — the harness (
tests/cp5-register-restart/run.sh)Isolated
--internalnetwork. rustisk (fixed IP, kept out of the dynamic poolvia
--ip-range) + asip-bridgecontainer. A and B are read at runtime(
docker inspect), never hardcoded. Every INVITE is verified receiver-side(the datagram's arrival on the bridge/sentinel), never a rustisk TX log.
401 → digest → 200) advertising IPA;
Dial(PJSIP/bridge)INVITE arrives at A (captured on the bridge).seizes the vacated A (so a stale-route INVITE has a receiver and Docker
can't hand A back); a fresh bridge comes up with a new IP B (
B ≠ A,enforced) and re-REGISTERs from B.
Dial(PJSIP/bridge)INVITE follows to B (captured on thenew bridge) and not to stale A (sentinel silent). rustisk holds both
the stale-A and fresh-B bindings live, so
Registrar::best_contact'snewest-wins is genuinely load-bearing — not a trivial single-binding route.
(
bridge_pinned, whose AoR iscontact = sip:bridge@A:5060and neverre-registers).
Dial(PJSIP/bridge_pinned)misroutes to stale A, the sentinelcatches the INVITE, and the follow-to-B assertion goes RED. This negative
control proves the A-detection is real (the harness can fail). The
complementary
best_contactoldest-wins RED is covered in-process bye2e_dynamic_register.rs.Captured proof (representative run, A/B assigned at runtime)
The INVITE follows A → B receiver-side; the RED (static pin) misroutes to
stale A and is caught. This is test infrastructure only — no product code
changes — so merging it is safe.
Part 2 — full isolated FreeSWITCH regression (master @
8a3cc2d)tests/freeswitch-pin-gate/run.sh(full case), on the same rustisk build. Allsub-cases PASS — no M6 regression.
REJECTED_B_SIDEdelta=0; TX/RX voice + DTMF)GRANTED_BRIDGEM2 two-way; TX/RX voice + DTMF)Newexten/PinGate+VarSet/PINGATESTATUS)RTPVoiceFramesRx=0), Dial-timeout CANCELZERO_HIT_SECRET_AUDIT(both patterns, concatenated + per-digit; every artifact; AMI live)Constraints honored
Chime / Mumble. Not wired into CI (on-demand).
userns-remapped docker, where
docker rm -f/stophang).never appears in code, logs, or evidence.
cargo clippy --workspace --exclude pjsip-shim -- -D warningsandcargo test --workspace --exclude pjsip-shimboth green (no Rust changed).