Files
rutster/docs/adr/0001-sip-strategy.md
adlee-was-taken 0370347642 ADR-0001: SIP strategy — native Rust core behind Kamailio + rtpengine
Record the SIP edge decision and align the docs:
- docs/adr/0001-sip-strategy.md: layered strategy (own Rust parser, rent the
  interop tail via a Kamailio + rtpengine SBC, grow native core behind the shield);
  pjproject FFI explicitly rejected for breaking the memory-safety thesis at the
  most exposed seam.
- PORT_PLAN §1 + open decisions: SIP row updated to the decided strategy.
- ARCHITECTURE: "biggest technical risk" now points at ADR-0001.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01C2bfD7MkqEdfnMXxXBu456
2026-06-26 21:49:36 -04:00

4.3 KiB

ADR-0001 — SIP strategy: native Rust core behind a Kamailio + rtpengine edge

  • Status: Accepted
  • Date: 2026-06
  • Supersedes: the 🔌 Edge/FFI (pjproject) disposition in PORT_PLAN.md §1

Context

SIP is rutster's highest-risk subsystem. Three forces pull against each other:

  1. The security thesis. rutster's headline is "memory-safe by construction, fuzzed parsers, no buffer-overflow/RCE class." The SIP parser is the first thing hostile internet packets touch — the most exposed surface in the whole system.
  2. The interop tail. The hard, unbounded part of SIP isn't RFC 3261 — it's 20 years of device/carrier/NAT quirks. Owning that tail forever is what killed chan_sip and drove Asterisk to retreat to pjproject. It is a permanent tax, not a one-time cost.
  3. Time-to-first-call. We want interoperable SIP trunking early, without blocking on a young stack maturing.

These don't resolve as a single build-vs-buy call, because they live at different layers of SIP:

Layer CVE-class risk Interop-tail cost Verdict
Parser (bytes off the wire) Highest Low Own in Rust — cheap, and the thesis depends on it
Transaction / dialog / timers Medium Medium (bounded by RFCs) Own over time; sans-IO Rust fits
Interop tail (UA quirks, NAT, carrier glare) Low Unbounded, forever Rent — this is what a mature edge is for

Pure FFI to pjproject was rejected: it pays the interop cost and puts C at the most exposed seam, gutting the memory-safety headline at the one place it matters most. It is the right default for a generic PBX and the wrong one for a security-first platform.

Decision

A layered strategy:

  1. Own the SIP parser in Rust, from day one. Build on rsip (message types/parsing) and the ezk-sip family (sans-IO transaction/dialog core). No hostile bytes ever hit a C parser. This is non-negotiable — the security story requires it.
  2. Front the public SIP edge with Kamailio + rtpengine. Kamailio handles signaling (proxy, registrar, TLS termination, topology hiding, rate-limiting, anti-scan); rtpengine handles the media leg (kernel-forwarded RTP relay, SRTP/DTLS-SRTP bridging, ICE, NAT). This is the most-proven, most-scaled FOSS SIP edge — carrier-grade on the open internet. It absorbs the interop tail and gives us a hardened edge in weeks.
  3. Grow the native Rust transaction/dialog core behind that shield. It matures in production where its early bugs aren't customer-facing outages. As it hardens, Kamailio's role shrinks from "does all the SIP" → "just the public edge" → optionally nothing.

Chosen edge: Kamailio + rtpengine (over OpenSIPS and drachtio). Rationale: largest proven deployment pedigree and max-scale proxy track record; rtpengine is the canonical media companion. OpenSIPS and drachtio remain valid alternatives (drachtio noted for a future programmable edge once rutster's API model is solid).

Topology

  hostile internet                 trusted core
  ───────────────►  Kamailio  ──signaling──►  rutster control plane
  (SIP trunks,      + rtpengine ──media─────►  rutster media plane
   far-end UAs)     (edge SBC)                 (native Rust SIP core
                                                grows here, behind the shield)

WebRTC ingress is unaffected and ships first (WebRTC signaling is app-defined, not SIP), so first-call does not block on any of this.

Consequences

  • Positive: security headline holds (Rust parser at the edge of our trust boundary); interoperable SIP trunking available early; native stack de-risked by maturing behind a proven shield; no pjproject license/threading/unsafe entanglement.
  • Negative / costs: an operational dependency on Kamailio + rtpengine (config, deployment, expertise) for the foreseeable future; two media-handling points during the shield phase (rtpengine at the edge, rutster media plane inside) to reason about.
  • Open follow-ups: Kamailio↔rutster control-plane integration (KEMI vs. dispatcher vs. a custom control channel); how far inward rtpengine relays vs. rutster terminating media directly; the graduation criteria for retiring the shield.