Using in-band WebRTC DCEP (ppid 50) for SDP DataChannels

Hi Forum,

Are there any, if remote, plans to support the SCTP Payload Protocol Identifier 50 (WebRTC Data Channel Establishment Protocol, DCEP) in mediasoup?

I made my research and know that as of this writing, receiving DCEP is explicitly ignored in SctpAssociation.cpp, and on the sending side it doesn’t seem to be used either.

My problem comes from writing a SFU server that should be able to open DataChannels (and/or media) with 3rd-party WebRTC-spec compliant clients. Thus, I don’t control the client at all, this is just a server-side development. With vanilla WebRTC, the official solution seemed to converge into passing DataChannel parameters in-band:

  • When the server acts as an offerer (sends an m=application SDP), clients expect a DCEP packet (DATA_CHANNEL_OPEN) that informs them about stuff like reliability, protocol, or label.

    See, to merely name an example, how a Pion client wouldn’t even progress if the first received packet is not DCEP: datachannel.go

  • Conversely, when clients act as offerer, they usually send a DCEP packet to inform the server about, most importantly, their chosen SCTP Stream ID.

Knowing that mediasoup doesn’t support these, is there any way I could “man in the middle” its recv/send loops to handle these packets myself? I think it would be quite the loophole but I don’t see much better alternatives.

Is this something supported by usrsctp library? If so, is this something that can be included in client side in the options given to transport.produceData() and consumeData()? And same question for same methods in server.

Now that I read it more carefully I think it doesn’t make sense for mediasoup. In mediasoup we only support DataChannel creation via outband negotiation.

Oh I see!

I was kind of visualizing an event emitted from the SCTP-enabled WebRtcTransport, claiming that a new in-band DataChannel info has been received. Then that info could be used to manually call produceData() on the transport (because having a DataProducer automatically created --while more similar to how WebRTC’s PeerConnection works-- would feel strange to the style of mediasoup API, I think).

And similarly, consumeData() on a WebRtcTransport might be able to send a similar packet in-band when the consumer is unpaused for the first time. Thus a connected client would receive it and its PeerConnection emit the ondatachannel event.

But alas I understand this goes against the current design and the out-of-band mode of operation that is already implemented between mediasoup server and mediasoup-client. I guess it must be based on using PeerConnection’s createDataChannel({ negotiated: true }), but that’s not possible in my case (no access to client’s code)

Will be thinking on alternatives!

I’ve been reading other mediasoup forum topics about “talking” SDP, and just had the thought that if the mediasoup-sdp-bridge idea is to be done one day, this kind of feature will be needed anyhow. Otherwise what would the bridge do, upon receiving an SDP Offer with m=application.

(in some way, this is my case actually; I work behind the SDP level, A/V works fine, but finding that there’s not much that can be done for datachannels)

This requires that:

  • A client can send a SCTP message with PPID 50 at any time and a data like this: RFC 8832 - WebRTC Data Channel Establishment Protocol
  • mediasoup should react on that message with PPID 50 by parsing the data as per that message format.
  • Then it should notify Node/Rust layer with a new “inbanddatachannel” notification that should contain all the parsed info (streamId, reliable, label, protocol, ordered, unordered, delivery attemots, timeout attempts, etc).
  • Node/Rust app can then decide to create a DataProducer with that info.
  • Node/Rust app can then dataConsume() it so SCTP messages received in that SCTP streamId are forwarded to clients. This last part is unclear to me. The app should call transport.consumeData({ dataProducerId }) in mediasoup side as usual but it may decide to signal the info of the created DataConsumer to receivers (so negotiated datachannels as usual) or may decide to send the datachannel info via SCTP (with PPID 50) to receivers that are pure SDP clients.
  • Problem I see here is that mediasoup doesn’t deal with bidirectional streamId (a DC streamId can be used to send or to receive, but both, although I may be totally wrong and such a limitation only exists in mediasoup-client, probably).

if the mediasoup-sdp-bridge idea is to be done one day

The idea behind having a library that makes it possible for pure SDP clients to interact with mediasoup was not that we should make tons of changes in mediasoup to allow creating things without server side consent. I understand your point: Clients that are just capable of sending a SDP cannot indicate (within that SDP) anything about DataChannel streams since that info is not in the SDP (even if the DataChannel is negotiated).

If you can confirm/fix/ellaborate steps above, please create a feature request in GH, although no idea when we can address this. Honestly it sounds more like a feature for mediasoup v4 which is not even started and for which we already have many huge tasks planned.

That’s it. The SCTP Association is internally owned by the mediasoup Transport, so the most logical place to have such event is in the SCTP-enabled transports.

This is a very WebRTC-specific feature, so maybe it makes sense only as a WebRtcTransport event. But I can visualize an app wanting to build these packets on its own so maybe any SCTP-enabled transport should be allowed to have it (something to think more about):

sctpTransport.on("inbanddatachannel", fn(dcParams: DataChannelParameters))

// Or maybe just "datachannel", the same name used by WebRTC.
sctpTransport.on("datachannel", fn(dcParams: DataChannelParameters))

with DataChannelParameters being very similar to DataProducerOptions:

type DataChannelParameters = {
    sctpStreamParameters: SctpStreamParameters;
    label: string;
    protocol: string;
};

Yes:

const dataProducer = await sctpTransport.produceData({
    sctpStreamParameters: dcParams.sctpStreamParameters,
    label: dcParams.label,
    protocol: dcParams.protocol,
});

Correct. Same as the official WebRTC API allowing to distinguish between out- or in-band with the negotiated boolean, I believe it would make sense a similar toggle here.

DataConsumerOptions would have an inband (if you like that name more than “negotiated”, which admitely is a bit confusing) so the call could look like this:

const dataConsumer = await sctpTransport.consumeData({
    dataProducerId: "...",
    inband: true,
});

// Or `inbandNotify` or `inbandDcep` to be more technical, or any variation.

This, of course, would be one of those parameters that are documented as “Just if consuming over SCTP”. The effect of true is to tell mediasoup that it needs to send a WebRTC DCEP packet to the remote side on its Stream ID.

I’m currently having a separate issue with this, but will talk in a different topic to keep things separate.

EDIT: Done – Need to specify DataConsumer SCTP StreamId

The thing is, mediasoup is 99% already there in being able to work as a fully standalone WebRTC server! :slight_smile:

Was not for this specific thing, needing collaboration from a helper client (like mediasoup-client) would really be just a nice-to-have, not a strict requirement. And that’s quite a good property for the server, I think.

Things work fine if the server is supplemented with an SDP-ORTC translation library. That’s a solved problem, it’s easy to convert m= sections into lists of codecs. But a support library only goes so far without help from the server for core protocol features. For stuff that WebRTC specs decided must be in-band, no library can “polyfill” anything, such as DTLS (no other way to do it) or… in-band DataChannels.


I’m working on a dirty “let’s break everything” solution to verify the feasibility of doing all this. Will report when I know more.