summaryrefslogtreecommitdiffhomepage
diff options
context:
space:
mode:
authorSamuel Holland <samuel@sholland.org>2018-01-07 00:24:56 -0600
committerSamuel Holland <samuel@sholland.org>2018-01-07 00:24:56 -0600
commitbe8b6017d504cdc791f9578ea196934e652b1af6 (patch)
tree98c46e87485d5ba3032d77a4e4ee9bec11fd3d1a
parent5a2f692d7323f3bc7f5b2347fb8490be9057f302 (diff)
Make TunnelManager the point of asynchronicity
Signed-off-by: Samuel Holland <samuel@sholland.org>
-rw-r--r--app/src/main/java/com/wireguard/android/Application.java10
-rw-r--r--app/src/main/java/com/wireguard/android/backend/Backend.java42
-rw-r--r--app/src/main/java/com/wireguard/android/backend/WgQuickBackend.java60
-rw-r--r--app/src/main/java/com/wireguard/android/configStore/ConfigStore.java29
-rw-r--r--app/src/main/java/com/wireguard/android/configStore/FileConfigStore.java80
-rw-r--r--app/src/main/java/com/wireguard/android/model/TunnelManager.java58
6 files changed, 125 insertions, 154 deletions
diff --git a/app/src/main/java/com/wireguard/android/Application.java b/app/src/main/java/com/wireguard/android/Application.java
index c0fb046a..e8d0d7db 100644
--- a/app/src/main/java/com/wireguard/android/Application.java
+++ b/app/src/main/java/com/wireguard/android/Application.java
@@ -81,17 +81,15 @@ public class Application extends android.app.Application {
@ApplicationScope
@Provides
- public static Backend getBackend(final AsyncWorker asyncWorker,
- @ApplicationContext final Context context,
+ public static Backend getBackend(@ApplicationContext final Context context,
final RootShell rootShell) {
- return new WgQuickBackend(asyncWorker, context, rootShell);
+ return new WgQuickBackend(context, rootShell);
}
@ApplicationScope
@Provides
- public static ConfigStore getConfigStore(final AsyncWorker asyncWorker,
- @ApplicationContext final Context context) {
- return new FileConfigStore(asyncWorker, context);
+ public static ConfigStore getConfigStore(@ApplicationContext final Context context) {
+ return new FileConfigStore(context);
}
diff --git a/app/src/main/java/com/wireguard/android/backend/Backend.java b/app/src/main/java/com/wireguard/android/backend/Backend.java
index fc174be1..fb71a3d3 100644
--- a/app/src/main/java/com/wireguard/android/backend/Backend.java
+++ b/app/src/main/java/com/wireguard/android/backend/Backend.java
@@ -7,61 +7,53 @@ import com.wireguard.config.Config;
import java.util.Set;
-import java9.util.concurrent.CompletionStage;
-
/**
* Interface for implementations of the WireGuard secure network tunnel.
*/
public interface Backend {
/**
- * Update the volatile configuration of a running tunnel, asynchronously, and return the
- * resulting configuration. If the tunnel is not up, return the configuration that would result
- * (if known), or else simply return the given configuration.
+ * Update the volatile configuration of a running tunnel and return the resulting configuration.
+ * If the tunnel is not up, return the configuration that would result (if known), or else
+ * simply return the given configuration.
*
* @param tunnel The tunnel to apply the configuration to.
* @param config The new configuration for this tunnel.
- * @return A future completed when the configuration of the tunnel has been updated, and the new
- * volatile configuration has been determined. This future will always be completed on the main
- * thread.
+ * @return The updated configuration of the tunnel.
*/
- CompletionStage<Config> applyConfig(Tunnel tunnel, Config config);
+ Config applyConfig(Tunnel tunnel, Config config) throws Exception;
/**
* Enumerate the names of currently-running tunnels.
*
- * @return A future completed when the set of running tunnel names is available. This future
- * will always be completed on the main thread.
+ * @return The set of running tunnel names.
*/
- CompletionStage<Set<String>> enumerate();
+ Set<String> enumerate() throws Exception;
/**
- * Get the actual state of a tunnel, asynchronously.
+ * Get the actual state of a tunnel.
*
* @param tunnel The tunnel to examine the state of.
- * @return A future completed when the state of the tunnel has been determined. This future will
- * always be completed on the main thread.
+ * @return The state of the tunnel.
*/
- CompletionStage<State> getState(Tunnel tunnel);
+ State getState(Tunnel tunnel) throws Exception;
/**
- * Get statistics about traffic and errors on this tunnel, asynchronously. If the tunnel is not
- * running, the statistics object will be filled with zero values.
+ * 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 A future completed when statistics for the tunnel are available. This future will
- * always be completed on the main thread.
+ * @return The statistics for the tunnel.
*/
- CompletionStage<Statistics> getStatistics(Tunnel tunnel);
+ Statistics getStatistics(Tunnel tunnel) throws Exception;
/**
- * Set the state of a tunnel, asynchronously.
+ * Set the state of a tunnel.
*
* @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}.
- * @return A future completed when the state of the tunnel has changed, containing the new state
- * of the tunnel. This future will always be completed on the main thread.
+ * @return The updated state of the tunnel.
*/
- CompletionStage<State> setState(Tunnel tunnel, State state);
+ State setState(Tunnel tunnel, State state) throws Exception;
}
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 656cfa9c..6703f735 100644
--- a/app/src/main/java/com/wireguard/android/backend/WgQuickBackend.java
+++ b/app/src/main/java/com/wireguard/android/backend/WgQuickBackend.java
@@ -6,7 +6,6 @@ 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.android.util.AsyncWorker;
import com.wireguard.android.util.RootShell;
import com.wireguard.config.Config;
@@ -17,25 +16,20 @@ import java.util.LinkedList;
import java.util.List;
import java.util.Set;
-import java9.util.concurrent.CompletableFuture;
-import java9.util.concurrent.CompletionStage;
import java9.util.stream.Collectors;
import java9.util.stream.Stream;
/**
- * Created by samuel on 12/19/17.
+ * WireGuard backend that uses {@code wg-quick} to implement tunnel configuration.
*/
public final class WgQuickBackend implements Backend {
private static final String TAG = WgQuickBackend.class.getSimpleName();
- private final AsyncWorker asyncWorker;
private final Context context;
private final RootShell rootShell;
- public WgQuickBackend(final AsyncWorker asyncWorker, final Context context,
- final RootShell rootShell) {
- this.asyncWorker = asyncWorker;
+ public WgQuickBackend(final Context context, final RootShell rootShell) {
this.context = context;
this.rootShell = rootShell;
}
@@ -49,47 +43,47 @@ public final class WgQuickBackend implements Backend {
}
@Override
- public CompletionStage<Config> applyConfig(final Tunnel tunnel, final Config config) {
+ public Config applyConfig(final Tunnel tunnel, final Config config) {
if (tunnel.getState() == State.UP)
- return CompletableFuture.failedFuture(new UnsupportedOperationException("stub"));
- return CompletableFuture.completedFuture(config);
+ throw new UnsupportedOperationException("Not implemented");
+ return config;
}
@Override
- public CompletionStage<Set<String>> enumerate() {
- return asyncWorker.supplyAsync(() -> {
- final List<String> output = new LinkedList<>();
- // Don't throw an exception here or nothing will show up in the UI.
- if (rootShell.run(output, "wg show interfaces") != 0 || output.isEmpty())
- 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());
- });
+ public Set<String> enumerate() {
+ final List<String> output = new LinkedList<>();
+ // Don't throw an exception here or nothing will show up in the UI.
+ if (rootShell.run(output, "wg show interfaces") != 0 || output.isEmpty())
+ 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 CompletionStage<State> getState(final Tunnel tunnel) {
+ public State getState(final Tunnel tunnel) {
Log.v(TAG, "Requested state for tunnel " + tunnel.getName());
- return enumerate().thenApply(set -> set.contains(tunnel.getName()) ? State.UP : State.DOWN);
+ return enumerate().contains(tunnel.getName()) ? State.UP : State.DOWN;
}
@Override
- public CompletionStage<Statistics> getStatistics(final Tunnel tunnel) {
- return CompletableFuture.completedFuture(new Statistics());
+ public Statistics getStatistics(final Tunnel tunnel) {
+ return new Statistics();
}
@Override
- public CompletionStage<State> setState(final Tunnel tunnel, final State state) {
+ public State setState(final Tunnel tunnel, final State state) throws IOException {
Log.v(TAG, "Requested state change to " + state + " for tunnel " + tunnel.getName());
- return tunnel.getStateAsync().thenCompose(currentState -> asyncWorker.supplyAsync(() -> {
- final String stateName = resolveState(currentState, state).name().toLowerCase();
- final File file = new File(context.getFilesDir(), tunnel.getName() + ".conf");
- final String path = file.getAbsolutePath();
+ final State originalState = getState(tunnel);
+ final State resolvedState = resolveState(originalState, state);
+ if (resolvedState == State.UP) {
// FIXME: Assumes file layout from FileConfigStore. Use a temporary file.
- if (rootShell.run(null, String.format("wg-quick %s '%s'", stateName, path)) != 0)
+ final File file = new File(context.getFilesDir(), tunnel.getName() + ".conf");
+ if (rootShell.run(null, String.format("wg-quick up '%s'", file.getAbsolutePath())) != 0)
+ throw new IOException("wg-quick failed");
+ } else {
+ if (rootShell.run(null, String.format("wg-quick down '%s'", tunnel.getName())) != 0)
throw new IOException("wg-quick failed");
- return tunnel;
- })).thenCompose(this::getState);
+ }
+ return getState(tunnel);
}
}
diff --git a/app/src/main/java/com/wireguard/android/configStore/ConfigStore.java b/app/src/main/java/com/wireguard/android/configStore/ConfigStore.java
index 19bb6bf5..c1f5b4e3 100644
--- a/app/src/main/java/com/wireguard/android/configStore/ConfigStore.java
+++ b/app/src/main/java/com/wireguard/android/configStore/ConfigStore.java
@@ -4,8 +4,6 @@ import com.wireguard.config.Config;
import java.util.Set;
-import java9.util.concurrent.CompletionStage;
-
/**
* Interface for persistent storage providers for WireGuard configurations.
*/
@@ -17,39 +15,32 @@ public interface ConfigStore {
*
* @param name The name of the tunnel to create.
* @param config Configuration for the new tunnel.
- * @return A future completed when the tunnel and its configuration have been saved to
- * persistent storage. This future encapsulates the configuration that was actually saved to
- * persistent storage. This future will always be completed on the main thread.
+ * @return The configuration that was actually saved to persistent storage.
*/
- CompletionStage<Config> create(final String name, final Config config);
+ Config create(final String name, final Config config) throws Exception;
/**
* Delete a persistent tunnel.
*
* @param name The name of the tunnel to delete.
- * @return A future completed when the tunnel and its configuration have been deleted. This
- * future will always be completed on the main thread.
*/
- CompletionStage<Void> delete(final String name);
+ void delete(final String name) throws Exception;
/**
* Enumerate the names of tunnels present in persistent storage.
*
- * @return A future completed when the set of present tunnel names is available. This future
- * will always be completed on the main thread.
+ * @return The set of present tunnel names.
*/
- CompletionStage<Set<String>> enumerate();
+ Set<String> enumerate() throws Exception;
/**
* Load the configuration for the tunnel given by {@code name}.
*
* @param name The identifier for the configuration in persistent storage (i.e. the name of the
* tunnel).
- * @return A future completed when an in-memory representation of the configuration is
- * available. This future encapsulates the configuration loaded from persistent storage. This
- * future will always be completed on the main thread.
+ * @return An in-memory representation of the configuration loaded from persistent storage.
*/
- CompletionStage<Config> load(final String name);
+ Config load(final String name) throws Exception;
/**
* Save the configuration for an existing tunnel given by {@code name}.
@@ -57,9 +48,7 @@ public interface ConfigStore {
* @param name The identifier for the configuration in persistent storage (i.e. the name of
* the tunnel).
* @param config An updated configuration object for the tunnel.
- * @return A future completed when the configuration has been saved to persistent storage. This
- * future encapsulates the configuration that was actually saved to persistent storage. This
- * future will always be completed on the main thread.
+ * @return The configuration that was actually saved to persistent storage.
*/
- CompletionStage<Config> save(final String name, final Config config);
+ Config save(final String name, final Config config) throws Exception;
}
diff --git a/app/src/main/java/com/wireguard/android/configStore/FileConfigStore.java b/app/src/main/java/com/wireguard/android/configStore/FileConfigStore.java
index 099bc0d3..30b2fba8 100644
--- a/app/src/main/java/com/wireguard/android/configStore/FileConfigStore.java
+++ b/app/src/main/java/com/wireguard/android/configStore/FileConfigStore.java
@@ -4,7 +4,6 @@ import android.content.Context;
import android.util.Log;
import com.wireguard.android.Application.ApplicationContext;
-import com.wireguard.android.util.AsyncWorker;
import com.wireguard.config.Config;
import java.io.File;
@@ -14,56 +13,48 @@ import java.io.IOException;
import java.nio.charset.StandardCharsets;
import java.util.Set;
-import java9.util.concurrent.CompletionStage;
import java9.util.stream.Collectors;
import java9.util.stream.Stream;
/**
- * Created by samuel on 12/28/17.
+ * Configuration store that uses a {@code wg-quick}-style file for each configured tunnel.
*/
public final class FileConfigStore implements ConfigStore {
private static final String TAG = FileConfigStore.class.getSimpleName();
- private final AsyncWorker asyncWorker;
private final Context context;
- public FileConfigStore(final AsyncWorker asyncWorker,
- @ApplicationContext final Context context) {
- this.asyncWorker = asyncWorker;
+ public FileConfigStore(@ApplicationContext final Context context) {
this.context = context;
}
@Override
- public CompletionStage<Config> create(final String name, final Config config) {
- return asyncWorker.supplyAsync(() -> {
- final File file = fileFor(name);
- if (!file.createNewFile()) {
- final String message = "Configuration file " + file.getName() + " already exists";
- throw new IllegalStateException(message);
- }
- try (FileOutputStream stream = new FileOutputStream(file, false)) {
- stream.write(config.toString().getBytes(StandardCharsets.UTF_8));
- return config;
- }
- });
+ public Config create(final String name, final Config config) throws IOException {
+ final File file = fileFor(name);
+ if (!file.createNewFile()) {
+ final String message = "Configuration file " + file.getName() + " already exists";
+ throw new IllegalStateException(message);
+ }
+ try (FileOutputStream stream = new FileOutputStream(file, false)) {
+ stream.write(config.toString().getBytes(StandardCharsets.UTF_8));
+ return config;
+ }
}
@Override
- public CompletionStage<Void> delete(final String name) {
- return asyncWorker.runAsync(() -> {
- final File file = fileFor(name);
- if (!file.delete())
- throw new IOException("Cannot delete configuration file " + file.getName());
- });
+ public void delete(final String name) throws IOException {
+ final File file = fileFor(name);
+ if (!file.delete())
+ throw new IOException("Cannot delete configuration file " + file.getName());
}
@Override
- public CompletionStage<Set<String>> enumerate() {
- return asyncWorker.supplyAsync(() -> Stream.of(context.fileList())
+ public Set<String> enumerate() {
+ return Stream.of(context.fileList())
.filter(name -> name.endsWith(".conf"))
.map(name -> name.substring(0, name.length() - ".conf".length()))
- .collect(Collectors.toUnmodifiableSet()));
+ .collect(Collectors.toUnmodifiableSet());
}
private File fileFor(final String name) {
@@ -71,28 +62,23 @@ public final class FileConfigStore implements ConfigStore {
}
@Override
- public CompletionStage<Config> load(final String name) {
- return asyncWorker.supplyAsync(() -> {
- try (FileInputStream stream = new FileInputStream(fileFor(name))) {
- return Config.from(stream);
- }
- });
+ public Config load(final String name) throws IOException {
+ try (FileInputStream stream = new FileInputStream(fileFor(name))) {
+ return Config.from(stream);
+ }
}
@Override
- public CompletionStage<Config> save(final String name, final Config config) {
+ public Config save(final String name, final Config config) throws IOException {
Log.d(TAG, "Requested save config for tunnel " + name);
- return asyncWorker.supplyAsync(() -> {
- final File file = fileFor(name);
- if (!file.isFile()) {
- final String message = "Configuration file " + file.getName() + " not found";
- throw new IllegalStateException(message);
- }
- try (FileOutputStream stream = new FileOutputStream(file, false)) {
- Log.d(TAG, "Writing out config for tunnel " + name);
- stream.write(config.toString().getBytes(StandardCharsets.UTF_8));
- return config;
- }
- });
+ final File file = fileFor(name);
+ if (!file.isFile()) {
+ final String message = "Configuration file " + file.getName() + " not found";
+ throw new IllegalStateException(message);
+ }
+ try (FileOutputStream stream = new FileOutputStream(file, false)) {
+ stream.write(config.toString().getBytes(StandardCharsets.UTF_8));
+ return config;
+ }
}
}
diff --git a/app/src/main/java/com/wireguard/android/model/TunnelManager.java b/app/src/main/java/com/wireguard/android/model/TunnelManager.java
index 22bf1190..e802c152 100644
--- a/app/src/main/java/com/wireguard/android/model/TunnelManager.java
+++ b/app/src/main/java/com/wireguard/android/model/TunnelManager.java
@@ -1,12 +1,14 @@
package com.wireguard.android.model;
import android.content.SharedPreferences;
+import android.support.annotation.NonNull;
import com.wireguard.android.Application.ApplicationScope;
import com.wireguard.android.backend.Backend;
import com.wireguard.android.configStore.ConfigStore;
import com.wireguard.android.model.Tunnel.State;
import com.wireguard.android.model.Tunnel.Statistics;
+import com.wireguard.android.util.AsyncWorker;
import com.wireguard.android.util.ExceptionLoggers;
import com.wireguard.android.util.ObservableKeyedList;
import com.wireguard.android.util.ObservableSortedKeyedArrayList;
@@ -38,6 +40,7 @@ public final class TunnelManager {
private static final String KEY_RUNNING_TUNNELS = "enabled_configs";
private static final String TAG = TunnelManager.class.getSimpleName();
+ private final AsyncWorker asyncWorker;
private final Backend backend;
private final ConfigStore configStore;
private final SharedPreferences preferences;
@@ -45,45 +48,55 @@ public final class TunnelManager {
new ObservableSortedKeyedArrayList<>(COMPARATOR);
@Inject
- public TunnelManager(final Backend backend, final ConfigStore configStore,
- final SharedPreferences preferences) {
+ public TunnelManager(final AsyncWorker asyncWorker, final Backend backend,
+ final ConfigStore configStore, final SharedPreferences preferences) {
+ this.asyncWorker = asyncWorker;
this.backend = backend;
this.configStore = configStore;
this.preferences = preferences;
}
- private Tunnel add(final String name, final Config config, final State state) {
+ private Tunnel addToList(final String name, final Config config, final State state) {
final Tunnel tunnel = new Tunnel(this, name, config, state);
tunnels.add(tunnel);
return tunnel;
}
- public CompletionStage<Tunnel> create(final String name, final Config config) {
+ public CompletionStage<Tunnel> create(@NonNull final String name, final Config config) {
if (!Tunnel.isNameValid(name))
return CompletableFuture.failedFuture(new IllegalArgumentException("Invalid name"));
if (tunnels.containsKey(name)) {
final String message = "Tunnel " + name + " already exists";
return CompletableFuture.failedFuture(new IllegalArgumentException(message));
}
- return configStore.create(name, config).thenApply(cfg -> add(name, cfg, State.DOWN));
+ return asyncWorker.supplyAsync(() -> configStore.create(name, config))
+ .thenApply(savedConfig -> addToList(name, savedConfig, State.DOWN));
}
CompletionStage<Void> delete(final Tunnel tunnel) {
- return setTunnelState(tunnel, State.DOWN)
- .thenCompose(x -> configStore.delete(tunnel.getName()))
- .thenAccept(x -> remove(tunnel));
+ return asyncWorker.runAsync(() -> {
+ backend.setState(tunnel, State.DOWN);
+ configStore.delete(tunnel.getName());
+ }).thenAccept(x -> {
+ if (tunnel.getName().equals(preferences.getString(KEY_PRIMARY_TUNNEL, null)))
+ preferences.edit().remove(KEY_PRIMARY_TUNNEL).apply();
+ tunnels.remove(tunnel);
+ });
}
CompletionStage<Config> getTunnelConfig(final Tunnel tunnel) {
- return configStore.load(tunnel.getName()).thenApply(tunnel::onConfigChanged);
+ return asyncWorker.supplyAsync(() -> configStore.load(tunnel.getName()))
+ .thenApply(tunnel::onConfigChanged);
}
CompletionStage<State> getTunnelState(final Tunnel tunnel) {
- return backend.getState(tunnel).thenApply(tunnel::onStateChanged);
+ return asyncWorker.supplyAsync(() -> backend.getState(tunnel))
+ .thenApply(tunnel::onStateChanged);
}
CompletionStage<Statistics> getTunnelStatistics(final Tunnel tunnel) {
- return backend.getStatistics(tunnel).thenApply(tunnel::onStatisticsChanged);
+ return asyncWorker.supplyAsync(() -> backend.getStatistics(tunnel))
+ .thenApply(tunnel::onStatisticsChanged);
}
public ObservableKeyedList<String, Tunnel> getTunnels() {
@@ -91,16 +104,14 @@ public final class TunnelManager {
}
public void onCreate() {
- configStore.enumerate().thenAcceptBoth(backend.enumerate(), (names, running) -> {
- for (final String name : names)
- add(name, null, running.contains(name) ? State.UP : State.DOWN);
- }).whenComplete(ExceptionLoggers.E);
+ asyncWorker.supplyAsync(configStore::enumerate)
+ .thenAcceptBoth(asyncWorker.supplyAsync(backend::enumerate), this::onTunnelsLoaded)
+ .whenComplete(ExceptionLoggers.E);
}
- private void remove(final Tunnel tunnel) {
- if (tunnel.getName().equals(preferences.getString(KEY_PRIMARY_TUNNEL, null)))
- preferences.edit().remove(KEY_PRIMARY_TUNNEL).apply();
- tunnels.remove(tunnel);
+ private void onTunnelsLoaded(final Set<String> present, final Set<String> running) {
+ for (final String name : present)
+ addToList(name, null, running.contains(name) ? State.UP : State.DOWN);
}
public CompletionStage<Void> restoreState() {
@@ -125,13 +136,14 @@ public final class TunnelManager {
}
CompletionStage<Config> setTunnelConfig(final Tunnel tunnel, final Config config) {
- return backend.applyConfig(tunnel, config)
- .thenCompose(cfg -> configStore.save(tunnel.getName(), cfg))
- .thenApply(tunnel::onConfigChanged);
+ return asyncWorker.supplyAsync(() -> {
+ final Config appliedConfig = backend.applyConfig(tunnel, config);
+ return configStore.save(tunnel.getName(), appliedConfig);
+ }).thenApply(tunnel::onConfigChanged);
}
CompletionStage<State> setTunnelState(final Tunnel tunnel, final State state) {
- return backend.setState(tunnel, state)
+ return asyncWorker.supplyAsync(() -> backend.setState(tunnel, state))
.thenApply(tunnel::onStateChanged);
}
}