summaryrefslogtreecommitdiffhomepage
path: root/app/src/main/java/com/wireguard/android
diff options
context:
space:
mode:
Diffstat (limited to 'app/src/main/java/com/wireguard/android')
-rw-r--r--app/src/main/java/com/wireguard/android/backend/Backend.java63
-rw-r--r--app/src/main/java/com/wireguard/android/backend/BackendException.java30
-rw-r--r--app/src/main/java/com/wireguard/android/backend/GoBackend.java292
-rw-r--r--app/src/main/java/com/wireguard/android/backend/Statistics.java62
-rw-r--r--app/src/main/java/com/wireguard/android/backend/Tunnel.java46
-rw-r--r--app/src/main/java/com/wireguard/android/backend/WgQuickBackend.java158
-rw-r--r--app/src/main/java/com/wireguard/android/util/AsyncWorker.java63
-rw-r--r--app/src/main/java/com/wireguard/android/util/RootShell.java211
-rw-r--r--app/src/main/java/com/wireguard/android/util/SharedLibraryLoader.java94
-rw-r--r--app/src/main/java/com/wireguard/android/util/ToolsInstaller.java196
10 files changed, 0 insertions, 1215 deletions
diff --git a/app/src/main/java/com/wireguard/android/backend/Backend.java b/app/src/main/java/com/wireguard/android/backend/Backend.java
deleted file mode 100644
index ed3a5ebd..00000000
--- a/app/src/main/java/com/wireguard/android/backend/Backend.java
+++ /dev/null
@@ -1,63 +0,0 @@
-/*
- * Copyright © 2017-2019 WireGuard LLC. All Rights Reserved.
- * SPDX-License-Identifier: Apache-2.0
- */
-
-package com.wireguard.android.backend;
-
-import com.wireguard.config.Config;
-
-import java.util.Collection;
-import java.util.Set;
-
-import androidx.annotation.Nullable;
-
-/**
- * Interface for implementations of the WireGuard secure network tunnel.
- */
-
-public interface Backend {
- /**
- * Enumerate names of currently-running tunnels.
- *
- * @return The set of running tunnel names.
- */
- Set<String> getRunningTunnelNames();
-
- /**
- * Get the state of a tunnel.
- *
- * @param tunnel The tunnel to examine the state of.
- * @return The state of the tunnel.
- */
- Tunnel.State getState(Tunnel tunnel) throws Exception;
-
- /**
- * Get statistics about traffic and errors on this tunnel. If the tunnel is not running, the
- * statistics object will be filled with zero values.
- *
- * @param tunnel The tunnel to retrieve statistics for.
- * @return The statistics for the tunnel.
- */
- Statistics getStatistics(Tunnel tunnel) throws Exception;
-
- /**
- * Determine version of underlying backend.
- *
- * @return The version of the backend.
- * @throws Exception
- */
- String getVersion() throws Exception;
-
- /**
- * Set the state of a tunnel, updating it's configuration. If the tunnel is already up, config
- * may update the running configuration; config may be null when setting the tunnel down.
- *
- * @param tunnel The tunnel to control the state of.
- * @param state The new state for this tunnel. Must be {@code UP}, {@code DOWN}, or
- * {@code TOGGLE}.
- * @param config The configuration for this tunnel, may be null if state is {@code DOWN}.
- * @return The updated state of the tunnel.
- */
- Tunnel.State setState(Tunnel tunnel, Tunnel.State state, @Nullable Config config) throws Exception;
-}
diff --git a/app/src/main/java/com/wireguard/android/backend/BackendException.java b/app/src/main/java/com/wireguard/android/backend/BackendException.java
deleted file mode 100644
index e1e8eaa9..00000000
--- a/app/src/main/java/com/wireguard/android/backend/BackendException.java
+++ /dev/null
@@ -1,30 +0,0 @@
-/*
- * Copyright © 2020 WireGuard LLC. All Rights Reserved.
- * SPDX-License-Identifier: Apache-2.0
- */
-
-package com.wireguard.android.backend;
-
-public final class BackendException extends Exception {
- public enum Reason {
- UNKNOWN_KERNEL_MODULE_NAME,
- WG_QUICK_CONFIG_ERROR_CODE,
- TUNNEL_MISSING_CONFIG,
- VPN_NOT_AUTHORIZED,
- UNABLE_TO_START_VPN,
- TUN_CREATION_ERROR,
- GO_ACTIVATION_ERROR_CODE
- }
- private final Reason reason;
- private final Object[] format;
- public BackendException(final Reason reason, final Object ...format) {
- this.reason = reason;
- this.format = format;
- }
- public Reason getReason() {
- return reason;
- }
- public Object[] getFormat() {
- return format;
- }
-}
diff --git a/app/src/main/java/com/wireguard/android/backend/GoBackend.java b/app/src/main/java/com/wireguard/android/backend/GoBackend.java
deleted file mode 100644
index 6ad5afa4..00000000
--- a/app/src/main/java/com/wireguard/android/backend/GoBackend.java
+++ /dev/null
@@ -1,292 +0,0 @@
-/*
- * Copyright © 2017-2019 WireGuard LLC. All Rights Reserved.
- * SPDX-License-Identifier: Apache-2.0
- */
-
-package com.wireguard.android.backend;
-
-import android.content.Context;
-import android.content.Intent;
-import android.os.Build;
-import android.os.ParcelFileDescriptor;
-import androidx.annotation.Nullable;
-import androidx.collection.ArraySet;
-import android.util.Log;
-
-import com.wireguard.android.backend.BackendException.Reason;
-import com.wireguard.android.backend.Tunnel.State;
-import com.wireguard.android.util.SharedLibraryLoader;
-import com.wireguard.config.Config;
-import com.wireguard.config.InetNetwork;
-import com.wireguard.config.Peer;
-import com.wireguard.crypto.Key;
-import com.wireguard.crypto.KeyFormatException;
-
-import java.net.InetAddress;
-import java.util.Collections;
-import java.util.Set;
-import java.util.concurrent.TimeUnit;
-import java.util.concurrent.TimeoutException;
-
-import java9.util.concurrent.CompletableFuture;
-
-public final class GoBackend implements Backend {
- private static final String TAG = "WireGuard/" + GoBackend.class.getSimpleName();
- private static CompletableFuture<VpnService> vpnService = new CompletableFuture<>();
- public interface AlwaysOnCallback {
- void alwaysOnTriggered();
- }
- @Nullable private static AlwaysOnCallback alwaysOnCallback;
- public static void setAlwaysOnCallback(AlwaysOnCallback cb) {
- alwaysOnCallback = cb;
- }
-
- private final Context context;
- @Nullable private Tunnel currentTunnel;
- @Nullable private Config currentConfig;
- private int currentTunnelHandle = -1;
-
- public GoBackend(final Context context) {
- SharedLibraryLoader.loadSharedLibrary(context, "wg-go");
- this.context = context;
- }
-
- private static native String wgGetConfig(int handle);
-
- 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);
-
- private static native String wgVersion();
-
- @Override
- public Set<String> getRunningTunnelNames() {
- 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) {
- final Statistics stats = new Statistics();
- if (tunnel != currentTunnel) {
- return stats;
- }
- final String config = wgGetConfig(currentTunnelHandle);
- Key key = null;
- long rx = 0, tx = 0;
- for (final String line : config.split("\\n")) {
- if (line.startsWith("public_key=")) {
- if (key != null)
- stats.add(key, rx, tx);
- rx = 0;
- tx = 0;
- try {
- key = Key.fromHex(line.substring(11));
- } catch (final KeyFormatException ignored) {
- key = null;
- }
- } else if (line.startsWith("rx_bytes=")) {
- if (key == null)
- continue;
- try {
- rx = Long.parseLong(line.substring(9));
- } catch (final NumberFormatException ignored) {
- rx = 0;
- }
- } else if (line.startsWith("tx_bytes=")) {
- if (key == null)
- continue;
- try {
- tx = Long.parseLong(line.substring(9));
- } catch (final NumberFormatException ignored) {
- tx = 0;
- }
- }
- }
- if (key != null)
- stats.add(key, rx, tx);
- return stats;
- }
-
- @Override
- public String getVersion() {
- return wgVersion();
- }
-
- @Override
- public State setState(final Tunnel tunnel, State state, @Nullable final Config config) throws Exception {
- final State originalState = getState(tunnel);
-
- if (state == State.TOGGLE)
- state = originalState == State.UP ? State.DOWN : State.UP;
- if (state == originalState && tunnel == currentTunnel && config == currentConfig)
- return originalState;
- if (state == State.UP) {
- final Config originalConfig = currentConfig;
- final Tunnel originalTunnel = currentTunnel;
- if (currentTunnel != null)
- setStateInternal(currentTunnel, null, State.DOWN);
- try {
- setStateInternal(tunnel, config, state);
- } catch(final Exception e) {
- if (originalTunnel != null)
- setStateInternal(originalTunnel, originalConfig, State.UP);
- throw e;
- }
- } else if (state == State.DOWN && tunnel == currentTunnel) {
- setStateInternal(tunnel, null, State.DOWN);
- }
- return getState(tunnel);
- }
-
- private void setStateInternal(final Tunnel tunnel, @Nullable final Config config, final State state)
- throws Exception {
- Log.i(TAG, "Bringing tunnel " + tunnel.getName() + " " + state);
-
- if (state == State.UP) {
- if (config == null)
- throw new BackendException(Reason.TUNNEL_MISSING_CONFIG);
-
- if (VpnService.prepare(context) != null)
- throw new BackendException(Reason.VPN_NOT_AUTHORIZED);
-
- final VpnService service;
- if (!vpnService.isDone())
- startVpnService();
-
- try {
- service = vpnService.get(2, TimeUnit.SECONDS);
- } catch (final TimeoutException e) {
- final Exception be = new BackendException(Reason.UNABLE_TO_START_VPN);
- be.initCause(e);
- throw be;
- }
- service.setOwner(this);
-
- if (currentTunnelHandle != -1) {
- Log.w(TAG, "Tunnel already up");
- return;
- }
-
- // Build config
- final String goConfig = config.toWgUserspaceString();
-
- // Create the vpn tunnel with android API
- final VpnService.Builder builder = service.getBuilder();
- builder.setSession(tunnel.getName());
-
- for (final String excludedApplication : config.getInterface().getExcludedApplications())
- builder.addDisallowedApplication(excludedApplication);
-
- for (final InetNetwork addr : config.getInterface().getAddresses())
- builder.addAddress(addr.getAddress(), addr.getMask());
-
- for (final InetAddress addr : config.getInterface().getDnsServers())
- builder.addDnsServer(addr.getHostAddress());
-
- for (final Peer peer : config.getPeers()) {
- for (final InetNetwork addr : peer.getAllowedIps())
- builder.addRoute(addr.getAddress(), addr.getMask());
- }
-
- builder.setMtu(config.getInterface().getMtu().orElse(1280));
-
- if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q)
- builder.setMetered(false);
- if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M)
- service.setUnderlyingNetworks(null);
-
- builder.setBlocking(true);
- try (final ParcelFileDescriptor tun = builder.establish()) {
- if (tun == null)
- throw new BackendException(Reason.TUN_CREATION_ERROR);
- Log.d(TAG, "Go backend v" + wgVersion());
- currentTunnelHandle = wgTurnOn(tunnel.getName(), tun.detachFd(), goConfig);
- }
- if (currentTunnelHandle < 0)
- throw new BackendException(Reason.GO_ACTIVATION_ERROR_CODE, currentTunnelHandle);
-
- currentTunnel = tunnel;
- currentConfig = config;
-
- service.protect(wgGetSocketV4(currentTunnelHandle));
- service.protect(wgGetSocketV6(currentTunnelHandle));
- } else {
- if (currentTunnelHandle == -1) {
- Log.w(TAG, "Tunnel already down");
- return;
- }
-
- wgTurnOff(currentTunnelHandle);
- currentTunnel = null;
- currentTunnelHandle = -1;
- currentConfig = null;
- }
-
- tunnel.onStateChange(state);
- }
-
- private void startVpnService() {
- Log.d(TAG, "Requesting to start VpnService");
- context.startService(new Intent(context, VpnService.class));
- }
-
- public static class VpnService extends android.net.VpnService {
- @Nullable private GoBackend owner;
-
- public void setOwner(final GoBackend owner) {
- this.owner = owner;
- }
-
- public Builder getBuilder() {
- return new Builder();
- }
-
- @Override
- public void onCreate() {
- vpnService.complete(this);
- super.onCreate();
- }
-
- @Override
- public void onDestroy() {
- if (owner != null) {
- final Tunnel tunnel = owner.currentTunnel;
- if (tunnel != null) {
- if (owner.currentTunnelHandle != -1)
- wgTurnOff(owner.currentTunnelHandle);
- owner.currentTunnel = null;
- owner.currentTunnelHandle = -1;
- owner.currentConfig = null;
- tunnel.onStateChange(State.DOWN);
- }
- }
- vpnService = vpnService.newIncompleteFuture();
- super.onDestroy();
- }
-
- @Override
- public int onStartCommand(@Nullable final Intent intent, final int flags, final int startId) {
- vpnService.complete(this);
- if (intent == null || intent.getComponent() == null || !intent.getComponent().getPackageName().equals(getPackageName())) {
- Log.d(TAG, "Service started by Always-on VPN feature");
- if (alwaysOnCallback != null)
- alwaysOnCallback.alwaysOnTriggered();
- }
- return super.onStartCommand(intent, flags, startId);
- }
- }
-}
diff --git a/app/src/main/java/com/wireguard/android/backend/Statistics.java b/app/src/main/java/com/wireguard/android/backend/Statistics.java
deleted file mode 100644
index 2ca87d23..00000000
--- a/app/src/main/java/com/wireguard/android/backend/Statistics.java
+++ /dev/null
@@ -1,62 +0,0 @@
-/*
- * Copyright © 2020 WireGuard LLC. All Rights Reserved.
- * SPDX-License-Identifier: Apache-2.0
- */
-
-package com.wireguard.android.backend;
-
-import android.os.SystemClock;
-import android.util.Pair;
-
-import com.wireguard.crypto.Key;
-
-import java.util.HashMap;
-import java.util.Map;
-
-public class Statistics {
- private long lastTouched = SystemClock.elapsedRealtime();
- private final Map<Key, Pair<Long, Long>> peerBytes = new HashMap<>();
-
- Statistics() { }
-
- void add(final Key key, final long rx, final long tx) {
- peerBytes.put(key, Pair.create(rx, tx));
- lastTouched = SystemClock.elapsedRealtime();
- }
-
- public boolean isStale() {
- return SystemClock.elapsedRealtime() - lastTouched > 900;
- }
-
- public Key[] peers() {
- return peerBytes.keySet().toArray(new Key[0]);
- }
-
- public long peerRx(final Key peer) {
- if (!peerBytes.containsKey(peer))
- return 0;
- return peerBytes.get(peer).first;
- }
-
- public long peerTx(final Key peer) {
- if (!peerBytes.containsKey(peer))
- return 0;
- return peerBytes.get(peer).second;
- }
-
- public long totalRx() {
- long rx = 0;
- for (final Pair<Long, Long> val : peerBytes.values()) {
- rx += val.first;
- }
- return rx;
- }
-
- public long totalTx() {
- long tx = 0;
- for (final Pair<Long, Long> val : peerBytes.values()) {
- tx += val.second;
- }
- return tx;
- }
-}
diff --git a/app/src/main/java/com/wireguard/android/backend/Tunnel.java b/app/src/main/java/com/wireguard/android/backend/Tunnel.java
deleted file mode 100644
index af2f59f7..00000000
--- a/app/src/main/java/com/wireguard/android/backend/Tunnel.java
+++ /dev/null
@@ -1,46 +0,0 @@
-/*
- * Copyright © 2020 WireGuard LLC. All Rights Reserved.
- * SPDX-License-Identifier: Apache-2.0
- */
-
-package com.wireguard.android.backend;
-
-import java.util.regex.Pattern;
-
-/**
- * Represents a WireGuard tunnel.
- */
-
-public interface Tunnel {
- enum State {
- DOWN,
- TOGGLE,
- UP;
-
- public static State of(final boolean running) {
- return running ? UP : DOWN;
- }
- }
-
- int NAME_MAX_LENGTH = 15;
- Pattern NAME_PATTERN = Pattern.compile("[a-zA-Z0-9_=+.-]{1,15}");
-
- static boolean isNameInvalid(final CharSequence name) {
- return !NAME_PATTERN.matcher(name).matches();
- }
-
- /**
- * Get the name of the tunnel, which should always pass the !isNameInvalid test.
- *
- * @return The name of the tunnel.
- */
- String getName();
-
- /**
- * React to a change in state of the tunnel. Should only be directly called by Backend.
- *
- * @param newState The new state of the tunnel.
- * @return The new state of the tunnel.
- */
- void onStateChange(State newState);
-}
diff --git a/app/src/main/java/com/wireguard/android/backend/WgQuickBackend.java b/app/src/main/java/com/wireguard/android/backend/WgQuickBackend.java
deleted file mode 100644
index 9695aab7..00000000
--- a/app/src/main/java/com/wireguard/android/backend/WgQuickBackend.java
+++ /dev/null
@@ -1,158 +0,0 @@
-/*
- * Copyright © 2017-2019 WireGuard LLC. All Rights Reserved.
- * SPDX-License-Identifier: Apache-2.0
- */
-
-package com.wireguard.android.backend;
-
-import androidx.annotation.Nullable;
-
-import android.content.Context;
-import android.util.Log;
-
-import com.wireguard.android.backend.BackendException.Reason;
-import com.wireguard.android.backend.Tunnel.State;
-import com.wireguard.android.util.RootShell;
-import com.wireguard.android.util.ToolsInstaller;
-import com.wireguard.config.Config;
-import com.wireguard.crypto.Key;
-
-import java.io.File;
-import java.io.FileOutputStream;
-import java.nio.charset.StandardCharsets;
-import java.util.ArrayList;
-import java.util.Collection;
-import java.util.Collections;
-import java.util.List;
-import java.util.Locale;
-import java.util.Map;
-import java.util.Objects;
-import java.util.Set;
-import java.util.HashMap;
-
-import java9.util.stream.Collectors;
-import java9.util.stream.Stream;
-
-/**
- * WireGuard backend that uses {@code wg-quick} to implement tunnel configuration.
- */
-
-public final class WgQuickBackend implements Backend {
- private static final String TAG = "WireGuard/" + WgQuickBackend.class.getSimpleName();
-
- private final RootShell rootShell;
- private final ToolsInstaller toolsInstaller;
- private final File localTemporaryDir;
- private final Map<Tunnel, Config> runningConfigs = new HashMap<>();
-
- public WgQuickBackend(final Context context, final RootShell rootShell, final ToolsInstaller toolsInstaller) {
- localTemporaryDir = new File(context.getCacheDir(), "tmp");
- this.rootShell = rootShell;
- this.toolsInstaller = toolsInstaller;
- }
-
- @Override
- public Set<String> getRunningTunnelNames() {
- final List<String> output = new ArrayList<>();
- // Don't throw an exception here or nothing will show up in the UI.
- try {
- toolsInstaller.ensureToolsAvailable();
- if (rootShell.run(output, "wg show interfaces") != 0 || output.isEmpty())
- return Collections.emptySet();
- } catch (final Exception e) {
- Log.w(TAG, "Unable to enumerate running tunnels", e);
- return Collections.emptySet();
- }
- // wg puts all interface names on the same line. Split them into separate elements.
- return Stream.of(output.get(0).split(" ")).collect(Collectors.toUnmodifiableSet());
- }
-
- @Override
- public State getState(final Tunnel tunnel) {
- return getRunningTunnelNames().contains(tunnel.getName()) ? State.UP : State.DOWN;
- }
-
- @Override
- public Statistics getStatistics(final Tunnel tunnel) {
- final Statistics stats = new Statistics();
- final Collection<String> output = new ArrayList<>();
- try {
- if (rootShell.run(output, String.format("wg show '%s' transfer", tunnel.getName())) != 0)
- return stats;
- } catch (final Exception ignored) {
- return stats;
- }
- for (final String line : output) {
- final String[] parts = line.split("\\t");
- if (parts.length != 3)
- continue;
- try {
- stats.add(Key.fromBase64(parts[0]), Long.parseLong(parts[1]), Long.parseLong(parts[2]));
- } catch (final Exception ignored) {
- }
- }
- return stats;
- }
-
- @Override
- public String getVersion() throws Exception {
- final List<String> output = new ArrayList<>();
- if (rootShell.run(output, "cat /sys/module/wireguard/version") != 0 || output.isEmpty())
- throw new BackendException(Reason.UNKNOWN_KERNEL_MODULE_NAME);
- return output.get(0);
- }
-
- @Override
- public State setState(final Tunnel tunnel, State state, @Nullable final Config config) throws Exception {
- final State originalState = getState(tunnel);
- final Config originalConfig = runningConfigs.get(tunnel);
-
- if (state == State.TOGGLE)
- state = originalState == State.UP ? State.DOWN : State.UP;
- if ((state == State.UP && originalState == State.UP && originalConfig != null && originalConfig == config) ||
- (state == State.DOWN && originalState == State.DOWN))
- return originalState;
- if (state == State.UP) {
- toolsInstaller.ensureToolsAvailable();
- if (originalState == State.UP)
- setStateInternal(tunnel, originalConfig == null ? config : originalConfig, State.DOWN);
- try {
- setStateInternal(tunnel, config, State.UP);
- } catch(final Exception e) {
- if (originalState == State.UP && originalConfig != null)
- setStateInternal(tunnel, originalConfig, State.UP);
- throw e;
- }
- } else if (state == State.DOWN) {
- setStateInternal(tunnel, originalConfig == null ? config : originalConfig, State.DOWN);
- }
- return state;
- }
-
- private void setStateInternal(final Tunnel tunnel, @Nullable final Config config, final State state) throws Exception {
- Log.i(TAG, "Bringing tunnel " + tunnel.getName() + " " + state);
-
- Objects.requireNonNull(config, "Trying to set state up with a null config");
-
- final File tempFile = new File(localTemporaryDir, tunnel.getName() + ".conf");
- try (final FileOutputStream stream = new FileOutputStream(tempFile, false)) {
- stream.write(config.toWgQuickString().getBytes(StandardCharsets.UTF_8));
- }
- String command = String.format("wg-quick %s '%s'",
- state.toString().toLowerCase(Locale.ENGLISH), tempFile.getAbsolutePath());
- if (state == State.UP)
- command = "cat /sys/module/wireguard/version && " + command;
- final int result = rootShell.run(null, command);
- // noinspection ResultOfMethodCallIgnored
- tempFile.delete();
- if (result != 0)
- throw new BackendException(Reason.WG_QUICK_CONFIG_ERROR_CODE, result);
-
- if (state == State.UP)
- runningConfigs.put(tunnel, config);
- else
- runningConfigs.remove(tunnel);
-
- tunnel.onStateChange(state);
- }
-}
diff --git a/app/src/main/java/com/wireguard/android/util/AsyncWorker.java b/app/src/main/java/com/wireguard/android/util/AsyncWorker.java
deleted file mode 100644
index 1d041851..00000000
--- a/app/src/main/java/com/wireguard/android/util/AsyncWorker.java
+++ /dev/null
@@ -1,63 +0,0 @@
-/*
- * Copyright © 2017-2019 WireGuard LLC. All Rights Reserved.
- * SPDX-License-Identifier: Apache-2.0
- */
-
-package com.wireguard.android.util;
-
-import android.os.Handler;
-
-import java.util.concurrent.Executor;
-
-import java9.util.concurrent.CompletableFuture;
-import java9.util.concurrent.CompletionStage;
-
-/**
- * Helper class for running asynchronous tasks and ensuring they are completed on the main thread.
- */
-
-public class AsyncWorker {
- private final Executor executor;
- private final Handler handler;
-
- public AsyncWorker(final Executor executor, final Handler handler) {
- this.executor = executor;
- this.handler = handler;
- }
-
- public CompletionStage<Void> runAsync(final AsyncRunnable<?> runnable) {
- final CompletableFuture<Void> future = new CompletableFuture<>();
- executor.execute(() -> {
- try {
- runnable.run();
- handler.post(() -> future.complete(null));
- } catch (final Throwable t) {
- handler.post(() -> future.completeExceptionally(t));
- }
- });
- return future;
- }
-
- public <T> CompletionStage<T> supplyAsync(final AsyncSupplier<T, ?> supplier) {
- final CompletableFuture<T> future = new CompletableFuture<>();
- executor.execute(() -> {
- try {
- final T result = supplier.get();
- handler.post(() -> future.complete(result));
- } catch (final Throwable t) {
- handler.post(() -> future.completeExceptionally(t));
- }
- });
- return future;
- }
-
- @FunctionalInterface
- public interface AsyncRunnable<E extends Throwable> {
- void run() throws E;
- }
-
- @FunctionalInterface
- public interface AsyncSupplier<T, E extends Throwable> {
- T get() throws E;
- }
-}
diff --git a/app/src/main/java/com/wireguard/android/util/RootShell.java b/app/src/main/java/com/wireguard/android/util/RootShell.java
deleted file mode 100644
index 1fc2c9f2..00000000
--- a/app/src/main/java/com/wireguard/android/util/RootShell.java
+++ /dev/null
@@ -1,211 +0,0 @@
-/*
- * Copyright © 2017-2019 WireGuard LLC. All Rights Reserved.
- * SPDX-License-Identifier: Apache-2.0
- */
-
-package com.wireguard.android.util;
-
-import android.content.Context;
-import androidx.annotation.Nullable;
-import android.util.Log;
-
-import com.wireguard.android.util.RootShell.RootShellException.Reason;
-
-import java.io.BufferedReader;
-import java.io.File;
-import java.io.IOException;
-import java.io.InputStreamReader;
-import java.io.OutputStreamWriter;
-import java.nio.charset.StandardCharsets;
-import java.util.Collection;
-import java.util.UUID;
-
-/**
- * Helper class for running commands as root.
- */
-
-public class RootShell {
- private static final String SU = "su";
- private static final String TAG = "WireGuard/" + RootShell.class.getSimpleName();
-
- private final File localBinaryDir;
- private final File localTemporaryDir;
- private final Object lock = new Object();
- private final String preamble;
- @Nullable private Process process;
- @Nullable private BufferedReader stderr;
- @Nullable private OutputStreamWriter stdin;
- @Nullable private BufferedReader stdout;
-
- public RootShell(final Context context) {
- localBinaryDir = new File(context.getCodeCacheDir(), "bin");
- localTemporaryDir = new File(context.getCacheDir(), "tmp");
- preamble = String.format("export CALLING_PACKAGE=%s PATH=\"%s:$PATH\" TMPDIR='%s'; id -u\n",
- context.getPackageName(), localBinaryDir, localTemporaryDir);
- }
-
- private static boolean isExecutableInPath(final String name) {
- final String path = System.getenv("PATH");
- if (path == null)
- return false;
- for (final String dir : path.split(":"))
- if (new File(dir, name).canExecute())
- return true;
- return false;
- }
-
- private boolean isRunning() {
- synchronized (lock) {
- try {
- // Throws an exception if the process hasn't finished yet.
- if (process != null)
- process.exitValue();
- return false;
- } catch (final IllegalThreadStateException ignored) {
- // The existing process is still running.
- return true;
- }
- }
- }
-
- /**
- * Run a command in a root shell.
- *
- * @param output Lines read from stdout are appended to this list. Pass null if the
- * output from the shell is not important.
- * @param command Command to run as root.
- * @return The exit value of the command.
- */
- public int run(@Nullable final Collection<String> output, final String command)
- throws IOException, RootShellException {
- synchronized (lock) {
- /* Start inside synchronized block to prevent a concurrent call to stop(). */
- start();
- final String marker = UUID.randomUUID().toString();
- final String script = "echo " + marker + "; echo " + marker + " >&2; (" + command +
- "); ret=$?; echo " + marker + " $ret; echo " + marker + " $ret >&2\n";
- Log.v(TAG, "executing: " + command);
- stdin.write(script);
- stdin.flush();
- String line;
- int errnoStdout = Integer.MIN_VALUE;
- int errnoStderr = Integer.MAX_VALUE;
- int markersSeen = 0;
- while ((line = stdout.readLine()) != null) {
- if (line.startsWith(marker)) {
- ++markersSeen;
- if (line.length() > marker.length() + 1) {
- errnoStdout = Integer.valueOf(line.substring(marker.length() + 1));
- break;
- }
- } else if (markersSeen > 0) {
- if (output != null)
- output.add(line);
- Log.v(TAG, "stdout: " + line);
- }
- }
- while ((line = stderr.readLine()) != null) {
- if (line.startsWith(marker)) {
- ++markersSeen;
- if (line.length() > marker.length() + 1) {
- errnoStderr = Integer.valueOf(line.substring(marker.length() + 1));
- break;
- }
- } else if (markersSeen > 2) {
- Log.v(TAG, "stderr: " + line);
- }
- }
- if (markersSeen != 4)
- throw new RootShellException(Reason.SHELL_MARKER_COUNT_ERROR, markersSeen);
- if (errnoStdout != errnoStderr)
- throw new RootShellException(Reason.SHELL_EXIT_STATUS_READ_ERROR);
- Log.v(TAG, "exit: " + errnoStdout);
- return errnoStdout;
- }
- }
-
- public void start() throws IOException, RootShellException {
- if (!isExecutableInPath(SU))
- throw new RootShellException(Reason.NO_ROOT_ACCESS);
- synchronized (lock) {
- if (isRunning())
- return;
- if (!localBinaryDir.isDirectory() && !localBinaryDir.mkdirs())
- throw new RootShellException(Reason.CREATE_BIN_DIR_ERROR);
- if (!localTemporaryDir.isDirectory() && !localTemporaryDir.mkdirs())
- throw new RootShellException(Reason.CREATE_TEMP_DIR_ERROR);
- try {
- final ProcessBuilder builder = new ProcessBuilder().command(SU);
- builder.environment().put("LC_ALL", "C");
- try {
- process = builder.start();
- } catch (final IOException e) {
- // A failure at this stage means the device isn't rooted.
- final RootShellException rse = new RootShellException(Reason.NO_ROOT_ACCESS);
- rse.initCause(e);
- throw rse;
- }
- stdin = new OutputStreamWriter(process.getOutputStream(), StandardCharsets.UTF_8);
- stdout = new BufferedReader(new InputStreamReader(process.getInputStream(),
- StandardCharsets.UTF_8));
- stderr = new BufferedReader(new InputStreamReader(process.getErrorStream(),
- StandardCharsets.UTF_8));
- stdin.write(preamble);
- stdin.flush();
- // Check that the shell started successfully.
- final String uid = stdout.readLine();
- if (!"0".equals(uid)) {
- Log.w(TAG, "Root check did not return correct UID: " + uid);
- throw new RootShellException(Reason.NO_ROOT_ACCESS);
- }
- if (!isRunning()) {
- String line;
- while ((line = stderr.readLine()) != null) {
- Log.w(TAG, "Root check returned an error: " + line);
- if (line.contains("Permission denied"))
- throw new RootShellException(Reason.NO_ROOT_ACCESS);
- }
- throw new RootShellException(Reason.SHELL_START_ERROR, process.exitValue());
- }
- } catch (final IOException | RootShellException e) {
- stop();
- throw e;
- }
- }
- }
-
- public void stop() {
- synchronized (lock) {
- if (process != null) {
- process.destroy();
- process = null;
- }
- }
- }
-
- public static class RootShellException extends Exception {
- public enum Reason {
- NO_ROOT_ACCESS,
- SHELL_MARKER_COUNT_ERROR,
- SHELL_EXIT_STATUS_READ_ERROR,
- SHELL_START_ERROR,
- CREATE_BIN_DIR_ERROR,
- CREATE_TEMP_DIR_ERROR
- }
- private final Reason reason;
- private final Object[] format;
- public RootShellException(final Reason reason, final Object ...format) {
- this.reason = reason;
- this.format = format;
- }
- public boolean isIORelated() {
- return reason != Reason.NO_ROOT_ACCESS;
- }
- public Reason getReason() {
- return reason;
- }
- public Object[] getFormat() {
- return format;
- }
- }
-}
diff --git a/app/src/main/java/com/wireguard/android/util/SharedLibraryLoader.java b/app/src/main/java/com/wireguard/android/util/SharedLibraryLoader.java
deleted file mode 100644
index 93e44b64..00000000
--- a/app/src/main/java/com/wireguard/android/util/SharedLibraryLoader.java
+++ /dev/null
@@ -1,94 +0,0 @@
-/*
- * Copyright © 2017-2019 WireGuard LLC. All Rights Reserved.
- * SPDX-License-Identifier: Apache-2.0
- */
-
-package com.wireguard.android.util;
-
-import android.content.Context;
-import android.os.Build;
-import android.util.Log;
-
-import java.io.File;
-import java.io.FileOutputStream;
-import java.io.IOException;
-import java.io.InputStream;
-import java.util.Arrays;
-import java.util.Collection;
-import java.util.HashSet;
-import java.util.zip.ZipEntry;
-import java.util.zip.ZipFile;
-
-public final class SharedLibraryLoader {
- private static final String TAG = "WireGuard/" + SharedLibraryLoader.class.getSimpleName();
-
- private SharedLibraryLoader() {
- }
-
- public static boolean extractLibrary(final Context context, final String libName, final File destination) throws IOException {
- final Collection<String> apks = new HashSet<>();
- if (context.getApplicationInfo().sourceDir != null)
- apks.add(context.getApplicationInfo().sourceDir);
- if (context.getApplicationInfo().splitSourceDirs != null)
- apks.addAll(Arrays.asList(context.getApplicationInfo().splitSourceDirs));
-
- for (final String abi : Build.SUPPORTED_ABIS) {
- for (final String apk : apks) {
- final ZipFile zipFile;
- try {
- zipFile = new ZipFile(new File(apk), ZipFile.OPEN_READ);
- } catch (final IOException e) {
- throw new RuntimeException(e);
- }
-
- final String mappedLibName = System.mapLibraryName(libName);
- final byte[] buffer = new byte[1024 * 32];
- final String libZipPath = "lib" + File.separatorChar + abi + File.separatorChar + mappedLibName;
- final ZipEntry zipEntry = zipFile.getEntry(libZipPath);
- if (zipEntry == null)
- continue;
- Log.d(TAG, "Extracting apk:/" + libZipPath + " to " + destination.getAbsolutePath());
- try (final FileOutputStream out = new FileOutputStream(destination);
- final InputStream in = zipFile.getInputStream(zipEntry)) {
- int len;
- while ((len = in.read(buffer)) != -1) {
- out.write(buffer, 0, len);
- }
- out.getFD().sync();
- }
- zipFile.close();
- return true;
- }
- }
- return false;
- }
-
- public static void loadSharedLibrary(final Context context, final String libName) {
- Throwable noAbiException;
- try {
- System.loadLibrary(libName);
- return;
- } catch (final UnsatisfiedLinkError e) {
- Log.d(TAG, "Failed to load library normally, so attempting to extract from apk", e);
- noAbiException = e;
- }
- File f = null;
- try {
- f = File.createTempFile("lib", ".so", context.getCodeCacheDir());
- if (extractLibrary(context, libName, f)) {
- System.load(f.getAbsolutePath());
- return;
- }
- } catch (final Exception e) {
- Log.d(TAG, "Failed to load library apk:/" + libName, e);
- noAbiException = e;
- } finally {
- if (f != null)
- // noinspection ResultOfMethodCallIgnored
- f.delete();
- }
- if (noAbiException instanceof RuntimeException)
- throw (RuntimeException) noAbiException;
- throw new RuntimeException(noAbiException);
- }
-}
diff --git a/app/src/main/java/com/wireguard/android/util/ToolsInstaller.java b/app/src/main/java/com/wireguard/android/util/ToolsInstaller.java
deleted file mode 100644
index ac18cabf..00000000
--- a/app/src/main/java/com/wireguard/android/util/ToolsInstaller.java
+++ /dev/null
@@ -1,196 +0,0 @@
-/*
- * Copyright © 2017-2019 WireGuard LLC. All Rights Reserved.
- * SPDX-License-Identifier: Apache-2.0
- */
-
-package com.wireguard.android.util;
-
-import android.content.Context;
-import androidx.annotation.Nullable;
-import android.system.OsConstants;
-import android.util.Log;
-
-import com.wireguard.android.util.RootShell.RootShellException;
-
-import java.io.File;
-import java.io.FileNotFoundException;
-import java.io.IOException;
-import java.util.Arrays;
-import java.util.List;
-
-/**
- * Helper to install WireGuard tools to the system partition.
- */
-
-public final class ToolsInstaller {
- public static final int ERROR = 0x0;
- public static final int MAGISK = 0x4;
- public static final int NO = 0x2;
- public static final int SYSTEM = 0x8;
- public static final int YES = 0x1;
- private static final String[] EXECUTABLES = {"wg", "wg-quick"};
- private static final File[] INSTALL_DIRS = {
- new File("/system/xbin"),
- new File("/system/bin"),
- };
- @Nullable private static final File INSTALL_DIR = getInstallDir();
- private static final String TAG = "WireGuard/" + ToolsInstaller.class.getSimpleName();
-
- private final Context context;
- private final RootShell rootShell;
- private final File localBinaryDir;
- private final Object lock = new Object();
- @Nullable private Boolean areToolsAvailable;
- @Nullable private Boolean installAsMagiskModule;
-
- public ToolsInstaller(final Context context, final RootShell rootShell) {
- localBinaryDir = new File(context.getCodeCacheDir(), "bin");
- this.context = context;
- this.rootShell = rootShell;
- }
-
- @Nullable
- private static File getInstallDir() {
- final String path = System.getenv("PATH");
- if (path == null)
- return INSTALL_DIRS[0];
- final List<String> paths = Arrays.asList(path.split(":"));
- for (final File dir : INSTALL_DIRS) {
- if (paths.contains(dir.getPath()) && dir.isDirectory())
- return dir;
- }
- return null;
- }
-
- public int areInstalled() throws RootShellException {
- if (INSTALL_DIR == null)
- return ERROR;
- final StringBuilder script = new StringBuilder();
- for (final String name : EXECUTABLES) {
- script.append(String.format("cmp -s '%s' '%s' && ",
- new File(localBinaryDir, name).getAbsolutePath(),
- new File(INSTALL_DIR, name).getAbsolutePath()));
- }
- script.append("exit ").append(OsConstants.EALREADY).append(';');
- try {
- final int ret = rootShell.run(null, script.toString());
- if (ret == OsConstants.EALREADY)
- return willInstallAsMagiskModule() ? YES | MAGISK : YES | SYSTEM;
- else
- return willInstallAsMagiskModule() ? NO | MAGISK : NO | SYSTEM;
- } catch (final IOException ignored) {
- return ERROR;
- } catch (final RootShellException e) {
- if (e.isIORelated())
- return ERROR;
- throw e;
- }
- }
-
- public void ensureToolsAvailable() throws FileNotFoundException {
- synchronized (lock) {
- if (areToolsAvailable == null) {
- try {
- Log.d(TAG, extract() ? "Tools are now extracted into our private binary dir" :
- "Tools were already extracted into our private binary dir");
- areToolsAvailable = true;
- } catch (final IOException e) {
- Log.e(TAG, "The wg and wg-quick tools are not available", e);
- areToolsAvailable = false;
- }
- }
- if (!areToolsAvailable)
- throw new FileNotFoundException("Required tools unavailable");
- }
- }
-
- public int install() throws RootShellException, IOException {
- if (!context.getPackageName().startsWith("com.wireguard."))
- throw new SecurityException("The tools may only be installed system-wide from the main WireGuard app.");
- return willInstallAsMagiskModule() ? installMagisk() : installSystem();
- }
-
- private int installMagisk() throws RootShellException, IOException {
- extract();
- final StringBuilder script = new StringBuilder("set -ex; ");
-
- script.append("trap 'rm -rf /sbin/.magisk/img/wireguard' INT TERM EXIT; ");
- script.append(String.format("rm -rf /sbin/.magisk/img/wireguard/; mkdir -p /sbin/.magisk/img/wireguard%s; ", INSTALL_DIR));
- script.append("printf 'name=WireGuard Command Line Tools\nversion=1.0\nversionCode=1\nauthor=zx2c4\ndescription=Command line tools for WireGuard\nminMagisk=1500\n' > /sbin/.magisk/img/wireguard/module.prop; ");
- script.append("touch /sbin/.magisk/img/wireguard/auto_mount; ");
- for (final String name : EXECUTABLES) {
- final File destination = new File("/sbin/.magisk/img/wireguard" + INSTALL_DIR, name);
- script.append(String.format("cp '%s' '%s'; chmod 755 '%s'; chcon 'u:object_r:system_file:s0' '%s' || true; ",
- new File(localBinaryDir, name), destination, destination, destination));
- }
- script.append("trap - INT TERM EXIT;");
-
- try {
- return rootShell.run(null, script.toString()) == 0 ? YES | MAGISK : ERROR;
- } catch (final IOException ignored) {
- return ERROR;
- } catch (final RootShellException e) {
- if (e.isIORelated())
- return ERROR;
- throw e;
- }
- }
-
- private int installSystem() throws RootShellException, IOException {
- if (INSTALL_DIR == null)
- return OsConstants.ENOENT;
- extract();
- final StringBuilder script = new StringBuilder("set -ex; ");
- script.append("trap 'mount -o ro,remount /system' EXIT; mount -o rw,remount /system; ");
- for (final String name : EXECUTABLES) {
- final File destination = new File(INSTALL_DIR, name);
- script.append(String.format("cp '%s' '%s'; chmod 755 '%s'; restorecon '%s' || true; ",
- new File(localBinaryDir, name), destination, destination, destination));
- }
- try {
- return rootShell.run(null, script.toString()) == 0 ? YES | SYSTEM : ERROR;
- } catch (final IOException ignored) {
- return ERROR;
- } catch (final RootShellException e) {
- if (e.isIORelated())
- return ERROR;
- throw e;
- }
- }
-
- public boolean extract() throws IOException {
- localBinaryDir.mkdirs();
- final File files[] = new File[EXECUTABLES.length];
- final File tempFiles[] = new File[EXECUTABLES.length];
- boolean allExist = true;
- for (int i = 0; i < files.length; ++i) {
- files[i] = new File(localBinaryDir, EXECUTABLES[i]);
- tempFiles[i] = new File(localBinaryDir, EXECUTABLES[i] + ".tmp");
- allExist &= files[i].exists();
- }
- if (allExist)
- return false;
- for (int i = 0; i < files.length; ++i) {
- if (!SharedLibraryLoader.extractLibrary(context, EXECUTABLES[i], tempFiles[i]))
- throw new FileNotFoundException("Unable to find " + EXECUTABLES[i]);
- if (!tempFiles[i].setExecutable(true, false))
- throw new IOException("Unable to mark " + tempFiles[i].getAbsolutePath() + " as executable");
- if (!tempFiles[i].renameTo(files[i]))
- throw new IOException("Unable to rename " + tempFiles[i].getAbsolutePath() + " to " + files[i].getAbsolutePath());
- }
- return true;
- }
-
- private boolean willInstallAsMagiskModule() {
- synchronized (lock) {
- if (installAsMagiskModule == null) {
- try {
- installAsMagiskModule = rootShell.run(null, "[ -d /sbin/.magisk/mirror -a -d /sbin/.magisk/img -a ! -f /cache/.disable_magisk ]") == OsConstants.EXIT_SUCCESS;
- } catch (final Exception ignored) {
- installAsMagiskModule = false;
- }
- }
- return installAsMagiskModule;
- }
- }
-}