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
This commit is contained in:
adlee-was-taken
2026-06-26 21:49:36 -04:00
parent d3bd621aa0
commit 0370347642
3 changed files with 85 additions and 5 deletions

View File

@@ -49,6 +49,8 @@ microservices for stateful/business/billing logic.
## Biggest technical risk
The **SIP stack**. No mature pure-Rust option exists → FFI pjproject or front the edge
with a battle-tested SBC initially; treat a pure-Rust stack as a long-term goal, not a
v1 dependency. Everything else builds on the existing Rust media ecosystem.
The **SIP stack****decided in [ADR-0001](adr/0001-sip-strategy.md)**: own the Rust
parser from day one (the security thesis depends on it), front the public edge with a
proven **Kamailio + rtpengine** SBC to absorb the interop tail, and grow the native Rust
transaction/dialog core behind that shield. No pjproject FFI. Everything else builds on
the existing Rust media ecosystem.

View File

@@ -33,7 +33,7 @@ Grounded against the Asterisk 22.10.1 tree (~1.18M LOC C/H).
| Asterisk subsystem | Module(s) | Disp. | Rationale |
|---|---|---|---|
| SIP signaling | `chan_pjsip` + `res_pjsip*` (48 mods) | 🔌 Edge/FFI | No mature pure-Rust SIP stack. FFI pjproject or front an SBC; pure-Rust long-term. The single biggest risk item. |
| SIP signaling | `chan_pjsip` + `res_pjsip*` (48 mods) | 🔌 Edge → 🦀 Core | **Decided (ADR-0001):** public SIP edge = **Kamailio + rtpengine** (proven SBC shield); native **Rust** parser + transaction/dialog core (`rsip`/`ezk-sip`) grows behind it. **No pjproject FFI** — it would put C at the most exposed seam and break the memory-safety thesis. The single biggest risk item. |
| ↳ registration (in/out) | `res_pjsip_registrar`, `_outbound_registration` | 🦀 Core | Registrar state is core control-plane; back it with the state store. |
| ↳ SDP / media negotiation | `res_pjsip_session`, `_sdp_rtp` | 🦀 Core | Offer/answer drives the media plane directly. |
| ↳ auth | `res_pjsip_outbound_authenticator_digest`, etc. | 🦀 Core | Digest + token auth, deny-by-default, rate-limited. Security-critical. |
@@ -205,7 +205,7 @@ These are the "modern" deltas — load-bearing, not optional polish.
## Open decisions
- **SIP:** FFI pjproject vs. SBC front (Kamailio/drachtio) vs. pure-Rust (`ezk-sip`). Affects §1 heavily.
- ~~**SIP:** FFI pjproject vs. SBC front vs. pure-Rust.~~ **Decided — ADR-0001:** native Rust core behind a **Kamailio + rtpengine** edge.
- **WASM runtime:** `wasmtime` component model (presumptive).
- **Event bus:** NATS vs. Kafka vs. Redis Streams.
- **License** (see README).

View File

@@ -0,0 +1,78 @@
# 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.