Comments (4)
Let keep the whole conversation here:
I create two srtp_t sessions, set policy.ssrc.type = ssrc_any_outbound in both of them, and then call to srtp_protect() to both of them by passing the same RTP packet/data with the same SSRC value in both calls (note that before calling srtp_protect() I do a memcpy of the original RTP data, so the original one remains unchanged when the second call to srtp_protect() takes place in the second session).
In this scenario I get lot of err_status_replay_old errors ("replay check failed (index too old)").
So I wonder: may be libsrtp handles a global/static list of streams so two different and independent srtp_t sessions should NEVER try to protect the same RTP packet?
If this is true, please confirm it. Thanks a lot.
Collaborator
jfigus commented on 22 Sep 2014
Sorry for the delayed response. I was on travel last week. Are you using the same srtp_ctx_t* when invoking srtp_protect() for each packet? If so, it's probably reusing the same stream context for both packets since it uses the SSRC to lookup the stream context. The replay database is under the stream context.
ibc commented on 22 Sep 2014
Thanks for the response. I think I know what is happening:
The server (in which I use libsrtp) receives RTP from peer-A, unprotects it, sets its SSRC value to a fixed "1234" and protects it using the srtp_ctx_t* of peer-B.
Later peer-A disconnects and reconnects again, which means a new DTLS negotiation (WebRTC) so also a new SRTP keys and a new srtp_ctx_t* for peer-A (the previous one is properly freed).
When the "new" peer-A sends RTP, the server again unprotects it, sets its SSRC value to "1234" and protects it using the srtp_ctx_t* of peer-B.
It is in that point when it fails with the err_status_replay_old error.
AFAIU this is ok since the new RTP packets form peer-A are different than the previous ones, but in the server I'm overriding its SSRC value to "1234" and then pass them to the srtp_ctx_t* of peer-B to protect them, so the srtp_ctx_t* of peer-B is provided with a stream already known (SSRC = 1234) and thus it fails to encrypt it given that the new timestamp values and so on don't follow those in the first stream of peer-A (already finished).
So basically, in my scenario I need to set a different SSRC before calling srtp_protect(), am I right?
Collaborator
jfigus commented on 22 Sep 2014
If you are using two srtp_ctx_t* references, one for peer A and one for peer B, then the err_status_replay_old error is likely due to a repeating sequence number on the context for peer B. Changing the SSRC will probably not make a difference. You'll probably have to maintain a separate RTP sequence number for peer B to make sure it's not reused after the peer A session is restarted.
If you're not rekeying the context with peer B, then you shouldn't reuse that context when sending repeated packets. You'll probably want to rekey the peer B context.
ibc commented on 22 Sep 2014
Thanks for your response. However I do not fully understand.
I expect that, when calling srtp_protect(packet):
First the SSRC of the packet is inspected within the srtp_ctx_t session.
Then the session internally creates a new "stream" or not (depending on whether the SSRC was previously present or not in another call to srtp_protect() in that session.
And then, within the resulting "stream", the packet's sequence number is inspected in order to check for errors (like mine).
Is this the order?
ibc commented on 22 Sep 2014
I would like to explain better my scenario:
My server is a conference server that does not perform media mixing.
Peer A, Peer B and Peer C establish a separate and single DTLS-SRTP connection with my server and send a single audio stream on it with an arbitrary SSRC chosen by the peer (Chrome).
In the server, for each peer two srtp_ctx_t are created as usual, one for receiving RTP (with policy.ssrc.type = ssrc_any_inbound and another for sending RTP to that peer with policy.ssrc.type = ssrc_any_outbound.
When Peer A sends RTP to the server, the server decrypts it using the inbound srtp_ctx_t of Peer A (as usual), then clones the resulting plain RTP packet resulting in two RTP packets rtp-for-peer-B and rtp-for-peer-C.
Then it sets the SSRC of rtp-for-peer-B to (let's say) 2222, and the SSRC of rtp-for-peer-C to 3333, and encrypts them with their corresponding outbound srtp_ctx_t of Peer B and Peer C, and sends them to those peers.
All works fine. The problem happens when Peer A is restarted. Its DTLS-SRTP context is deleted (so also its outbound and inbound srtp_ctx_t). Peer A negotiates again DTLS-SRTP resulting in two new srtp_ctx_t for inbound and outbound.
Then Peer A sends again RTP (note that this RTP has nothing to do with the previous one as the client has been restarted).
The server performs the same steps as above, and then sometimes I get the err_status_replay_old when calling srtp_protect() on the outbound srtp_ctx_t of Peer B and Peer C.
So in this case, I understand that, indeed, the sequence numbers of those new RTP packets form Peer A may create a conflict with the previous RTP stream from Peer A, and hence the encrypt error. Am I right?
If so, I understand that when Peer A is restarted and performs again the DTLS-SRTP setup, the server must choose a different SSRC value for the RTP coming from Peer A so the outbound srtp_ctx_t of Peer B and Peer C will see those packets belonging to a new "stream" so there won't be sequence number problems. Am I right?
Thanks a lot.
Collaborator
jfigus commented on 22 Sep 2014
Q: So in this case, I understand that, indeed, the sequence numbers of those new RTP packets form Peer A may create a conflict with the previous RTP stream from Peer A, and hence the encrypt error. Am I right?
A: I believe you're correct. Peer A is likely repeating the sequence number of some packets, causing the error when you re-encrypt it for peer B (or C).
Q: If so, I understand that when Peer A is restarted and performs again the DTLS-SRTP setup, the server must choose a different SSRC value for the RTP coming from Peer A so the outbound srtp_ctx_t of Peer B and Peer C will see those packets belonging to a new "stream" so there won't be sequence number problems. Am I right?
A: Yes, choosing a new SSRC may resolve the issue. But it's highly recommended to rekey the session between the server and peer B (and peer C). Another approach may be to use the EKT feature in libsrtp. Unfortunately the EKT draft was never ratified. But EKT support was added to libsrtp at some point in the past. This is beyond my expertise since the EKT code was contributed by another developer. But my understanding is EKT was developed specifically to solve the problem you're addressing. You may want to take a look at the EKT draft.
ibc commented on 22 Sep 2014
Thanks a lot!
I will take a look to EKT, but first I want the "simple" case to properly work (and EKT may not be suitable for all the use-cases in my server given that in some cases it will behave as a gateway between DTLS-SRTP peers and SDES-SRTP or plain RTP peers.
Anyhow, you replied:
Yes, choosing a new SSRC may resolve the issue. But it's highly recommended to rekey the session between the server and peer B (and peer C)
I understand that it is recommended not to leak the session between the server and peer B (and peer C) with unused streams. What about if I do the following?
Instead of setting policy.ssrc.type = ssrc_any_outbound I may call to srtp_add_stream() when a new peer joins the "conference" and srtp_remove_stream() when the peer leaves it. This would work even if I set the SSRC from Peer A to the same value when it reconnects since at the time it has been disconnected I've removed its stream (SSRC) from the sessions of both Peer B and Peer C. Does it sound ok?
Collaborator
jfigus commented on 22 Sep 2014
That should resolve your problem since invoking srtp_add_stream() will result in a new initialization of the anti-replay database for the stream. The streams are indexed by SSRC and normally created implicitly when using ssrc_any_outbound. You'll simply be creating the stream explicitly. But you really should rekey the other SRTP sessions to peers B & C. Otherwise you'll end up reusing the initialization vector for AES encryption, which is fatal when GCM mode is used.
Collaborator
jesup commented on 23 Sep 2014
Yes. It is a different source, in fact. ALternatively, make sure the peer-B context doesn't see the source as having stopped, and modify any after-reattachment seqnums and timestamps to make the interrupted source streams appear as a single stream with an interruption.
ibc commented on 23 Sep 2014
But you really should rekey the other SRTP sessions to peers B & C. Otherwise you'll end up reusing the initialization vector for AES encryption, which is fatal when GCM mode is used.
Does "rekey the other SRTP session to B & C" mean force a new SRTP key exchange with both peers B and C? I cannot do that just because A has re-connected as it would involve a new SDP O/A with all the conference peers every time a peer reconnects.
So, is it enough if I set a new SSRC for the incoming RTP of Peer A after it reconnects to the conference?
ALternatively, make sure the peer-B context doesn't see the source as having stopped, and modify any after-reattachment seqnums and timestamps to make the interrupted source streams appear as a single stream with an interruption.
I understand that this is not needed if I do not reuse a previously used SSRC, am I right?
Thanks a lot to both.
Collaborator
jfigus commented on 24 Sep 2014
Yes, you would have to perform a new SRTP key exchange with both peers (B&C). I'm assuming you're using AES-CTR mode. The problem is CTR mode is a stream cipher. Any nonce reuse is considered catastrophic. Since the nonce is derived from the sequence number in the RTP packet, and you're potentially reusing sequence numbers (indicated by the error you received that started this discussion), then you're at risk for nonce reuse. The same problem is presented when using AES-GCM mode. This needs to be avoided if you wish to provide a secure solution. Take a look at EKT. It was designed with intent to solve the problem you've presented.
ibc commented on 24 Sep 2014
Thanks a lot, but I would like to confirm that this problem:
Since the nonce is derived from the sequence number in the RTP packet, and you're potentially reusing sequence numbers (indicated by the error you received that started this discussion), then you're at risk for nonce reuse
This problem won't happen in case I do NOT reuse the same SSRC for a new stream (which replaces an old one) within the same calls to srtp_protect(same_session). Am I right?
BTW: How to select AES-CTR, AES-GCM or any other mode? I don't see that in the API. Is it something that I can set when creating a srtp session or policy? or does it depend on the peer?
Regarding EKT, AFAIK it just make sense when both peers use SRTP, which may not be true in my case. Even more, I'm building a conference server (which does not mix but relays all the streams to other participants).
Collaborator
jfigus commented on 24 Sep 2014
The SSRC is also a component into the nonce (a.k.a. IV) used for AES-CTR mode. If you're changing the SSRC, then this may help prevent nonce resuse. Bottom line, you'll need to ensure the nonce is never reused. Please refer to section 4.1.1 of RFC 3711 which warns about this and discusses the need to rekey. You'll need to understand how the nonce is derived and ensure it's never reused. Your approach of cloning the packing and hacking the SSRC may circumvent the nonce reuse checks within libsrtp.
You would select the AES mode on the SRTP policy. The cipher_type member on the policy can be set to AES_ICM, AES_192_ICM, AES_256_ICM, AES_128_GCM, or AES_256_GCM. There are some other settings for this defined in crypto_types.h, but I've never seen them used and not sure if they are supported (SEAL, AES_CBC).
ibc commented on 24 Sep 2014
Really thanks a lot. I just use crypto_policy_set_aes_cm_128_hmac_sha1_80() and crypto_policy_set_aes_cm_128_hmac_sha1_32() (depending on the SRTP suite negotiated via DTLS).
Definitely I need to go in depth with RFC 3711. Will do it.
I strongly appreciate all the help provided in this thread. I will close the issue given that it is not an issue :)
Collaborator
jfigus commented on 25 Sep 2014
Using those policy helper functions, you're using AES-CTR mode. If you want GCM mode, you can use one of the GCM policy helpers (e.g. crypto_policy_set_aes_gcm_128_8_auth). GCM mode is only supported when the --enable-openssl option is used to configure libsrtp.
from mediasoup.
TODO: Suggest in libsrtp project that streams/ssrcs are not globally stored by per SRTP session.
/cc @jmillan
from mediasoup.
UPDATE: I was wrong:
SSRC (and hence srtp_stream_t
objects) are handled per srtp_t
session, so as longer as the same srtp_t
session does not attempt to handle two different streams with the same ssrc, we are fine.
from mediasoup.
So closing this issue.
from mediasoup.
Related Issues (20)
- Mediasoup worker died, exiting in 2 seconds... HOT 2
- [Rust] Error log “XXX closing failed on drop: Channel already closed” HOT 2
- Add interface for RTC::Shared, Channel::ChannelNotifier and ChannelMessageRegistrator HOT 3
- [Rust] Transport with only audio producer results in server-side transport cc is not created. HOT 2
- Bug Simulcast plainTransport -> webrtcTransport HOT 5
- Video software encoding Quality Limitation vs hardware encoding HOT 1
- Ci tests fail consistently in Windows (Node and Rust) HOT 10
- Failed to run custom build command for `mediasoup-sys v0.9.0` HOT 6
- H264 temporal layers no longer detected due to removal of frame-marking RTP extension in libwebrtc (must implement dependency descriptor RTP extension) HOT 6
- AddressSanitizer issues HOT 6
- Couldn't run on ARM64 platform. HOT 3
- consumer#resume/pause not working as it should HOT 2
- worker process silently exits if TcpConnectionHandle::SetPeerAddress() fails HOT 2
- Simulcast frozen video due to dropping RTP packets HOT 4
- the "PacketRouter* packet_router_" and "PacedSender pacer_" is different class, but the pacer_ is assigned by the "packet_router_".
- Update ESLint and its ecosystem
- Docker and io_uring don't work together - add prebuilt binary without liburing HOT 16
- libsrtp: wraparound with loss decryption failure HOT 2
- Include ubuntu-24.04 in CI HOT 8
- mediasoup-rs segfault HOT 3
Recommend Projects
-
React
A declarative, efficient, and flexible JavaScript library for building user interfaces.
-
Vue.js
🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.
-
Typescript
TypeScript is a superset of JavaScript that compiles to clean JavaScript output.
-
TensorFlow
An Open Source Machine Learning Framework for Everyone
-
Django
The Web framework for perfectionists with deadlines.
-
Laravel
A PHP framework for web artisans
-
D3
Bring data to life with SVG, Canvas and HTML. 📊📈🎉
-
Recommend Topics
-
javascript
JavaScript (JS) is a lightweight interpreted programming language with first-class functions.
-
web
Some thing interesting about web. New door for the world.
-
server
A server is a program made to process requests and deliver data to clients.
-
Machine learning
Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.
-
Visualization
Some thing interesting about visualization, use data art
-
Game
Some thing interesting about game, make everyone happy.
Recommend Org
-
Facebook
We are working to build community through open source technology. NB: members must have two-factor auth.
-
Microsoft
Open source projects and samples from Microsoft.
-
Google
Google ❤️ Open Source for everyone.
-
Alibaba
Alibaba Open Source for everyone
-
D3
Data-Driven Documents codes.
-
Tencent
China tencent open source team.
from mediasoup.