Demystifying WebRTC's Data Channel Message Size Limitations
Posted on Thu, 01 Dec 2016 in WebRTC • 6 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:
-
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.
-
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 handlesEAGAIN
. Older Firefox versions simply discard messages onEMSGSIZE
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.
Update - Fri, 03 Nov 2017
A bugfix for this issue has been shipped as part of Firefox 57. From that version on, messages of 64 KiB can be exchanged safely between Chromium and Firefox.
Update - Sun, 19 Aug 2018
Jeroen de Borst picked up the issue that has been filed for Chromium and raised the receive limit to 256 KiB. While this is hardly pushing the limit of whats possible, it is a considerable improvement. Unfortunately the violation of message integrity has not been addressed which means that messages larger than 256 KiB will still be delivered in chunks. Furthermore, Chromium does not signal the maximum message size which means that the remote peer is unable to figure out the effective maximum message size it may use to send data.
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 changes to Firefox 57 it's possible to send large application messages from and to the Firefox browser as well.