Demystifying WebRTC's Data Channel Message Size Limitations

Posted on Thu, 01 Dec 2016 in WebRTC • 5 min read

Many application developers who utilise WebRTC data channel know that there is a message size limitation. Some have experimentally tried to find the maximum message size. However, it seems that noone has ever looked deep enough to find out why that message size limitation exists in the first place and why sending messages from Firefox to Chromium in particular dictates a bizarrely low message size. So, I've read through most of Firefox's and Chromium's SCTP data channel implementations. And here are my results.

Chromium

Chromium unintentionally fragments messages. Rather than implementing fragmentation/reassembly on the (now deprecated) SCTP PPID (payload protocol identifier) level Chromium simply passes the data as is to the usrsctp (the SCTP implementation used by both Chromium and Firefox) stack. Now, behaviour depends on usrsctp: When a message is greater than 256 KiB (which is usrsctp's default maximum buffer size), Chromium simply closes the data channel, because usrsctp returns EMSGSIZE when calling usrsctp_sendv which the Chromium code is not able to handle. It simply closes the data channel in that case. Messages up to 256 KiB are split up into very small packets by usrsctp. These packets are then encrypted and sent on the DTLS layer to the recipient.

On the receiver side, usrsctp buffers the small chunks until the buffer reaches the partial delivery point (which is ~64 KiB at default). It then passes the still incomplete message to the browser. Chromium is supposed to buffer the message and wait for the EOR (end of record) flag which indicates that a message is complete. However, Chromium ignores that flag and simply delivers the incomplete message to the user application, violating the message-oriented principle.

Furthermore, Chromium does not check the return code of usrsctp_sendv for EAGAIN which means that sending a data channel message via Chromium can potentially lead to arbitrarily dropped packets when the internal buffer of usrsctp is full. Theoretically this means that you do not know how much data you can reliably send. However, I've not encountered this particular type of error so far, so there's probably enough time for usrsctp between send calls to deliver buffered data and clear the buffers. Still, be aware that this might be an issue.

Firefox

Current Firefox versions have two modes for sending data:

  1. If the data channel is reliable and ordered, Firefox uses a deprecated fragmentation/reassembly on SCTP PPID level. In this mode, Firefox can send an unlimited amount of data to another Firefox browser (or someone who has also implemented that deprecated fragmentation and reassembly mechanism). This has an unintended side-effect for Chromium browsers explained in the following section.

  2. If the data channel is not reliable or not ordered, Firefox behaves the same as Chromium, including the EOR bug. However, Firefox does not close the data channel on EMSGSIZE and also handles EAGAIN. Older Firefox versions simply discard messages on EMSGSIZE without raising an error event at all (ouch!). Read the Chromium section above for details on the EOR bug.

Demystifying the 16 KiB Firefox to Chromium Message Size Limit

First of all, this only applies to ordered and reliable channels! For unordered or unreliable channels, the limit remains 64 KiB.

To demistify why Firefox cannot send more than 16 KiB to Chromium as stated here, you'll have to understand that Chromium does not implement the deprecated fragmentation/reassembly implemented by Firefox for ordered and reliable channels. As Firefox fragments the messages into 16 KiB chunks, it sends the first chunks with a different SCTP PPID (52 for Binary Partial or 54 for String Partial) than the last one (53 for Binary or 51 for String). However, Chromium uses the same handling mechanism for packets with PPID 52 and 54 or 53 and 51, so it receives the packets one by one as sent by Firefox. Thus, the application gets 16 KiB messages because that's the chunk size Firefox uses to fragment the message. Chromium just does not reassemble it.

Can We Fix It?

Well, yes, but we'll have to be patient. The following subsections indicate what browser vendors have to do to get rid of the size limitation or at least increase the limit.

> 16 KiB

Either Firefox needs to drop support for the already deprecated fragmentation/reassembly at SCTP PPID level or Chromium needs to implement it. This is particularly annoying because it's not as easy to fix as the latter one but remains the lowest common denominator for Firefox and Chromium interoperation until it has been resolved.

> 64 KiB

Both Firefox and Chromium have to obey the EOR flag to receive messages as they were sent. This is an easy fix.

> 256 KiB

While the browsers could simply increase the buffer of usrsctp, this is not a good idea. A better solution would be to use the EOR flag when passing messages to usrsctp via a call to usrsctp_sendv. Again, should be an easy fix.

But there's another problem and this is most probably the reason why it hasn't been done yet: A very big data channel message would lead to head-of-line blocking for messages sent by other data channels. To solve that problem, the SCTP ndata specification has been created. Now, browser vendors wait until it is stable before it's being activated in the browsers. This will take a bit of time.

Contribution

Thanks to Michael Tüxen for answering my questions regarding usrsctp.

TL;DR, Gimme Instructions!

My tests and the code of both Chromium and Firefox indicate that it's safe to send 64 KiB of data between current versions of Firefox and Chromium unless you're sending from Firefox to Chromium on an ordered and reliable data channel where 16 KiB is the maximum message size. You'll have to fragment and reassemble on application level.

For full support for both reliable and unreliable, ordered and unordered channels, use the SaltyRTC's chunking specification which has been implemented for JavaScript and Java. Set the chunk size to 16384 if you want to be safe. Otherwise, use a chunk size of 65536 in case you're sure that there'll never be a Firefox browser talking to a Chromium browser. You could also implement a detection mechanism for that. If you're doing that, the maximum message size needs to be distributed to the other peer because the problem occurs on the receiver's side.

And while you're at it, consider using SaltyRTC for seriously secure signalling to protect your WebRTC peer-to-peer connection.

Update - Thu, 30 Jul 2017

The bug has been filed as bug 979417 for Firefox and as issue 7774 for Chromium.

RAWRTC

There's a standalone WebRTC data channel implementation called RAWRTC written by me in C that you can use to send and receive messages of arbitrary size. Browser interoperation works great as well. With the imminent changes to Firefox, which I will describe in an upcoming blog post, it's possible to send large application messages from and to the Firefox browser as well.

Check RAWRTC out on GitHub!