libdatachannel: five submodules, three TLS backends and three build systems
C/C++ WebRTC network library featuring Data Channels, Media Transport, and WebSockets
At a glance
- What is it?
- A C++ implementation of WebRTC data channels, media transport and WebSockets with C bindings, offered as an alternative to Google's reference library. The build surface is where the detail lives: five dependencies arrive as submodules, the security backend is a make variable that refuses two values at once, and media and WebSocket support are compile time switches that default on.
- Who is it for?
- libdatachannel is a serious piece of infrastructure for anyone who needs peer to peer connectivity from native code to a browser without pulling in the full WebRTC stack, and its interface deliberately mirrors the browser JavaScript APIs so the same design works in both places.
- Can I use it commercially?
- Yes, with conditions. MPL-2.0 is a weak copyleft licence: you can use it inside commercial and closed-source software, but if you distribute changes to its own files, you must publish those changes under the same licence.
- Is it still maintained?
- Yes. The repository last received commits 11 days ago.
- What is it written in?
- Mainly C++, according to GitHub's language statistics.
Answers come from the project's GitHub data, last synced on November 13, 2026, and from our analysis. They are not legal advice.
Star history
- paullouisageneau/libdatachannel2,784 ★ · +26
| Repository | 2026-10-04 | 2026-11-13 |
|---|---|---|
| paullouisageneau/libdatachannel | 2.8k | 2.8k |
26 Stars in the last 40 days, measured from the daily GitHub snapshot. Compare with other projects
Editorial analysis
Five of the six dependencies arrive as submodules
The dependency list is short and specific, and four of the entries carry the note that they come in as submodules by default: usrsctp, plog, libjuice and libsrtp, with nlohmann JSON also marked as a submodule. Only the security library, chosen as GnuTLS, Mbed TLS or OpenSSL, is expected from the system. A `.gitmodules` file sits at the repository root, so a plain clone leaves those directories empty and the build has nothing to link. Two of the submodules are conditional in practice. libsrtp is required only if the library is compiled with media support, and nlohmann JSON is required only to build the examples, so a data channel only consumer has a shorter list than the one printed in the README. The ICE backend is the one genuine either or: libjuice, described as the author's ad-hoc ICE library, is the default submodule, while libnice from the freedesktop project replaces it when selected. libnice brings its own three pkg-config names into the link line, so choosing it swaps one connectivity stack for another rather than adding to what is already there.
The TLS backend is a make variable, and setting two of them is the only error the build prints
The Makefile picks the security layer with two variables that both default to zero:
USE_GNUTLS ?= 0
USE_MBEDTLS ?= 0
ifneq ($(USE_GNUTLS), 0)
ifneq ($(USE_MBEDTLS), 0)
$(error Both USE_MBEDTLS and USE_GNUTLS cannot be enabled at the same time)
endifLeft alone, the third branch wins and OpenSSL is linked, which is why an unconfigured build works on a machine with OpenSSL and fails on one without. GnuTLS adds `gnutls` to the library list, Mbed TLS adds `mbedtls`, and the OpenSSL branch additionally passes `--enable-openssl` to the bundled libsrtp configure step. One dependency appears in only one combination: when GnuTLS is selected together with the default libjuice backend, `nettle` is added to the libraries, so switching TLS backend silently changes the set of system libraries pkg-config is asked for. Every system library on those lines is resolved with pkg-config, which is why the recipe shells out for both cflags and libs at once.
Media and WebSocket support are compiled in unless you ask for them not to be
Three switches decide how much of the library exists in your binary. `NO_MEDIA ?= 0` means media is on by default and defines `RTC_ENABLE_MEDIA=1`; setting it defines `RTC_ENABLE_MEDIA=0` instead and drops both libsrtp and its include path, which is the switch to reach for when you only want data channels. `USE_SYSTEM_SRTP ?= 0` chooses between the submodule copy and a system library, defining `RTC_SYSTEM_SRTP=1` and adding `srtp` when the system copy is used, and `NO_WEBSOCKET ?= 0` gates `RTC_ENABLE_WEBSOCKET` the same way. `RTC_EXPORTS` is appended unconditionally, which is a symbol visibility decision baked into the flags rather than left to the build system. The bundled pieces are static: `libusrsctp.a` is always in the local library list, and `libjuice.a` joins it whenever the default ICE backend is in use, so the submodule builds end up linked into the result rather than resolved from the system. Underneath those switches sits the protocol stack the library implements: SCTP based data channels as RFC8831, SRTP based media transport as RFC8834, DTLS over UDP per RFC7350 and RFC8261, and ICE per RFC8445 with STUN per RFC8489. Media transport is the only one of those that NO_MEDIA removes, which is why a data channel only build keeps DTLS and ICE, the two layers a channel needs for encryption and for connectivity.
Three build systems sit in one tree, and one of them targets C++17 by hand
The repository root carries a `CMakeLists.txt`, a `Makefile` and a `Jamfile`, plus a `BUILDING.md` that the README points at for build instructions, a `.travis.yml` from the era when that was the CI, a `.clang-format` and an `.editorconfig`. The hand written Makefile is the one with the detail visible. It sets `CXX=$(CROSS)g++`, so cross compilation works by setting a prefix rather than by a separate toolchain file, and it compiles with `-std=c++17` together with `-O2 -pthread -fPIC -Wall`. Source files are collected by a shell glob over `src/*.cpp` and `src/impl/*.cpp` and turned into object names by substitution, and the static pieces are built from subdirectories under `deps/`: usrsctp at `deps/usrsctp`, libsrtp at `deps/libsrtp`, libjuice at `deps/libjuice` and plog at `deps/plog`. The include list mixes those dependency roots with the project's own `src` and `include` directories. The object list is not written out: sources are collected by a shell glob over `src/*.cpp` and `src/impl/*.cpp` and turned into object names by substitution, so a new file is picked up without editing the Makefile and compile order comes from the glob rather than from declared dependencies. A `TEST_SRCS` variable is defined as well, and the tree carries a `test/` directory, so the hand written build knows about the suite even though the recipes shown stop before the test targets.
The licence changed from LGPL to MPL at version 0.18
libdatachannel has been licensed MPL 2.0 since version 0.18, and the README says plainly that previous versions were licensed LGPLv2.1 or later. That is a change in the kind of obligation attached to the code rather than a cosmetic edit: an LGPL library can be replaced as a unit, while MPL obligations attach at the level of individual files, which is the kind of detail a distributor needs before vendoring a specific version. The bundled examples are covered too, since the signalling server in the examples directory is stated to be under MPL 2.0. Distribution runs through the usual packaging channels: an Arch User Repository package, vcpkg, conan and the FreeBSD ports tree, with bindings published separately for Rust on crates.io and for Node.js on npm. The release numbering is still pre 1.0, at v0.24.6 published on 2026-09-26, three days after the last push to the default branch, which is named master rather than main. The spacing below it is uneven: v0.24.4 and v0.24.5 landed four days apart in June 2026, and the next release waited more than three months, so the patch level in a lockfile is a weak predictor of when a fix became available.
Messages arrive as a variant, and the two examples discriminate it differently
Every payload callback in the interface takes the same union type, so each handler has to decide what it received before touching it:
dc->onMessage([](std::variant<rtc::binary, rtc::string> message) {
if (std::holds_alternative<rtc::string>(message)) {
std::cout << "Received: " << get<rtc::string>(message) << std::endl;
}
});The data channel example tests with `std::holds_alternative` and then extracts with an unqualified `get`, while the WebSocket example further down tests the same way and extracts with `std::get`. Both compile where `using namespace std` is in effect, and the difference only matters if you copy one into a translation unit that is not. The shape of the interface otherwise follows the browser: `pc.onDataChannel` receives an incoming channel as a shared pointer, `dc->send("Hello world!")` writes to it, `onOpen` and `onStateChange` cover the lifecycle, and a WebSocket is constructed directly and opened with `ws.open("wss://my.websocket/service")` rather than being attached to a peer connection.
The sample hardcodes a public STUN server, and the signaling block stops mid-lambda
The first example configures ICE with a literal server address:
rtc::Configuration config;
config.iceServers.emplace_back("mystunserver.org:3478");Nothing in the sample explains the choice or points at configuration for your own infrastructure, so a reader who copies it verbatim sends its connection attempts to somebody else's STUN server. The signalling itself is left as placeholders rather than implemented: `onLocalDescription` calls `MY_SEND_DESCRIPTION_TO_REMOTE`, `onLocalCandidate` calls `MY_SEND_CANDIDATE_TO_REMOTE` with the candidate and its mid, and the receiving half uses matching `MY_ON_RECV_...` macros. The candidate callback is cut off partway through its lambda. Four signalling server examples exist for Node.js, Python, Qt and Rust, alongside a media SFU example, and the protocol stack listing ends mid-word at ICE with STUN and its extensions, so the WebSocket protocol details sit below the point where the text stops. Compatibility is claimed with Firefox, Chromium and Safari, and with other WebRTC implementations through a separate interoperability test project, while code using only data channels and WebSockets is said to compile unchanged to WebAssembly through a companion project, which is the route to take when the peer is a browser and the native binary is not wanted.
Editorial conclusion
libdatachannel is a serious piece of infrastructure for anyone who needs peer to peer connectivity from native code to a browser without pulling in the full WebRTC stack, and its interface deliberately mirrors the browser JavaScript APIs so the same design works in both places. Read the build configuration before anything else, because the shape of the checkout decides what compiles: five of the six listed dependencies are submodules, the security backend has to be selected deliberately or you get OpenSSL, and media and WebSocket support are compiled in unless you switch them off. Two details deserve a decision rather than an assumption. The licence changed from LGPL to MPL at version 0.18, which is a per file obligation rather than a per library one and matters if you vendor it. And the example configuration hardcodes a public STUN server, so anyone copying the sample sends its own connection attempts to a third party until that line is changed.
Frequently asked questions
What is libdatachannel used for?
It is a standalone C++ implementation of WebRTC Data Channels, WebRTC Media Transport and WebSockets with C bindings, whose interface mirrors the browser JavaScript APIs. The stated goal is direct connectivity between native applications and web browsers without importing Google's reference library.
Which security backends can libdatachannel be compiled against?
GnuTLS, Mbed TLS or OpenSSL. The build variables USE_GNUTLS and USE_MBEDTLS both default to 0, which leaves OpenSSL linked by default, and enabling both at once stops the build with an error naming the two variables.
Which platforms and package managers does libdatachannel cover?
GNU/Linux, Android, FreeBSD, Apple macOS, iOS and Microsoft Windows, with media transport optional at compile time. It is packaged on AUR, vcpkg, conan and the FreeBSD ports tree, and bindings exist for Rust and Node.js.
What licence is libdatachannel released under?
MPL 2.0 since version 0.18, and the README notes that earlier versions were licensed LGPLv2.1 or later. The bundled examples, including the signalling server, are also under MPL 2.0.
How is libdatachannel built?
The README points at BUILDING.md, and the tree carries three build systems: CMakeLists.txt, a hand written Makefile and a Jamfile. That Makefile compiles with -std=c++17, honours a CROSS prefix on the compiler, and expects usrsctp, libsrtp, libjuice and plog under deps/ as submodules.
Official sources
Add this badge to your README
If you maintain this project, the badge below links readers to this analysis and shows its maintenance status from the daily GitHub snapshot. Paste the markdown into your README; add ?metric=license or ?metric=stars to the image URL for a different field.
[](https://hysenlabs.com/projects/paullouisageneau-libdatachannel)Star history chart for your README
[](https://hysenlabs.com/projects/paullouisageneau-libdatachannel)Was this useful?