-
-
Notifications
You must be signed in to change notification settings - Fork 1.1k
DataChannel
Here the roadmap for adding DataChannel support to mediasoup. It includes a preliminary architecture design among other considerations (below) such as implementation details.
The DataChannel design and exposed API must follow the principles of the already exiting audio/video Producer
and Consumer
design in mediasoup v3. So, the DataChannel design must conform to the following requirements:
-
The DataChannel message unit is a SCTP message (in the same way, the message unit in audio/video is a RTP packet).
-
There must two new classes:
DataProducer
andDataConsumer
(in both mediasoup server and mediasoup-client).- A
DataProducer
represents a data channel source (so a SCTP stream with a specificstream_id
) being injected into a mediasoup router. It's created on top of a transport that defines how the SCTP packets are carried. -
DataProducers
are created using the already existing transport.produce() JavaScript API with new arguments. - A
DataConsumer
represents a data channel source (so a SCTP stream with a specificstream_id
) being forwarded from a mediasoup router to an endpoint. It's created on top of a transport that defines how the SCTP packets are carried. -
DataConsumers
are created using the already existing transport.consume() JavaScript API with new arguments. -
DataConsumers
inherit the same SCTP settings as theDataProducer
they consume from (ordered/unordered, reliable/unreliable, etc).
- A
-
STCP packets can be injected into a mediasoup router on top of a WebRtcTransport (so STCP over DTLS over UDP/TCP) and also on top of a PlainRtpTransport (so SCTP over UDP).
- NOTE: We may want to rename
PlainRtpTransport
toPlainTransport
.
- NOTE: We may want to rename
-
The
WebRtcTransport
andPlainRtpTransport
will hold a newSctpAssociation
class instance to manage the SCTP connection itself, STCP retransmission, etc. -
When a SCTP endpoint sends a SCTP packet on a
DataProducer
:- The SCTP packet reaches the mediasoup
WebRtcTransport
orPlainRtpTransport
which delivers the SCTP packet to itsSctpAssociation
class instance. - The
SctpAssociation
will pass it to the SCTP stack which will eventually call recv callbacks (notifications and messages). Let's assume it's a full message. - The
SctpAssociation
will notify the transport about "new SCTP message with thisstream_id
". - The transport looks for the corresponding
DataProducer
using itsSctpListener
(similar to theRtpListener
) and passes the message to it. - The
DataProducer
may need to do something with the SCTP message (stats and so on) then notifies its transport back with the same message. - The transport notifies it to the router (
Router
C++ class). - The router iterates the
DataConsumers
associated to the senderDataProducer
and callsdataConsumer->SendSctpMessage()
on all them. - Each
DataConsumer
gets the message and does some stats stuff and so on, and the notifies its transport about it. - The transport of the
DataConsumer
receives the message and passes it to itsSctpAssociation
instance (that will manage retransmissions, etc) by also indicating thestream_id
it must use. - The
SctpAssociation
passes it to the SCTP stack which will eventually invoke the send callback, in which the generated SCTP packets will be delivered to the transport again to deliver them to the remote endpoint:- If it's a
WebRtcTransport
, it will send the SCTP packet as DTLS data using the already existingDtlsTransport::SendApplicationData()
method. - If it's a
PlainRtpTransport
it will send the SCTP packet using raw UDP.
- If it's a
- The
DataConsumer
then restores the SCTP packet fields.
- The SCTP packet reaches the mediasoup
-
Similar to audio/video
Producers
andConsumers
, it must be possible for the Node.js app running mediasoup to decide which endpoints consume (by means of aDataConsumer
) from a specificDataProducer
. Once aDataConsumer
is created, theRouter
(at C++ level) will route to it SCTP messages from the associatedDataProducer
. -
It must be possible for the Node.js app (the application that integrates mediasoup lib and uses its JS API) to inject SCTP messages into a mediasoup router so those messages are routed to endpoints via WebRTC DataChannel. And vice-versa: the Node.js app must be able to receive SCTP packets sent by endpoints to the mediasoup router. To do it, the Node.js app:
- must create a
PlainRtpTransport
in the mediasoup router, - must call
produceData()
on it with appropriate arguments to create aDataProducer
instance, - must create a UDP+SCTP client that sends (and/or receives) SCTP packets (with the announced
stream_id
) to the IP:port of the previously created mediasoupPlainRtpTransport
.- The
sctp_port
must have a hardcoded value (5000 is good) to demux it with RTP, RTCP, STUN and DTLS.
- The
- must create a
-
Such a "UDP+SCTP client" could be a Node.js library/module which, for example, uses the Node.js UDP stack plus the node-sctp or @nodertc/sctp Node.js libraries.
- Of course, we (the mediasoup team) will provide developers with such a client, probably as a separate library.
- The node-sctp can, indeed, send SCTP packets over UDP: example.
- Since Chrome does not allow SCTP messages bigger than 256KB (sad!!!) we may need to tell the application to use chunked-dc-js (credits to @@lgrahl).
UPDATE: Unfortunately this cool design is not possible due to usrsctp
(no SCTP I-DATA support) and Chrome (which limits max SCTP message size to 256 KB). So mediasoup must route full SCTP messages from DataProducers
to DataConsumers
and not SCTP I-DATA chunks.
The DataChannel design and exposed API must follow the principles of the already exiting audio/video Producer
and Consumer
design in mediasoup v3. So, the DataChannel design must conform to the following requirements:
-
The DataChannel message unit is a SCTP I-DATA chunk (in the same way, the message unit in audio/video is a RTP packet).
-
There must two new classes:
DataProducer
andDataConsumer
(in both mediasoup server and mediasoup-client).- A
DataProducer
represents a data channel source (so a SCTP stream with a specificstream_id
) being injected into a mediasoup router. It's created on top of a transport that defines how the SCTP packets are carried. -
DataProducers
are created using the already existing transport.produce() JavaScript API with new arguments. - A
DataConsumer
represents a data channel source (so a SCTP stream with a specificstream_id
) being forwarded from a mediasoup router to an endpoint. It's created on top of a transport that defines how the SCTP packets are carried. -
DataConsumers
are created using the already existing transport.consume() JavaScript API with new arguments. -
DataConsumers
inherit the same SCTP settings as theDataProducer
they consume from (ordered/unordered, reliable/unreliable, etc).
- A
-
STCP packets can be injected into a mediasoup router on top of a WebRtcTransport (so STCP over DTLS over UDP/TCP) and also on top of a PlainRtpTransport (so SCTP over UDP).
- NOTE: We may want to rename
PlainRtpTransport
toPlainTransport
.
- NOTE: We may want to rename
-
The
WebRtcTransport
andPlainRtpTransport
will hold a newSctpAssociation
class instance to manage the SCTP connection itself, STCP retransmission, etc. -
When a SCTP endpoint sends a SCTP packet on a
DataProducer
:- The SCTP packet reaches the mediasoup
WebRtcTransport
orPlainRtpTransport
which delivers the SCTP packet to itsSctpAssociation
class instance. - The
SctpAssociation
will split the packet into chunks and determine whether each chunk is a SCTP control packet (init, reset, new stream created, alert, etc) or a SCTP I-DATA chunk. Let's assume it's a I-DATA chunk. - The
SctpAssociation
will notify the transport about "new SCTP I-DATA chunk". - The transport reads the SCTP
stream_id
of the chunk and looks for the correspondingDataProducer
using itsSctpListener
(similar to theRtpListener
) and passes the chunk to it. - The
DataProducer
may need to do something with the SCTP chunk (TSN, etc) and then notifies its transport back with the same SCTP I-DATA chunk. - The transport notifies it to the router (
Router
C++ class). - The router iterates the
DataConsumers
associated to the senderDataProducer
and callsdataConsumer->SendSctpIDataChunk(chunk)
on all them. - Each
DataConsumer
gets the chunk, mangles its SCTPstreamId
field and other fields in the chunk and notifies its transport to deliver the chunk. - The transport of the
DataConsumer
receives the chunk and passes it to itsSctpAssociation
instance (that will manage retransmissions, etc). - The
SctpAssociation
will then create a SCTP packet, put the chunk into it and notify back the SCTP chunk to the transport, which will finally deliver the packet to the remote endpoint:- If it's a
WebRtcTransport
, it will send the SCTP packet as DTLS data using the already existingDtlsTransport::SendApplicationData()
method. - If it's a
PlainRtpTransport
it will send the SCTP packet using raw UDP.
- If it's a
- The
DataConsumer
then restores the SCTP packet fields.
- The SCTP packet reaches the mediasoup
-
The
WebRtcTransport
andPlainRtpTransport
may need a newSctpAssociation
class instance to manage the SCTP connection itself. -
Similar to audio/video
Producers
andConsumers
, it must be possible for the Node.js app running mediasoup to decide which endpoints consume (by means of aDataConsumer
) from a specificDataProducer
. Once aDataConsumer
is created, theRouter
(at C++ level) will route to it SCTP I-DATA chunks from the associatedDataProducer
. -
It must be possible for the Node.js app (the application that integrates mediasoup lib and uses its JS API) to inject SCTP I-DATA chunks into a mediasoup router so those messages are routed to endpoints via WebRTC DataChannel. And vice-versa: the Node.js app must be able to receive SCTP packets sent by endpoints to the mediasoup router. To do it, the Node.js app:
- must create a
PlainRtpTransport
in the mediasoup router, - must call
produce()
on it with appropriate arguments to create aDataProducer
instance, - must create a UDP+SCTP client that sends (and/or receives) SCTP packets (with the announced
stream_id
) to the IP:port of the previously created mediasoupPlainRtpTransport
.- The
sctp_port
must have a hardcoded value to demux it with RTP, RTCP, STUN and DTLS.
- The
- must create a
-
Such a "UDP+SCTP client" could be a Node.js library/module which, for example, uses the Node.js UDP stack plus the node-sctp or @nodertc/sctp Node.js libraries.
- Of course, we (the mediasoup team) will provide developers with such a client, probably as a separate library.
- The node-sctp can, indeed, send SCTP packets over UDP: example.
- Since Chrome does not allow SCTP messages bigger than 256KB (sad!!!) we may need to tell the application to use chunked-dc-js (credits to @@lgrahl).
- An issue in the design above is that a
DataProducer
with vey good uplink connection may send chunks super fast, while aDataConsumer
may not send those chunks so fast, and that could break the Internet connection.- So mediasoup may need to "slow down" the data sending to the consuming endpoint, and that means buffering, congestion control, etc.
- If we used
usrsctp
instead (SCTP message based), the lib itself would slow down the data transmission of the given message.
The WebRtcTransport
of mediasoup already has an (unused yet) API to send and receive DTLS application data (so SCTP packets):
(we would add the same OnDtlsApplicationData
callback into the PlainRtpTransport
for SCTP over UDP).
So we need to plug a C++ SCTP stack to handle SCTP packets. It would be done within a new C++ class named SctpHandler
or whatever.
As per now there are the following C or C++ SCTP stacks out there we can consider:
-
Create our own stack, which is the only one that can implement the design above (based on STCP I-DATA chunk routing instead of SCTP message routing).
-
sctplab/usrsctp, the "SCTP stack". Used by libwebrtc, Janus among many others. Problems:
- It's multi-thread (question). We need a single-thread stack.
- There is a Pull Request that makes it single thread, but was not merged.
- Then @lgrahl said "No need to wait, you can use usrsctp-neat which will be merged back eventually", but that did never happen.
- I also do not like the idea of integrating a library with more than 50 open issues, some of them definitively relevant as a memory leak.
- It does not provide a GYP file, so we should do it (plus PR of course).
-
usrsctp-neat, which is a fork of
usrsctp
with same changes to make it single thread (which is far from perfect). Unfortunately it's not maintained at all and it does not merge new commits in the main stream project (sctplab/usrsctp
). -
rawrtc/rawrtc-data-channel, which is a layer on top of
usrsctp-neat
. Problems:- It depends on
usrsctp-neat
, so same concerns as above apply. - It also depends on
re
andrew
C libraries (due the usage of some helpers and utils included in them). Not a super big problem, but I don't like the idea of having mediasoup depend on a C library that implements SIP, WebSocket, DNS, HTTP, etc. - It depends on
meson
andninja
for the building stage. This is a no-go for mediasoup. Users of mediasoup are supposed to just runnpm install mediasoup
and they are done. The"postinstall": "make -C worker"
inpackage.json
builds the mediasoup C++ code and its C/C++ deps (included in the source) by usinggyp
(which is also included in the package). All C/C++ dependencies of mediasoup include GYP files. - In other words, we should create GYP files for
rawrtc-data-channel
,usrsctp-neat
,re
andrew
libraries...
- It depends on
-
medooze/libdatachannels, whose API matches the most what we need (single thread, and provides an API to send and receive packets by letting the external application manage the socket, timers, the DTLS, etc). Unfortunately it's nor finished neither active.
-
Another option is coding our own stack. Not desirable, but we have a clear design and whichever C/C++ SCTP stack we choose (or build) must conform to it.
We are gonna fork sctplab/usrsctp
, make it single thread (as usrsctp-neat
and a PR do) and add GYP support.
- peer-to-peer-data-api: Peer-to-Peer Data API
- draft-ietf-rtcweb-transports: Transports for WebRTC
- draft-ietf-rtcweb-data-channel: WebRTC Data Channels
-
draft-ietf-rtcweb-data-protocol: WebRTC Data Channel Establishment Protocol
- This is for in-band DataChannel (pair of send & recv SCTP streams with same
stream_id
over the same STCP association). We (mediasoup) may prefer to use out-of-band negotiation (so DataChannelstream_id
and other parameters are exchanged between client and server via signaling).
- This is for in-band DataChannel (pair of send & recv SCTP streams with same
-
draft-ietf-mmusic-sctp-sdp: SDP Offer/Answer Procedures For SCTP over DTLS
- Q: It's not clear to me which endpoint (DTLS client? DLTS server?) sends the SCTP INIT message. Section 9.3 says "When an SCTP association is established, both SCTP endpoints MUST initiate the SCTP association (i.e. both SCTP endpoints take the 'active' role)". I don't understand it at all. Isn't the SCTP association established with the INIT chunk?
- R: The SCTP association is just determined by the SCTP ports negotiated in the SDP O/A (plus 'max-message-size', etc), so it "just" exists. Both endpoints must "initiate" it (via SCTP INIT) to setup the initial TSN and so on.
- RFC 4960: Stream Control Transmission Protocol
- RFC 3758: SCTP Partial Reliability Extension
-
RFC 5061: SCTP Dynamic Address Reconfiguration
- Needed by RFC 6525.
- RFC 6458: Sockets API Extensions for SCTP
- RFC 6525: SCTP Stream Reconfiguration
-
RFC 7496: Additional Policies for the Partially Reliable SCTP Extension
- Provides limited retransmission policy (limiting the number of retransmissions).
- RFC 8260: Stream Schedulers and User Message Interleaving for SCTP
- RFC 8261: DTLS Encapsulation of SCTP Packets