i2pd-sys 0.0.2

Raw FFI bindings to a minimal C shim over libi2pd (PurpleI2P/i2pd).
Documentation

i2pd-sys

Raw FFI bindings to a minimal C shim over libi2pd (PurpleI2P/i2pd), the reference C++ implementation of the I2P anonymous-networking protocol.

This crate builds a real I2P router in-process — not a client for a separately-running i2pd daemon — and exposes just enough of it (destinations and streams) through a small, stable extern "C" surface for Rust to call into safely at the ABI level.

Status: pre-1.0 (0.0.2), developed alongside and consumed by the tachyon-web workspace. The API may still change without notice.

Is this the crate you want?

This crate is 100% unsafe — raw pointers, manual lifetime management, no ownership types. Unless you specifically need the bindgen-generated FFI surface itself (e.g. you're building your own safe wrapper), you almost certainly want tachyon-i2p instead: an async, safe tokio-based wrapper built directly on top of this crate.

How it's built

build.rs does the following, entirely offline (no network access, no shelling out to configure/b2/bootstrap.sh):

  1. Compiles a trimmed zlib snapshot (vendor/zlib-src) directly via cc::Build.
  2. Builds a minimal Boost subset (vendor/boost-src, extracted from the upstream git superproject with bcp) via Boost's own CMake support.
  3. Builds AWS-LC (via the aws-lc-sys dependency) as the crypto/TLS backend, standing in for OpenSSL — nothing OpenSSL-shaped is vendored or linked.
  4. Builds libi2pd/libi2pdclient (vendor/i2pd-src, a trimmed snapshot of upstream i2pd) via its own CMake build, as static libraries only (WITH_BINARY=OFF — no daemon binary).
  5. Compiles shim/shim.cpp, a hand-written C++ file implementing the flat C ABI declared in shim/shim.h, and links everything together.
  6. Runs bindgen against shim/shim.h only — never against libi2pd's own C++ headers, which use std::shared_ptr/STL types with no stable C ABI.

Every dependency is a committed source snapshot, not a submodule or a build-time fetch, so the crate builds reproducibly and works from a crates.io tarball with no network egress at build time. See scripts/update-vendor.sh for how each snapshot was produced and how to refresh one against a newer upstream release.

Hardening: every C/C++ translation unit this crate compiles (zlib, i2pd, and the shim — all three either handle attacker-controlled network/decompression input directly or sit right next to code that does) is built with -D_FORTIFY_SOURCE=2 and -fstack-protector-strong, applied as plain compiler flags in build.rs rather than relying on i2pd's own WITH_HARDENING CMake option (which is wired up for GCC only upstream — a silent no-op under Clang).

Requirements

  • A C++17 compiler (cc/c++ on the host, found automatically).
  • CMake (used to build Boost and libi2pd).
  • Rust 1.89+ (edition 2024).

No system boost, openssl, or i2pd packages are required or used — everything is vendored and statically linked.

Cross-compilation

Native builds (including building natively inside a musl system, e.g. an Alpine container) need nothing beyond the ambient cc/c++. True cross-compilation (e.g. a glibc host targeting x86_64-unknown-linux-musl) requires a real musl C++ cross toolchain, since no C++ compiler shipped by default on a glibc distro can target musl. build.rs looks for one, in order:

  1. CC_<target> / CXX_<target> env vars (the standard cc crate convention), e.g. CXX_x86_64_unknown_linux_musl.
  2. MUSL_CROSS_TOOLCHAIN pointing at a musl-cross-make-style install (expects <dir>/bin/<target-triple>-g++).
  3. A same-named compiler already on $PATH (<target-triple>-g++).

If none is found, the build fails with an explanation rather than attempting to build a toolchain from scratch (a 20–40 minute surprise on a library crate's first build).

Usage

[dependencies]
i2pd-sys = "0.0.2"
use i2pd_sys::*;
use std::ffi::{c_void, CString};

unsafe {
    // 1. Initialize i2pd's config/crypto/router-context globals — exactly once, before
    //    anything else.
    let app_name = CString::new("my-app").unwrap();
    i2pd_init(app_name.as_ptr());
    i2pd_start();

    // 2. Create a destination (an I2P address you can send/receive on).
    let dest = i2pd_create_transient_destination();
    assert!(!dest.is_null());

    let addr = i2pd_destination_b32_address(dest);
    // ... read the `<52 chars>.b32.i2p` C string, then:
    i2pd_free_string(addr);

    // 3. Register an acceptor, then create streams to other destinations with
    //    i2pd_create_stream / i2pd_stream_send / i2pd_stream_receive.

    // 4. Tear down in reverse order.
    i2pd_destroy_destination(dest);
    i2pd_stop();
    i2pd_terminate();
}

See shim/shim.h for the full, documented API surface (lifecycle, destinations, streams), and tests/roundtrip.rs for a complete two-destination, real-network round trip.

API overview

Group Functions
Lifecycle i2pd_init, i2pd_start, i2pd_stop, i2pd_terminate
Destinations i2pd_create_transient_destination, i2pd_generate_keys, i2pd_create_persistent_destination, i2pd_destroy_destination, i2pd_destination_b32_address, i2pd_destination_ident_hash, i2pd_accept_stream
Streams i2pd_create_stream, i2pd_stream_send, i2pd_stream_receive, i2pd_stream_is_open, i2pd_stream_close, i2pd_destroy_stream
Memory i2pd_free_buffer, i2pd_free_string

Thread safety

i2pd_stream_send/i2pd_stream_receive are safe to call concurrently from different threads on the same stream (one thread sending, another receiving) — each posts work onto i2pd's internal io_service and blocks the calling thread on a condition variable, the same pattern i2pd's own SAM/BOB bridges use. The inbound-stream callback registered via i2pd_accept_stream runs on i2pd's own internal thread and must return quickly without blocking, or it stalls the event loop for every destination sharing that router.

Every shim function catches all C++ exceptions internally (try { ... } catch (...) {}), since an exception unwinding across the extern "C" boundary would be undefined behavior — failures surface as NULL/0/-1 return values instead.

Testing

cargo test -p i2pd-sys                              # build + unit tests, no network
cargo test -p i2pd-sys --test roundtrip -- --ignored --nocapture   # live I2P network round trip

The round-trip test is #[ignore]d by default: it builds real tunnels over the live I2P network between two in-process destinations, which can take several minutes.

Vendored third-party code

Component License Snapshot
i2pd BSD-3-Clause tag 2.61.0, commit 635b013
Boost (minimal subset via bcp) Boost Software License 1.0 tag boost-1.86.0
zlib zlib License 1.3.1
AWS-LC (via aws-lc-sys, not vendored directly) Apache-2.0 OR ISC

Five small compatibility patches (scripts/patches/) are applied automatically (by i2pd_snapshot in scripts/update-vendor.sh) on top of pristine upstream i2pd source. Four are #ifdef OPENSSL_IS_AWSLC-guarded, working around AWS-LC's OpenSSL-1.1.1 API-compatibility gaps (siphash, GOST curve naming, raw ChaCha20, compressed EC point decoding); those four apply equally to the fips backend below, since AWS-LC-FIPS defines the same OPENSSL_IS_AWSLC marker. The fifth is unconditional (a missing #include <openssl/hmac.h> that only matters — see the "FIPS" section below — under the fips backend's slightly different header graph). Full attribution and license text: LICENSE-THIRD-PARTY.

FIPS

By default this crate links regular AWS-LC (via aws-lc-sys). Enabling the fips feature instead (default-features = false, features = ["fips"]) swaps in the FIPS 140-3-validated AWS-LC-FIPS module via aws-lc-fips-sys instead, with no manual source changes needed on i2pd's side beyond the five patches above (four shared with the default backend, one FIPS-specific) applied automatically by scripts/update-vendor.sh.

aws-lc/fips aren't a hard-exclusive pair: if a dependent crate's own default pulls in aws-lc while something further up the dependency tree also asks for fips (e.g. a workspace-wide FIPS switch), fips silently takes priority — this crate only ever emits link directives for one backend (see build.rs), and aws-lc-sys/aws-lc-fips-sys link with per-version-prefixed symbol names, so an inert, unused copy of the other backend being compiled in alongside it is harmless. You only need default-features = false if you want to guarantee aws-lc-sys isn't compiled at all (e.g. to keep build times down).

What this does and does not get you: linking a FIPS-validated crypto module is not the same thing as a FIPS-certified application. i2pd's own protocol code hasn't been evaluated by NIST, and some of i2pd's supported algorithm choices (legacy ElGamal destinations, GOST signatures — libi2pd/Gost.cpp) aren't FIPS-approved at all; those code paths still compile and still work identically either way (they only use generic, algorithm-agnostic EC/bignum primitives, not a FIPS "service"), they just don't contribute to any compliance claim. Treat fips as "the underlying crypto library has been through FIPS 140-3 validation," not as "this application is FIPS compliant" — check with whoever owns your compliance requirement before relying on it as one.

Costs of enabling it:

  • A much larger build: aws-lc-fips-sys vendors the full AWS-LC-FIPS source tree (~53 MiB) and compiles considerably more (self-test and integrity-check infrastructure the non-FIPS module doesn't have).
  • A mandatory runtime self-check: AWS-LC-FIPS links a startup hook into every dependent binary that calls FIPS_mode() and aborts the process if the runtime library isn't actually running in FIPS mode. This is by design (it's what "FIPS-validated" requires operationally), but it's a real behavior change worth knowing about before flipping the feature on in production — a binary that built fine can still abort at startup on a host where FIPS mode isn't available.
  • Narrower pregenerated-bindings platform support than the default backend: currently {x86_64,aarch64}-unknown-linux-{gnu,musl} and {x86_64,aarch64}-apple-darwin. Other targets may still work (bindgen can generate bindings on the fly) but aren't validated here.
  • Go and Perl toolchains are used for some AWS-LC-FIPS code generation if present, falling back to committed pre-generated code if not — so, same as the rest of this crate, no new hard build-time tool requirement, just potentially slower/less-optimized codegen without them.

Verified (2026-07-23): aws-lc-fips-sys builds successfully standalone on this workspace's host target, and its compiled libcrypto exports (BoringSSL-prefix-renamed, transparently to i2pd's own source via the crate's generated headers) CRYPTO_chacha_20, EC_GROUP_new_curve_GFp, EC_POINT_set_compressed_coordinates_GFp, and FIPS_mode/BORINGSSL_integrity_test (confirming a genuine FIPS build, not a silent non-FIPS fallback). cargo build --no-default-features --features fips compiles and links all of i2pd against it successfully end-to-end (this needed the fifth patch mentioned above — the build failed with an undeclared-identifier error on plain HMAC() without it), and cargo test --no-default-features --features fips passes the same non-network test suite as the default backend, including real Ed25519/X25519 key generation and persistent-destination round-tripping through AWS-LC-FIPS.

License

Licensed under either of

at your option, in addition to the vendored third-party licenses listed above (which govern the statically-linked C/C++ code, not this crate's own Rust source) — see LICENSE-THIRD-PARTY for the full text and attribution of each.

Unless you explicitly state otherwise, any contribution intentionally submitted for inclusion in this crate, as defined in the Apache-2.0 license, shall be dual licensed as above, without any additional terms or conditions.