Explorar el Código

GoBackend: integrate into app

Signed-off-by: Jason A. Donenfeld <Jason@zx2c4.com>
master
Jason A. Donenfeld hace 6 años
padre
commit
0ea6f73332
Se han modificado 15 ficheros con 348 adiciones y 59 borrados
  1. +3
    -0
      .gitmodules
  2. +2
    -3
      README.md
  3. +14
    -1
      app/src/main/java/com/wireguard/android/Application.java
  4. +8
    -0
      app/src/main/java/com/wireguard/android/activity/SettingsActivity.java
  5. +125
    -0
      app/src/main/java/com/wireguard/android/backend/GoBackend.java
  6. +0
    -12
      app/src/main/java/com/wireguard/android/backend/WgQuickBackend.java
  7. +5
    -25
      app/src/main/java/com/wireguard/android/fragment/TunnelController.java
  8. +0
    -17
      app/src/main/res/values/strings.xml
  9. +1
    -1
      app/src/main/res/xml/preferences.xml
  10. +13
    -0
      app/tools/CMakeLists.txt
  11. +4
    -0
      app/tools/libwg-go/.gitignore
  12. +21
    -0
      app/tools/libwg-go/Makefile
  13. +111
    -0
      app/tools/libwg-go/api-android.go
  14. +40
    -0
      app/tools/libwg-go/jni.c
  15. +1
    -0
      app/tools/wireguard-go

+ 3
- 0
.gitmodules Ver fichero

@@ -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

+ 2
- 3
README.md Ver fichero

@@ -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



+ 14
- 1
app/src/main/java/com/wireguard/android/Application.java Ver fichero

@@ -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


+ 8
- 0
app/src/main/java/com/wireguard/android/activity/SettingsActivity.java Ver fichero

@@ -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);
}
}
}
}

+ 125
- 0
app/src/main/java/com/wireguard/android/backend/GoBackend.java Ver fichero

@@ -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;
}
}
}

+ 0
- 12
app/src/main/java/com/wireguard/android/backend/WgQuickBackend.java Ver fichero

@@ -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);
}
}
}

+ 5
- 25
app/src/main/java/com/wireguard/android/fragment/TunnelController.java Ver fichero

@@ -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);
});
}
}

+ 0
- 17
app/src/main/res/values/strings.xml Ver fichero

@@ -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&rsquo;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>


+ 1
- 1
app/src/main/res/xml/preferences.xml Ver fichero

@@ -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>

+ 13
- 0
app/tools/CMakeLists.txt Ver fichero

@@ -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)

+ 4
- 0
app/tools/libwg-go/.gitignore Ver fichero

@@ -0,0 +1,4 @@
go/
*.go
libwg-go.h
jni.o

+ 21
- 0
app/tools/libwg-go/Makefile Ver fichero

@@ -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

+ 111
- 0
app/tools/libwg-go/api-android.go Ver fichero

@@ -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() {}

+ 40
- 0
app/tools/libwg-go/jni.c Ver fichero

@@ -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);
}

+ 1
- 0
app/tools/wireguard-go

@@ -0,0 +1 @@
Subproject commit 8f1d1b8c54d747309d9fdf06b157823af2a823bd

Cargando…
Cancelar
Guardar