diff options
-rw-r--r-- | .gitmodules | 3 | ||||
-rw-r--r-- | README.md | 5 | ||||
-rw-r--r-- | app/src/main/java/com/wireguard/android/Application.java | 15 | ||||
-rw-r--r-- | app/src/main/java/com/wireguard/android/activity/SettingsActivity.java | 8 | ||||
-rw-r--r-- | app/src/main/java/com/wireguard/android/backend/GoBackend.java | 125 | ||||
-rw-r--r-- | app/src/main/java/com/wireguard/android/backend/WgQuickBackend.java | 12 | ||||
-rw-r--r-- | app/src/main/java/com/wireguard/android/fragment/TunnelController.java | 30 | ||||
-rw-r--r-- | app/src/main/res/values/strings.xml | 17 | ||||
-rw-r--r-- | app/src/main/res/xml/preferences.xml | 2 | ||||
-rw-r--r-- | app/tools/CMakeLists.txt | 13 | ||||
-rw-r--r-- | app/tools/libwg-go/.gitignore | 4 | ||||
-rw-r--r-- | app/tools/libwg-go/Makefile | 21 | ||||
-rw-r--r-- | app/tools/libwg-go/api-android.go | 111 | ||||
-rw-r--r-- | app/tools/libwg-go/jni.c | 40 | ||||
m--------- | app/tools/wireguard-go | 0 |
15 files changed, 347 insertions, 59 deletions
diff --git a/.gitmodules b/.gitmodules index 32ab0d51..5d767f4f 100644 --- a/.gitmodules +++ b/.gitmodules @@ -4,3 +4,6 @@ [submodule "app/tools/wireguard"] path = app/tools/wireguard url = https://git.zx2c4.com/WireGuard +[submodule "app/tools/wireguard-go"] + path = app/tools/wireguard-go + url = https://git.zx2c4.com/wireguard-go @@ -1,9 +1,8 @@ # Android GUI for [WireGuard](https://www.wireguard.com/) -##### [Test this app on the Play Store](https://play.google.com/apps/testing/com.wireguard.android). - -This is a work in progress Android GUI for [WireGuard](https://www.wireguard.com/). The ultimate goal is to [opportunistically use the kernel implementation](https://git.zx2c4.com/android_kernel_wireguard/about/), and fallback to using the non-root userspace implementation. At the time of writing, this only supports using the kernel module, but this should change in the near future. +### [Test this app on the Play Store](https://play.google.com/apps/testing/com.wireguard.android). +This is an Android GUI for [WireGuard](https://www.wireguard.com/). It [opportunistically uses the kernel implementation](https://git.zx2c4.com/android_kernel_wireguard/about/), and falls back to using the non-root [userspace implementation](https://git.zx2c4.com/wireguard-go/about/). ## License diff --git a/app/src/main/java/com/wireguard/android/Application.java b/app/src/main/java/com/wireguard/android/Application.java index 4676e0bc..c1b21373 100644 --- a/app/src/main/java/com/wireguard/android/Application.java +++ b/app/src/main/java/com/wireguard/android/Application.java @@ -8,6 +8,7 @@ import android.os.Looper; import android.preference.PreferenceManager; import com.wireguard.android.backend.Backend; +import com.wireguard.android.backend.GoBackend; import com.wireguard.android.backend.WgQuickBackend; import com.wireguard.android.configStore.ConfigStore; import com.wireguard.android.configStore.FileConfigStore; @@ -16,6 +17,7 @@ import com.wireguard.android.util.AsyncWorker; import com.wireguard.android.util.RootShell; import com.wireguard.android.util.ToolsInstaller; +import java.io.File; import java.util.concurrent.Executor; import javax.inject.Qualifier; @@ -56,6 +58,8 @@ public class Application extends android.app.Application { ToolsInstaller getToolsInstaller(); TunnelManager getTunnelManager(); + + Class getBackendType(); } @Qualifier @@ -83,7 +87,16 @@ public class Application extends android.app.Application { public static Backend getBackend(@ApplicationContext final Context context, final RootShell rootShell, final ToolsInstaller toolsInstaller) { - return new WgQuickBackend(context, rootShell, toolsInstaller); + if (new File("/sys/module/wireguard").exists()) + return new WgQuickBackend(context, rootShell, toolsInstaller); + else + return new GoBackend(context); + } + + @ApplicationScope + @Provides + public static Class getBackendType(final Backend backend) { + return backend.getClass(); } @ApplicationScope diff --git a/app/src/main/java/com/wireguard/android/activity/SettingsActivity.java b/app/src/main/java/com/wireguard/android/activity/SettingsActivity.java index 3eb633ff..e3cd46b1 100644 --- a/app/src/main/java/com/wireguard/android/activity/SettingsActivity.java +++ b/app/src/main/java/com/wireguard/android/activity/SettingsActivity.java @@ -2,9 +2,12 @@ package com.wireguard.android.activity; import android.app.Activity; import android.os.Bundle; +import android.preference.Preference; import android.preference.PreferenceFragment; +import com.wireguard.android.Application; import com.wireguard.android.R; +import com.wireguard.android.backend.WgQuickBackend; /** * Interface for changing application-global persistent settings. @@ -26,6 +29,11 @@ public class SettingsActivity extends Activity { public void onCreate(final Bundle savedInstanceState) { super.onCreate(savedInstanceState); addPreferencesFromResource(R.xml.preferences); + if (Application.getComponent().getBackendType() != WgQuickBackend.class) { + final Preference toolsInstaller = + getPreferenceManager().findPreference("tools_installer"); + getPreferenceScreen().removePreference(toolsInstaller); + } } } } diff --git a/app/src/main/java/com/wireguard/android/backend/GoBackend.java b/app/src/main/java/com/wireguard/android/backend/GoBackend.java new file mode 100644 index 00000000..db059e22 --- /dev/null +++ b/app/src/main/java/com/wireguard/android/backend/GoBackend.java @@ -0,0 +1,125 @@ +package com.wireguard.android.backend; + +import android.content.Context; +import android.support.v4.util.ArraySet; +import android.util.Log; + +import com.wireguard.android.model.Tunnel; +import com.wireguard.android.model.Tunnel.State; +import com.wireguard.android.model.Tunnel.Statistics; +import com.wireguard.config.Config; +import com.wireguard.config.Interface; +import com.wireguard.config.Peer; +import com.wireguard.crypto.KeyEncoding; + +import java.util.Collections; +import java.util.Formatter; +import java.util.Set; + +public final class GoBackend implements Backend { + private static final String TAG = "WireGuard/" + GoBackend.class.getSimpleName(); + + static { + System.loadLibrary("wg-go"); + } + + private final Context context; + private Tunnel currentTunnel; + + public GoBackend(final Context context) { + this.context = context; + } + + private static native int wgGetSocketV4(int handle); + + private static native int wgGetSocketV6(int handle); + + private static native void wgTurnOff(int handle); + + private static native int wgTurnOn(String ifName, int tunFd, String settings); + + @Override + public Config applyConfig(final Tunnel tunnel, final Config config) throws Exception { + if (tunnel.getState() == State.UP) { + // Restart the tunnel to apply the new config. + setStateInternal(tunnel, tunnel.getConfig(), State.DOWN); + try { + setStateInternal(tunnel, config, State.UP); + } catch (final Exception e) { + // The new configuration didn't work, so try to go back to the old one. + setStateInternal(tunnel, tunnel.getConfig(), State.UP); + throw e; + } + } + return config; + } + + @Override + public Set<String> enumerate() { + if (currentTunnel != null) { + final Set<String> runningTunnels = new ArraySet<>(); + runningTunnels.add(currentTunnel.getName()); + return runningTunnels; + } + return Collections.emptySet(); + } + + @Override + public State getState(final Tunnel tunnel) { + return currentTunnel == tunnel ? State.UP : State.DOWN; + } + + @Override + public Statistics getStatistics(final Tunnel tunnel) { + return new Statistics(); + } + + @Override + public State setState(final Tunnel tunnel, State state) throws Exception { + final State originalState = getState(tunnel); + if (state == State.TOGGLE) + state = originalState == State.UP ? State.DOWN : State.UP; + if (state == originalState) + return originalState; + if (state == State.UP && currentTunnel != null) + throw new IllegalStateException("Only one userspace tunnel can run at a time"); + Log.d(TAG, "Changing tunnel " + tunnel.getName() + " to state " + state); + setStateInternal(tunnel, tunnel.getConfig(), state); + return getState(tunnel); + } + + private void setStateInternal(final Tunnel tunnel, final Config config, final State state) + throws Exception { + if (state == State.UP) { + // Do something (context.startService()...). + currentTunnel = tunnel; + + Formatter fmt = new Formatter(new StringBuilder()); + final Interface iface = config.getInterface(); + fmt.format("replace_peers=true\n"); + if (iface.getPrivateKey() != null) + fmt.format("private_key=%s\n", KeyEncoding.keyToHex(KeyEncoding.keyFromBase64(iface.getPrivateKey()))); + if (iface.getListenPort() != null) + fmt.format("listen_port=%d\n", Integer.parseInt(config.getInterface().getListenPort())); + for (final Peer peer : config.getPeers()) { + if (peer.getPublicKey() != null) + fmt.format("public_key=%s\n", KeyEncoding.keyToHex(KeyEncoding.keyFromBase64(peer.getPublicKey()))); + if (peer.getPreSharedKey() != null) + fmt.format("preshared_key=%s\n", KeyEncoding.keyToHex(KeyEncoding.keyFromBase64(peer.getPreSharedKey()))); + if (peer.getEndpoint() != null) + fmt.format("endpoint=%s\n", peer.getEndpoint()); + if (peer.getPersistentKeepalive() != null) + fmt.format("persistent_keepalive_interval=%d\n", Integer.parseInt(peer.getPersistentKeepalive())); + if (peer.getAllowedIPs() != null) { + for (final String allowedIp : peer.getAllowedIPs().split(" *, *")) { + fmt.format("allowed_ip=%s\n", allowedIp); + } + } + } + wgTurnOn(tunnel.getName(), -1, fmt.toString()); + } else { + // Do something else. + currentTunnel = null; + } + } +} diff --git a/app/src/main/java/com/wireguard/android/backend/WgQuickBackend.java b/app/src/main/java/com/wireguard/android/backend/WgQuickBackend.java index 907f5d6d..760aac85 100644 --- a/app/src/main/java/com/wireguard/android/backend/WgQuickBackend.java +++ b/app/src/main/java/com/wireguard/android/backend/WgQuickBackend.java @@ -88,8 +88,6 @@ public final class WgQuickBackend implements Backend { state = originalState == State.UP ? State.DOWN : State.UP; if (state == originalState) return originalState; - if (state == State.UP && !new File("/sys/module/wireguard").exists()) - throw new ModuleNotLoadedException("WireGuard module not loaded"); Log.d(TAG, "Changing tunnel " + tunnel.getName() + " to state " + state); toolsInstaller.ensureToolsAvailable(); setStateInternal(tunnel, tunnel.getConfig(), state); @@ -113,14 +111,4 @@ public final class WgQuickBackend implements Backend { if (result != 0) throw new Exception("Unable to configure tunnel (wg-quick returned " + result + ')'); } - - public static class ModuleNotLoadedException extends Exception { - public ModuleNotLoadedException(final String message, final Throwable cause) { - super(message, cause); - } - - public ModuleNotLoadedException(final String message) { - super(message); - } - } } diff --git a/app/src/main/java/com/wireguard/android/fragment/TunnelController.java b/app/src/main/java/com/wireguard/android/fragment/TunnelController.java index ca40654e..990509e0 100644 --- a/app/src/main/java/com/wireguard/android/fragment/TunnelController.java +++ b/app/src/main/java/com/wireguard/android/fragment/TunnelController.java @@ -1,18 +1,13 @@ package com.wireguard.android.fragment; -import android.app.AlertDialog; import android.content.Context; import android.databinding.DataBindingUtil; import android.databinding.ViewDataBinding; -import android.text.Html; -import android.text.method.LinkMovementMethod; import android.util.Log; import android.view.View; -import android.widget.TextView; import com.commonsware.cwac.crossport.design.widget.Snackbar; import com.wireguard.android.R; -import com.wireguard.android.backend.WgQuickBackend; import com.wireguard.android.databinding.TunnelDetailFragmentBinding; import com.wireguard.android.databinding.TunnelListItemBinding; import com.wireguard.android.model.Tunnel; @@ -47,26 +42,11 @@ public final class TunnelController { if (throwable == null) return; final Context context = view.getContext(); - if (ExceptionLoggers.unwrap(throwable) - instanceof WgQuickBackend.ModuleNotLoadedException) { - final String message = context.getString(R.string.not_supported_message); - final String title = context.getString(R.string.not_supported_title); - final AlertDialog dialog = new AlertDialog.Builder(context) - .setMessage(Html.fromHtml(message)) - .setPositiveButton(R.string.ok, null) - .setTitle(title) - .show(); - // Make links work. - ((TextView) dialog.findViewById(android.R.id.message)) - .setMovementMethod(LinkMovementMethod.getInstance()); - Log.e(TAG, title, throwable); - } else { - final String error = ExceptionLoggers.unwrap(throwable).getMessage(); - final int messageResId = checked ? R.string.error_up : R.string.error_down; - final String message = context.getString(messageResId, error); - Snackbar.make(view, message, Snackbar.LENGTH_LONG).show(); - Log.e(TAG, message, throwable); - } + final String error = ExceptionLoggers.unwrap(throwable).getMessage(); + final int messageResId = checked ? R.string.error_up : R.string.error_down; + final String message = context.getString(messageResId, error); + Snackbar.make(view, message, Snackbar.LENGTH_LONG).show(); + Log.e(TAG, message, throwable); }); } } diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 7140bb7b..7de33f15 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -39,23 +39,6 @@ <string name="listen_port">Listen port</string> <string name="mtu">MTU</string> <string name="name">Name</string> - <string name="not_supported_message" tools:ignore="TypographyQuotes"><![CDATA[ - <p>Your Android device does not <em>currently</em> have the WireGuard kernel module. Please - talk to the manufacturer of your Android device or the author of your device’s ROM - about including the WireGuard kernel module.</p> - - <p>Fortunately, we are in the process of implementing support for WireGuard in a way that - will work on all devices, without any need for the kernel module. This means that while you - may not be able to use WireGuard today, you will very likely be able to use WireGuard in - several weeks. Things are looking up!</p> - - <p>Sorry for the wait. In the mean time, you may stay up to date on the latest project news - by <a href="https://lists.zx2c4.com/mailman/listinfo/wireguard">subscribing to our mailing - list</a>. General information about the project is available at - <a href="https://www.wireguard.com/">WireGuard.com</a>.</p> - ]]></string> - <string name="not_supported_title">WireGuard not installed</string> - <string name="ok">OK</string> <string name="peer">Peer</string> <string name="persistent_keepalive">Persistent keepalive</string> <string name="pre_shared_key">Pre-shared key</string> diff --git a/app/src/main/res/xml/preferences.xml b/app/src/main/res/xml/preferences.xml index 17fd4fc8..b032bea7 100644 --- a/app/src/main/res/xml/preferences.xml +++ b/app/src/main/res/xml/preferences.xml @@ -5,5 +5,5 @@ android:key="restore_on_boot" android:summary="@string/restore_on_boot_summary" android:title="@string/restore_on_boot_title" /> - <com.wireguard.android.preference.ToolsInstallerPreference /> + <com.wireguard.android.preference.ToolsInstallerPreference android:key="tools_installer" /> </PreferenceScreen> diff --git a/app/tools/CMakeLists.txt b/app/tools/CMakeLists.txt index 22ddbcc1..4cf030bf 100644 --- a/app/tools/CMakeLists.txt +++ b/app/tools/CMakeLists.txt @@ -10,3 +10,16 @@ target_compile_options(libwg-quick.so PUBLIC -O3 -std=gnu11 -Wall -pedantic -Wno file(GLOB WG_SOURCES wireguard/src/tools/*.c libmnl/src/*.c) add_executable(libwg.so ${WG_SOURCES}) target_compile_options(libwg.so PUBLIC "-I${CMAKE_CURRENT_SOURCE_DIR}libmnl/src/" "-I${CMAKE_CURRENT_SOURCE_DIR}/libmnl/include/" "-I${CMAKE_CURRENT_SOURCE_DIR}/wireguard/src/tools/" -O3 -std=gnu11 -D_GNU_SOURCE -DHAVE_VISIBILITY_HIDDEN -DRUNSTATEDIR=\"\\\"/data/data/com.wireguard.android/cache\\\"\" -Wno-pointer-arith -Wno-unused-parameter) + +add_custom_target(libwg-go.so WORKING_DIRECTORY "${CMAKE_CURRENT_SOURCE_DIR}/libwg-go" COMMENT "Building wireguard-go" VERBATIM COMMAND make + ANDROID_ARCH_NAME=${ANDROID_ARCH_NAME} + ANDROID_C_COMPILER=${ANDROID_C_COMPILER} + ANDROID_TOOLCHAIN_ROOT=${ANDROID_TOOLCHAIN_ROOT} + ANDROID_LLVM_TRIPLE=${ANDROID_LLVM_TRIPLE} + ANDROID_SYSROOT=${ANDROID_SYSROOT} + CFLAGS=${CMAKE_C_FLAGS}\ -Wno-unused-command-line-argument + LDFLAGS=${CMAKE_SHARED_LINKER_FLAGS}\ -fuse-ld=gold + DESTDIR=${CMAKE_LIBRARY_OUTPUT_DIRECTORY} +) +# Hack to make it actually build as part of the default target +add_dependencies(libwg.so libwg-go.so) diff --git a/app/tools/libwg-go/.gitignore b/app/tools/libwg-go/.gitignore new file mode 100644 index 00000000..c039ddfb --- /dev/null +++ b/app/tools/libwg-go/.gitignore @@ -0,0 +1,4 @@ +go/ +*.go +libwg-go.h +jni.o diff --git a/app/tools/libwg-go/Makefile b/app/tools/libwg-go/Makefile new file mode 100644 index 00000000..300b3841 --- /dev/null +++ b/app/tools/libwg-go/Makefile @@ -0,0 +1,21 @@ +containing = $(foreach v,$2,$(if $(findstring $1,$v),$v)) +FILES := $(wildcard ../wireguard-go/*/*.go) $(wildcard ../wireguard-go/*.go) +FILES := $(filter-out %/main.go $(filter-out %_linux.go,$(call containing,_,$(FILES))),$(FILES)) + +export GOPATH := $(CURDIR)/go +CLANG_FLAGS := --target=$(ANDROID_LLVM_TRIPLE) --gcc-toolchain=$(ANDROID_TOOLCHAIN_ROOT) --sysroot=$(ANDROID_SYSROOT) +export CGO_CFLAGS := $(CLANG_FLAGS) $(CFLAGS) +export CGO_LDFLAGS := $(CLANG_FLAGS) $(LDFLAGS) +export CC := $(ANDROID_C_COMPILER) +GO_ARCH_FILTER := case "$(ANDROID_ARCH_NAME)" in x86) echo 386 ;; x86_64) echo amd64 ;; *) echo $(ANDROID_ARCH_NAME) ;; esac +export GOARCH := $(shell $(GO_ARCH_FILTER)) +export GOOS := android +export CGO_ENABLED := 1 + +$(DESTDIR)/libwg-go.so: $(FILES) api-android.go jni.c + find . -name '*.go' -type l -delete + find . -type d -empty -delete + mkdir -p $(subst ../wireguard-go/,./,$(dir $(FILES))) + $(foreach FILE,$(FILES),ln -sfrt $(subst ../wireguard-go/,./,$(dir $(FILE))) $(FILE);) + go get -v -d + go build -v -o $(DESTDIR)/libwg-go.so -buildmode c-shared diff --git a/app/tools/libwg-go/api-android.go b/app/tools/libwg-go/api-android.go new file mode 100644 index 00000000..b2e3da17 --- /dev/null +++ b/app/tools/libwg-go/api-android.go @@ -0,0 +1,111 @@ +package main + +// #cgo LDFLAGS: -llog +// #include <android/log.h> +import "C" + +import ( + "bufio" + "io/ioutil" + "log" + "math" + "os" + "strings" +) + +type AndroidLogger struct { + level C.int + interfaceName string +} + +func (l AndroidLogger) Write(p []byte) (int, error) { + C.__android_log_write(l.level, C.CString("WireGuard/GoBackend/"+l.interfaceName), C.CString(string(p))) + return len(p), nil +} + +var tunnelHandles map[int32]*Device + +func init() { + tunnelHandles = make(map[int32]*Device) +} + +//export wgTurnOn +func wgTurnOn(ifnameRef string, tun_fd int32, settings string) int32 { + interfaceName := string([]byte(ifnameRef)) + + logger := &Logger{ + Debug: log.New(&AndroidLogger{level: C.ANDROID_LOG_DEBUG, interfaceName: interfaceName}, "", 0), + Info: log.New(&AndroidLogger{level: C.ANDROID_LOG_INFO, interfaceName: interfaceName}, "", 0), + Error: log.New(&AndroidLogger{level: C.ANDROID_LOG_ERROR, interfaceName: interfaceName}, "", 0), + } + + logger.Debug.Println("Debug log enabled") + + tun := &NativeTun{ + fd: os.NewFile(uintptr(tun_fd), ""), + events: make(chan TUNEvent, 5), + errors: make(chan error, 5), + } + device := NewDevice(tun, logger) + device.tun.mtu = DefaultMTU //TODO: make dynamic + + bufferedSettings := bufio.NewReadWriter(bufio.NewReader(strings.NewReader(settings)), bufio.NewWriter(ioutil.Discard)) + setError := ipcSetOperation(device, bufferedSettings) + if setError != nil { + logger.Debug.Println(setError) + return -1 + } + + device.Up() + logger.Info.Println("Device started") + + var i int32 + for i = 0; i < math.MaxInt32; i++ { + if _, exists := tunnelHandles[i]; !exists { + break + } + } + if i == math.MaxInt32 { + return -1 + } + tunnelHandles[i] = device + return i +} + +//export wgTurnOff +func wgTurnOff(tunnelHandle int32) { + device, ok := tunnelHandles[tunnelHandle] + if !ok { + return + } + delete(tunnelHandles, tunnelHandle) + device.Close() +} + +//export wgGetSocketV4 +func wgGetSocketV4(tunnelHandle int32) int32 { + device, ok := tunnelHandles[tunnelHandle] + if !ok { + return -1 + } + native, ok := device.net.bind.(NativeBind) + if !ok { + return -1 + } + return int32(native.sock4) +} + +//export wgGetSocketV6 +func wgGetSocketV6(tunnelHandle int32) int32 { + device, ok := tunnelHandles[tunnelHandle] + if !ok { + return -1 + } + native, ok := device.net.bind.(NativeBind) + if !ok { + return -1 + } + return int32(native.sock6) +} + +func main() {} diff --git a/app/tools/libwg-go/jni.c b/app/tools/libwg-go/jni.c new file mode 100644 index 00000000..1476dab7 --- /dev/null +++ b/app/tools/libwg-go/jni.c @@ -0,0 +1,40 @@ +#include <jni.h> + +struct go_string { const char *str; long n; }; +extern int wgTurnOn(struct go_string ifname, int tun_fd, struct go_string settings); +extern void wgTurnOff(int handle); +extern int wgGetSocketV4(int handle); +extern int wgGetSocketV6(int handle); + +JNIEXPORT jint JNICALL Java_com_wireguard_android_backend_GoBackend_wgTurnOn(JNIEnv *env, jclass c, jstring ifname, jint tun_fd, jstring settings) +{ + const char *ifname_str = (*env)->GetStringUTFChars(env, ifname, 0); + size_t ifname_len = (*env)->GetStringUTFLength(env, ifname); + const char *settings_str = (*env)->GetStringUTFChars(env, settings, 0); + size_t settings_len = (*env)->GetStringUTFLength(env, settings); + int ret = wgTurnOn((struct go_string){ + .str = ifname_str, + .n = ifname_len + }, tun_fd, (struct go_string){ + .str = settings_str, + .n = settings_len + }); + (*env)->ReleaseStringUTFChars(env, ifname, ifname_str); + (*env)->ReleaseStringUTFChars(env, settings, settings_str); + return ret; +} + +JNIEXPORT void JNICALL Java_com_wireguard_android_backend_GoBackend_wgTurnOff(JNIEnv *env, jclass c, jint handle) +{ + wgTurnOff(handle); +} + +JNIEXPORT jint JNICALL Java_com_wireguard_android_backend_GoBackend_wgGetSocketV4(JNIEnv *env, jclass c, jint handle) +{ + return wgGetSocketV4(handle); +} + +JNIEXPORT jint JNICALL Java_com_wireguard_android_backend_GoBackend_wgGetSocketV6(JNIEnv *env, jclass c, jint handle) +{ + return wgGetSocketV6(handle); +} diff --git a/app/tools/wireguard-go b/app/tools/wireguard-go new file mode 160000 +Subproject 8f1d1b8c54d747309d9fdf06b157823af2a823b |