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.1), developed alongside and consumed by thetachyon-webworkspace. 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):
- Compiles a trimmed zlib snapshot (
vendor/zlib-src) directly viacc::Build. - Builds a minimal Boost subset (
vendor/boost-src, extracted from the upstream git superproject withbcp) via Boost's own CMake support. - Builds AWS-LC (via the
aws-lc-sysdependency) as the crypto/TLS backend, standing in for OpenSSL — nothing OpenSSL-shaped is vendored or linked. - 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). - Compiles
shim/shim.cpp, a hand-written C++ file implementing the flat C ABI declared inshim/shim.h, and links everything together. - Runs
bindgenagainstshim/shim.honly — never againstlibi2pd's own C++ headers, which usestd::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:
CC_<target>/CXX_<target>env vars (the standardcccrate convention), e.g.CXX_x86_64_unknown_linux_musl.MUSL_CROSS_TOOLCHAINpointing at amusl-cross-make-style install (expects<dir>/bin/<target-triple>-g++).- 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
[]
= "0.0.1"
use *;
use ;
unsafe
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
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 (mutually exclusive with the default aws-lc feature —
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.
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-sysvendors 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 (bindgencan 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.