How to Use iroh from Non-Rust Languages via iroh-ffi: A Complete Guide
You can use iroh-ffi to expose iroh's encrypted peer-to-peer networking through a C-compatible API, enabling integration with Python, Go, C, and other languages via shared libraries and language-specific bindings.
The iroh library from n0-computer/iroh provides high-performance, encrypted networking primitives written in Rust. To make these capabilities available to developers working in other programming languages, the project maintains iroh-ffi, a dedicated crate that wraps the core Rust API in a stable C interface.
Architecture of iroh-ffi
The integration follows a three-layer architecture that keeps security-critical code in Rust while exposing minimal, language-agnostic interfaces.
The core layer (iroh crate) implements the full protocol stack, including QUIC transport, relay handling, DNS resolution, and the high-level Endpoint API. This code resides in iroh/src/lib.rs and iroh/src/endpoint.rs.
The FFI layer (iroh-ffi crate) provides a thin C-compatible wrapper around the core API. It translates Rust types into opaque pointers and simple structs that any language with FFI support can consume. According to the source code in iroh-ffi/src/lib.rs, this layer deliberately exposes only essential operations: creating an Endpoint, opening streams, sending/receiving bytes, and shutdown.
Language bindings consist of small glue code that loads the shared library and forwards calls to the C API. This might be extern "C" declarations in C++, ctypes definitions in Python, or cgo imports in Go.
The data flow follows this pattern:
Your Program → Language Binding → iroh-ffi (C API) → iroh Core (Rust)
Because the API uses only stable C types (pointers, integers, and byte buffers), the same shared library works across Windows, macOS, and Linux without modification.
Building the iroh-ffi Shared Library
To begin using iroh from non-Rust languages, you must first compile the FFI crate as a dynamic library.
-
Clone the iroh-ffi repository (maintained separately from the main iroh repo):
git clone https://github.com/n0-computer/iroh-ffi cd iroh-ffi -
Compile the shared library using Cargo. The
iroh-ffi/Cargo.tomldefines acdylibtarget, so the build produces platform-specific binaries:cargo build --releaseThis creates
target/release/libiroh.so(Linux),target/release/libiroh.dylib(macOS), ortarget/release/iroh.dll(Windows). -
Locate the header file at
iroh-ffi/include/iroh.h, which declares the C symbols for your target language to import.
Using iroh-ffi from C
The C integration requires including the header and linking against the shared library. The pattern follows: initialize a runtime, create an endpoint, open a bidirectional stream, then read/write data.
#include "iroh.h"
#include <string.h>
#include <stdio.h>
int main() {
// Initialize the runtime (handles async execution)
IrohRuntime *rt = iroh_runtime_new();
// Create an endpoint with default configuration
IrohEndpoint *ep = iroh_endpoint_new(rt, NULL);
// Open a bidirectional stream to a remote peer
IrohStream *stream = iroh_endpoint_open_bi(ep, "iroh://<remote_hash>");
// Send payload
const char *msg = "hello from C";
iroh_stream_write(stream, (const uint8_t *)msg, strlen(msg));
// Receive response (blocking read)
uint8_t buf[256];
size_t n = iroh_stream_read(stream, buf, sizeof(buf));
buf[n] = '\0';
printf("remote said: %s\n", buf);
// Cleanup resources
iroh_stream_close(stream);
iroh_endpoint_close(ep);
iroh_runtime_free(rt);
return 0;
}
Key functions include iroh_runtime_new() for initialization, iroh_endpoint_open_bi() for establishing connections, and iroh_stream_read()/iroh_stream_write() for data transfer.
Using iroh-ffi from Python
Python accesses the library through ctypes, loading the shared library and explicitly defining argument and return types for type safety.
import ctypes
from pathlib import Path
# Load the compiled library
lib = ctypes.CDLL(Path("target/release/libiroh.so").as_posix())
# Declare function signatures matching iroh.h
lib.iroh_runtime_new.restype = ctypes.c_void_p
lib.iroh_endpoint_new.argtypes = [ctypes.c_void_p, ctypes.c_char_p]
lib.iroh_endpoint_new.restype = ctypes.c_void_p
lib.iroh_endpoint_open_bi.argtypes = [ctypes.c_void_p, ctypes.c_char_p]
lib.iroh_endpoint_open_bi.restype = ctypes.c_void_p
lib.iroh_stream_write.argtypes = [ctypes.c_void_p,
ctypes.POINTER(ctypes.c_uint8),
ctypes.c_size_t]
lib.iroh_stream_read.argtypes = [ctypes.c_void_p,
ctypes.POINTER(ctypes.c_uint8),
ctypes.c_size_t]
lib.iroh_stream_read.restype = ctypes.c_size_t
# Initialize
rt = lib.iroh_runtime_new()
ep = lib.iroh_endpoint_new(rt, None)
# Connect
stream = lib.iroh_endpoint_open_bi(ep, b"iroh://<remote_hash>")
# Send data
msg = b"hello from Python"
buf = (ctypes.c_uint8 * len(msg)).from_buffer_copy(msg)
lib.iroh_stream_write(stream, buf, len(msg))
# Receive data
out = (ctypes.c_uint8 * 256)()
n = lib.iroh_stream_read(stream, out, 256)
print("remote said:", bytes(out[:n]).decode())
# Cleanup
lib.iroh_stream_close(stream)
lib.iroh_endpoint_close(ep)
lib.iroh_runtime_free(rt)
This approach requires manually mapping C types to Python ctypes, but provides full access to the underlying iroh_endpoint_open_bi() and stream operations without native Python extensions.
Using iroh-ffi from Go
Go utilizes cgo to import the C headers and link against the shared library, using unsafe.Pointer for buffer operations.
// #cgo LDFLAGS: -L${SRCDIR}/target/release -liroh
// #include "iroh.h"
import "C"
import (
"unsafe"
)
func main() {
// Initialize runtime
rt := C.iroh_runtime_new()
// Create endpoint (nil uses default config)
ep := C.iroh_endpoint_new(rt, nil)
// Open bidirectional stream
remote := C.CString("iroh://<remote_hash>")
defer C.free(unsafe.Pointer(remote))
stream := C.iroh_endpoint_open_bi(ep, remote)
// Send message
msg := []byte("hello from Go")
C.iroh_stream_write(stream,
(*C.uint8_t)(unsafe.Pointer(&msg[0])),
C.size_t(len(msg)))
// Read response
buf := make([]byte, 256)
n := C.iroh_stream_read(stream,
(*C.uint8_t)(unsafe.Pointer(&buf[0])),
C.size_t(len(buf)))
println("remote said:", string(buf[:n]))
// Teardown
C.iroh_stream_close(stream)
C.iroh_endpoint_close(ep)
C.iroh_runtime_free(rt)
}
The cgo directives link against libiroh while the Go code manages the lifecycle of opaque pointers returned by iroh_runtime_new() and iroh_endpoint_new().
Core API Patterns Across Languages
Regardless of language, using iroh via FFI follows a consistent lifecycle pattern:
- Initialize the runtime with
iroh_runtime_new()— this creates the async execution context required by the underlying Rust code. - Create an endpoint using
iroh_endpoint_new()— this configures the local peer identity and network stack. - Open streams via
iroh_endpoint_open_bi()— establishes bidirectional QUIC streams to remote endpoints identified by their iroh URL. - Transfer data using
iroh_stream_write()andiroh_stream_read()— both functions handle raw byte buffers and return counts of bytes processed. - Cleanup by calling
iroh_stream_close(),iroh_endpoint_close(), andiroh_runtime_free()in reverse order to prevent resource leaks.
For reference implementations demonstrating these patterns in pure Rust, consult iroh/examples/connect.rs in the main repository.
Summary
- iroh-ffi provides a C-compatible wrapper around the Rust iroh networking stack, located in
iroh-ffi/src/lib.rsandiroh-ffi/include/iroh.h. - Build the shared library using
cargo build --releaseto generatelibiroh.so,iroh.dll, orlibiroh.dylibdepending on your platform. - The API centers on three opaque types:
IrohRuntime,IrohEndpoint, andIrohStream, managed through explicit lifecycle functions. - All encryption and protocol logic remains in the Rust core (
iroh/src/endpoint.rs), ensuring security regardless of the calling language. - Integration requires only standard FFI mechanisms available in C, Python (ctypes), Go (cgo), and most other systems languages.
Frequently Asked Questions
What languages are supported by iroh-ffi?
Any language capable of calling C functions through FFI can use iroh-ffi. The repository provides tested examples for C, Python (via ctypes), and Go (via cgo), but the underlying C API in iroh-ffi/include/iroh.h enables bindings for Node.js (N-API), Java (JNI), C#, and others.
Where does the encryption logic reside when using iroh-ffi?
All cryptographic operations, QUIC handshakes, and protocol implementations remain in the core Rust library (iroh/src/lib.rs and iroh/src/endpoint.rs). The FFI layer only forwards calls to these Rust functions and translates data types, ensuring that security-critical code stays in memory-safe Rust regardless of the calling language.
How do I handle memory management when using the C API?
The API follows explicit resource management: you must call iroh_runtime_free(), iroh_endpoint_close(), and iroh_stream_close() to release resources. The opaque pointers returned by initialization functions (e.g., iroh_runtime_new()) are owned by the caller and must be freed to prevent memory leaks, as the Rust side does not automatically clean up when the calling process drops references.
Can I use iroh-ffi on Windows, macOS, and Linux?
Yes. The iroh-ffi/Cargo.toml defines a cdylib crate type that compiles to platform-specific dynamic libraries (.so, .dylib, .dll) from the same source code. Because the C API uses only primitive types and opaque pointers defined in iroh.h, the generated library is portable across all three platforms without modification to your application code.
Have a question about this repo?
These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:
curl -s "https://instagit.com/install.md" Maintain an open-source project? Get it listed too →