Fail startup when a SIP transport cannot bind#65
Merged
Conversation
This was referenced Jul 14, 2026
ryanmurf
added a commit
that referenced
this pull request
Jul 17, 2026
…ated netns (#163) Selects and proves the mechanism M9 will use to switch the public UDP trunk endpoint (tron:45070 + RTP 20000-20100) between FreeSWITCH and rustisk. The whole proof runs INSIDE ONE isolated container network namespace (--privileged --network none) on SYNTHETIC high ports over an internal veth pair + child netns ("chime" -> "tron"), so packets traverse prerouting+input exactly like the live trunk. It never touches the host nftables, host ports, the live trunk, port 45070, the router, or the cluster. No `flush ruleset`; the cutover table is separate and independently deletable. Selected mechanism: LISTENER SWAP. FS trunk profile stopped -> rustisk binds hostIP:45070 directly. Delivery is per-packet socket lookup — no NAT, no conntrack, no redirect — so the conntrack objection is structurally absent. A preinstalled fail-closed DROP on the guarded dport is enabled only across the handover so the sender sees loss (SIP retransmits) not ICMP port-unreachable. M9 invokes exactly these artifacts: - cutover_lib.sh — the filter-only nft primitives (table up/down, handover drop on/off). The only host-net surface. - apply-fs-to-rustisk.sh — APPLY (FS->rustisk): drop-on, stop FS, bind rustisk, drop-off. STOP_OLD/START_NEW injected as commands, so the orchestration is identical in the synthetic proof and the live cutover (M9 substitutes `sofia profile <trunk> stop/start`). Measures window. - rollback-rustisk-to-fs.sh — ROLLBACK (rustisk->FS), the exact mirror. Proof (tests/m8a-cutover, run-proof.sh; RESULTS.md holds the passing transcript): 1. one fixed UDP five-tuple primed, numbered datagrams flowing continuously (seq 1..2296) across BOTH transitions, never restarted; 2. switch FS->rustisk: single clean delivery boundary, FS through seq 745, rustisk from 758 (12-datagram handover-drop gap), overlap_count=0 — no split-brain / no interleaving; 3. rollback rustisk->FS under the SAME flow: reverse boundary just as clean (rustisk through 1519, FS from 1533); 4. source-drop holds throughout both transitions for v4 AND v6 — untrusted 10.9.0.3 (v4) and fd00::3 (v6) delivered to NEITHER stand-in (0/0), a genuine DROP in an accept-policy chain; 5. handover windows measured both directions: FS->rustisk ~47-53 ms, rustisk->FS ~49-52 ms (mechanism floor; the live M9 window adds FS profile stop/start + DNS re-resolve). bind-on-command (main.rs:2189-2192 / PR #65) is NOT required on this evidence — flagged conditional, not built. RED control (teeth): a stateful dnat/redirect lever FAILS the proof where the listener swap passes — it rewrites the dport past the source-drop, delivering the untrusted source to the successor (delivered_to_RUSTISK=695). Honest finding: PLAN-v3's conntrack-persistence rollback objection did NOT reproduce on tron's kernel (redirect reverts cleanly on rule removal); the listener swap is still chosen for introducing no NAT/conntrack into a NAT-free path. A detector self-test proves assert_boundary rejects overlap/interleave/missing-rollback/ untrusted-delivery and accepts a clean capture. Synthetic ports only; live voice stack, Helm, host nft, router untouched. Co-authored-by: Repin Agent <repin-agent@herodevs.dev>
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.
Summary\n\n- propagate configured SIP-stack initialization failures out of startup\n- terminate with a non-zero exit before publishing fully-booted readiness\n- run normal shutdown cleanup on the failed-startup path\n\nRFC 3261 section 18 defines SIP transport listeners as the entry points for received SIP messages. A configured PBX cannot be considered ready when that listener failed to initialize.\n\n## Regression test\n\n
sip_bind_failure_exits_without_becoming_fully_bootedruns the real rustisk binary while holding its configured UDP port. It asserts a prompt non-zero exit, an explicit SIP startup error, and absence of the fully-booted announcement. Before this change the process continued running and timed out in the test.\n\n## Validation\n\n-cargo +1.97.0 test -p rustisk-cli --test startup_modes sip_bind_failure_exits_without_becoming_fully_booted -- --nocapture\n-cargo +1.97.0 clippy -p rustisk-cli --all-targets -- -D warnings\n-cargo +1.97.0 clippy --workspace --exclude pjsip-shim -- -D warnings\n-cargo +1.97.0 test --workspace --exclude pjsip-shim\n\nFixes #64