diff --git a/build.zig b/build.zig index 8e4235f..6bf4b72 100644 --- a/build.zig +++ b/build.zig @@ -15,12 +15,40 @@ pub fn build(b: *std.Build) void { const is_macos = resolved_os == .macos; const is_freebsd = resolved_os == .freebsd; - // Build option: disable libsodium linkage (for cross-compilation CI where - // the target arch's libsodium is not available). Falls back to std.crypto. - const no_sodium = b.option(bool, "no-sodium", "Disable libsodium linkage (use std.crypto)") orelse false; - - // Platforms that never use libsodium - const skip_sodium = no_sodium or is_android or is_macos or is_ios or is_freebsd; + // ─── Crypto backend selection (meshguard#102) ─── + // libsodium is an OPTIONAL accelerator (AVX2 ChaCha20-Poly1305 on Linux), + // not a required dependency. std.crypto is the portable fallback. + // auto → libsodium on Linux desktop (non-Android), std.crypto elsewhere + // std → std.crypto everywhere (no libsodium link) + // sodium → force libsodium (link must be available for the target) + const CryptoBackend = enum { auto, std, sodium }; + const crypto_backend_opt = b.option(CryptoBackend, "crypto-backend", "Crypto backend: auto|std|sodium (default auto)"); + // Back-compat alias: -Dno-sodium=true is equivalent to -Dcrypto-backend=std. + const no_sodium = b.option(bool, "no-sodium", "Alias for -Dcrypto-backend=std") orelse false; + + // Fail fast on contradictory flags rather than silently letting one win. + if (no_sodium and (crypto_backend_opt orelse .std) == .sodium) { + std.debug.print("error: -Dno-sodium=true conflicts with -Dcrypto-backend=sodium\n" ++ + " (-Dno-sodium is an alias for -Dcrypto-backend=std)\n", .{}); + std.process.exit(1); + } + const crypto_backend = crypto_backend_opt orelse .auto; + + // libsodium is only auto-selected on Linux desktop (non-Android), where the + // vendored/system .so provides the AVX2 assembly. Everywhere else → std.crypto. + const auto_libsodium = (resolved_os == .linux and resolved_abi != .android); + const use_libsodium = if (no_sodium) false else switch (crypto_backend) { + .std => false, + .sodium => true, + .auto => auto_libsodium, + }; + + // Exposed to source via @import("build_options") so tunnel.zig / main.zig + // select the same backend the linker is wired for. Without this the source + // chose the backend from builtin.os.tag alone and ignored the build option. + const build_options = b.addOptions(); + build_options.addOption(bool, "use_libsodium", use_libsodium); + const build_options_mod = build_options.createModule(); // ─── FFI module (for mobile embedding — not built on Windows) ─── if (!is_windows) { @@ -31,6 +59,7 @@ pub fn build(b: *std.Build) void { .link_libc = true, .strip = if (is_android) true else null, }); + ffi_mod.addImport("build_options", build_options_mod); const ffi_lib = b.addLibrary(.{ .name = "meshguard-ffi", @@ -39,9 +68,9 @@ pub fn build(b: *std.Build) void { .linkage = if (is_ios) .static else .dynamic, }); - // Link libsodium on Linux desktop targets for AVX2-accelerated crypto. - // On Android, macOS, iOS, FreeBSD, and Windows, the Zig std.crypto software fallback is used. - if (!skip_sodium) { + // Link libsodium only when the resolved backend uses it (Linux desktop). + // Otherwise the Zig std.crypto software path is used (no link needed). + if (use_libsodium) { ffi_mod.linkSystemLibrary("sodium", .{}); } @@ -57,6 +86,7 @@ pub fn build(b: *std.Build) void { .optimize = optimize, .link_libc = true, }); + exe_mod.addImport("build_options", build_options_mod); const lib_mod = b.createModule(.{ .root_source_file = b.path("src/lib.zig"), @@ -64,15 +94,16 @@ pub fn build(b: *std.Build) void { .optimize = optimize, .link_libc = true, }); + lib_mod.addImport("build_options", build_options_mod); // ─── Main executable ─── const exe = b.addExecutable(.{ .name = "meshguard", .root_module = exe_mod, }); - // Link libsodium on Linux desktop only (AVX2 ChaCha20-Poly1305 assembly) - // macOS, FreeBSD, and Windows use std.crypto - if (!is_windows and !skip_sodium) { + // Link libsodium only when the resolved backend uses it (Linux desktop, + // AVX2 ChaCha20-Poly1305 assembly). std.crypto everywhere else. + if (use_libsodium) { exe_mod.linkSystemLibrary("sodium", .{}); } // On Windows, link ws2_32 for Winsock2 sockets @@ -94,11 +125,12 @@ pub fn build(b: *std.Build) void { .optimize = optimize, .link_libc = true, }); + interop_mod.addImport("build_options", build_options_mod); const interop_exe = b.addExecutable(.{ .name = "wg-interop-test", .root_module = interop_mod, }); - if (!skip_sodium) { + if (use_libsodium) { interop_mod.linkSystemLibrary("sodium", .{}); } b.installArtifact(interop_exe); @@ -128,11 +160,12 @@ pub fn build(b: *std.Build) void { .optimize = optimize, .link_libc = true, }); + test_mod.addImport("build_options", build_options_mod); const unit_tests = b.addTest(.{ .root_module = test_mod, }); - if (!is_windows and !skip_sodium) { + if (use_libsodium) { test_mod.linkSystemLibrary("sodium", .{}); } if (is_windows) { diff --git a/src/main.zig b/src/main.zig index 413bed9..0715e3a 100644 --- a/src/main.zig +++ b/src/main.zig @@ -111,9 +111,9 @@ pub fn main(init: std.process.Init) !void { const allocator = init.gpa; const arena = init.arena.allocator(); - // Initialize libsodium (AVX2 ChaCha20-Poly1305 assembly) — Linux only - // macOS and Windows use std.crypto (no libsodium dependency) - if (comptime @import("builtin").os.tag == .linux) { + // Initialize libsodium (AVX2 ChaCha20-Poly1305 assembly) only when the + // resolved backend uses it. std.crypto path needs no init (meshguard#102). + if (comptime @import("build_options").use_libsodium) { @import("crypto/sodium.zig").init(); } diff --git a/src/wireguard/tunnel.zig b/src/wireguard/tunnel.zig index ef5c64b..d9d336c 100644 --- a/src/wireguard/tunnel.zig +++ b/src/wireguard/tunnel.zig @@ -11,11 +11,12 @@ const std = @import("std"); const noise = @import("noise.zig"); -// Compile-time AEAD backend selection: -// - Linux/native (non-Android): libsodium (AVX2 assembly, ~2× faster) -// - Android/Windows/other: std.crypto (no libsodium dependency) -const builtin = @import("builtin"); -const use_libsodium = (builtin.os.tag == .linux and builtin.target.abi != .android); +// Compile-time AEAD backend selection, resolved in build.zig and passed in via +// build_options so the source and the linker always agree (meshguard#102). +// Default (auto): libsodium on Linux desktop (AVX2 assembly, ~2× faster at MTU/ +// bulk), std.crypto everywhere else. -Dcrypto-backend=std / -Dno-sodium forces +// the std.crypto path on Linux too (no libsodium link required). +const use_libsodium = @import("build_options").use_libsodium; /// Returns a blocking Io instance for synchronous operations. fn zio() std.Io {