Skip to content

feat(proxy): negotiate SDES-SRTP on SIP legs (secure in -> secure out)#226

Merged
shenjinti merged 1 commit into
restsend:mainfrom
v0l:feat/sdes-srtp-sip-legs
Jun 25, 2026
Merged

feat(proxy): negotiate SDES-SRTP on SIP legs (secure in -> secure out)#226
shenjinti merged 1 commit into
restsend:mainfrom
v0l:feat/sdes-srtp-sip-legs

Conversation

@v0l

@v0l v0l commented Jun 24, 2026

Copy link
Copy Markdown
Contributor

Problem

The proxy media layer selects a binary WebRTC-vs-RTP transport for each leg. There is no path that picks SDES-SRTP, so an inbound SDES-SRTP offer (m=audio … RTP/SAVP + a=crypto, e.g. a Twilio Elastic SIP Trunk with Secure Media enabled) is answered with plain RTP/AVP and no crypto.

That is an invalid answer — you can't downgrade an RTP/SAVP offer to RTP/AVP — so the trunk drops its leg immediately after the call is answered while the callee leg stays up. Observed against Twilio:

Twilio offer : m=audio 19960 RTP/SAVP 0 8 101   a=crypto:1 AES_CM_128_HMAC_SHA1_80 inline:...
proxy answer : m=audio 16914 RTP/AVP  8   101   (no a=crypto)   -> Twilio sends BYE

Change

Introduce a three-way transport selection on the anchored / media_proxy path:

  • sdp_transport_mode(sdp) — classify a peer SDP as WebRtc (ICE + DTLS), Srtp (RTP/SAVP profile or an a=crypto line, without DTLS), or plain Rtp.
  • caller_transport_mode() — answer the caller (UAS) leg with a profile that matches its offer, so an RTP/SAVP offer is answered with RTP/SAVP + a=crypto.
  • callee_transport_mode(callee_is_webrtc) — for SIP callees, mirror SDES-SRTP from the caller leg ("secure in → secure out") so anchored SIP↔SIP media stays encrypted end to end. WebRTC and plain-RTP calls are unchanged.

build_rtp_track_builder now takes a rustrtc::TransportMode and applies it via with_mode (SDES-SRTP reuses the plain-RTP port range, not the WebRTC range). Leg transport metadata (set_transport) is populated from the same helpers; the existing WebRTC↔RTP media-bridge/transcoding path is intentionally left untouched.

Tests

  • Added test_sdp_transport_mode_classification (plain RTP, RTP/SAVP, crypto-only, and WebRTC precedence).
  • Full proxy::proxy_call::sip_session::tests (78) and media:: (318) suites pass; no behavioral change for WebRTC or plain-RTP calls.

Dependency

Requires rustrtc to actually emit a valid SDES-SRTP offer/answer (RTP/SAVP profile, no DTLS attributes). That fix is restsend/rustrtc#31; this PR should land after a rustrtc release containing it (then bump the rustrtc dependency). Verified locally by patching rustrtc to that branch.

Validation status

Built and unit-tested locally against the patched rustrtc. Live end-to-end validation against a Twilio trunk with Secure Media is still recommended before release.

The proxy media layer only ever selected a binary WebRTC-vs-RTP transport,
so an inbound SDES-SRTP offer (RTP/SAVP + a=crypto, e.g. a Twilio Elastic
SIP Trunk with Secure Media) was answered with plain RTP/AVP. The trunk
rejects the downgraded answer and tears the call down immediately after it
is answered, while the callee leg stays up.

Introduce a three-way transport selection on the anchored/media-proxy path:

- sdp_transport_mode(): classify a peer SDP as WebRtc (ICE+DTLS), Srtp
  (RTP/SAVP or a=crypto, no DTLS), or plain Rtp.
- caller_transport_mode(): answer the caller (UAS) leg with a profile that
  matches its offer, so an RTP/SAVP offer is answered with RTP/SAVP.
- callee_transport_mode(): for SIP callees, mirror SDES-SRTP from the caller
  leg so anchored SIP<->SIP media stays encrypted end to end; WebRTC and
  plain-RTP calls are unchanged.

build_rtp_track_builder now takes a rustrtc::TransportMode and applies it
(SDES-SRTP reuses the plain-RTP port range). Leg transport metadata is set
from the same helpers. Adds unit coverage for the SDP classification.

Requires rustrtc with the SDES-SRTP RTP/SAVP profile fix (restsend/rustrtc#31).

Co-authored-by: Claude <noreply@anthropic.com>
@v0l v0l force-pushed the feat/sdes-srtp-sip-legs branch from ce65772 to 4f479c4 Compare June 24, 2026 17:14
@shenjinti shenjinti merged commit a06b0a0 into restsend:main Jun 25, 2026
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.

2 participants