# libjuice: a dependency-free C library for ICE over UDP

> libjuice implements enough of the Interactive Connectivity Establishment protocol to open bidirectional UDP streams through a NAT, in C with no dependencies, for Linux, macOS, Windows, Android, FreeBSD and iOS. It is deliberately partial, and the README is honest about exactly which parts it leaves out.

**paullouisageneau/libjuice** — JUICE is a UDP Interactive Connectivity Establishment library

- Repository: https://github.com/paullouisageneau/libjuice
- Stars: 633 · Forks: 143
- Language: C
- License: MPL-2.0
- Published: 2026-10-22 · Updated: 2026-10-22 · Language: en
- Canonical page: https://hysenlabs.com/projects/paullouisageneau-libjuice

## ICE, reduced to the part that works over UDP

The README describes libjuice as a UDP Interactive Connectivity Establishment library that allows opening bidirectional User Datagram Protocol streams with Network Address Translator traversal. It is a simplified implementation of the ICE protocol, both client-side and server-side, written in C without dependencies for multiple platforms, including GNU/Linux, Android, FreeBSD, Apple macOS, iOS and Microsoft Windows.

The problem being solved is the one every peer-to-peer application hits. Two hosts behind NATs cannot simply send UDP packets to each other, because the NATs will not forward traffic to an address that has not been punched open. ICE is the procedure for finding a path that works: gather candidate addresses, try to reach them, and keep the ones that respond.

What makes libjuice's framing interesting is the last sentence of that paragraph: the client supports only a single component over UDP per session in a standard single-gateway network topology, and the README gives the reason, that this should be sufficient for the majority of use cases nowadays.

So the design decision is to implement the common case completely and let the rest go. For a data channel or a multiplexed media stream, one component over UDP is enough, and that is a large fraction of what people actually want from ICE.

## The RFC list is also the limit list

The Compatibility section is the most useful part of the documentation, because it names both what is implemented and what is not.

The implemented list is a simplified full ICE agent, RFC 5245 then RFC 8445, with STUN from RFC 5389 then RFC 8489, TURN relaying from RFC 5766 then RFC 8656, TCP candidates from RFC 6544, ICE consent freshness from RFC 7675, ICE patiently awaiting connectivity from RFC 8863, an SDP-based interface from RFC 8839, IPv4 and IPv6 dual-stack support, and optional multiplexing on a single UDP port.

Then the limitations are stated just as plainly. Only UDP is supported as the transport protocol and other protocols are ignored. Only one component is supported, which the README says is sufficient for WebRTC Data Channels and multiplexed RTP+RTCP. And only RFC 8828 mode 2 is supported, which is the default route plus all local addresses.

The pattern of successive RFCs in that list is the protocol maturing: STUN from 5389 to 8489, TURN from 5766 to 8656, ICE from 5245 to 8445. A library that tracks those transitions is one that has to be updated as the documents are revised, and the pairing of an implemented list with an explicit limits list is what lets you decide quickly whether the gap between the two matters for your use case.

## A STUN and TURN server comes with it, and can be removed

Alongside the ICE agent, libjuice implements a lightweight STUN and TURN server, under RFC 8489 and RFC 8656. That is an unusual thing for a client library to carry, and the reason is the test and demo story: you need something on the other end to test against.

The server is compile-time optional. The `NO_SERVER` flag disables it, and the Makefile shows how the switch is wired:

```
NO_SERVER ?= 0
ifneq ($(NO_SERVER), 0)
        CFLAGS+=-DNO_SERVER
endif
```

The same `?=` pattern appears for `USE_NETTLE` and for `FORCE_M32`, so all three are set the same way, either as a make variable or as a CMake option.

For a production deployment, the README points at a separate project, Violet, described as a STUN/TURN server application based on libjuice. So the split is clear: libjuice gives you the building blocks and a small server for testing, and a dedicated application exists if you want to run the server side seriously.

That arrangement is what keeps the library honest. The embedded server is small, which is why it is fine for tests, and the fact that a separate application is recommended for real deployments is the README telling you where the boundary is.

## No dependencies, with Nettle as an opt-in

The Dependencies section of the README contains one word, None, followed by a single optional dependency.

Optionally, Nettle can provide the SHA1 and SHA256 algorithms instead of the internal implementation. So the library ships its own hashing, and the only way to introduce an external dependency is to ask for it deliberately.

That is a meaningful property for a networking library, because a UDP hole punching agent sits on exactly the kind of platform where adding a dependency is expensive. A router vendor, an embedded automotive target or a mobile application all have constraints on what they can link, and a library that needs nothing but the C library and the platform's sockets is a library that can go there.

The build switch is a one-liner on either build system. With CMake:

```bash
$ cmake -B build -DUSE_NETTLE=1
```

and with the plain Makefile on Linux, the equivalent is `make USE_NETTLE=1`.

The Makefile shows what the option actually does, and it is more than a define:

```
USE_NETTLE ?= 0
ifneq ($(USE_NETTLE), 0)
        CFLAGS+=-DUSE_NETTLE=1
        LIBS+=nettle
else
        CFLAGS+=-DUSE_NETTLE=0
endif
```

The compile flag selects the implementation and the library is added to the link line, with the pkg-config flags pulled in for Nettle when it is enabled.

## Three toolchains, two library flavours

The build section covers the platforms separately, and the differences are practical rather than cosmetic.

On POSIX-compliant systems, including Linux and Apple macOS, the sequence is to configure with cmake, enter the build directory and compile:

```bash
$ cmake -B build
$ cd build
$ make -j2
```

