From a4ea04cbfe75c031259e4cd3abbd8d6860309a22 Mon Sep 17 00:00:00 2001 From: Igor Lins e Silva <4753812+igorls@users.noreply.github.com> Date: Sun, 7 Jun 2026 08:51:06 -0300 Subject: [PATCH 1/2] feat(crypto): make libsodium a build-selectable optional backend (#102) libsodium was meant to be an optional accelerator, but the build option never reached the source: tunnel.zig and main.zig chose the AEAD backend from builtin.os.tag alone, so -Dno-sodium skipped linkage yet still compiled the sodium path, leaving unresolved symbols on Linux. Add a build_options module carrying use_libsodium (resolved in build.zig from -Dcrypto-backend=auto|std|sodium, with -Dno-sodium kept as a std alias) and thread it into every module. tunnel.zig and main.zig now read build_options.use_libsodium, so source and linker always agree; linkage collapses to a single `if (use_libsodium)`. auto preserves the prior default (libsodium on Linux desktop, std.crypto elsewhere). -Dno-sodium / -Dcrypto-backend=std builds a pure std.crypto binary on Linux x86_64 with no libsodium dependency (verified end-to-end). --- build.zig | 53 +++++++++++++++++++++++++++++----------- src/main.zig | 6 ++--- src/wireguard/tunnel.zig | 11 +++++---- 3 files changed, 48 insertions(+), 22 deletions(-) diff --git a/build.zig b/build.zig index 8e4235f..74c447e 100644 --- a/build.zig +++ b/build.zig @@ -15,12 +15,32 @@ 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 = b.option(CryptoBackend, "crypto-backend", "Crypto backend: auto|std|sodium (default auto)") orelse .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; + + // 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 +51,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 +60,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 +78,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 +86,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 +117,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 +152,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 { From c843ff41f4eba6b723b3a80f4a2687dbf1ae5fbe Mon Sep 17 00:00:00 2001 From: Igor Lins e Silva <4753812+igorls@users.noreply.github.com> Date: Sun, 7 Jun 2026 20:28:58 -0300 Subject: [PATCH 2/2] =?UTF-8?q?fix(crypto):=20fail=20fast=20on=20conflicti?= =?UTF-8?q?ng=20-Dno-sodium=20+=20-Dcrypto-backend=3Dsodium=20(PR=20#103?= =?UTF-8?q?=20review=20=E2=80=94=20Copilot)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- build.zig | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/build.zig b/build.zig index 74c447e..6bf4b72 100644 --- a/build.zig +++ b/build.zig @@ -22,10 +22,18 @@ pub fn build(b: *std.Build) void { // 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 = b.option(CryptoBackend, "crypto-backend", "Crypto backend: auto|std|sodium (default auto)") orelse .auto; + 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);