From fe7dc5f8a69c8e548906fc8a910aa9ef01593222 Mon Sep 17 00:00:00 2001 From: jmwample <8297368+jmwample@users.noreply.github.com> Date: Wed, 16 Oct 2024 22:14:57 -0600 Subject: [PATCH 1/5] initial compiling amnezia lib exchanged for wireguard-go basic structs and feature for amnezia configuration - libs still build --- Makefile | 3 + nym-vpn-core/crates/nym-vpn-lib/build.rs | 35 +++ nym-vpn-core/crates/nym-vpnd/build.rs | 35 +++ nym-vpn-core/crates/nym-wg-go/Cargo.toml | 4 + nym-vpn-core/crates/nym-wg-go/src/netstack.rs | 43 ++++ .../crates/nym-wg-go/src/wireguard_go.rs | 43 ++++ wireguard/build-wireguard-go.sh | 112 ++++++---- wireguard/libamnezia/Android.mk | 39 ++++ .../Dockerfile_AndroidPatchedGoruntime | 50 +++++ wireguard/libamnezia/README.md | 26 +++ wireguard/libamnezia/build-android.sh | 74 +++++++ wireguard/libamnezia/container/container.go | 63 ++++++ wireguard/libamnezia/go.mod | 21 ++ wireguard/libamnezia/go.sum | 18 ++ ...untime-boottime-over-monotonic-darwin.diff | 61 ++++++ .../goruntime-boottime-over-monotonic.diff | 170 +++++++++++++++ wireguard/libamnezia/libwg.go | 74 +++++++ wireguard/libamnezia/libwg_android.go | 103 +++++++++ wireguard/libamnezia/libwg_default.go | 97 +++++++++ wireguard/libamnezia/libwg_ios.go | 97 +++++++++ wireguard/libamnezia/libwg_mobile.go | 40 ++++ wireguard/libamnezia/libwg_windows.go | 153 ++++++++++++++ wireguard/libamnezia/logging/logging.go | 60 ++++++ wireguard/libamnezia/netstack_android.go | 39 ++++ wireguard/libamnezia/netstack_mobile.go | 200 ++++++++++++++++++ .../libamnezia/udp_forwarder/udp_forwarder.go | 194 +++++++++++++++++ 26 files changed, 1814 insertions(+), 40 deletions(-) create mode 100644 wireguard/libamnezia/Android.mk create mode 100644 wireguard/libamnezia/Dockerfile_AndroidPatchedGoruntime create mode 100644 wireguard/libamnezia/README.md create mode 100755 wireguard/libamnezia/build-android.sh create mode 100644 wireguard/libamnezia/container/container.go create mode 100644 wireguard/libamnezia/go.mod create mode 100644 wireguard/libamnezia/go.sum create mode 100644 wireguard/libamnezia/goruntime-boottime-over-monotonic-darwin.diff create mode 100644 wireguard/libamnezia/goruntime-boottime-over-monotonic.diff create mode 100644 wireguard/libamnezia/libwg.go create mode 100644 wireguard/libamnezia/libwg_android.go create mode 100644 wireguard/libamnezia/libwg_default.go create mode 100644 wireguard/libamnezia/libwg_ios.go create mode 100644 wireguard/libamnezia/libwg_mobile.go create mode 100644 wireguard/libamnezia/libwg_windows.go create mode 100644 wireguard/libamnezia/logging/logging.go create mode 100644 wireguard/libamnezia/netstack_android.go create mode 100644 wireguard/libamnezia/netstack_mobile.go create mode 100644 wireguard/libamnezia/udp_forwarder/udp_forwarder.go diff --git a/Makefile b/Makefile index 88dddeb6d9..62fb7e3689 100644 --- a/Makefile +++ b/Makefile @@ -6,6 +6,9 @@ all: build-wireguard build-nym-vpn-core build-wireguard: ./wireguard/build-wireguard-go.sh +build-amnezia-wg: + ./wireguard/build-wireguard-go.sh --amnezia + build-wireguard-ios: ./wireguard/build-wireguard-go.sh --ios diff --git a/nym-vpn-core/crates/nym-vpn-lib/build.rs b/nym-vpn-core/crates/nym-vpn-lib/build.rs index 17b0b39446..425025e065 100644 --- a/nym-vpn-core/crates/nym-vpn-lib/build.rs +++ b/nym-vpn-core/crates/nym-vpn-lib/build.rs @@ -1,6 +1,7 @@ // Copyright 2024 - Nym Technologies SA // SPDX-License-Identifier: GPL-3.0-only +use std::{env, path::PathBuf}; use vergen::EmitBuilder; fn main() -> Result<(), Box> { @@ -11,5 +12,39 @@ fn main() -> Result<(), Box> { .all_cargo() .emit() .expect("failed to extract build metadata"); + + let manifest_path = env::var_os("CARGO_MANIFEST_DIR").expect("manifest dir is not set"); + let target = env::var("TARGET").expect("target is not set"); + let target_os = env::var("CARGO_CFG_TARGET_OS").expect("target os is not set"); + + let mut build_dir = PathBuf::from(manifest_path) + .join("../../../build/lib") + .canonicalize() + .expect("failed to canonicalize build dir path"); + + build_dir.push(target); + + // CI may only provide universal builds + if target_os == "macos" { + let target_dir_exists = build_dir + .try_exists() + .expect("failed to check existence of target dir"); + + if !target_dir_exists { + build_dir.pop(); + build_dir.push("universal-apple-darwin"); + } + } + + println!("cargo::rustc-link-search={}", build_dir.display()); + + let link_type = match target_os.as_str() { + "android" => "", + "linux" | "macos" | "ios" => "=static", + "windows" => "dylib", + _ => panic!("Unsupported platform: {}", target_os), + }; + println!("cargo:rustc-link-lib{}=wg", link_type); + Ok(()) } diff --git a/nym-vpn-core/crates/nym-vpnd/build.rs b/nym-vpn-core/crates/nym-vpnd/build.rs index 17b0b39446..425025e065 100644 --- a/nym-vpn-core/crates/nym-vpnd/build.rs +++ b/nym-vpn-core/crates/nym-vpnd/build.rs @@ -1,6 +1,7 @@ // Copyright 2024 - Nym Technologies SA // SPDX-License-Identifier: GPL-3.0-only +use std::{env, path::PathBuf}; use vergen::EmitBuilder; fn main() -> Result<(), Box> { @@ -11,5 +12,39 @@ fn main() -> Result<(), Box> { .all_cargo() .emit() .expect("failed to extract build metadata"); + + let manifest_path = env::var_os("CARGO_MANIFEST_DIR").expect("manifest dir is not set"); + let target = env::var("TARGET").expect("target is not set"); + let target_os = env::var("CARGO_CFG_TARGET_OS").expect("target os is not set"); + + let mut build_dir = PathBuf::from(manifest_path) + .join("../../../build/lib") + .canonicalize() + .expect("failed to canonicalize build dir path"); + + build_dir.push(target); + + // CI may only provide universal builds + if target_os == "macos" { + let target_dir_exists = build_dir + .try_exists() + .expect("failed to check existence of target dir"); + + if !target_dir_exists { + build_dir.pop(); + build_dir.push("universal-apple-darwin"); + } + } + + println!("cargo::rustc-link-search={}", build_dir.display()); + + let link_type = match target_os.as_str() { + "android" => "", + "linux" | "macos" | "ios" => "=static", + "windows" => "dylib", + _ => panic!("Unsupported platform: {}", target_os), + }; + println!("cargo:rustc-link-lib{}=wg", link_type); + Ok(()) } diff --git a/nym-vpn-core/crates/nym-wg-go/Cargo.toml b/nym-vpn-core/crates/nym-wg-go/Cargo.toml index 533ad587a5..aaed1be3e1 100644 --- a/nym-vpn-core/crates/nym-wg-go/Cargo.toml +++ b/nym-vpn-core/crates/nym-wg-go/Cargo.toml @@ -8,6 +8,10 @@ documentation.workspace = true edition.workspace = true license.workspace = true +[features] +default = [] +amnezia = [] + [dependencies] ipnetwork.workspace = true thiserror.workspace = true diff --git a/nym-vpn-core/crates/nym-wg-go/src/netstack.rs b/nym-vpn-core/crates/nym-wg-go/src/netstack.rs index 0da81c1825..003e2fd140 100644 --- a/nym-vpn-core/crates/nym-wg-go/src/netstack.rs +++ b/nym-vpn-core/crates/nym-wg-go/src/netstack.rs @@ -20,6 +20,8 @@ pub struct InterfaceConfig { pub local_addrs: Vec, pub dns_addrs: Vec, pub mtu: u16, + #[cfg(feature = "amnezia")] + pub azwg_config: Option, } impl fmt::Debug for InterfaceConfig { @@ -33,6 +35,47 @@ impl fmt::Debug for InterfaceConfig { } } +/// Hold Amnezia-wireguard configuration parameters. +/// +/// All parameters should be the same between Client and Server, except Jc - it can vary. +/// +/// - Jc — 1 ≤ Jc ≤ 128; recommended range is from 3 to 10 inclusive +/// - Jmin — Jmin < Jmax; recommended value is 50 +/// - Jmax — Jmin < Jmax ≤ 1280; recommended value is 1000 +/// - S1 — S1 < 1280; S1 + 56 ≠ S2; recommended range is from 15 to 150 inclusive +/// - S2 — S2 < 1280; recommended range is from 15 to 150 inclusive +/// - H1/H2/H3/H4 — must be unique among each other; +/// recommended range is from 5 to 2_147_483_647 (2^31 - 1 i.e. signed 32 bit int) inclusive +#[cfg(feature = "amnezia")] +#[derive(Debug)] +pub struct AmneziaConfig { + pub junk_packet_count: i32, // Jc + pub junk_packet_min_size: i32, // Jmin + pub junk_packet_max_size: i32, // Jmax + pub init_packet_junk_size: i32, // S0 + pub response_packet_junk_size: i32, // S1 + pub init_packet_magic_header: u32, // H1 + pub response_packet_magic_header: u32, // H2 + pub under_load_packet_magic_header: u32, // H3 + pub transport_packet_magic_header: u32, // H4 +} + +#[cfg(feature = "amnezia")] +impl Default for AmneziaConfig { + fn default() -> Self { + Self { + junk_packet_count: 4_i32, + junk_packet_min_size: 40_i32, + junk_packet_max_size: 70_i32, + init_packet_junk_size: 0_i32, + response_packet_junk_size: 0_i32, + init_packet_magic_header: 1_u32, + response_packet_magic_header: 2_u32, + under_load_packet_magic_header: 3_u32, + transport_packet_magic_header: 4_u32, + } + } +} /// Netstack configuration. #[derive(Debug)] pub struct Config { diff --git a/nym-vpn-core/crates/nym-wg-go/src/wireguard_go.rs b/nym-vpn-core/crates/nym-wg-go/src/wireguard_go.rs index f37a6154e8..2f347beb09 100644 --- a/nym-vpn-core/crates/nym-wg-go/src/wireguard_go.rs +++ b/nym-vpn-core/crates/nym-wg-go/src/wireguard_go.rs @@ -20,6 +20,8 @@ pub struct InterfaceConfig { pub mtu: u16, #[cfg(target_os = "linux")] pub fwmark: Option, + #[cfg(feature = "amnezia")] + pub azwg_config: Option, } impl fmt::Debug for InterfaceConfig { @@ -33,6 +35,47 @@ impl fmt::Debug for InterfaceConfig { d.finish() } } +/// Hold Amnezia-wireguard configuration parameters. +/// +/// All parameters should be the same between Client and Server, except Jc - it can vary. +/// +/// - Jc — 1 ≤ Jc ≤ 128; recommended range is from 3 to 10 inclusive +/// - Jmin — Jmin < Jmax; recommended value is 50 +/// - Jmax — Jmin < Jmax ≤ 1280; recommended value is 1000 +/// - S1 — S1 < 1280; S1 + 56 ≠ S2; recommended range is from 15 to 150 inclusive +/// - S2 — S2 < 1280; recommended range is from 15 to 150 inclusive +/// - H1/H2/H3/H4 — must be unique among each other; +/// recommended range is from 5 to 2_147_483_647 (2^31 - 1 i.e. signed 32 bit int) inclusive +#[cfg(feature = "amnezia")] +#[derive(Debug)] +pub struct AmneziaConfig { + pub junk_packet_count: i32, // Jc + pub junk_packet_min_size: i32, // Jmin + pub junk_packet_max_size: i32, // Jmax + pub init_packet_junk_size: i32, // S0 + pub response_packet_junk_size: i32, // S1 + pub init_packet_magic_header: u32, // H1 + pub response_packet_magic_header: u32, // H2 + pub under_load_packet_magic_header: u32, // H3 + pub transport_packet_magic_header: u32, // H4 +} + +#[cfg(feature = "amnezia")] +impl Default for AmneziaConfig { + fn default() -> Self { + Self { + junk_packet_count: 4_i32, + junk_packet_min_size: 40_i32, + junk_packet_max_size: 70_i32, + init_packet_junk_size: 0_i32, + response_packet_junk_size: 0_i32, + init_packet_magic_header: 1_u32, + response_packet_magic_header: 2_u32, + under_load_packet_magic_header: 3_u32, + transport_packet_magic_header: 4_u32, + } + } +} /// Classic WireGuard configuration. #[derive(Debug)] diff --git a/wireguard/build-wireguard-go.sh b/wireguard/build-wireguard-go.sh index f7cd2110bf..d16584f20e 100755 --- a/wireguard/build-wireguard-go.sh +++ b/wireguard/build-wireguard-go.sh @@ -2,52 +2,67 @@ # This script is used to build wireguard-go libraries for all the platforms. +TEMP=$(getopt -o aiz --long android,docker,ios,amnezia \ + -n 'build-wireguard-go.sh' -- "$@") + +if [ $? != 0 ] ; then echo "Terminating..." >&2 ; exit 1 ; fi + +# Note the quotes around '$TEMP': they are essential! +eval set -- "$TEMP" + +ANDROID_BUILD=false +IOS_BUILD=false +DOCKER_BUILD=true +AMNEZIA_BUILD=false +while true; do + case "$1" in + "-a" | "--android" ) ANDROID_BUILD=true; shift ;; + "-i" | "--ios" ) IOS_BUILD=true; shift ;; + "-z" | "--amnezia" ) AMNEZIA_BUILD=true; shift ;; + "--no-docker" ) DOCKER_BUILD=false; shift ;; + -- ) shift; break ;; + * ) break ;; + esac +done + set -eu function is_android_build { - for arg in "$@" - do - case "$arg" in - "--android") - return 0 - esac - done + if [ "$ANDROID_BUILD" = true ]; then + return 0 + fi return 1 } function is_ios_build { - for arg in "$@" - do - case "$arg" in - "--ios") - return 0 - esac - done + if [ "$IOS_BUILD" = true ]; then + return 0 + fi return 1 } function is_docker_build { - for arg in "$@" - do - case "$arg" in - "--no-docker") - return 1 - esac - done - return 0 + if [ "$DOCKER_BUILD" = true ]; then + return 0 + fi + return 1 } -function is_win_arm64 { - for arg in "$@" - do - case "$arg" in - "--arm64") - return 0 - esac - done +function is_amnezia_build { + if [ "$AMNEZIA_BUILD" = true ]; then + return 0 + fi return 1 } +function win_deduce_lib_executable_path { + msbuild_path="$(which msbuild.exe)" + msbuild_dir=$(dirname "$msbuild_path") + find "$msbuild_dir/../../../../" -name "lib.exe" | \ + grep -i "hostx64/x64" | \ + head -n1 +} + function win_gather_export_symbols { grep -Eo "\/\/export \w+" libwg.go libwg_windows.go | cut -d' ' -f2 } @@ -75,8 +90,16 @@ function win_create_lib_file { } function build_windows { +<<<<<<< HEAD export CGO_ENABLED=1 export GOOS=windows +======= + echo "Building wireguard-go for Windows" + pushd $LIB_DIR + export CGO_ENABLED=1 + go build -trimpath -v -o libwg.dll -buildmode c-shared + win_create_lib_file +>>>>>>> c0b0f37b (initial compiling amnezia lib exchanged for wireguard-go) if is_win_arm64 $@; then local arch="aarch64" @@ -132,7 +155,7 @@ function build_unix { fi fi - pushd libwg + pushd $LIB_DIR create_folder_and_build $1 popd } @@ -141,14 +164,14 @@ function build_android { echo "Building for android" local docker_image_hash="992c4d5c7dcd00eacf6f3e3667ce86b8e185f011352bdd9f79e467fef3e27abd" - if is_docker_build $@; then + if is_docker_build; then docker run --rm \ -v "$(pwd)/../":/workspace \ - --entrypoint "/workspace/wireguard/libwg/build-android.sh" \ + --entrypoint "/workspace/wireguard/$LIB_DIR/build-android.sh" \ --env ANDROID_NDK_HOME="/opt/android/android-ndk-r20b" \ docker.io/pronebird1337/nymtech-android-app@sha256:$docker_image_hash else - ./libwg/build-android.sh + ./$LIB_DIR/build-android.sh fi } @@ -166,7 +189,7 @@ function build_macos_universal { export MACOSX_DEPLOYMENT_TARGET=10.13 echo "🍎 Building for aarch64" - pushd libwg + pushd $LIB_DIR export GOOS=darwin export GOARCH=arm64 create_folder_and_build "aarch64-apple-darwin" @@ -189,7 +212,7 @@ function build_ios { export CGO_ENABLED=1 export IPHONEOS_DEPLOYMENT_TARGET=16.0 - pushd libwg + pushd $LIB_DIR echo "🍎 Building for ios/aarch64" export GOARCH=arm64 @@ -240,17 +263,23 @@ function patch_darwin_goruntime { REAL_GOROOT=$(go env GOROOT 2>/dev/null) export GOROOT="$BUILDDIR/goroot" mkdir -p "$GOROOT" - rsync -a --delete --exclude=pkg/obj/go-build "$REAL_GOROOT/" "$GOROOT/" - cat libwg/goruntime-boottime-over-monotonic-darwin.diff | patch -p1 -f -N -r- -d "$GOROOT" + rsync -a --delete --exclude=pkg/obj/go-build "$REAL_GOROOT/" "$GOROOT/" + cat $LIB_DIR/goruntime-boottime-over-monotonic-darwin.diff | patch -p1 -f -N -r- -d "$GOROOT" } function build_wireguard_go { - if is_android_build $@; then + + if is_amnezia_build ; then + LIB_DIR=$AMNEZIA_DIR + echo "amnezia wireguard build enabled" + fi + + if is_android_build ; then build_android $@ return fi - if is_ios_build $@; then + if is_ios_build ; then build_ios $@ return fi @@ -263,6 +292,9 @@ function build_wireguard_go { esac } +AMNEZIA_DIR="libamnezia" +LIB_DIR="libwg" + # Ensure we are in the correct directory for the execution of this script script_dir="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )" cd $script_dir diff --git a/wireguard/libamnezia/Android.mk b/wireguard/libamnezia/Android.mk new file mode 100644 index 0000000000..1f8603ed54 --- /dev/null +++ b/wireguard/libamnezia/Android.mk @@ -0,0 +1,39 @@ +# SPDX-License-Identifier: Apache-2.0 +# +# Copyright © 2017-2019 WireGuard LLC. All Rights Reserved. + +DESTDIR ?= $(CURDIR)/../../build/lib/$(RUST_TARGET_TRIPLE) + +NDK_GO_ARCH_MAP_x86 := 386 +NDK_GO_ARCH_MAP_x86_64 := amd64 +NDK_GO_ARCH_MAP_arm := arm +NDK_GO_ARCH_MAP_arm64 := arm64 +NDK_GO_ARCH_MAP_mips := mipsx +NDK_GO_ARCH_MAP_mips64 := mips64x + +export CGO_CFLAGS := $(CFLAGS) +export CGO_LDFLAGS := $(LDFLAGS) +export CC := $(ANDROID_C_COMPILER) +export GOARCH := $(NDK_GO_ARCH_MAP_$(ANDROID_ARCH_NAME)) +export GOOS := android +export CGO_ENABLED := 1 + +default: $(DESTDIR)/libwg.so + +GOBUILDARCH := $(NDK_GO_ARCH_MAP_$(shell uname -m)) +GOBUILDOS := $(shell uname -s | tr '[:upper:]' '[:lower:]') +GOBUILDVERSION := 1.22.6 +# TODO: Add checksum? +GOBUILDTARBALL := https://go.dev/dl/go$(GOBUILDVERSION).$(GOBUILDOS)-$(GOBUILDARCH).tar.gz +GOBUILDVERSION_NEEDED := go version go$(GOBUILDVERSION) $(GOBUILDOS)/$(GOBUILDARCH) + +$(DESTDIR)/libwg.so: + mkdir -p $(DESTDIR) + go get -tags "linux android" + chmod -fR +w "$(GOPATH)/pkg/mod" + go build -tags "linux android" -ldflags="-X main.socketDirectory=/data/data/$(ANDROID_PACKAGE_NAME)/cache/wireguard" -v -o "$@" -buildmode c-shared + rm -f $(DESTDIR)/libwg.h + + +clean: + rm -f $(DESTDIR)/libwg.so diff --git a/wireguard/libamnezia/Dockerfile_AndroidPatchedGoruntime b/wireguard/libamnezia/Dockerfile_AndroidPatchedGoruntime new file mode 100644 index 0000000000..cbdec91784 --- /dev/null +++ b/wireguard/libamnezia/Dockerfile_AndroidPatchedGoruntime @@ -0,0 +1,50 @@ +# To build the image: +# docker build . -t docker.io/pronebird1337/nymtech-android-app -f Dockerfile_AndroidPatchedGoruntime +# To push the image to docker.io: +# docker push docker.io/pronebird1337/nymtech-android-app + +FROM debian@sha256:77f46c1cf862290e750e913defffb2828c889d291a93bdd10a7a0597720948fc + +RUN apt-get update -y && apt-get install -y \ + curl \ + file \ + gcc \ + git \ + make \ + python \ + unzip + +# Install Android NDK +RUN cd /tmp && \ + curl -sf -L -o ndk.zip https://dl.google.com/android/repository/android-ndk-r20b-linux-x86_64.zip && \ + echo "8381c440fe61fcbb01e209211ac01b519cd6adf51ab1c2281d5daad6ca4c8c8c ndk.zip" | sha256sum -c - && \ + mkdir /opt/android && \ + cd /opt/android && \ + unzip -q /tmp/ndk.zip && \ + rm /tmp/ndk.zip + + +ENV ANDROID_NDK_HOME="/opt/android/android-ndk-r20b" +ENV NDK_TOOLCHAIN_DIR="${ANDROID_NDK_HOME}/toolchains/llvm/prebuilt/linux-x86_64/bin" + +ENV GOLANG_VERSION=1.22.6 +ENV GOLANG_HASH=999805bed7d9039ec3da1a53bfbcafc13e367da52aa823cb60b68ba22d44c616 + +# Install Go-lang and patch it to use the appropriate monotonic clock +COPY goruntime-boottime-over-monotonic.diff /opt/goruntime-boottime-over-monotonic.diff +RUN cd /tmp && \ + curl -sf -L -o go.tgz https://go.dev/dl/go${GOLANG_VERSION}.linux-amd64.tar.gz && \ + echo "${GOLANG_HASH} go.tgz" | sha256sum -c - && \ + cd /opt && \ + tar -xzf /tmp/go.tgz && \ + rm /tmp/go.tgz && \ + patch -p1 -f -N -r- -d "/opt/go" < /opt/goruntime-boottime-over-monotonic.diff + +ENV PATH=${PATH}:/opt/go/bin +ENV GOROOT=/opt/go +ENV GOPATH=/opt/go-path + +RUN apt-get remove -y curl && \ + apt-get autoremove -y + +ENTRYPOINT [] diff --git a/wireguard/libamnezia/README.md b/wireguard/libamnezia/README.md new file mode 100644 index 0000000000..e5b96928f7 --- /dev/null +++ b/wireguard/libamnezia/README.md @@ -0,0 +1,26 @@ +# Introduction + +`libwg` is a tiny wrapper around `wireguard-go`, with the main purpose of providing a simple FFI-friendly interface. + +It currently offers support for the following platforms: + +- Linux +- macOS +- Android +- Windows + +# Organization + +`libwg.go` has shared code that is used on all platforms. + +`libwg_default.go` has default implementations for Linux-based systems. + +`libwg_android.go` has code specifically for Android. + +`libwg_windows.go` has code specifically for Windows. + +# Usage + +Call `wgTurnOn` to create and activate a tunnel. The prototype is different on different platforms, see the code for details. + +Call `wgTurnOff` to destroy the tunnel. diff --git a/wireguard/libamnezia/build-android.sh b/wireguard/libamnezia/build-android.sh new file mode 100755 index 0000000000..0d269919f2 --- /dev/null +++ b/wireguard/libamnezia/build-android.sh @@ -0,0 +1,74 @@ +#!/usr/bin/env bash + +set -eu + +# Ensure we are in the correct directory for the execution of this script +script_dir="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )" +cd $script_dir + +# Keep a GOPATH in the build directory to maintain a cache of downloaded libraries +export GOPATH=$script_dir/../../build/android-go-path/ +mkdir -p $GOPATH + +for arch in ${ARCHITECTURES:-armv7 aarch64 x86_64 i686}; do + case "$arch" in + "aarch64") + export ANDROID_C_COMPILER="${NDK_TOOLCHAIN_DIR}/aarch64-linux-android21-clang" + export ANDROID_STRIP_TOOL="${NDK_TOOLCHAIN_DIR}/aarch64-linux-android-strip" + export RUST_TARGET_TRIPLE="aarch64-linux-android" + export ANDROID_ABI="arm64-v8a" + export ANDROID_ARCH_NAME="arm64" + ;; + "x86_64") + export ANDROID_C_COMPILER="${NDK_TOOLCHAIN_DIR}/x86_64-linux-android21-clang" + export ANDROID_STRIP_TOOL="${NDK_TOOLCHAIN_DIR}/x86_64-linux-android-strip" + export RUST_TARGET_TRIPLE="x86_64-linux-android" + export ANDROID_ABI="x86_64" + export ANDROID_ARCH_NAME="x86_64" + ;; + "armv7") + export ANDROID_C_COMPILER="${NDK_TOOLCHAIN_DIR}/armv7a-linux-androideabi21-clang" + export ANDROID_STRIP_TOOL="${NDK_TOOLCHAIN_DIR}/arm-linux-androideabi-strip" + export RUST_TARGET_TRIPLE="armv7-linux-androideabi" + export ANDROID_ABI="armeabi-v7a" + export ANDROID_ARCH_NAME="arm" + ;; + "i686") + export ANDROID_C_COMPILER="${NDK_TOOLCHAIN_DIR}/i686-linux-android21-clang" + export ANDROID_STRIP_TOOL="${NDK_TOOLCHAIN_DIR}/i686-linux-android-strip" + export RUST_TARGET_TRIPLE="i686-linux-android" + export ANDROID_ABI="x86" + export ANDROID_ARCH_NAME="x86" + ;; + esac + + # Build Wireguard-Go + echo $(pwd) + make -f Android.mk clean + +# this is determined by the NDK +# export CFLAGS="-D__ANDROID_API__=21" + + make -f Android.mk + + # Strip and copy the libray to `android/build/extraJni/$ANDROID_ABI` to be able to build the APK + UNSTRIPPED_LIB_PATH="../../build/lib/$RUST_TARGET_TRIPLE/libwg.so" + STRIPPED_LIB_PATH="../../android/app/build/extraJni/$ANDROID_ABI/libwg.so" + + # Create the directories with RWX permissions for all users so that the build server can clean + # the directories afterwards + mkdir -m 777 -p "$(dirname "$STRIPPED_LIB_PATH")" + + cp "$UNSTRIPPED_LIB_PATH" "$STRIPPED_LIB_PATH" + +# this is not available in the newer NDK +# $ANDROID_STRIP_TOOL --strip-unneeded --strip-debug -o "$STRIPPED_LIB_PATH" "$UNSTRIPPED_LIB_PATH" + + # Set permissions so that the build server can clean the outputs afterwards + chmod 777 "$STRIPPED_LIB_PATH" + + rm -rf build +done + +# ensure `git clean -fd` does not require root permissions +find $GOPATH -exec chmod +rw {} \; diff --git a/wireguard/libamnezia/container/container.go b/wireguard/libamnezia/container/container.go new file mode 100644 index 0000000000..226616dbc0 --- /dev/null +++ b/wireguard/libamnezia/container/container.go @@ -0,0 +1,63 @@ +/* SPDX-License-Identifier: Apache-2.0 + * + * Copyright (C) 2017-2019 Jason A. Donenfeld . All Rights Reserved. + * Copyright (C) 2020 Mullvad VPN AB. All Rights Reserved. + * Copyright 2024 - Nym Technologies SA + */ + +package container + +import ( + "errors" + "math" +) + +// Generic index-based memory storage +type Container[Context any] struct { + tunnels map[int32]Context +} + +func New[Context any]() Container[Context] { + return Container[Context]{ + tunnels: make(map[int32]Context), + } +} + +func (wself *Container[Context]) Insert(context Context) (int32, error) { + var i int32 + for i = 0; i < math.MaxInt32; i++ { + if _, exists := wself.tunnels[i]; !exists { + break + } + } + + if i == math.MaxInt32 { + return 0, errors.New("container is full") + } + + wself.tunnels[i] = context + return i, nil +} + +func (wself *Container[Context]) Get(handle int32) (*Context, error) { + context, ok := wself.tunnels[handle] + if !ok { + return nil, errors.New("invalid context handle") + } + return &context, nil +} + +func (wself *Container[Context]) Remove(handle int32) (*Context, error) { + context, ok := wself.tunnels[handle] + if !ok { + return nil, errors.New("invalid context handle") + } + delete(wself.tunnels, handle) + return &context, nil +} + +func (wself *Container[Context]) ForEach(callback func(Context)) { + for _, tunnel := range wself.tunnels { + callback(tunnel) + } +} diff --git a/wireguard/libamnezia/go.mod b/wireguard/libamnezia/go.mod new file mode 100644 index 0000000000..56da2bbe59 --- /dev/null +++ b/wireguard/libamnezia/go.mod @@ -0,0 +1,21 @@ +module github.com/nymtech/nym-vpn-client/wireguard/libwg + +go 1.22.3 + +toolchain go1.23.1 + +require ( + // golang.zx2c4.com/wireguard v0.0.0-20231211153847-12269c276173 + github.com/amnezia-vpn/amneziawg-go v0.2.12 + golang.org/x/sys v0.18.0 + gvisor.dev/gvisor v0.0.0-20230927004350-cbd86285d259 +) + +require ( + github.com/google/btree v1.0.1 // indirect + github.com/tevino/abool/v2 v2.1.0 // indirect + golang.org/x/crypto v0.21.0 // indirect + golang.org/x/net v0.21.0 // indirect + golang.org/x/time v0.0.0-20220210224613-90d013bbcef8 // indirect + golang.zx2c4.com/wintun v0.0.0-20230126152724-0fa3db229ce2 // indirect +) diff --git a/wireguard/libamnezia/go.sum b/wireguard/libamnezia/go.sum new file mode 100644 index 0000000000..42f90fd40d --- /dev/null +++ b/wireguard/libamnezia/go.sum @@ -0,0 +1,18 @@ +github.com/amnezia-vpn/amneziawg-go v0.2.12 h1:CxIQETy5kZ0ip/dFBpmnDxAcS/KuIQaJkOxDv5OQhVI= +github.com/amnezia-vpn/amneziawg-go v0.2.12/go.mod h1:d7WpNfzCRLy7ufGElJBYpD58WRmNjyLyt3IDHPY8AmM= +github.com/google/btree v1.0.1 h1:gK4Kx5IaGY9CD5sPJ36FHiBJ6ZXl0kilRiiCj+jdYp4= +github.com/google/btree v1.0.1/go.mod h1:xXMiIv4Fb/0kKde4SpL7qlzvu5cMJDRkFDxJfI9uaxA= +github.com/tevino/abool/v2 v2.1.0 h1:7w+Vf9f/5gmKT4m4qkayb33/92M+Um45F2BkHOR+L/c= +github.com/tevino/abool/v2 v2.1.0/go.mod h1:+Lmlqk6bHDWHqN1cbxqhwEAwMPXgc8I1SDEamtseuXY= +golang.org/x/crypto v0.21.0 h1:X31++rzVUdKhX5sWmSOFZxx8UW/ldWx55cbf08iNAMA= +golang.org/x/crypto v0.21.0/go.mod h1:0BP7YvVV9gBbVKyeTG0Gyn+gZm94bibOW5BjDEYAOMs= +golang.org/x/net v0.21.0 h1:AQyQV4dYCvJ7vGmJyKki9+PBdyvhkSd8EIx/qb0AYv4= +golang.org/x/net v0.21.0/go.mod h1:bIjVDfnllIU7BJ2DNgfnXvpSvtn8VRwhlsaeUTyUS44= +golang.org/x/sys v0.18.0 h1:DBdB3niSjOA/O0blCZBqDefyWNYveAYMNF1Wum0DYQ4= +golang.org/x/sys v0.18.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/time v0.0.0-20220210224613-90d013bbcef8 h1:vVKdlvoWBphwdxWKrFZEuM0kGgGLxUOYcY4U/2Vjg44= +golang.org/x/time v0.0.0-20220210224613-90d013bbcef8/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= +golang.zx2c4.com/wintun v0.0.0-20230126152724-0fa3db229ce2 h1:B82qJJgjvYKsXS9jeunTOisW56dUokqW/FOteYJJ/yg= +golang.zx2c4.com/wintun v0.0.0-20230126152724-0fa3db229ce2/go.mod h1:deeaetjYA+DHMHg+sMSMI58GrEteJUUzzw7en6TJQcI= +gvisor.dev/gvisor v0.0.0-20230927004350-cbd86285d259 h1:TbRPT0HtzFP3Cno1zZo7yPzEEnfu8EjLfl6IU9VfqkQ= +gvisor.dev/gvisor v0.0.0-20230927004350-cbd86285d259/go.mod h1:AVgIgHMwK63XvmAzWG9vLQ41YnVHN0du0tEC46fI7yY= diff --git a/wireguard/libamnezia/goruntime-boottime-over-monotonic-darwin.diff b/wireguard/libamnezia/goruntime-boottime-over-monotonic-darwin.diff new file mode 100644 index 0000000000..2f7f54edd0 --- /dev/null +++ b/wireguard/libamnezia/goruntime-boottime-over-monotonic-darwin.diff @@ -0,0 +1,61 @@ +From 516dc0c15ff1ab781e0677606b5be72919251b3e Mon Sep 17 00:00:00 2001 +From: "Jason A. Donenfeld" +Date: Wed, 9 Dec 2020 14:07:06 +0100 +Subject: [PATCH] runtime: use libc_mach_continuous_time in nanotime on Darwin + +This makes timers account for having expired while a computer was +asleep, which is quite common on mobile devices. Note that +continuous_time absolute_time, except that it takes into account +time spent in suspend. + +Fixes #24595 + +Change-Id: Ia3282e8bd86f95ad2b76427063e60a005563f4eb +--- + src/runtime/sys_darwin.go | 2 +- + src/runtime/sys_darwin_amd64.s | 2 +- + src/runtime/sys_darwin_arm64.s | 2 +- + 3 files changed, 3 insertions(+), 3 deletions(-) + +diff --git a/src/runtime/sys_darwin.go b/src/runtime/sys_darwin.go +index 4a3f2fc453..4a69403b32 100644 +--- a/src/runtime/sys_darwin.go ++++ b/src/runtime/sys_darwin.go +@@ -440,7 +440,7 @@ func setNonblock(fd int32) { + //go:cgo_import_dynamic libc_usleep usleep "/usr/lib/libSystem.B.dylib" + + //go:cgo_import_dynamic libc_mach_timebase_info mach_timebase_info "/usr/lib/libSystem.B.dylib" +-//go:cgo_import_dynamic libc_mach_absolute_time mach_absolute_time "/usr/lib/libSystem.B.dylib" ++//go:cgo_import_dynamic libc_mach_continuous_time mach_continuous_time "/usr/lib/libSystem.B.dylib" + //go:cgo_import_dynamic libc_clock_gettime clock_gettime "/usr/lib/libSystem.B.dylib" + //go:cgo_import_dynamic libc_sigaction sigaction "/usr/lib/libSystem.B.dylib" + //go:cgo_import_dynamic libc_pthread_sigmask pthread_sigmask "/usr/lib/libSystem.B.dylib" +diff --git a/src/runtime/sys_darwin_amd64.s b/src/runtime/sys_darwin_amd64.s +index 630fb5df64..4499c88802 100644 +--- a/src/runtime/sys_darwin_amd64.s ++++ b/src/runtime/sys_darwin_amd64.s +@@ -114,7 +114,7 @@ TEXT runtime·nanotime_trampoline(SB),NOSPLIT,$0 + PUSHQ BP + MOVQ SP, BP + MOVQ DI, BX +- CALL libc_mach_absolute_time(SB) ++ CALL libc_mach_continuous_time(SB) + MOVQ AX, 0(BX) + MOVL timebase<>+machTimebaseInfo_numer(SB), SI + MOVL timebase<>+machTimebaseInfo_denom(SB), DI // atomic read +diff --git a/src/runtime/sys_darwin_arm64.s b/src/runtime/sys_darwin_arm64.s +index 96d2ed1076..f046545395 100644 +--- a/src/runtime/sys_darwin_arm64.s ++++ b/src/runtime/sys_darwin_arm64.s +@@ -143,7 +143,7 @@ GLOBL timebase<>(SB),NOPTR,$(machTimebaseInfo__size) + + TEXT runtime·nanotime_trampoline(SB),NOSPLIT,$40 + MOVD R0, R19 +- BL libc_mach_absolute_time(SB) ++ BL libc_mach_continuous_time(SB) + MOVD R0, 0(R19) + MOVW timebase<>+machTimebaseInfo_numer(SB), R20 + MOVD $timebase<>+machTimebaseInfo_denom(SB), R21 +-- +2.30.1 + diff --git a/wireguard/libamnezia/goruntime-boottime-over-monotonic.diff b/wireguard/libamnezia/goruntime-boottime-over-monotonic.diff new file mode 100644 index 0000000000..73eb999a95 --- /dev/null +++ b/wireguard/libamnezia/goruntime-boottime-over-monotonic.diff @@ -0,0 +1,170 @@ +From 61f3ae8298d1c503cbc31539e0f3a73446c7db9d Mon Sep 17 00:00:00 2001 +From: "Jason A. Donenfeld" +Date: Tue, 21 Mar 2023 15:33:56 +0100 +Subject: [PATCH] [release-branch.go1.20] runtime: use CLOCK_BOOTTIME in + nanotime on Linux + +This makes timers account for having expired while a computer was +asleep, which is quite common on mobile devices. Note that BOOTTIME is +identical to MONOTONIC, except that it takes into account time spent +in suspend. In Linux 4.17, the kernel will actually make MONOTONIC act +like BOOTTIME anyway, so this switch will additionally unify the +timer behavior across kernels. + +BOOTTIME was introduced into Linux 2.6.39-rc1 with 70a08cca1227d in +2011. + +Fixes #24595 + +Change-Id: I7b2a6ca0c5bc5fce57ec0eeafe7b68270b429321 +--- + src/runtime/sys_linux_386.s | 4 ++-- + src/runtime/sys_linux_amd64.s | 2 +- + src/runtime/sys_linux_arm.s | 4 ++-- + src/runtime/sys_linux_arm64.s | 4 ++-- + src/runtime/sys_linux_mips64x.s | 4 ++-- + src/runtime/sys_linux_mipsx.s | 2 +- + src/runtime/sys_linux_ppc64x.s | 2 +- + src/runtime/sys_linux_s390x.s | 2 +- + 8 files changed, 12 insertions(+), 12 deletions(-) + +diff --git a/src/runtime/sys_linux_386.s b/src/runtime/sys_linux_386.s +index 12a294153d..17e3524b40 100644 +--- a/src/runtime/sys_linux_386.s ++++ b/src/runtime/sys_linux_386.s +@@ -352,13 +352,13 @@ noswitch: + + LEAL 8(SP), BX // &ts (struct timespec) + MOVL BX, 4(SP) +- MOVL $1, 0(SP) // CLOCK_MONOTONIC ++ MOVL $7, 0(SP) // CLOCK_BOOTTIME + CALL AX + JMP finish + + fallback: + MOVL $SYS_clock_gettime, AX +- MOVL $1, BX // CLOCK_MONOTONIC ++ MOVL $7, BX // CLOCK_BOOTTIME + LEAL 8(SP), CX + INVOKE_SYSCALL + +diff --git a/src/runtime/sys_linux_amd64.s b/src/runtime/sys_linux_amd64.s +index c7a89ba536..01f0a6a26e 100644 +--- a/src/runtime/sys_linux_amd64.s ++++ b/src/runtime/sys_linux_amd64.s +@@ -255,7 +255,7 @@ noswitch: + SUBQ $16, SP // Space for results + ANDQ $~15, SP // Align for C code + +- MOVL $1, DI // CLOCK_MONOTONIC ++ MOVL $7, DI // CLOCK_BOOTTIME + LEAQ 0(SP), SI + MOVQ runtime·vdsoClockgettimeSym(SB), AX + CMPQ AX, $0 +diff --git a/src/runtime/sys_linux_arm.s b/src/runtime/sys_linux_arm.s +index 7b8c4f0e04..9798a1334e 100644 +--- a/src/runtime/sys_linux_arm.s ++++ b/src/runtime/sys_linux_arm.s +@@ -11,7 +11,7 @@ + #include "textflag.h" + + #define CLOCK_REALTIME 0 +-#define CLOCK_MONOTONIC 1 ++#define CLOCK_BOOTTIME 7 + + // for EABI, as we don't support OABI + #define SYS_BASE 0x0 +@@ -374,7 +374,7 @@ finish: + + // func nanotime1() int64 + TEXT runtime·nanotime1(SB),NOSPLIT,$12-8 +- MOVW $CLOCK_MONOTONIC, R0 ++ MOVW $CLOCK_BOOTTIME, R0 + MOVW $spec-12(SP), R1 // timespec + + MOVW runtime·vdsoClockgettimeSym(SB), R4 +diff --git a/src/runtime/sys_linux_arm64.s b/src/runtime/sys_linux_arm64.s +index 38ff6ac330..6b819c5441 100644 +--- a/src/runtime/sys_linux_arm64.s ++++ b/src/runtime/sys_linux_arm64.s +@@ -14,7 +14,7 @@ + #define AT_FDCWD -100 + + #define CLOCK_REALTIME 0 +-#define CLOCK_MONOTONIC 1 ++#define CLOCK_BOOTTIME 7 + + #define SYS_exit 93 + #define SYS_read 63 +@@ -338,7 +338,7 @@ noswitch: + BIC $15, R1 + MOVD R1, RSP + +- MOVW $CLOCK_MONOTONIC, R0 ++ MOVW $CLOCK_BOOTTIME, R0 + MOVD runtime·vdsoClockgettimeSym(SB), R2 + CBZ R2, fallback + +diff --git a/src/runtime/sys_linux_mips64x.s b/src/runtime/sys_linux_mips64x.s +index 47f2da524d..a8b387f193 100644 +--- a/src/runtime/sys_linux_mips64x.s ++++ b/src/runtime/sys_linux_mips64x.s +@@ -326,7 +326,7 @@ noswitch: + AND $~15, R1 // Align for C code + MOVV R1, R29 + +- MOVW $1, R4 // CLOCK_MONOTONIC ++ MOVW $7, R4 // CLOCK_BOOTTIME + MOVV $0(R29), R5 + + MOVV runtime·vdsoClockgettimeSym(SB), R25 +@@ -336,7 +336,7 @@ noswitch: + // see walltime for detail + BEQ R2, R0, finish + MOVV R0, runtime·vdsoClockgettimeSym(SB) +- MOVW $1, R4 // CLOCK_MONOTONIC ++ MOVW $7, R4 // CLOCK_BOOTTIME + MOVV $0(R29), R5 + JMP fallback + +diff --git a/src/runtime/sys_linux_mipsx.s b/src/runtime/sys_linux_mipsx.s +index 5e6b6c1504..7f5fd2a80e 100644 +--- a/src/runtime/sys_linux_mipsx.s ++++ b/src/runtime/sys_linux_mipsx.s +@@ -243,7 +243,7 @@ TEXT runtime·walltime(SB),NOSPLIT,$8-12 + RET + + TEXT runtime·nanotime1(SB),NOSPLIT,$8-8 +- MOVW $1, R4 // CLOCK_MONOTONIC ++ MOVW $7, R4 // CLOCK_BOOTTIME + MOVW $4(R29), R5 + MOVW $SYS_clock_gettime, R2 + SYSCALL +diff --git a/src/runtime/sys_linux_ppc64x.s b/src/runtime/sys_linux_ppc64x.s +index d0427a4807..05ee9fede9 100644 +--- a/src/runtime/sys_linux_ppc64x.s ++++ b/src/runtime/sys_linux_ppc64x.s +@@ -298,7 +298,7 @@ fallback: + JMP return + + TEXT runtime·nanotime1(SB),NOSPLIT,$16-8 +- MOVD $1, R3 // CLOCK_MONOTONIC ++ MOVD $7, R3 // CLOCK_BOOTTIME + + MOVD R1, R15 // R15 is unchanged by C code + MOVD g_m(g), R21 // R21 = m +diff --git a/src/runtime/sys_linux_s390x.s b/src/runtime/sys_linux_s390x.s +index 1448670b91..7d2ee3231c 100644 +--- a/src/runtime/sys_linux_s390x.s ++++ b/src/runtime/sys_linux_s390x.s +@@ -296,7 +296,7 @@ fallback: + RET + + TEXT runtime·nanotime1(SB),NOSPLIT,$32-8 +- MOVW $1, R2 // CLOCK_MONOTONIC ++ MOVW $7, R2 // CLOCK_BOOTTIME + + MOVD R15, R7 // Backup stack pointer + +-- +2.17.1 diff --git a/wireguard/libamnezia/libwg.go b/wireguard/libamnezia/libwg.go new file mode 100644 index 0000000000..a519822002 --- /dev/null +++ b/wireguard/libamnezia/libwg.go @@ -0,0 +1,74 @@ +/* SPDX-License-Identifier: Apache-2.0 + * + * Copyright (C) 2017-2019 Jason A. Donenfeld . All Rights Reserved. + * Copyright (C) 2021 Mullvad VPN AB. All Rights Reserved. + * Copyright (C) 2024 Nym Technologies SA . All Rights Reserved. + */ + +package main + +// #include +import "C" + +import ( + "bufio" + "bytes" + "runtime" + "unsafe" + + "github.com/nymtech/nym-vpn-client/wireguard/libwg/container" + "github.com/amnezia-vpn/amneziawg-go/device" +) + +const ( + ERROR_GENERAL_FAILURE = -1 + ERROR_INTERMITTENT_FAILURE = -2 +) + +type TunnelContext struct { + Device *device.Device + Logger *device.Logger +} + +var tunnels container.Container[TunnelContext] + +func init() { + tunnels = container.New[TunnelContext]() +} + +//export wgTurnOff +func wgTurnOff(tunnelHandle int32) { + { + tunnel, err := tunnels.Remove(tunnelHandle) + if err != nil { + return + } + tunnel.Device.Close() + } + // Calling twice convinces the GC to release NOW. + runtime.GC() + runtime.GC() +} + +//export wgGetConfig +func wgGetConfig(tunnelHandle int32) *C.char { + tunnel, err := tunnels.Get(tunnelHandle) + if err != nil { + return nil + } + settings := new(bytes.Buffer) + writer := bufio.NewWriter(settings) + if err := tunnel.Device.IpcGetOperation(writer); err != nil { + tunnel.Logger.Errorf("Failed to get config for tunnel: %s\n", err) + return nil + } + writer.Flush() + return C.CString(settings.String()) +} + +//export wgFreePtr +func wgFreePtr(ptr unsafe.Pointer) { + C.free(ptr) +} + +func main() {} diff --git a/wireguard/libamnezia/libwg_android.go b/wireguard/libamnezia/libwg_android.go new file mode 100644 index 0000000000..9470ed378b --- /dev/null +++ b/wireguard/libamnezia/libwg_android.go @@ -0,0 +1,103 @@ +/* SPDX-License-Identifier: Apache-2.0 + * + * Copyright (C) 2017-2019 Jason A. Donenfeld . All Rights Reserved. + * Copyright (C) 2021 Mullvad VPN AB. All Rights Reserved. + * Copyright (C) 2024 Nym Technologies SA . All Rights Reserved. + */ + +package main + +import ( + "C" + "bufio" + "strings" + "unsafe" + + "golang.org/x/sys/unix" + + "github.com/amnezia-vpn/amneziawg-go/conn" + "github.com/amnezia-vpn/amneziawg-go/device" + "github.com/amnezia-vpn/amneziawg-go/tun" + + "github.com/nymtech/nym-vpn-client/wireguard/libwg/logging" +) + +// Redefined here because otherwise the compiler doesn't realize it's a type alias for a type that's safe to export. +// Taken from the contained logging package. +type LogSink = unsafe.Pointer +type LogContext = unsafe.Pointer + +//export wgTurnOn +func wgTurnOn(cSettings *C.char, fd int, logSink LogSink, logContext LogContext) int32 { + logger := logging.NewLogger(logSink, logContext) + + if cSettings == nil { + logger.Errorf("cSettings is null\n") + return ERROR_GENERAL_FAILURE + } + settings := C.GoString(cSettings) + + tunDevice, _, err := tun.CreateUnmonitoredTUNFromFD(fd) + if err != nil { + logger.Errorf("%s\n", err) + unix.Close(fd) + if err.Error() == "bad file descriptor" { + return ERROR_INTERMITTENT_FAILURE + } + return ERROR_GENERAL_FAILURE + } + + device := device.NewDevice(tunDevice, conn.NewStdNetBind(), logger) + + setErr := device.IpcSetOperation(bufio.NewReader(strings.NewReader(settings))) + if setErr != nil { + logger.Errorf("%s\n", setErr) + device.Close() + return ERROR_INTERMITTENT_FAILURE + } + + device.DisableSomeRoamingForBrokenMobileSemantics() + device.Up() + + context := TunnelContext{ + Device: device, + Logger: logger, + } + + handle, err := tunnels.Insert(context) + if err != nil { + logger.Errorf("%s\n", err) + device.Close() + return ERROR_GENERAL_FAILURE + } + + return handle +} + +//export wgGetSocketV4 +func wgGetSocketV4(tunnelHandle int32) int32 { + tunnel, err := tunnels.Get(tunnelHandle) + if err != nil { + return ERROR_GENERAL_FAILURE + } + peek := tunnel.Device.Bind().(conn.PeekLookAtSocketFd) + fd, err := peek.PeekLookAtSocketFd4() + if err != nil { + return ERROR_GENERAL_FAILURE + } + return int32(fd) +} + +//export wgGetSocketV6 +func wgGetSocketV6(tunnelHandle int32) int32 { + tunnel, err := tunnels.Get(tunnelHandle) + if err != nil { + return ERROR_GENERAL_FAILURE + } + peek := tunnel.Device.Bind().(conn.PeekLookAtSocketFd) + fd, err := peek.PeekLookAtSocketFd6() + if err != nil { + return ERROR_GENERAL_FAILURE + } + return int32(fd) +} diff --git a/wireguard/libamnezia/libwg_default.go b/wireguard/libamnezia/libwg_default.go new file mode 100644 index 0000000000..050cdb8799 --- /dev/null +++ b/wireguard/libamnezia/libwg_default.go @@ -0,0 +1,97 @@ +//go:build (darwin || linux) && !android && !ios + +/* SPDX-License-Identifier: Apache-2.0 + * + * Copyright (C) 2017-2019 Jason A. Donenfeld . All Rights Reserved. + * Copyright (C) 2021 Mullvad VPN AB. All Rights Reserved. + * Copyright (C) 2024 Nym Technologies SA . All Rights Reserved. + */ + +package main + +// #include +import "C" +import ( + "bufio" + "os" + "strings" + "unsafe" + + "github.com/amnezia-vpn/amneziawg-go/conn" + "github.com/amnezia-vpn/amneziawg-go/device" + "github.com/amnezia-vpn/amneziawg-go/tun" + + "github.com/nymtech/nym-vpn-client/wireguard/libwg/logging" +) + +// Redefined here because otherwise the compiler doesn't realize it's a type alias for a type that's safe to export. +// Taken from the contained logging package. +type LogSink = unsafe.Pointer +type LogContext = unsafe.Pointer + +//export wgTurnOn +func wgTurnOn(mtu int, cSettings *C.char, fd int, logSink LogSink, logContext LogContext) int32 { + logger := logging.NewLogger(logSink, logContext) + + if cSettings == nil { + logger.Errorf("cSettings is null\n") + return ERROR_GENERAL_FAILURE + } + settings := C.GoString(cSettings) + + file := os.NewFile(uintptr(fd), "") + tunDevice, err := tun.CreateTUNFromFile(file, mtu) + if err != nil { + logger.Errorf("%s\n", err) + if err.Error() == "bad file descriptor" { + return ERROR_INTERMITTENT_FAILURE + } + return ERROR_GENERAL_FAILURE + } + + device := device.NewDevice(tunDevice, conn.NewDefaultBind(), logger) + + setErr := device.IpcSetOperation(bufio.NewReader(strings.NewReader(settings))) + if setErr != nil { + logger.Errorf("%s\n", setErr) + device.Close() + return ERROR_INTERMITTENT_FAILURE + } + + device.Up() + + context := TunnelContext{ + Device: device, + Logger: logger, + } + + handle, err := tunnels.Insert(context) + if err != nil { + logger.Errorf("%s\n", err) + device.Close() + return ERROR_GENERAL_FAILURE + } + + return handle +} + +//export wgSetConfig +func wgSetConfig(tunnelHandle int32, cSettings *C.char) int32 { + tunnel, err := tunnels.Get(tunnelHandle) + if err != nil { + return ERROR_GENERAL_FAILURE + } + if cSettings == nil { + tunnel.Logger.Errorf("cSettings is null\n") + return ERROR_GENERAL_FAILURE + } + settings := C.GoString(cSettings) + + setError := tunnel.Device.IpcSetOperation(bufio.NewReader(strings.NewReader(settings))) + if setError != nil { + tunnel.Logger.Errorf("Failed to set device configuration\n") + tunnel.Logger.Errorf("%s\n", setError) + return ERROR_GENERAL_FAILURE + } + return 0 +} diff --git a/wireguard/libamnezia/libwg_ios.go b/wireguard/libamnezia/libwg_ios.go new file mode 100644 index 0000000000..d45ef6a4cd --- /dev/null +++ b/wireguard/libamnezia/libwg_ios.go @@ -0,0 +1,97 @@ +/* SPDX-License-Identifier: MIT + * + * Copyright (C) 2018-2019 Jason A. Donenfeld . All Rights Reserved. + * Copyright (C) 2024 Nym Technologies SA . All Rights Reserved. + */ + +package main + +import "C" +import ( + "os" + "time" + "unsafe" + + "github.com/nymtech/nym-vpn-client/wireguard/libwg/logging" + "golang.org/x/sys/unix" + "github.com/amnezia-vpn/amneziawg-go/conn" + "github.com/amnezia-vpn/amneziawg-go/device" + "github.com/amnezia-vpn/amneziawg-go/tun" +) + +// Redefined here because otherwise the compiler doesn't realize it's a type alias for a type that's safe to export. +// Taken from the contained logging package. +type LogSink = unsafe.Pointer +type LogContext = unsafe.Pointer + +//export wgTurnOn +func wgTurnOn(settings *C.char, tunFd int32, logSink LogSink, logContext LogContext) int32 { + logger := logging.NewLogger(logSink, logContext) + + dupTunFd, err := unix.Dup(int(tunFd)) + if err != nil { + logger.Errorf("Unable to dup tun fd: %v", err) + return ERROR_GENERAL_FAILURE + } + + err = unix.SetNonblock(dupTunFd, true) + if err != nil { + logger.Errorf("Unable to set tun fd as non blocking: %v", err) + unix.Close(dupTunFd) + return ERROR_GENERAL_FAILURE + } + tun, err := tun.CreateTUNFromFile(os.NewFile(uintptr(dupTunFd), "/dev/tun"), 0) + if err != nil { + logger.Errorf("Unable to create new tun device from fd: %v", err) + unix.Close(dupTunFd) + return ERROR_INTERMITTENT_FAILURE + } + logger.Verbosef("Attaching to interface") + dev := device.NewDevice(tun, conn.NewStdNetBind(), logger) + + err = dev.IpcSet(C.GoString(settings)) + if err != nil { + logger.Errorf("Unable to set IPC settings: %v", err) + unix.Close(dupTunFd) + return ERROR_GENERAL_FAILURE + } + + dev.DisableSomeRoamingForBrokenMobileSemantics() + dev.Up() + + logger.Verbosef("Device started") + + context := TunnelContext{ + Device: dev, + Logger: logger, + } + + handle, err := tunnels.Insert(context) + if err != nil { + logger.Errorf("%s", err) + dev.Close() + return ERROR_GENERAL_FAILURE + } + + return handle +} + +//export wgBumpSockets +func wgBumpSockets(tunnelHandle int32) { + tunnel, err := tunnels.Get(tunnelHandle) + if err != nil { + return + } + go func() { + for i := 0; i < 10; i++ { + err := tunnel.Device.BindUpdate() + if err == nil { + tunnel.Device.SendKeepalivesToPeersWithCurrentKeypair() + return + } + tunnel.Logger.Errorf("Unable to update bind, try %d: %v", i+1, err) + time.Sleep(time.Second / 2) + } + tunnel.Logger.Errorf("Gave up trying to update bind; tunnel is likely dysfunctional") + }() +} diff --git a/wireguard/libamnezia/libwg_mobile.go b/wireguard/libamnezia/libwg_mobile.go new file mode 100644 index 0000000000..d80d4b0126 --- /dev/null +++ b/wireguard/libamnezia/libwg_mobile.go @@ -0,0 +1,40 @@ +//go:build ios || android + +/* SPDX-License-Identifier: MIT + * + * Copyright (C) 2018-2019 Jason A. Donenfeld . All Rights Reserved. + * Copyright (C) 2024 Nym Technologies SA . All Rights Reserved. + */ + +package main + +import "C" + +import ( + "bufio" + "strings" +) + +//export wgSetConfig +func wgSetConfig(tunnelHandle int32, cSettings *C.char) int32 { + tunnel, err := tunnels.Get(tunnelHandle) + if err != nil { + return ERROR_GENERAL_FAILURE + } + if cSettings == nil { + tunnel.Logger.Errorf("cSettings is null\n") + return ERROR_GENERAL_FAILURE + } + settings := C.GoString(cSettings) + + err = tunnel.Device.IpcSetOperation(bufio.NewReader(strings.NewReader(settings))) + if err != nil { + tunnel.Logger.Errorf("Failed to set device configuration\n") + tunnel.Logger.Errorf("%s\n", err) + return ERROR_GENERAL_FAILURE + } + + tunnel.Device.DisableSomeRoamingForBrokenMobileSemantics() + + return 0 +} diff --git a/wireguard/libamnezia/libwg_windows.go b/wireguard/libamnezia/libwg_windows.go new file mode 100644 index 0000000000..0a13431dd6 --- /dev/null +++ b/wireguard/libamnezia/libwg_windows.go @@ -0,0 +1,153 @@ +/* SPDX-License-Identifier: Apache-2.0 + * + * Copyright (C) 2017-2019 Jason A. Donenfeld . All Rights Reserved. + * Copyright (C) 2021 Mullvad VPN AB. All Rights Reserved. + * Copyright (C) 2024 Nym Technologies SA . All Rights Reserved. + */ + +package main + +// #include +import "C" + +import ( + "bufio" + "strings" + "unsafe" + + "golang.org/x/sys/windows" + + "github.com/amnezia-vpn/amneziawg-go/conn" + "github.com/amnezia-vpn/amneziawg-go/device" + "github.com/amnezia-vpn/amneziawg-go/tun" + + "github.com/nymtech/nym-vpn-client/wireguard/libwg/logging" +) + +// Redefined here because otherwise the compiler doesn't realize it's a type alias for a type that's safe to export. +// Taken from the contained logging package. +type LogSink = unsafe.Pointer +type LogContext = unsafe.Pointer + +//export wgTurnOn +func wgTurnOn(cIfaceName *C.char, mtu int, cSettings *C.char, cIfaceNameOut **C.char, cLuidOut *uint64, logSink LogSink, logContext LogContext) int32 { + logger := logging.NewLogger(logSink, logContext) + if cIfaceNameOut != nil { + *cIfaceNameOut = nil + } + + if cIfaceName == nil { + logger.Errorf("cIfaceName is null\n") + return ERROR_GENERAL_FAILURE + } + + if cSettings == nil { + logger.Errorf("cSettings is null\n") + return ERROR_GENERAL_FAILURE + } + + settings := C.GoString(cSettings) + ifaceName := C.GoString(cIfaceName) + + // {AFE43773-E1F8-4EBB-8536-576AB86AFE9A} + networkId := windows.GUID{0xafe43773, 0xe1f8, 0x4ebb, [8]byte{0x85, 0x36, 0x57, 0x6a, 0xb8, 0x6a, 0xfe, 0x9a}} + + tun.WintunTunnelType = "Mullvad" + + wintun, err := tun.CreateTUNWithRequestedGUID(ifaceName, &networkId, mtu) + if err != nil { + logger.Errorf("Failed to create tunnel\n") + logger.Errorf("%s\n", err) + return ERROR_INTERMITTENT_FAILURE + } + + nativeTun := wintun.(*tun.NativeTun) + + actualInterfaceName, err := nativeTun.Name() + if err != nil { + nativeTun.Close() + logger.Errorf("Failed to determine name of wintun adapter\n") + return ERROR_GENERAL_FAILURE + } + if actualInterfaceName != ifaceName { + // WireGuard picked a different name for the adapter than the one we expected. + // This indicates there is already an adapter with the name we intended to use. + logger.Verbosef("Failed to create adapter with specific name\n") + } + + device := device.NewDevice(wintun, conn.NewDefaultBind(), logger) + + setError := device.IpcSetOperation(bufio.NewReader(strings.NewReader(settings))) + if setError != nil { + logger.Errorf("Failed to set device configuration\n") + logger.Errorf("%s\n", setError) + device.Close() + return ERROR_GENERAL_FAILURE + } + + device.Up() + + context := TunnelContext{ + Device: device, + Logger: logger, + } + + handle, err := tunnels.Insert(context) + if err != nil { + logger.Errorf("%s\n", err) + device.Close() + return ERROR_GENERAL_FAILURE + } + + if cIfaceNameOut != nil { + *cIfaceNameOut = C.CString(actualInterfaceName) + } + if cLuidOut != nil { + *cLuidOut = nativeTun.LUID() + } + + return handle +} + +//export wgSetConfig +func wgSetConfig(tunnelHandle int32, cSettings *C.char) int32 { + tunnel, err := tunnels.Get(tunnelHandle) + if err != nil { + return ERROR_GENERAL_FAILURE + } + if cSettings == nil { + tunnel.Logger.Errorf("cSettings is null\n") + return ERROR_GENERAL_FAILURE + } + settings := C.GoString(cSettings) + + setError := tunnel.Device.IpcSetOperation(bufio.NewReader(strings.NewReader(settings))) + if setError != nil { + tunnel.Logger.Errorf("Failed to set device configuration\n") + tunnel.Logger.Errorf("%s\n", setError) + return ERROR_GENERAL_FAILURE + } + return 0 +} + +//export wgRebindTunnelSocket +func wgRebindTunnelSocket(family uint16, interfaceIndex uint32) { + tunnels.ForEach(func(tunnel TunnelContext) { + blackhole := (interfaceIndex == 0) + bind := tunnel.Device.Bind().(conn.BindSocketToInterface) + + if family == windows.AF_INET { + tunnel.Logger.Verbosef("Binding v4 socket to interface %d (blackhole=%v)\n", interfaceIndex, blackhole) + err := bind.BindSocketToInterface4(interfaceIndex, blackhole) + if err != nil { + tunnel.Logger.Verbosef("%s\n", err) + } + } else if family == windows.AF_INET6 { + tunnel.Logger.Verbosef("Binding v6 socket to interface %d (blackhole=%v)\n", interfaceIndex, blackhole) + err := bind.BindSocketToInterface6(interfaceIndex, blackhole) + if err != nil { + tunnel.Logger.Verbosef("%s\n", err) + } + } + }) +} diff --git a/wireguard/libamnezia/logging/logging.go b/wireguard/libamnezia/logging/logging.go new file mode 100644 index 0000000000..6d81ccf7a0 --- /dev/null +++ b/wireguard/libamnezia/logging/logging.go @@ -0,0 +1,60 @@ +/* SPDX-License-Identifier: Apache-2.0 + * + * Copyright (C) 2017-2019 Jason A. Donenfeld . All Rights Reserved. + * Copyright (C) 2021 Mullvad VPN AB. All Rights Reserved. + */ + +package logging + +// #include +// #include +// #ifndef WIN32 +// #define __stdcall +// #endif +// typedef void (__stdcall *LogSink)(unsigned int, const char *, void *); +// static void callLogSink(void *logSink, int level, const char *message, void *context) +// { +// ((LogSink)logSink)((unsigned int)level, message, context); +// } +import "C" + +import ( + "log" + "unsafe" + + "github.com/amnezia-vpn/amneziawg-go/device" +) + +// Define type aliases. +type LogSink = unsafe.Pointer +type LogContext = unsafe.Pointer + +type Logger struct { + sink LogSink + context LogContext + level C.int +} + +func (l *Logger) Write(message []byte) (int, error) { + msg := C.CString(string(message)) + C.callLogSink(l.sink, l.level, msg, l.context) + C.free(unsafe.Pointer(msg)) + return len(message), nil +} + +func NewLogger(logSink LogSink, logContext LogContext) *device.Logger { + logger := new(device.Logger) + + logger.Verbosef = log.New( + &Logger{sink: logSink, context: logContext, level: device.LogLevelVerbose}, + "", + 0, + ).Printf + logger.Errorf = log.New( + &Logger{sink: logSink, context: logContext, level: device.LogLevelError}, + "", + 0, + ).Printf + + return logger +} diff --git a/wireguard/libamnezia/netstack_android.go b/wireguard/libamnezia/netstack_android.go new file mode 100644 index 0000000000..1c2b8d2690 --- /dev/null +++ b/wireguard/libamnezia/netstack_android.go @@ -0,0 +1,39 @@ +/* SPDX-License-Identifier: MIT + * + * Copyright (C) 2018-2019 Jason A. Donenfeld . All Rights Reserved. + * Copyright (C) 2024 Nym Technologies SA . All Rights Reserved. + */ + +package main + +import "C" + +import "github.com/amnezia-vpn/amneziawg-go/conn" + +//export wgNetGetSocketV4 +func wgNetGetSocketV4(tunnelHandle int32) int32 { + tunnel, err := netTunnelHandles.Get(tunnelHandle) + if err != nil { + return ERROR_GENERAL_FAILURE + } + peek := tunnel.Device.Bind().(conn.PeekLookAtSocketFd) + fd, err := peek.PeekLookAtSocketFd4() + if err != nil { + return ERROR_GENERAL_FAILURE + } + return int32(fd) +} + +//export wgNetGetSocketV6 +func wgNetGetSocketV6(tunnelHandle int32) int32 { + tunnel, err := netTunnelHandles.Get(tunnelHandle) + if err != nil { + return ERROR_GENERAL_FAILURE + } + peek := tunnel.Device.Bind().(conn.PeekLookAtSocketFd) + fd, err := peek.PeekLookAtSocketFd6() + if err != nil { + return ERROR_GENERAL_FAILURE + } + return int32(fd) +} diff --git a/wireguard/libamnezia/netstack_mobile.go b/wireguard/libamnezia/netstack_mobile.go new file mode 100644 index 0000000000..b1f197c8f6 --- /dev/null +++ b/wireguard/libamnezia/netstack_mobile.go @@ -0,0 +1,200 @@ +//go:build ios || android + +/* SPDX-License-Identifier: MIT + * + * Copyright (C) 2018-2019 Jason A. Donenfeld . All Rights Reserved. + * Copyright (C) 2024 Nym Technologies SA . All Rights Reserved. + */ + +package main + +import "C" + +import ( + "net/netip" + "strings" + + "github.com/nymtech/nym-vpn-client/wireguard/libwg/container" + "github.com/nymtech/nym-vpn-client/wireguard/libwg/logging" + "github.com/nymtech/nym-vpn-client/wireguard/libwg/udp_forwarder" + "github.com/amnezia-vpn/amneziawg-go/conn" + "github.com/amnezia-vpn/amneziawg-go/device" + "github.com/amnezia-vpn/amneziawg-go/tun/netstack" +) + +type netTunnelHandle struct { + *device.Device + *netstack.Net + *device.Logger +} + +var netTunnelHandles container.Container[netTunnelHandle] +var udpForwarders container.Container[*udp_forwarder.UDPForwarder] + +func init() { + netTunnelHandles = container.New[netTunnelHandle]() + udpForwarders = container.New[*udp_forwarder.UDPForwarder]() +} + +//export wgNetTurnOn +func wgNetTurnOn(localAddresses *C.char, dnsAddresses *C.char, mtu int, settings *C.char, logSink LogSink, logContext LogContext) int32 { + logger := logging.NewLogger(logSink, logContext) + + // Parse comma separated list of IP addresses + tunAddrs, err := parseIPAddrs(C.GoString(localAddresses)) + if err != nil { + logger.Errorf("Failed to parse local addresses: %v", err) + return ERROR_GENERAL_FAILURE + } + + // Parse comma separated list of DNS addresses + dnsAddrs, err := parseIPAddrs(C.GoString(dnsAddresses)) + if err != nil { + logger.Errorf("Failed to parse dns addresses: %v", err) + return ERROR_GENERAL_FAILURE + } + + tun, tnet, err := netstack.CreateNetTUN(tunAddrs, dnsAddrs, mtu) + if err != nil { + logger.Errorf("Failed to create net tun: %v", err) + return ERROR_GENERAL_FAILURE + } + + dev := device.NewDevice( + tun, + conn.NewDefaultBind(), + logger, + ) + if dev == nil { + logger.Errorf("Failed to create device") + return ERROR_GENERAL_FAILURE + } + + err = dev.IpcSet(C.GoString(settings)) + if err != nil { + logger.Errorf("Unable to set IPC settings: %v", err) + dev.Close() + return ERROR_GENERAL_FAILURE + } + + dev.DisableSomeRoamingForBrokenMobileSemantics() + err = dev.Up() + if err != nil { + logger.Errorf("Failed to set device state to Up: %v", err) + dev.Close() + return ERROR_GENERAL_FAILURE + } + + logger.Verbosef("Net device started") + + i, err := netTunnelHandles.Insert(netTunnelHandle{dev, tnet, logger}) + if err != nil { + logger.Errorf("Failed to store tunnel: %v", err) + dev.Close() + return ERROR_GENERAL_FAILURE + } + + return i +} + +//export wgNetTurnOff +func wgNetTurnOff(tunnelHandle int32) { + dev, err := netTunnelHandles.Remove(tunnelHandle) + if err != nil { + return + } + dev.Close() +} + +//export wgNetSetConfig +func wgNetSetConfig(tunnelHandle int32, settings *C.char) int64 { + dev, err := netTunnelHandles.Get(tunnelHandle) + if err != nil { + return 0 + } + err = dev.IpcSet(C.GoString(settings)) + if err != nil { + dev.Errorf("Unable to set IPC settings: %v", err) + if ipcErr, ok := err.(*device.IPCError); ok { + return ipcErr.ErrorCode() + } + return ERROR_GENERAL_FAILURE + } + + dev.DisableSomeRoamingForBrokenMobileSemantics() + + return 0 +} + +//export wgNetGetConfig +func wgNetGetConfig(tunnelHandle int32) *C.char { + device, err := netTunnelHandles.Get(tunnelHandle) + if err != nil { + return nil + } + settings, err := device.IpcGet() + if err != nil { + return nil + } + return C.CString(settings) +} + +//export wgNetOpenConnectionThroughTunnel +func wgNetOpenConnectionThroughTunnel(entryTunnelHandle int32, listenPort uint16, clientPort uint16, exitEndpointStr *C.char, logSink LogSink, logContext LogContext) int32 { + logger := logging.NewLogger(logSink, logContext) + + dev, err := netTunnelHandles.Get(entryTunnelHandle) + if err != nil { + dev.Errorf("Invalid tunnel handle: %d", entryTunnelHandle) + return ERROR_GENERAL_FAILURE + } + + exitEndpoint, err := netip.ParseAddrPort(C.GoString(exitEndpointStr)) + if err != nil { + dev.Errorf("Failed to parse endpoint: %v", err) + return ERROR_GENERAL_FAILURE + } + + forwarderConfig := udp_forwarder.UDPForwarderConfig{ + ListenPort: listenPort, + ClientPort: clientPort, + ExitEndpoint: exitEndpoint, + } + + udpForwarder, err := udp_forwarder.New(forwarderConfig, dev.Net, logger) + if err != nil { + dev.Errorf("Failed to create udp forwarder: %v", err) + return ERROR_GENERAL_FAILURE + } + + forwarderHandle, err := udpForwarders.Insert(udpForwarder) + if err != nil { + dev.Errorf("Failed to store udp forwarder: %v", err) + udpForwarder.Close() + return ERROR_GENERAL_FAILURE + } + + return forwarderHandle +} + +//export wgNetCloseConnectionThroughTunnel +func wgNetCloseConnectionThroughTunnel(udpForwarderHandle int32) { + udpForwarder, err := udpForwarders.Remove(udpForwarderHandle) + if err != nil { + return + } + (*udpForwarder).Close() +} + +// Parse a list of comma-separated IP addresses into array of netip.Addr structs. +func parseIPAddrs(input string) ([]netip.Addr, error) { + addrs := []netip.Addr{} + for _, s := range strings.Split(input, ",") { + addr, err := netip.ParseAddr(strings.TrimSpace(s)) + if err != nil { + return addrs, err + } + addrs = append(addrs, addr) + } + return addrs, nil +} diff --git a/wireguard/libamnezia/udp_forwarder/udp_forwarder.go b/wireguard/libamnezia/udp_forwarder/udp_forwarder.go new file mode 100644 index 0000000000..54217ece85 --- /dev/null +++ b/wireguard/libamnezia/udp_forwarder/udp_forwarder.go @@ -0,0 +1,194 @@ +/* SPDX-License-Identifier: GPL-3.0-only + * + * Copyright 2024 - Nym Technologies SA + */ + +package udp_forwarder + +import ( + "net" + "net/netip" + "sync" + "time" + + "github.com/amnezia-vpn/amneziawg-go/device" + "github.com/amnezia-vpn/amneziawg-go/tun/netstack" + "gvisor.dev/gvisor/pkg/tcpip/adapters/gonet" +) + +const UDP_WRITE_TIMEOUT = time.Duration(5) * time.Second +const MAX_UDP_DATAGRAM_LEN = 65535 + +type UDPForwarderConfig struct { + // Listen port for incoming WireGuard traffic. + // For IPv4 exit endpoint, the listening port is bound to 127.0.0.1, for IPv6 it's ::1. + ListenPort uint16 + + // Client port on loopback from which the incoming WireGuard connection will be received. + // Only packets from this port will be passed through to the exit endpoint. + ClientPort uint16 + + // Exit endpoint which will receive the raw WireGuard packets received on the listen port. + // The connection to exit endpoint is established over the entry tunnel, thus it creates + // a tunnel inside of tunnel. + ExitEndpoint netip.AddrPort +} + +// UDP forwarder that creates a bidirectional connection between a local and exit UDP endpoints +// over the netstack-based WireGuard tunnel. +type UDPForwarder struct { + // Logger. + logger *device.Logger + + // Netstack tunnel wrapping the inbound WireGuard traffic. + tnet *netstack.Net + + // UDP listener that receives inbound WireGuard traffic destined to exit endpoint. + listener *net.UDPConn + + // Outbound connection to the exit endpoint over the entry tunnel. + outbound *gonet.UDPConn + + // Wait group used to signal when all goroutines have finished execution. + waitGroup *sync.WaitGroup +} + +func New(config UDPForwarderConfig, tnet *netstack.Net, logger *device.Logger) (*UDPForwarder, error) { + var listenAddr *net.UDPAddr + var clientAddr *net.UDPAddr + + // Use the same ip protocol family as exit endpoint. + if config.ExitEndpoint.Addr().Is4() { + loopback := netip.AddrFrom4([4]byte{127, 0, 0, 1}) + listenAddr = net.UDPAddrFromAddrPort(netip.AddrPortFrom(loopback, config.ListenPort)) + clientAddr = net.UDPAddrFromAddrPort(netip.AddrPortFrom(loopback, config.ClientPort)) + } else { + listenAddr = net.UDPAddrFromAddrPort(netip.AddrPortFrom(netip.IPv6Loopback(), config.ListenPort)) + clientAddr = net.UDPAddrFromAddrPort(netip.AddrPortFrom(netip.IPv6Loopback(), config.ClientPort)) + } + + listener, err := net.ListenUDP("udp", listenAddr) + if err != nil { + return nil, err + } + + outbound, err := tnet.DialUDPAddrPort(netip.AddrPort{}, config.ExitEndpoint) + if err != nil { + return nil, err + } + + waitGroup := &sync.WaitGroup{} + wrapper := &UDPForwarder{ + logger, + tnet, + listener, + outbound, + waitGroup, + } + + waitGroup.Add(2) + go wrapper.RoutineHandleInbound(listener, outbound, clientAddr) + go wrapper.RoutineHandleOutbound(listener, outbound, clientAddr) + + return wrapper, nil +} + +func (w *UDPForwarder) Close() { + // Close all connections. This should release any blocking ReadFromUDP() calls. + w.listener.Close() + w.outbound.Close() + + // Wait for all routines to complete. + w.waitGroup.Wait() +} + +func (w *UDPForwarder) Wait() { + w.waitGroup.Wait() +} + +func (w *UDPForwarder) RoutineHandleInbound(inbound *net.UDPConn, outbound *gonet.UDPConn, clientAddr *net.UDPAddr) { + defer w.waitGroup.Done() + + inboundBuffer := make([]byte, MAX_UDP_DATAGRAM_LEN) + + w.logger.Verbosef("udpforwarder(inbound): listening on %s", inbound.LocalAddr().String()) + defer w.logger.Verbosef("udpforwarder(inbound): closed") + + for { + // Receive the WireGuard packet from local port + bytesRead, senderAddr, err := inbound.ReadFromUDP(inboundBuffer) + if err != nil { + w.logger.Errorf("udpforwarder(inbound): %s", err.Error()) + // todo: handle error + return + } + + // Drop packet from unknown sender. + if !senderAddr.IP.IsLoopback() || senderAddr.Port != clientAddr.Port { + w.logger.Verbosef("udpforwarder(inbound): drop packet from unknown sender: %s, expected: %s.", senderAddr.String(), clientAddr.String()) + continue + } + + // Set write timeout for outbound. + deadline := time.Now().Add(UDP_WRITE_TIMEOUT) + err = outbound.SetWriteDeadline(deadline) + if err != nil { + w.logger.Errorf("udpforwarder(inbound): %s", err.Error()) + // todo: handle error + continue + } + + // Forward the packet over the outbound connection via another WireGuard tunnel. + _, err = outbound.Write(inboundBuffer[:bytesRead]) + if err != nil { + w.logger.Errorf("udpforwarder(inbound): %s", err.Error()) + // todo: handle error + continue + } + } +} + +func (w *UDPForwarder) RoutineHandleOutbound(inbound *net.UDPConn, outbound *gonet.UDPConn, clientAddr *net.UDPAddr) { + defer w.waitGroup.Done() + + remoteAddr := outbound.RemoteAddr().(*net.UDPAddr) + w.logger.Verbosef("udpforwarder(outbound): dial %s", remoteAddr.String()) + defer w.logger.Verbosef("udpforwarder(outbound): closed") + + outboundBuffer := make([]byte, MAX_UDP_DATAGRAM_LEN) + + for { + // Receive WireGuard packet from remote server. + bytesRead, senderAddr, err := outbound.ReadFrom(outboundBuffer) + if err != nil { + w.logger.Errorf("udpforwarder(outbound): %s", err.Error()) + // todo: handle error + return + } + // Cast net.Addr to net.UDPAddr + senderUDPAddr := senderAddr.(*net.UDPAddr) + + // Drop packet from unknown sender. + if !senderUDPAddr.IP.Equal(remoteAddr.IP) || senderUDPAddr.Port != remoteAddr.Port { + w.logger.Verbosef("udpforwarder(outbound): drop packet from unknown sender: %s, expected: %s", senderUDPAddr.String(), remoteAddr.String()) + continue + } + + // Set write timeout for inbound. + deadline := time.Now().Add(UDP_WRITE_TIMEOUT) + err = inbound.SetWriteDeadline(deadline) + if err != nil { + w.logger.Errorf("udpforwarder(outbound): %s", err.Error()) + // todo: handle error + continue + } + + // Forward packet from remote to local client. + _, err = inbound.WriteToUDP(outboundBuffer[:bytesRead], clientAddr) + if err != nil { + w.logger.Errorf("udpforwarder(outbound): %s", err.Error()) + // todo: handle error + continue + } + } +} From 96f37a72fa2a7a2e4b1cceedf996c56afbb5424d Mon Sep 17 00:00:00 2001 From: Jack Wampler Date: Wed, 30 Oct 2024 11:14:56 -0600 Subject: [PATCH 2/5] AmneziaWG Gateway probe (#1416) --- nym-vpn-core/Cargo.lock | 1 + nym-vpn-core/Cargo.toml | 2 +- .../crates/nym-gateway-probe/README.md | 56 +++++ .../nym-gateway-probe/netstack_ping/gen.go | 4 + .../nym-gateway-probe/netstack_ping/go.mod | 7 +- .../nym-gateway-probe/netstack_ping/go.sum | 6 +- .../nym-gateway-probe/netstack_ping/impl.go | 38 +++- .../crates/nym-gateway-probe/src/lib.rs | 140 +++++++----- .../crates/nym-gateway-probe/src/netstack.rs | 8 +- .../crates/nym-gateway-probe/src/run.rs | 14 +- nym-vpn-core/crates/nym-vpnd/build.rs | 15 +- nym-vpn-core/crates/nym-wg-go/Cargo.toml | 1 + nym-vpn-core/crates/nym-wg-go/README.md | 39 ++++ nym-vpn-core/crates/nym-wg-go/src/amnezia.rs | 210 ++++++++++++++++++ nym-vpn-core/crates/nym-wg-go/src/lib.rs | 2 + nym-vpn-core/crates/nym-wg-go/src/netstack.rs | 48 +--- .../crates/nym-wg-go/src/wireguard_go.rs | 49 +--- wireguard/build-wireguard-go.sh | 64 ++++-- 18 files changed, 516 insertions(+), 188 deletions(-) create mode 100644 nym-vpn-core/crates/nym-gateway-probe/README.md create mode 100644 nym-vpn-core/crates/nym-wg-go/README.md create mode 100644 nym-vpn-core/crates/nym-wg-go/src/amnezia.rs diff --git a/nym-vpn-core/Cargo.lock b/nym-vpn-core/Cargo.lock index 766dd4cc35..19e50da430 100644 --- a/nym-vpn-core/Cargo.lock +++ b/nym-vpn-core/Cargo.lock @@ -4711,6 +4711,7 @@ dependencies = [ "base64 0.22.1", "hex", "ipnetwork", + "rand 0.8.5", "thiserror", "tracing", "x25519-dalek", diff --git a/nym-vpn-core/Cargo.toml b/nym-vpn-core/Cargo.toml index a5069f7f01..28517941ad 100644 --- a/nym-vpn-core/Cargo.toml +++ b/nym-vpn-core/Cargo.toml @@ -94,7 +94,7 @@ prost-types = "0.12.6" rand = "0.8.5" rand_chacha = "0.3.1" reqwest = { version = "0.11.27", default-features = false } -rust2go = "0.3.4" +rust2go = "0.3.16" serde = "1.0" serde_json = "1.0" sha2 = "0.10" diff --git a/nym-vpn-core/crates/nym-gateway-probe/README.md b/nym-vpn-core/crates/nym-gateway-probe/README.md new file mode 100644 index 0000000000..3389496d9b --- /dev/null +++ b/nym-vpn-core/crates/nym-gateway-probe/README.md @@ -0,0 +1,56 @@ +# Nym Gateway Probe + +Probe IPv4 and IPv6 interfaces of available gateways to check for the +set that passes a set of minumum service guarantees. + + +## Build + +These instructions assume a debian based system. Adjust accordingly for your +preffered platform. + +Install required dependencies +```sh +sudo apt install libdbus-1-dev libmnl-dev libnftnl-dev protobuf-compiler clang +``` + + +Build piece by piece +```sh +cd nym-vpn-core/ +# build the prober +cargo build -p nym-gateway-probe +``` + +You may need to adjust your `RUSTFLAGS` or `.cargo/config.toml` to ensure that +the golang wireguard library links properly. + +## Usage + +```sh +Usage: nym-gateway-probe [OPTIONS] + +Options: + -c, --config-env-file + Path pointing to an env file describing the network + -g, --gateway + The specific gateway specified by ID + -n, --no-log + Disable logging during probe + -a, --amnezia-args + Arguments to be appended to the wireguard config enabling amnezia-wg configuration + -h, --help + Print help + -V, --version + Print version +``` + +Examples + +```sh +# Run a basic probe against the node with id "qj3GgGYg..." +nym-gateway-probe -g "qj3GgGYgGZZ3HkFrtD1GU9UJ5oNXME9eD2xtmPLqYYw" + +# Run a probe against the node with id "qj3GgGYg..." using amnezia with junk packets enabled. +nym-gateway-probe -g "qj3GgGYgGZZ3HkFrtD1GU9UJ5oNXME9eD2xtmPLqYYw" -a "Jc=4\nJmin=40\mJmax=70\n" +``` diff --git a/nym-vpn-core/crates/nym-gateway-probe/netstack_ping/gen.go b/nym-vpn-core/crates/nym-gateway-probe/netstack_ping/gen.go index 91bda5cc0f..63567b5390 100644 --- a/nym-vpn-core/crates/nym-gateway-probe/netstack_ping/gen.go +++ b/nym-vpn-core/crates/nym-gateway-probe/netstack_ping/gen.go @@ -38,6 +38,7 @@ typedef struct NetstackRequestRef { uint8_t num_ping; uint64_t send_timeout_sec; uint64_t recv_timeout_sec; + struct StringRef awg_args; } NetstackRequestRef; // hack from: https://stackoverflow.com/a/69904977 @@ -225,6 +226,7 @@ type NetstackRequest struct { num_ping uint8 send_timeout_sec uint64 recv_timeout_sec uint64 + awg_args string } func newNetstackRequest(p C.NetstackRequestRef) NetstackRequest { @@ -239,6 +241,7 @@ func newNetstackRequest(p C.NetstackRequestRef) NetstackRequest { num_ping: newC_uint8_t(p.num_ping), send_timeout_sec: newC_uint64_t(p.send_timeout_sec), recv_timeout_sec: newC_uint64_t(p.recv_timeout_sec), + awg_args: newString(p.awg_args), } } func cntNetstackRequest(s *NetstackRequest, cnt *uint) [0]C.NetstackRequestRef { @@ -258,6 +261,7 @@ func refNetstackRequest(p *NetstackRequest, buffer *[]byte) C.NetstackRequestRef num_ping: refC_uint8_t(&p.num_ping, buffer), send_timeout_sec: refC_uint64_t(&p.send_timeout_sec, buffer), recv_timeout_sec: refC_uint64_t(&p.recv_timeout_sec, buffer), + awg_args: refString(&p.awg_args, buffer), } } diff --git a/nym-vpn-core/crates/nym-gateway-probe/netstack_ping/go.mod b/nym-vpn-core/crates/nym-gateway-probe/netstack_ping/go.mod index c4d4079c82..c72aecd60d 100644 --- a/nym-vpn-core/crates/nym-gateway-probe/netstack_ping/go.mod +++ b/nym-vpn-core/crates/nym-gateway-probe/netstack_ping/go.mod @@ -1,14 +1,17 @@ module github.com/nymtech/nym-vpn-client/nym-vpn-core/crates/nym-gateway-probe/netstack_ping -go 1.22 +go 1.22.3 + +toolchain go1.23.1 require ( + github.com/amnezia-vpn/amneziawg-go v0.2.12 golang.org/x/net v0.23.0 - golang.zx2c4.com/wireguard v0.0.0-20231211153847-12269c276173 ) require ( github.com/google/btree v1.0.1 // indirect + github.com/tevino/abool/v2 v2.1.0 // indirect golang.org/x/crypto v0.21.0 // indirect golang.org/x/sys v0.18.0 // indirect golang.org/x/time v0.0.0-20220210224613-90d013bbcef8 // indirect diff --git a/nym-vpn-core/crates/nym-gateway-probe/netstack_ping/go.sum b/nym-vpn-core/crates/nym-gateway-probe/netstack_ping/go.sum index a904fe6ec2..3bfc3cb95d 100644 --- a/nym-vpn-core/crates/nym-gateway-probe/netstack_ping/go.sum +++ b/nym-vpn-core/crates/nym-gateway-probe/netstack_ping/go.sum @@ -1,5 +1,9 @@ +github.com/amnezia-vpn/amneziawg-go v0.2.12 h1:CxIQETy5kZ0ip/dFBpmnDxAcS/KuIQaJkOxDv5OQhVI= +github.com/amnezia-vpn/amneziawg-go v0.2.12/go.mod h1:d7WpNfzCRLy7ufGElJBYpD58WRmNjyLyt3IDHPY8AmM= github.com/google/btree v1.0.1 h1:gK4Kx5IaGY9CD5sPJ36FHiBJ6ZXl0kilRiiCj+jdYp4= github.com/google/btree v1.0.1/go.mod h1:xXMiIv4Fb/0kKde4SpL7qlzvu5cMJDRkFDxJfI9uaxA= +github.com/tevino/abool/v2 v2.1.0 h1:7w+Vf9f/5gmKT4m4qkayb33/92M+Um45F2BkHOR+L/c= +github.com/tevino/abool/v2 v2.1.0/go.mod h1:+Lmlqk6bHDWHqN1cbxqhwEAwMPXgc8I1SDEamtseuXY= golang.org/x/crypto v0.21.0 h1:X31++rzVUdKhX5sWmSOFZxx8UW/ldWx55cbf08iNAMA= golang.org/x/crypto v0.21.0/go.mod h1:0BP7YvVV9gBbVKyeTG0Gyn+gZm94bibOW5BjDEYAOMs= golang.org/x/net v0.23.0 h1:7EYJ93RZ9vYSZAIb2x3lnuvqO5zneoD6IvWjuhfxjTs= @@ -10,7 +14,5 @@ golang.org/x/time v0.0.0-20220210224613-90d013bbcef8 h1:vVKdlvoWBphwdxWKrFZEuM0k golang.org/x/time v0.0.0-20220210224613-90d013bbcef8/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.zx2c4.com/wintun v0.0.0-20230126152724-0fa3db229ce2 h1:B82qJJgjvYKsXS9jeunTOisW56dUokqW/FOteYJJ/yg= golang.zx2c4.com/wintun v0.0.0-20230126152724-0fa3db229ce2/go.mod h1:deeaetjYA+DHMHg+sMSMI58GrEteJUUzzw7en6TJQcI= -golang.zx2c4.com/wireguard v0.0.0-20231211153847-12269c276173 h1:/jFs0duh4rdb8uIfPMv78iAJGcPKDeqAFnaLBropIC4= -golang.zx2c4.com/wireguard v0.0.0-20231211153847-12269c276173/go.mod h1:tkCQ4FQXmpAgYVh++1cq16/dH4QJtmvpRv19DWGAHSA= gvisor.dev/gvisor v0.0.0-20230927004350-cbd86285d259 h1:TbRPT0HtzFP3Cno1zZo7yPzEEnfu8EjLfl6IU9VfqkQ= gvisor.dev/gvisor v0.0.0-20230927004350-cbd86285d259/go.mod h1:AVgIgHMwK63XvmAzWG9vLQ41YnVHN0du0tEC46fI7yY= diff --git a/nym-vpn-core/crates/nym-gateway-probe/netstack_ping/impl.go b/nym-vpn-core/crates/nym-gateway-probe/netstack_ping/impl.go index 61df21e72c..aaef8ebd5e 100644 --- a/nym-vpn-core/crates/nym-gateway-probe/netstack_ping/impl.go +++ b/nym-vpn-core/crates/nym-gateway-probe/netstack_ping/impl.go @@ -8,11 +8,11 @@ import ( "strings" "time" + "github.com/amnezia-vpn/amneziawg-go/conn" + "github.com/amnezia-vpn/amneziawg-go/device" + "github.com/amnezia-vpn/amneziawg-go/tun/netstack" "golang.org/x/net/icmp" "golang.org/x/net/ipv4" - "golang.zx2c4.com/wireguard/conn" - "golang.zx2c4.com/wireguard/device" - "golang.zx2c4.com/wireguard/tun/netstack" ) type Netstack struct{} @@ -36,6 +36,10 @@ func (Netstack) ping(req NetstackRequest) NetstackResponse { ipc.WriteString("private_key=") ipc.WriteString(req.private_key) + if req.awg_args != "" { + awg := strings.ReplaceAll(req.awg_args, "\\n", "\n") + ipc.WriteString(fmt.Sprintf("\n%s", awg)) + } ipc.WriteString("\npublic_key=") ipc.WriteString(req.public_key) ipc.WriteString("\nendpoint=") @@ -45,6 +49,13 @@ func (Netstack) ping(req NetstackRequest) NetstackResponse { response := NetstackResponse{false, 0, 0, 0, 0, false} dev.IpcSet(ipc.String()) + + config, err := dev.IpcGet() + if err != nil { + log.Panic(err) + } + log.Printf("%s", config) + err = dev.Up() if err != nil { log.Panic(err) @@ -69,15 +80,18 @@ func (Netstack) ping(req NetstackRequest) NetstackResponse { for _, ip := range req.ping_ips { for i := uint8(0); i < req.num_ping; i++ { - log.Printf("Pinging %s seq=%d", ip, i) - response.sent_ips += 1 - rt, err := sendPing(ip, i, req.send_timeout_sec, req.recv_timeout_sec, tnet) - if err != nil { - log.Printf("Failed to send ping: %v\n", err) - continue - } - response.received_ips += 1 - log.Printf("Ping latency: %v\n", rt) + func() { + defer time.Sleep(5 * time.Second) + log.Printf("Pinging %s seq=%d", ip, i) + response.sent_ips += 1 + rt, err := sendPing(ip, i, req.send_timeout_sec, req.recv_timeout_sec, tnet) + if err != nil { + log.Printf("Failed to send ping: %v\n", err) + return + } + response.received_ips += 1 + log.Printf("Ping latency: %v\n", rt) + }() } } diff --git a/nym-vpn-core/crates/nym-gateway-probe/src/lib.rs b/nym-vpn-core/crates/nym-gateway-probe/src/lib.rs index c466c2d81d..7910f925de 100644 --- a/nym-vpn-core/crates/nym-gateway-probe/src/lib.rs +++ b/nym-vpn-core/crates/nym-gateway-probe/src/lib.rs @@ -58,78 +58,105 @@ pub async fn fetch_gateways_with_ipr() -> anyhow::Result { Ok(lookup_gateways().await?.into_exit_gateways()) } -pub async fn probe(entry_point: EntryPoint) -> anyhow::Result { - // Setup the entry gateways - let gateways = lookup_gateways().await?; - let entry_gateway = entry_point.lookup_gateway(&gateways).await?; - let exit_router_address = entry_gateway.ipr_address; - let authenticator = entry_gateway.authenticator_address; - let gateway_host = entry_gateway.host.clone().unwrap(); - let entry_gateway_id = entry_gateway.identity(); - - info!("Probing gateway: {entry_gateway:?}"); - - // Connect to the mixnet - let mixnet_client = MixnetClientBuilder::new_ephemeral() - .request_gateway(entry_gateway_id.to_string()) - .network_details(NymNetworkDetails::new_from_env()) - .debug_config(mixnet_debug_config()) - .build()? - .connect_to_mixnet() - .await; - - let mixnet_client = match mixnet_client { - Ok(mixnet_client) => mixnet_client, - Err(err) => { - error!("Failed to connect to mixnet: {err}"); - return Ok(ProbeResult { - gateway: entry_gateway_id.to_string(), - outcome: ProbeOutcome { - as_entry: Entry::fail_to_connect(), - as_exit: None, - wg: None, - }, - }); +pub struct Probe { + entrypoint: EntryPoint, + amnezia_args: String, +} + +impl Probe { + pub fn new(entrypoint: EntryPoint) -> Self { + Self { + entrypoint, + amnezia_args: "".into(), } - }; + } - let nym_address = *mixnet_client.nym_address(); - let entry_gateway = nym_address.gateway().to_base58_string(); + pub fn with_amnezia(&mut self, args: &str) -> &Self { + self.amnezia_args = args.to_string(); + self + } - info!("Successfully connected to entry gateway: {entry_gateway}"); - info!("Our nym address: {nym_address}"); + pub async fn probe(self) -> anyhow::Result { + let entry_point = self.entrypoint; + + // Setup the entry gateways + let gateways = lookup_gateways().await?; + let entry_gateway = entry_point.lookup_gateway(&gateways).await?; + let exit_router_address = entry_gateway.ipr_address; + let authenticator = entry_gateway.authenticator_address; + let gateway_host = entry_gateway.host.clone().unwrap(); + let entry_gateway_id = entry_gateway.identity(); + + info!("Probing gateway: {entry_gateway:?}"); + + // Connect to the mixnet + let mixnet_client = MixnetClientBuilder::new_ephemeral() + .request_gateway(entry_gateway_id.to_string()) + .network_details(NymNetworkDetails::new_from_env()) + .debug_config(mixnet_debug_config()) + .build()? + .connect_to_mixnet() + .await; + + let mixnet_client = match mixnet_client { + Ok(mixnet_client) => mixnet_client, + Err(err) => { + error!("Failed to connect to mixnet: {err}"); + return Ok(ProbeResult { + gateway: entry_gateway_id.to_string(), + outcome: ProbeOutcome { + as_entry: Entry::fail_to_connect(), + as_exit: None, + wg: None, + }, + }); + } + }; - let shared_client = Arc::new(tokio::sync::Mutex::new(Some(mixnet_client))); + let nym_address = *mixnet_client.nym_address(); + let entry_gateway = nym_address.gateway().to_base58_string(); - // Now that we have a connected mixnet client, we can start pinging - let shared_mixnet_client = SharedMixnetClient::from_shared(&shared_client); - let outcome = do_ping(shared_mixnet_client.clone(), exit_router_address).await; + info!("Successfully connected to entry gateway: {entry_gateway}"); + info!("Our nym address: {nym_address}"); - let wg_outcome = if let Some(authenticator) = authenticator { - wg_probe(authenticator, shared_client, &gateway_host) + let shared_client = Arc::new(tokio::sync::Mutex::new(Some(mixnet_client))); + + // Now that we have a connected mixnet client, we can start pinging + let shared_mixnet_client = SharedMixnetClient::from_shared(&shared_client); + let outcome = do_ping(shared_mixnet_client.clone(), exit_router_address).await; + + let wg_outcome = if let Some(authenticator) = authenticator { + wg_probe( + authenticator, + shared_client, + &gateway_host, + self.amnezia_args, + ) .await .unwrap_or_default() - } else { - WgProbeResults::default() - }; + } else { + WgProbeResults::default() + }; - let mixnet_client = shared_mixnet_client.lock().await.take().unwrap(); - mixnet_client.disconnect().await; + let mixnet_client = shared_mixnet_client.lock().await.take().unwrap(); + mixnet_client.disconnect().await; - // Disconnect the mixnet client gracefully - outcome.map(|mut outcome| { - outcome.wg = Some(wg_outcome); - ProbeResult { - gateway: entry_gateway.clone(), - outcome, - } - }) + // Disconnect the mixnet client gracefully + outcome.map(|mut outcome| { + outcome.wg = Some(wg_outcome); + ProbeResult { + gateway: entry_gateway.clone(), + outcome, + } + }) + } } async fn wg_probe( authenticator: AuthAddress, shared_mixnet_client: Arc>>, gateway_host: &nym_topology::NetworkAddress, + awg_args: String, ) -> anyhow::Result { let auth_shared_client = nym_authenticator_client::SharedMixnetClient::from_shared(&shared_mixnet_client); @@ -227,6 +254,7 @@ async fn wg_probe( private_key: private_key_hex, public_key: public_key_hex, endpoint: wg_endpoint.clone(), + awg_args, ..Default::default() }; diff --git a/nym-vpn-core/crates/nym-gateway-probe/src/netstack.rs b/nym-vpn-core/crates/nym-gateway-probe/src/netstack.rs index 6a701ca658..078f08eda2 100644 --- a/nym-vpn-core/crates/nym-gateway-probe/src/netstack.rs +++ b/nym-vpn-core/crates/nym-gateway-probe/src/netstack.rs @@ -15,6 +15,7 @@ pub struct NetstackRequest { pub num_ping: u8, pub send_timeout_sec: u64, pub recv_timeout_sec: u64, + pub awg_args: String, } impl Default for NetstackRequest { @@ -27,9 +28,10 @@ impl Default for NetstackRequest { dns: "1.1.1.1".to_string(), ping_hosts: vec!["nymtech.net".to_string()], ping_ips: vec!["1.1.1.1".to_string()], - num_ping: 3, - send_timeout_sec: 1, - recv_timeout_sec: 2, + num_ping: 10, + send_timeout_sec: 3, + recv_timeout_sec: 3, + awg_args: Default::default(), } } } diff --git a/nym-vpn-core/crates/nym-gateway-probe/src/run.rs b/nym-vpn-core/crates/nym-gateway-probe/src/run.rs index cdbb8dd4c9..20967eaf45 100644 --- a/nym-vpn-core/crates/nym-gateway-probe/src/run.rs +++ b/nym-vpn-core/crates/nym-gateway-probe/src/run.rs @@ -17,11 +17,17 @@ struct CliArgs { #[arg(short, long)] config_env_file: Option, + /// The specific gateway specified by ID. #[arg(long, short)] gateway: Option, + /// Disable logging during probe #[arg(long, short)] no_log: bool, + + /// Arguments to be appended to the wireguard config enabling amnezia-wg configuration + #[arg(long, short)] + amnezia_args: Option, } fn setup_logging() { @@ -38,7 +44,7 @@ fn setup_logging() { .init(); } -pub(crate) async fn run() -> anyhow::Result { +async fn run() -> anyhow::Result { let args = CliArgs::parse(); if !args.no_log { setup_logging(); @@ -52,7 +58,11 @@ pub(crate) async fn run() -> anyhow::Result { fetch_random_gateway_with_ipr().await? }; - nym_gateway_probe::probe(gateway).await + let mut trial = nym_gateway_probe::Probe::new(gateway); + if let Some(awg_args) = args.amnezia_args { + trial.with_amnezia(&awg_args); + } + trial.probe().await } async fn fetch_random_gateway_with_ipr() -> anyhow::Result { diff --git a/nym-vpn-core/crates/nym-vpnd/build.rs b/nym-vpn-core/crates/nym-vpnd/build.rs index 425025e065..9519095869 100644 --- a/nym-vpn-core/crates/nym-vpnd/build.rs +++ b/nym-vpn-core/crates/nym-vpnd/build.rs @@ -5,14 +5,6 @@ use std::{env, path::PathBuf}; use vergen::EmitBuilder; fn main() -> Result<(), Box> { - EmitBuilder::builder() - .all_build() - .all_git() - .all_rustc() - .all_cargo() - .emit() - .expect("failed to extract build metadata"); - let manifest_path = env::var_os("CARGO_MANIFEST_DIR").expect("manifest dir is not set"); let target = env::var("TARGET").expect("target is not set"); let target_os = env::var("CARGO_CFG_TARGET_OS").expect("target os is not set"); @@ -46,5 +38,12 @@ fn main() -> Result<(), Box> { }; println!("cargo:rustc-link-lib{}=wg", link_type); + EmitBuilder::builder() + .all_build() + .all_git() + .all_rustc() + .all_cargo() + .emit() + .expect("failed to extract build metadata"); Ok(()) } diff --git a/nym-vpn-core/crates/nym-wg-go/Cargo.toml b/nym-vpn-core/crates/nym-wg-go/Cargo.toml index aaed1be3e1..e3223992d5 100644 --- a/nym-vpn-core/crates/nym-wg-go/Cargo.toml +++ b/nym-vpn-core/crates/nym-wg-go/Cargo.toml @@ -20,3 +20,4 @@ hex.workspace = true base64.workspace = true x25519-dalek = { workspace = true, features = ["static_secrets"] } zeroize.workspace = true +rand.workspace = true diff --git a/nym-vpn-core/crates/nym-wg-go/README.md b/nym-vpn-core/crates/nym-wg-go/README.md new file mode 100644 index 0000000000..a69cb3fe28 --- /dev/null +++ b/nym-vpn-core/crates/nym-wg-go/README.md @@ -0,0 +1,39 @@ +# Rust Wireguard-go Wrapper + +This library wraps `wireguard-go` making it avaiable for transparent use to other +rust crates. + +## Usage + +Using this crate requires building the `libwg.a` library file and `libwg.h` header +file. + +```toml +nym-wg-go.workspace = true +``` + +```sh +# In the root of the repo +make build-wireguard + +# build this library (or downstream projects) +cargo build -p nym-wg-go +``` + +### Amnezia + +To use the AmneziaWG version we need to adjust the `libwg,a` file that gets built +to use the altered golang wrapper. Also you will need to enable the `amnezia` feature +in this crate. + +```toml +nym-wg-go = { workspace=true, features=["amnezia"]} +``` + +```sh +# In the root of the repo +make build-wireguard-amnezia + +# build this library (or downstream projects) in the same way as before. +cargo build -p nym-wg-go +``` diff --git a/nym-vpn-core/crates/nym-wg-go/src/amnezia.rs b/nym-vpn-core/crates/nym-wg-go/src/amnezia.rs new file mode 100644 index 0000000000..25f4e3ae16 --- /dev/null +++ b/nym-vpn-core/crates/nym-wg-go/src/amnezia.rs @@ -0,0 +1,210 @@ +//! Interface to amneziawg (fork of wireguard-go) allowing optional use of Amnezia features. +//! + +use crate::UapiConfigBuilder; + +use rand::{Rng, RngCore}; + +const OFF: AmneziaConfig = AmneziaConfig { + junk_pkt_count: 0, + junk_pkt_min_size: 0, + junk_pkt_max_size: 0, + init_pkt_junk_size: 0, + response_pkt_junk_size: 0, + init_pkt_magic_header: 1, + response_pkt_magic_header: 2, + under_load_pkt_magic_header: 3, + transport_pkt_magic_header: 4, +}; + +const BASE: AmneziaConfig = AmneziaConfig { + junk_pkt_count: 4, + junk_pkt_min_size: 40, + junk_pkt_max_size: 70, + init_pkt_junk_size: 0, + response_pkt_junk_size: 0, + init_pkt_magic_header: 1, + response_pkt_magic_header: 2, + under_load_pkt_magic_header: 3, + transport_pkt_magic_header: 4, +}; + +/// Hold Amnezia-wireguard configuration parameters. +/// +/// All parameters should be the same between Client and Server, except Jc - it can vary. +/// +/// - Jc — 1 ≤ Jc ≤ 128; recommended range is from 3 to 10 inclusive +/// - Jmin — Jmin < Jmax; recommended value is 50 +/// - Jmax — Jmin < Jmax ≤ 1280; recommended value is 1000 +/// - S1 — S1 < 1280; S1 + 56 ≠ S2; recommended range is from 15 to 150 inclusive +/// - S2 — S2 < 1280; recommended range is from 15 to 150 inclusive +/// - H1/H2/H3/H4 — must be unique among each other; +/// recommended range is from 5 to 2_147_483_647 (2^31 - 1 i.e. signed 32 bit int) inclusive +/// +/// Note: changes to S1, S2, H1, H2, H3, and H4 are required to match between client +/// and server. The connection will not work otherwise. +#[derive(Debug, Clone, PartialEq)] +pub struct AmneziaConfig { + /// Jc - Count of junk packets to send BEFORE sending the handshake Init message. + pub junk_pkt_count: u8, // Jc + /// Jmin - Minimum size in bytes of the Junk packets enabled by `junk_pkt_count` + pub junk_pkt_min_size: u16, // Jmin + /// Jmax - Maximum size in bytes of the Junk packets enabled by `junk_pkt_count` + pub junk_pkt_max_size: u16, // Jmax + /// S1 - Numer of byte to PREPEND to the Handshake init message + pub init_pkt_junk_size: u16, // S1 + /// S2 - Number of bytes to PREPEND to the Handshake response message + pub response_pkt_junk_size: u16, // S2 + /// H1 - Re-map handshake Init packet header type indicator to this value + pub init_pkt_magic_header: i32, // H1 + /// H2 - Re-map handshake reponse packet header type indicator to this value + pub response_pkt_magic_header: i32, // H2 + /// H3 - Re-map under load packet header type indicator to this value + pub under_load_pkt_magic_header: i32, // H3 + /// H4 - Re-map transport packet header type indicator to this value + pub transport_pkt_magic_header: i32, // H4 +} + +impl Default for AmneziaConfig { + fn default() -> Self { + OFF.clone() + } +} + +impl AmneziaConfig { + /// Creates a randomized configuration with parameters within suggested ranges. + /// + /// Attempts to retry if there is a collision in [H1, H2, H3, H4]. This should + /// almost never happen given the range available (5 to i32::MAX) unless the provided + /// rng is bad. If the rng is bad, then amneziawg will break anyways so we panic. + pub fn rand(rng: &mut impl RngCore) -> Self { + for _ in 0..16 { + let c = Self { + junk_pkt_count: rng.gen_range(3..10), + junk_pkt_min_size: rng.gen_range(0..900), + junk_pkt_max_size: 1000, + init_pkt_junk_size: rng.gen_range(15..150), + response_pkt_junk_size: rng.gen_range(15..150), + init_pkt_magic_header: rng.gen_range(5..i32::MAX), + response_pkt_magic_header: rng.gen_range(5..i32::MAX), + under_load_pkt_magic_header: rng.gen_range(5..i32::MAX), + transport_pkt_magic_header: rng.gen_range(5..i32::MAX), + }; + if c.validate() { + return c; + } + } + panic!("this should not be possible"); + } + + /// Returns a configuration that disables Amnezia. + pub fn off() -> Self { + OFF.clone() + } + + /// Returns a configuration that enables only the basic junk packet feature + /// of amneziawg + pub fn basic() -> Self { + BASE.clone() + } + + /// Adds the contained AmneziaWG parameters to the UAPI Config + pub fn append_to(&self, config_builder: &mut UapiConfigBuilder) { + if self == &OFF { + return; + } + config_builder.add("Jc", self.junk_pkt_count.to_string().as_str()); + config_builder.add("Jmin", self.junk_pkt_min_size.to_string().as_str()); + config_builder.add("Jmax", self.junk_pkt_max_size.to_string().as_str()); + + if self == &BASE { + return; + } + + config_builder.add("S1", self.init_pkt_junk_size.to_string().as_str()); + config_builder.add("S2", self.response_pkt_junk_size.to_string().as_str()); + config_builder.add("H1", self.init_pkt_magic_header.to_string().as_str()); + config_builder.add("H2", self.response_pkt_magic_header.to_string().as_str()); + config_builder.add("H3", self.under_load_pkt_magic_header.to_string().as_str()); + config_builder.add("H4", self.transport_pkt_magic_header.to_string().as_str()); + } + + /// Check if the provided configuration is valid + /// + /// - Jc — 1 ≤ Jc ≤ 128; recommended range is from 3 to 10 inclusive + /// - Jmin — Jmin < Jmax; recommended value is 50 + /// - Jmax — Jmin < Jmax ≤ 1280; recommended value is 1000 + /// - S1 — S1 < 1280; S1 + 56 ≠ S2; recommended range is from 15 to 150 inclusive + /// - S2 — S2 < 1280; recommended range is from 15 to 150 inclusive + /// - H1/H2/H3/H4 — must be unique among each other; + /// recommended range is from 5 to 2_147_483_647 (2^31 - 1 i.e. signed 32 bit int) inclusive + pub fn validate(&self) -> bool { + if self.junk_pkt_count > 128 + || self.junk_pkt_max_size > 1280 + || self.junk_pkt_min_size > self.junk_pkt_max_size + || self.init_pkt_junk_size > 1280 + || self.response_pkt_junk_size > 1280 + || [ + self.response_pkt_magic_header, + self.under_load_pkt_magic_header, + self.transport_pkt_magic_header, + ] + .contains(&self.init_pkt_magic_header) + || [ + self.init_pkt_magic_header, + self.under_load_pkt_magic_header, + self.transport_pkt_magic_header, + ] + .contains(&self.response_pkt_magic_header) + || [ + self.init_pkt_magic_header, + self.response_pkt_magic_header, + self.transport_pkt_magic_header, + ] + .contains(&self.under_load_pkt_magic_header) + || [ + self.init_pkt_magic_header, + self.response_pkt_magic_header, + self.under_load_pkt_magic_header, + ] + .contains(&self.transport_pkt_magic_header) + { + return false; + } + true + } +} + +#[cfg(test)] +mod test { + use super::*; + + #[test] + fn test_encode_amnezia_config() { + let mut config_builder = UapiConfigBuilder::new(); + OFF.append_to(&mut config_builder); + assert_eq!(config_builder.into_bytes(), b"\n"); + + let mut config_builder = UapiConfigBuilder::new(); + BASE.append_to(&mut config_builder); + assert_eq!(config_builder.into_bytes(), b"Jc=4\nJmin=40\nJmax=70\n\n"); + + let c = AmneziaConfig { + junk_pkt_count: 1, + junk_pkt_min_size: 20, + junk_pkt_max_size: 30, + init_pkt_junk_size: 40, + response_pkt_junk_size: 50, + init_pkt_magic_header: 11, + response_pkt_magic_header: 12, + under_load_pkt_magic_header: 13, + transport_pkt_magic_header: 14, + }; + let mut config_builder = UapiConfigBuilder::new(); + c.append_to(&mut config_builder); + assert_eq!( + config_builder.into_bytes(), + b"Jc=1\nJmin=20\nJmax=30\nS1=40\nS2=50\nH1=11\nH2=12\nH3=13\nH4=14\n\n" + ); + } +} diff --git a/nym-vpn-core/crates/nym-wg-go/src/lib.rs b/nym-vpn-core/crates/nym-wg-go/src/lib.rs index 895aacb00a..ae9de4b1d0 100644 --- a/nym-vpn-core/crates/nym-wg-go/src/lib.rs +++ b/nym-vpn-core/crates/nym-wg-go/src/lib.rs @@ -1,6 +1,8 @@ // Copyright 2024 - Nym Technologies SA // SPDX-License-Identifier: GPL-3.0-only +#[cfg(feature = "amnezia")] +pub mod amnezia; #[cfg(any(target_os = "ios", target_os = "android"))] pub mod netstack; pub mod uapi; diff --git a/nym-vpn-core/crates/nym-wg-go/src/netstack.rs b/nym-vpn-core/crates/nym-wg-go/src/netstack.rs index 003e2fd140..d582b0fdc3 100644 --- a/nym-vpn-core/crates/nym-wg-go/src/netstack.rs +++ b/nym-vpn-core/crates/nym-wg-go/src/netstack.rs @@ -13,6 +13,8 @@ use super::{ uapi::UapiConfigBuilder, Error, LoggingCallback, PeerConfig, PeerEndpointUpdate, PrivateKey, Result, }; +#[cfg(feature = "amnezia")] +use crate::amnezia::AmneziaConfig; /// Netstack interface configuration. pub struct InterfaceConfig { @@ -35,47 +37,6 @@ impl fmt::Debug for InterfaceConfig { } } -/// Hold Amnezia-wireguard configuration parameters. -/// -/// All parameters should be the same between Client and Server, except Jc - it can vary. -/// -/// - Jc — 1 ≤ Jc ≤ 128; recommended range is from 3 to 10 inclusive -/// - Jmin — Jmin < Jmax; recommended value is 50 -/// - Jmax — Jmin < Jmax ≤ 1280; recommended value is 1000 -/// - S1 — S1 < 1280; S1 + 56 ≠ S2; recommended range is from 15 to 150 inclusive -/// - S2 — S2 < 1280; recommended range is from 15 to 150 inclusive -/// - H1/H2/H3/H4 — must be unique among each other; -/// recommended range is from 5 to 2_147_483_647 (2^31 - 1 i.e. signed 32 bit int) inclusive -#[cfg(feature = "amnezia")] -#[derive(Debug)] -pub struct AmneziaConfig { - pub junk_packet_count: i32, // Jc - pub junk_packet_min_size: i32, // Jmin - pub junk_packet_max_size: i32, // Jmax - pub init_packet_junk_size: i32, // S0 - pub response_packet_junk_size: i32, // S1 - pub init_packet_magic_header: u32, // H1 - pub response_packet_magic_header: u32, // H2 - pub under_load_packet_magic_header: u32, // H3 - pub transport_packet_magic_header: u32, // H4 -} - -#[cfg(feature = "amnezia")] -impl Default for AmneziaConfig { - fn default() -> Self { - Self { - junk_packet_count: 4_i32, - junk_packet_min_size: 40_i32, - junk_packet_max_size: 70_i32, - init_packet_junk_size: 0_i32, - response_packet_junk_size: 0_i32, - init_packet_magic_header: 1_u32, - response_packet_magic_header: 2_u32, - under_load_packet_magic_header: 3_u32, - transport_packet_magic_header: 4_u32, - } - } -} /// Netstack configuration. #[derive(Debug)] pub struct Config { @@ -91,6 +52,11 @@ impl Config { self.interface.private_key.to_bytes().as_ref(), ); + #[cfg(feature = "amnezia")] + if let Some(azwg_config) = &self.interface.azwg_config { + azwg_config.append_to(&mut config_builder); + } + if !self.peers.is_empty() { config_builder.add("replace_peers", "true"); for peer in self.peers.iter() { diff --git a/nym-vpn-core/crates/nym-wg-go/src/wireguard_go.rs b/nym-vpn-core/crates/nym-wg-go/src/wireguard_go.rs index 2f347beb09..855d0adc34 100644 --- a/nym-vpn-core/crates/nym-wg-go/src/wireguard_go.rs +++ b/nym-vpn-core/crates/nym-wg-go/src/wireguard_go.rs @@ -12,6 +12,8 @@ use super::{ uapi::UapiConfigBuilder, Error, LoggingCallback, PeerConfig, PeerEndpointUpdate, PrivateKey, Result, }; +#[cfg(feature = "amnezia")] +use crate::amnezia::AmneziaConfig; /// Classic WireGuard interface configuration. pub struct InterfaceConfig { @@ -21,6 +23,7 @@ pub struct InterfaceConfig { #[cfg(target_os = "linux")] pub fwmark: Option, #[cfg(feature = "amnezia")] + /// Amnezia wireguard configuration disabled by default pub azwg_config: Option, } @@ -35,47 +38,6 @@ impl fmt::Debug for InterfaceConfig { d.finish() } } -/// Hold Amnezia-wireguard configuration parameters. -/// -/// All parameters should be the same between Client and Server, except Jc - it can vary. -/// -/// - Jc — 1 ≤ Jc ≤ 128; recommended range is from 3 to 10 inclusive -/// - Jmin — Jmin < Jmax; recommended value is 50 -/// - Jmax — Jmin < Jmax ≤ 1280; recommended value is 1000 -/// - S1 — S1 < 1280; S1 + 56 ≠ S2; recommended range is from 15 to 150 inclusive -/// - S2 — S2 < 1280; recommended range is from 15 to 150 inclusive -/// - H1/H2/H3/H4 — must be unique among each other; -/// recommended range is from 5 to 2_147_483_647 (2^31 - 1 i.e. signed 32 bit int) inclusive -#[cfg(feature = "amnezia")] -#[derive(Debug)] -pub struct AmneziaConfig { - pub junk_packet_count: i32, // Jc - pub junk_packet_min_size: i32, // Jmin - pub junk_packet_max_size: i32, // Jmax - pub init_packet_junk_size: i32, // S0 - pub response_packet_junk_size: i32, // S1 - pub init_packet_magic_header: u32, // H1 - pub response_packet_magic_header: u32, // H2 - pub under_load_packet_magic_header: u32, // H3 - pub transport_packet_magic_header: u32, // H4 -} - -#[cfg(feature = "amnezia")] -impl Default for AmneziaConfig { - fn default() -> Self { - Self { - junk_packet_count: 4_i32, - junk_packet_min_size: 40_i32, - junk_packet_max_size: 70_i32, - init_packet_junk_size: 0_i32, - response_packet_junk_size: 0_i32, - init_packet_magic_header: 1_u32, - response_packet_magic_header: 2_u32, - under_load_packet_magic_header: 3_u32, - transport_packet_magic_header: 4_u32, - } - } -} /// Classic WireGuard configuration. #[derive(Debug)] @@ -101,6 +63,11 @@ impl Config { config_builder.add("fwmark", fwmark.to_string().as_str()); } + #[cfg(feature = "amnezia")] + if let Some(azwg_config) = &self.interface.azwg_config { + azwg_config.append_to(&mut config_builder); + } + if !self.peers.is_empty() { config_builder.add("replace_peers", "true"); for peer in self.peers.iter() { diff --git a/wireguard/build-wireguard-go.sh b/wireguard/build-wireguard-go.sh index d16584f20e..ff2560d234 100755 --- a/wireguard/build-wireguard-go.sh +++ b/wireguard/build-wireguard-go.sh @@ -2,30 +2,53 @@ # This script is used to build wireguard-go libraries for all the platforms. -TEMP=$(getopt -o aiz --long android,docker,ios,amnezia \ - -n 'build-wireguard-go.sh' -- "$@") - -if [ $? != 0 ] ; then echo "Terminating..." >&2 ; exit 1 ; fi - -# Note the quotes around '$TEMP': they are essential! -eval set -- "$TEMP" +function stringContain { + case $2 in *$1* ) return 0;; *) return 1;; esac; +} ANDROID_BUILD=false IOS_BUILD=false DOCKER_BUILD=true AMNEZIA_BUILD=false -while true; do - case "$1" in - "-a" | "--android" ) ANDROID_BUILD=true; shift ;; - "-i" | "--ios" ) IOS_BUILD=true; shift ;; - "-z" | "--amnezia" ) AMNEZIA_BUILD=true; shift ;; - "--no-docker" ) DOCKER_BUILD=false; shift ;; - -- ) shift; break ;; - * ) break ;; - esac -done - -set -eu + +function parseArgs { + if stringContain "Darwin" "$(uname -s)"; then + # Mac builds require gnu-getopt because regular macos getopt doesn't allow long args. -_- + # This could be avoided using something like `getopts` instead, but then we don't + # have the ability to use long options which pre-date this script change. + # > brew install gnu-getopt + # Installed for CI in `.github/workflows/ci-nym-vpn-core.yml` + echo "using gnu-getopt" + export PATH="/opt/homebrew/opt/gnu-getopt/bin:$PATH" + fi + + which getopt + TEMP=$(getopt -o aiz --long android,docker,ios,amnezia \ + -n 'build-wireguard-go.sh' -- "$@") + + if [ $? != 0 ]; then + echo "encountered an error parsing args" + exit 2 + fi + + # Note the quotes around '$TEMP': they are essential! + eval set -- "$TEMP" + + while true; do + case "$1" in + "-a" | "--android" ) ANDROID_BUILD=true; shift ;; + "-i" | "--ios" ) IOS_BUILD=true; shift ;; + "-z" | "--amnezia" ) AMNEZIA_BUILD=true; shift ;; + "--no-docker" ) DOCKER_BUILD=false; shift ;; + -- ) shift; break ;; + * ) break ;; + esac + done + + echo "android:$ANDROID_BUILD ios:$IOS_BUILD docker:$DOCKER_BUILD amnezia:$AMNEZIA_BUILD" + + set -eu +} function is_android_build { if [ "$ANDROID_BUILD" = true ]; then @@ -110,7 +133,7 @@ function build_windows { export GOARCH=amd64 export CC="x86_64-w64-mingw32-cc" fi - + echo "Building wireguard-go for Windows ($arch)" pushd libwg @@ -268,6 +291,7 @@ function patch_darwin_goruntime { } function build_wireguard_go { + parseArgs $@ if is_amnezia_build ; then LIB_DIR=$AMNEZIA_DIR From c245bb7c30ece7eb90e698973083ddd3178eb37b Mon Sep 17 00:00:00 2001 From: jmwample Date: Wed, 30 Oct 2024 16:11:37 -0600 Subject: [PATCH 3/5] ensure CI env compatibility --- .github/workflows/ci-nym-vpn-core-ios.yml | 3 +++ .github/workflows/ci-nym-vpn-core-macos.yml | 5 ++++- 2 files changed, 7 insertions(+), 1 deletion(-) diff --git a/.github/workflows/ci-nym-vpn-core-ios.yml b/.github/workflows/ci-nym-vpn-core-ios.yml index 1e93f1146e..82e71828ab 100644 --- a/.github/workflows/ci-nym-vpn-core-ios.yml +++ b/.github/workflows/ci-nym-vpn-core-ios.yml @@ -46,6 +46,9 @@ jobs: version: "21.12" # 3.21.12: the version on ubuntu 24.04. Don't change this! repo-token: ${{ secrets.GITHUB_TOKEN }} + - name: Install script dependencies + run: brew install gnu-getopt + - name: Build wireguard shell: bash run: | diff --git a/.github/workflows/ci-nym-vpn-core-macos.yml b/.github/workflows/ci-nym-vpn-core-macos.yml index ae017291a0..54bab62690 100644 --- a/.github/workflows/ci-nym-vpn-core-macos.yml +++ b/.github/workflows/ci-nym-vpn-core-macos.yml @@ -24,7 +24,7 @@ jobs: rm -rf ./* || true rm -rf ./.??* || true ls -la ./ - + - name: Checkout repo uses: actions/checkout@v4 @@ -45,6 +45,9 @@ jobs: version: "21.12" # 3.21.12: the version on ubuntu 24.04. Don't change this! repo-token: ${{ secrets.GITHUB_TOKEN }} + - name: Install script dependencies + run: brew install gnu-getopt + - name: Build wireguard shell: bash run: | From d5fff03a1f622aa76f666a9db3658a1e6c89011f Mon Sep 17 00:00:00 2001 From: jmwample Date: Wed, 30 Oct 2024 16:15:35 -0600 Subject: [PATCH 4/5] correct missed conflict --- wireguard/build-wireguard-go.sh | 8 -------- 1 file changed, 8 deletions(-) diff --git a/wireguard/build-wireguard-go.sh b/wireguard/build-wireguard-go.sh index ff2560d234..7c51205fb4 100755 --- a/wireguard/build-wireguard-go.sh +++ b/wireguard/build-wireguard-go.sh @@ -113,16 +113,8 @@ function win_create_lib_file { } function build_windows { -<<<<<<< HEAD export CGO_ENABLED=1 export GOOS=windows -======= - echo "Building wireguard-go for Windows" - pushd $LIB_DIR - export CGO_ENABLED=1 - go build -trimpath -v -o libwg.dll -buildmode c-shared - win_create_lib_file ->>>>>>> c0b0f37b (initial compiling amnezia lib exchanged for wireguard-go) if is_win_arm64 $@; then local arch="aarch64" From e1d03fe4958009dcc033e34c0c1b58374a40649f Mon Sep 17 00:00:00 2001 From: jmwample Date: Wed, 30 Oct 2024 16:27:08 -0600 Subject: [PATCH 5/5] one more error from conflicts --- nym-vpn-core/crates/nym-gateway-probe/src/run.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/nym-vpn-core/crates/nym-gateway-probe/src/run.rs b/nym-vpn-core/crates/nym-gateway-probe/src/run.rs index 20967eaf45..123b0d8ae8 100644 --- a/nym-vpn-core/crates/nym-gateway-probe/src/run.rs +++ b/nym-vpn-core/crates/nym-gateway-probe/src/run.rs @@ -44,7 +44,7 @@ fn setup_logging() { .init(); } -async fn run() -> anyhow::Result { +pub(crate) async fn run() -> anyhow::Result { let args = CliArgs::parse(); if !args.no_log { setup_logging();