For Windows with MinGW cross-compilation, the same three steps are used with a toolchain file passed to cmake, and for Windows with Microsoft Visual C++ the generator is NMake Makefiles and the build is driven by nmake.

The CMake targets are `libjuice` and `libjuice-static` for the shared and static libraries, the default target builds the library and the tests, and the targets are exported under the namespace `LibJuice::LibJuice` and `LibJuice::LibJuiceStatic` so another CMake project can link against them with find_package.

There is also a plain Makefile path for Linux only, where `make` alone is enough, and the same USE_NETTLE option applies there.

The choice between shared and static matters more than it sounds for a NAT traversal library, because the agent has to own the UDP socket it is punching through. A static build avoids any question of two copies of the library in one process, which for a library that runs a STUN server and a client in the same binary is not a hypothetical.

## The Makefile is the switchboard for every build option

The plain Makefile is small enough to read in one pass, and it doubles as the documentation of the build options.

The compiler flags are worth noting. `CFLAGS=-O2 -pthread -fPIC -fvisibility=hidden` hides the symbols by default, and the export macro is added separately with `CFLAGS+=-DJUICE_EXPORTS`, which is the usual arrangement for a library that wants a small public surface. The `FORCE_M32` switch adds `-m32` to both the compile and link flags for a 32-bit build.

The targets are the three you would expect. The static library is built with `ar`, the shared library with the compiler and `-shared`, and the test binary links the test objects against the static archive:

```
tests: $(NAME).a $(TEST_OBJS)
	$(CC) $(LDFLAGS) -o $@ $(TEST_OBJS) $(LDLIBS) $(NAME).a
```

Everything else is derived from the source lists, which are globbed from `src/*.c` and `test/*.c`, with the dependency files included so that a change to a header triggers a rebuild.

For the examples, the README points at two files in the test directory, `test/connectivity.c` for a complete local connection example and `test/server.c` for a server example, so the shortest path to understanding the API is to read a working client rather than the headers.

## MPL-2.0, packaged widely, and released steadily

The licence is MPL 2.0, with the LICENSE file at the repository root and a matching badge in the README. The MPL is a file-level copyleft, which for a C library that many vendors will link into a larger product is a middle path between permissive and strongly copyleft, and the choice matters for how you redistribute.

The library is packaged for several distributions. The README says it is available on AUR and on vcpkg, and the badges show repology tracking the packaged versions, which is the usual way to tell whether a C library has made it into the distributions people actually use. Bindings are available for Rust, in the juice-rs repository, which is a sign of how the library is used in practice given that the core is C.

The release line is busy. The recent versions are v1.7.4 from 2026-09-26, v1.7.3 from 2026-08-07 and v1.7.2 from 2026-05-27, and the repository is not archived with the last push on 2026-10-06. So the library is on a 1.7 line and still moving.

The repository layout backs the portability claim, with a CMakeLists.txt and a cmake directory, a Makefile for the direct path, a src directory, an include directory, a test directory, a fuzzer directory, and the .clang-format and .clang-tidy configuration files that keep the style consistent across a codebase that has to compile cleanly on six platforms.

## Conclusion

libjuice fits a C or Rust project that needs to punch through a NAT for a single UDP flow, on a platform where pulling in a full WebRTC stack is not an option, and that can accept a deliberately partial ICE implementation. It is the wrong choice if you need TCP candidates in anger, multiple components, or a full standards-compliant agent, since the README lists those as limitations. Verify first which of the listed RFCs your peer expects, and check that the single-component and UDP-only constraints hold for your use case, because a peer that negotiates differently will not connect at all.

## FAQ

### What is libjuice used for?

libjuice is a UDP Interactive Connectivity Establishment library written in C that allows opening bidirectional UDP streams with NAT traversal. It is a simplified implementation of the ICE protocol, client-side and server-side, with no dependencies, for GNU/Linux, Android, FreeBSD, macOS, iOS and Windows.

### What are the limitations of libjuice compared to a full ICE agent?

Only UDP is supported as the transport protocol and other protocols are ignored, only one component is supported, which the README says is sufficient for WebRTC Data Channels and multiplexed RTP+RTCP, and only RFC 8828 mode 2 is supported, the default route plus all local addresses.

### Does libjuice need any dependencies?

The README states the dependencies as None. Nettle can optionally provide the SHA1 and SHA256 algorithms instead of the internal implementation, enabled with the USE_NETTLE option in either build system, and the embedded STUN and TURN server can be disabled at compile time with the NO_SERVER flag.

### How do I build libjuice on Linux?

With CMake, by running cmake -B build, changing into the build directory and running make -j2, or with the plain Makefile path on Linux with make alone. The CMake targets are libjuice and libjuice-static, exported as LibJuice::LibJuice and LibJuice::LibJuiceStatic.

### Where can I get libjuice from, and what licence is it under?

It is licensed under MPL 2.0, and the README says it is available on AUR and on vcpkg. Rust bindings are available separately in the juice-rs repository, and a STUN and TURN server application built on libjuice is the separate Violet project.

## Sources

- [Issues](https://github.com/paullouisageneau/libjuice/issues)
- [License: MPL-2.0](https://github.com/paullouisageneau/libjuice/blob/master/LICENSE)
- [paullouisageneau/libjuice on GitHub](https://github.com/paullouisageneau/libjuice)
- [README](https://github.com/paullouisageneau/libjuice/blob/master/README.md)
- [Releases](https://github.com/paullouisageneau/libjuice/releases)

---

Hysen Labs editorial analysis, written from the project's own repository and release notes. Cite the canonical page: https://hysenlabs.com/projects/paullouisageneau-libjuice
