feat: Add RTSPS (TLS) and SDES-SRTP support for RTSP pull streams#2004
feat: Add RTSPS (TLS) and SDES-SRTP support for RTSP pull streams#2004devedse wants to merge 5 commits intoOvenMediaLabs:masterfrom
Conversation
- Add rtsps:// scheme support with TLS handshake via TlsClientDataIoCallback - Parse a=crypto SDP attributes (RFC 4568) for SDES-SRTP key exchange - Per-channel SRTP sessions in SrtpTransport for interleaved RTSP - Preserve RtspData channel ID through SRTP decryption for track lookup - Support AES_CM_128_HMAC_SHA1_80, AES_CM_128_HMAC_SHA1_32, AEAD_AES_128_GCM - Register rtsps scheme in orchestrator as RtspPull provider
|
Thank you for the PR. Now that the new version (0.20.5) has been released, we can start reviewing this PR. Sorry for the long delay. In the meantime, there have been many changes, so this PR now has conflicts. Could you please resolve them? Also, unfortunately, I do not have a camera that supports RTSPS, so I am unable to test it myself. If you could provide an RTSPS camera URL, it would be very helpful for the review. If possible, I would appreciate it if you could send the RTSPS URL to support@ovenmedialabs.com |
getroot
left a comment
There was a problem hiding this comment.
First, I did a rough initial review at the code level. Once the conflicts are resolved and testing becomes possible, I will review it more thoroughly.
|
|
||
| stop_watch.Update(); | ||
|
|
||
| if (RequestDescribe() == false) |
There was a problem hiding this comment.
It is difficult to understand the intention behind merging the RequestDescribe step into ConnectTo. Was there a specific reason for this?
There was a problem hiding this comment.
So just to be absolutely honest, I'm not really a C++ developer nor experienced in the whole streaming stuff. My knowledge is mainly on usage as a consumer. I am though experienced in other programming languages so I can at least understand most of the changes that were made.
In this case this seems to have been a change done by AI without any specific reason. I've restored RequestDescribe() as a separate method now.
| return false; | ||
| } | ||
|
|
||
| constexpr size_t SRTP_KEY_MATERIAL_LENGTH = 30; // master key (16) + master salt (14) |
There was a problem hiding this comment.
For AEAD_AES_128_GCM, it should be 28 bytes. It seems this needs to be fixed.
There was a problem hiding this comment.
I honestly have no opinion nor knowledge on this. Copilot came up with this:
You're right, that's a bug. I had a single constant of 30 bytes for all suites, but AEAD_AES_128_GCM uses a 12-byte salt instead of 14, so it needs 28 bytes. Fixed it so the expected key length is now determined per crypto suite alongside the suite selection.
But please verify this one thoroughly.
| return true; | ||
| } | ||
|
|
||
| bool SrtpTransport::AddChannelKeyMaterial(uint8_t rtp_channel_id, uint64_t crypto_suite, std::shared_ptr<ov::Data> key) |
There was a problem hiding this comment.
It creates only the recv session and does not set up _send_session. Since OnDataReceivedFromPrevNode() immediately returns false when _send_session is null, all RTCP Receiver Reports sent from OME to the camera are dropped. In SDES-SRTP, key negotiation for OME's outgoing stream and initialization of _send_session are required.
There was a problem hiding this comment.
Again, no input from my side on this one:
Copilot response:
That's a significant oversight, thanks for the detailed explanation. You're absolutely right, without send sessions all outgoing RTCP gets silently dropped since [OnDataReceivedFromPrevNode()] bails out on the null check. I've fixed this by having [AddChannelKeyMaterial()] create both inbound and outbound sessions per channel using the same key (as SDES-SRTP uses symmetric keying). The outgoing path now looks up the correct per-channel send session using an SSRC to channel mapping learned from incoming RTP.
ConnectTo() now only handles TCP connection and TLS handshake. The RTSP DESCRIBE request is moved back to its own RequestDescribe() method, called separately from StartStream(), restoring the original separation of concerns.
AEAD_AES_128_GCM requires 28 bytes (16-byte key + 12-byte salt), not 30 bytes like AES_CM_128_HMAC_SHA1_80/32 (16-byte key + 14-byte salt). Replace the fixed constant with per-suite expected lengths.
AddChannelKeyMaterial() now creates both recv (inbound) and send (outbound) sessions per channel, using the same key for both directions as required by SDES-SRTP (RFC 4568). OnDataReceivedFromPrevNode() now supports per-channel send session lookup for outgoing RTCP. It learns SSRC-to-channel mappings from incoming RTP packets and uses the first report block SSRC in outgoing RTCP to select the correct send session, with a fallback to the first available session. The send path now forwards with explicit NodeType (Srtp/Srtcp) so downstream nodes can distinguish encrypted RTP from RTCP. RtspcStream::OnDataReceivedFromPrevNode() now handles NodeType::Srtcp alongside NodeType::Rtcp for SRTP-encrypted outgoing RTCP.
|
I merged the changes from main into this branch. Could you please carefully re-review again? |
There was a problem hiding this comment.
Pull request overview
Adds encrypted RTSP pull support by introducing rtsps:// (RTSP over TLS) signalling and SDES-SRTP handling for interleaved RTP/RTCP, including SDP a=crypto parsing and per-channel SRTP sessions.
Changes:
- Register
rtsps://URLs to use the existing RTSP pull provider path. - Add TLS handshake + encrypted RTSP signalling (send/receive) in
RtspcStream. - Parse SDP
a=crypto:attributes and configure per-interleaved-channel SRTP viaSrtpTransport, with track resolution updates inRtpRtcp.
Reviewed changes
Copilot reviewed 9 out of 9 changed files in this pull request and generated 6 comments.
Show a summary per file
| File | Description |
|---|---|
| src/projects/providers/rtspc/rtspc_stream.h | Adds TLS callback interface + SRTP/TLS state fields to RTSP pull stream. |
| src/projects/providers/rtspc/rtspc_stream.cpp | Implements RTSPS connect/handshake, TLS send/recv, SDP crypto detection, and SRTP transport chaining. |
| src/projects/orchestrator/orchestrator.cpp | Maps rtsps scheme to ProviderType::RtspPull. |
| src/projects/modules/sdp/sdp_regex_pattern.h | Adds regex + match function for a=crypto: attributes. |
| src/projects/modules/sdp/media_description.h | Introduces CryptoAttr storage/accessors for SDP crypto lines. |
| src/projects/modules/sdp/media_description.cpp | Implements parsing/storage of a=crypto: plus accessors. |
| src/projects/modules/rtp_rtcp/rtp_rtcp.cpp | Enables RTSP/SRTP channel-based track lookup using RtspData wrappers. |
| src/projects/modules/dtls_srtp/srtp_transport.h | Adds per-channel SRTP keying APIs/state for RTSP interleaved mode. |
| src/projects/modules/dtls_srtp/srtp_transport.cpp | Implements per-channel SRTP sessions and preserves RtspData channel IDs through decrypt/encrypt. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| session->Release(); | ||
| } | ||
| } | ||
| _channel_send_sessions.clear(); |
There was a problem hiding this comment.
SrtpTransport::Stop() clears the per-channel session maps but leaves _ssrc_to_channel populated. This mapping can persist across restarts and grow unnecessarily; clear it during Stop() alongside the other per-stream state.
| _channel_send_sessions.clear(); | |
| _channel_send_sessions.clear(); | |
| _ssrc_to_channel.clear(); |
| { | ||
| type = ProviderType::RtspPull; | ||
| } | ||
| else if (lower_scheme == "rtsps") | ||
| { | ||
| type = ProviderType::RtspPull; | ||
| } |
There was a problem hiding this comment.
The rtsps scheme handling is functionally identical to the rtsp branch and duplicates the assignment. This can be simplified to a single condition (e.g., (lower_scheme == "rtsp" || lower_scheme == "rtsps")) to reduce branching and keep scheme mappings easier to maintain.
| size_t received_length = 0; | ||
| auto error = _signalling_socket->Recv(data, length, &received_length); | ||
| if (error != nullptr) | ||
| { | ||
| if (error->GetCode() == EAGAIN || error->GetCode() == EWOULDBLOCK) | ||
| { | ||
| // Non-blocking socket would block | ||
| errno = EAGAIN; | ||
| return -1; | ||
| } |
There was a problem hiding this comment.
OnTlsReadData() calls _signalling_socket->Recv(...) without MSG_DONTWAIT / non_block=true. In PLAYING state ReceivePacket(true) relies on TLS reads being non-blocking; this callback can block indefinitely and stall the pull loop (ping, demux, etc.). Consider making socket reads non-blocking after the TLS handshake (or implementing a non-blocking handshake loop) and calling Recv(..., /non_block=/true) in that mode so SSL_read can return SSL_ERROR_WANT_READ instead of blocking.
| if (non_block) | ||
| { | ||
| // Retry later for non-blocking mode | ||
| return true; | ||
| } |
There was a problem hiding this comment.
In TLS mode, if Decrypt() returns false (fatal TLS error / protocol error), the non_block path returns true and continues as if no error occurred. This can hide TLS failures and leave the stream stuck. Treat Decrypt()==false as an error regardless of non_block (only empty decrypted data should be used for retry semantics).
| if (non_block) | |
| { | |
| // Retry later for non-blocking mode | |
| return true; | |
| } | |
| // Treat TLS decryption failure as a fatal error regardless of non_block |
|
@getroot I think it would be best if you pick up the remaining changes. I'm not sure which ones make sense. |
|
@devedse Understood. I have many high-priority tasks right now, so I will handle this later. |
Summary
Adds support for
rtsps://URLs in the RTSP pull provider, enabling encrypted RTSP streaming with TLS and SDES-SRTP.What this does
rtsps://scheme with TLS handshake, encrypted signalling and interleaved data viaTlsClientDataIoCallbacka=crypto:SDP attributes (RFC 4568) and sets up per-channel SRTP sessions for encrypted RTP/RTCPSrtpTransportbetweenRtpRtcpandRtspcStreamnodes when crypto is presentRtspDatachannel ID through SRTP decryption soRtpRtcpcan resolve tracks correctlyWhy it's needed
Many IP cameras and RTSP sources (e.g. Unifi cameras) only expose encrypted RTSPS endpoints. Without this, OvenMediaEngine cannot pull from these sources.
Files changed (9)
orchestrator.cpp— Registerrtspsscheme as RtspPull providerrtspc_stream.cpp/h— TLS connection, SRTP setup, encrypted send/recvsrtp_transport.cpp/h— Per-channel SRTP sessions for RTSP interleaved modertp_rtcp.cpp— Channel-based track lookup through SRTP nodemedia_description.cpp/h— Parsea=crypto:SDP attributessdp_regex_pattern.h— Regex for crypto attribute parsing