Skip to content

Fail startup when a SIP transport cannot bind#65

Merged
ryanmurf merged 1 commit into
masterfrom
fix/64-fail-on-sip-bind
Jul 13, 2026
Merged

Fail startup when a SIP transport cannot bind#65
ryanmurf merged 1 commit into
masterfrom
fix/64-fail-on-sip-bind

Conversation

@ryanmurf

Copy link
Copy Markdown
Owner

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\nsip_bind_failure_exits_without_becoming_fully_booted runs 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

@ryanmurf
ryanmurf merged commit f1cc674 into master Jul 13, 2026
3 checks passed
@ryanmurf
ryanmurf deleted the fix/64-fail-on-sip-bind branch July 13, 2026 23:15
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>
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