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.

  1. Clone the iroh-ffi repository (maintained separately from the main iroh repo):

    git clone https://github.com/n0-computer/iroh-ffi
    cd iroh-ffi
  2. Compile the shared library using Cargo. The iroh-ffi/Cargo.toml defines a cdylib target, so the build produces platform-specific binaries:

    cargo build --release

    This creates target/release/libiroh.so (Linux), target/release/libiroh.dylib (macOS), or target/release/iroh.dll (Windows).

  3. 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:

  1. Initialize the runtime with iroh_runtime_new() — this creates the async execution context required by the underlying Rust code.
  2. Create an endpoint using iroh_endpoint_new() — this configures the local peer identity and network stack.
  3. Open streams via iroh_endpoint_open_bi() — establishes bidirectional QUIC streams to remote endpoints identified by their iroh URL.
  4. Transfer data using iroh_stream_write() and iroh_stream_read() — both functions handle raw byte buffers and return counts of bytes processed.
  5. Cleanup by calling iroh_stream_close(), iroh_endpoint_close(), and iroh_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.rs and iroh-ffi/include/iroh.h.
  • Build the shared library using cargo build --release to generate libiroh.so, iroh.dll, or libiroh.dylib depending on your platform.
  • The API centers on three opaque types: IrohRuntime, IrohEndpoint, and IrohStream, 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:

Share the following with your agent to get started:
curl -s "https://instagit.com/install.md"

Works with
Claude Codex Cursor VS Code OpenClaw Any MCP Client

Maintain an open-source project? Get it listed too →