diff options
19 files changed, 303 insertions, 290 deletions
diff --git a/tunnel/src/main/java/com/wireguard/android/backend/BackendException.java b/tunnel/src/main/java/com/wireguard/android/backend/BackendException.java index 0f60701b..55fdb602 100644 --- a/tunnel/src/main/java/com/wireguard/android/backend/BackendException.java +++ b/tunnel/src/main/java/com/wireguard/android/backend/BackendException.java @@ -9,6 +9,21 @@ import com.wireguard.util.NonNullForAll; @NonNullForAll public final class BackendException extends Exception { + private final Object[] format; + private final Reason reason; + public BackendException(final Reason reason, final Object... format) { + this.reason = reason; + this.format = format; + } + + public Object[] getFormat() { + return format; + } + + public Reason getReason() { + return reason; + } + public enum Reason { UNKNOWN_KERNEL_MODULE_NAME, WG_QUICK_CONFIG_ERROR_CODE, @@ -18,16 +33,4 @@ public final class BackendException extends Exception { 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/tunnel/src/main/java/com/wireguard/android/backend/GoBackend.java b/tunnel/src/main/java/com/wireguard/android/backend/GoBackend.java index 54dc913a..70cdd844 100644 --- a/tunnel/src/main/java/com/wireguard/android/backend/GoBackend.java +++ b/tunnel/src/main/java/com/wireguard/android/backend/GoBackend.java @@ -34,25 +34,21 @@ import java9.util.concurrent.CompletableFuture; @NonNullForAll 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 static CompletableFuture<VpnService> vpnService = new CompletableFuture<>(); private final Context context; - @Nullable private Tunnel currentTunnel; @Nullable private Config currentConfig; + @Nullable private Tunnel currentTunnel; private int currentTunnelHandle = -1; - public GoBackend(final Context context) { SharedLibraryLoader.loadSharedLibrary(context, "wg-go"); this.context = context; } + public static void setAlwaysOnCallback(AlwaysOnCallback cb) { + alwaysOnCallback = cb; + } + private static native String wgGetConfig(int handle); private static native int wgGetSocketV4(int handle); @@ -143,7 +139,7 @@ public final class GoBackend implements Backend { setStateInternal(currentTunnel, null, State.DOWN); try { setStateInternal(tunnel, config, state); - } catch(final Exception e) { + } catch (final Exception e) { if (originalTunnel != null) setStateInternal(originalTunnel, originalConfig, State.UP); throw e; @@ -209,7 +205,7 @@ public final class GoBackend implements Backend { if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) builder.setMetered(false); if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) - service.setUnderlyingNetworks(null); + service.setUnderlyingNetworks(null); builder.setBlocking(true); try (final ParcelFileDescriptor tun = builder.establish()) { @@ -246,13 +242,13 @@ public final class GoBackend implements Backend { context.startService(new Intent(context, VpnService.class)); } + public interface AlwaysOnCallback { + void alwaysOnTriggered(); + } + 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(); } @@ -290,5 +286,9 @@ public final class GoBackend implements Backend { } return super.onStartCommand(intent, flags, startId); } + + public void setOwner(final GoBackend owner) { + this.owner = owner; + } } } diff --git a/tunnel/src/main/java/com/wireguard/android/backend/Statistics.java b/tunnel/src/main/java/com/wireguard/android/backend/Statistics.java index 54bbe912..b4e01e76 100644 --- a/tunnel/src/main/java/com/wireguard/android/backend/Statistics.java +++ b/tunnel/src/main/java/com/wireguard/android/backend/Statistics.java @@ -16,10 +16,11 @@ import java.util.Map; @NonNullForAll public class Statistics { - private long lastTouched = SystemClock.elapsedRealtime(); private final Map<Key, Pair<Long, Long>> peerBytes = new HashMap<>(); + private long lastTouched = SystemClock.elapsedRealtime(); - Statistics() { } + Statistics() { + } void add(final Key key, final long rx, final long tx) { peerBytes.put(key, Pair.create(rx, tx)); @@ -30,10 +31,6 @@ public class Statistics { 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; @@ -46,6 +43,10 @@ public class Statistics { return peerBytes.get(peer).second; } + public Key[] peers() { + return peerBytes.keySet().toArray(new Key[0]); + } + public long totalRx() { long rx = 0; for (final Pair<Long, Long> val : peerBytes.values()) { diff --git a/tunnel/src/main/java/com/wireguard/android/backend/Tunnel.java b/tunnel/src/main/java/com/wireguard/android/backend/Tunnel.java index fccda84f..b9508b1a 100644 --- a/tunnel/src/main/java/com/wireguard/android/backend/Tunnel.java +++ b/tunnel/src/main/java/com/wireguard/android/backend/Tunnel.java @@ -15,16 +15,6 @@ import java.util.regex.Pattern; @NonNullForAll 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}"); @@ -46,4 +36,14 @@ public interface Tunnel { * @return The new state of the tunnel. */ void onStateChange(State newState); + + enum State { + DOWN, + TOGGLE, + UP; + + public static State of(final boolean running) { + return running ? UP : DOWN; + } + } } diff --git a/tunnel/src/main/java/com/wireguard/android/backend/WgQuickBackend.java b/tunnel/src/main/java/com/wireguard/android/backend/WgQuickBackend.java index d57a92f9..e731a92c 100644 --- a/tunnel/src/main/java/com/wireguard/android/backend/WgQuickBackend.java +++ b/tunnel/src/main/java/com/wireguard/android/backend/WgQuickBackend.java @@ -42,11 +42,10 @@ import java9.util.stream.Stream; @NonNullForAll 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 RootShell rootShell; private final Map<Tunnel, Config> runningConfigs = new HashMap<>(); + private final ToolsInstaller toolsInstaller; private boolean multipleTunnels; public WgQuickBackend(final Context context, final RootShell rootShell, final ToolsInstaller toolsInstaller) { @@ -55,10 +54,6 @@ public final class WgQuickBackend implements Backend { this.toolsInstaller = toolsInstaller; } - public void setMultipleTunnels(boolean on) { - multipleTunnels = on; - } - @Override public Set<String> getRunningTunnelNames() { final List<String> output = new ArrayList<>(); @@ -110,6 +105,10 @@ public final class WgQuickBackend implements Backend { return output.get(0); } + public void setMultipleTunnels(boolean on) { + multipleTunnels = on; + } + @Override public State setState(final Tunnel tunnel, State state, @Nullable final Config config) throws Exception { final State originalState = getState(tunnel); @@ -135,7 +134,8 @@ public final class WgQuickBackend implements Backend { for (final Pair<Tunnel, Config> entry : rewind) { setStateInternal(entry.first, entry.second, State.UP); } - } catch (final Exception ignored) { } + } catch (final Exception ignored) { + } throw e; } } @@ -153,7 +153,8 @@ public final class WgQuickBackend implements Backend { setStateInternal(entry.getKey(), entry.getValue(), State.UP); } } - } catch (final Exception ignored) { } + } catch (final Exception ignored) { + } throw e; } } else if (state == State.DOWN) { diff --git a/tunnel/src/main/java/com/wireguard/android/util/ModuleLoader.java b/tunnel/src/main/java/com/wireguard/android/util/ModuleLoader.java index 519ad5cf..82e6a096 100644 --- a/tunnel/src/main/java/com/wireguard/android/util/ModuleLoader.java +++ b/tunnel/src/main/java/com/wireguard/android/util/ModuleLoader.java @@ -39,15 +39,14 @@ import androidx.annotation.Nullable; @NonNullForAll public class ModuleLoader { - private static final String MODULE_PUBLIC_KEY_BASE64 = "RWRmHuT9PSqtwfsLtEx+QS06BJtLgFYteL9WCNjH7yuyu5Y1DieSN7If"; private static final String MODULE_LIST_URL = "https://download.wireguard.com/android-module/modules.txt.sig"; - private static final String MODULE_URL = "https://download.wireguard.com/android-module/%s"; private static final String MODULE_NAME = "wireguard-%s.ko"; - - private final RootShell rootShell; - private final String userAgent; + private static final String MODULE_PUBLIC_KEY_BASE64 = "RWRmHuT9PSqtwfsLtEx+QS06BJtLgFYteL9WCNjH7yuyu5Y1DieSN7If"; + private static final String MODULE_URL = "https://download.wireguard.com/android-module/%s"; private final File moduleDir; + private final RootShell rootShell; private final File tmpDir; + private final String userAgent; public ModuleLoader(final Context context, final RootShell rootShell, final String userAgent) { moduleDir = new File(context.getCacheDir(), "kmod"); @@ -56,84 +55,17 @@ public class ModuleLoader { this.userAgent = userAgent; } - public boolean moduleMightExist() { - return moduleDir.exists() && moduleDir.isDirectory(); - } - - public void loadModule() throws IOException, RootShellException { - rootShell.run(null, String.format("insmod \"%s/wireguard-$(sha256sum /proc/version|cut -d ' ' -f 1).ko\"", moduleDir.getAbsolutePath())); - } - public static boolean isModuleLoaded() { return new File("/sys/module/wireguard").exists(); } - private static final class Sha256Digest { - private byte[] bytes; - private Sha256Digest(final String hex) { - if (hex.length() != 64) - throw new InvalidParameterException("SHA256 hashes must be 32 bytes long"); - bytes = new byte[32]; - for (int i = 0; i < 32; ++i) - bytes[i] = (byte)Integer.parseInt(hex.substring(i * 2, i * 2 + 2), 16); - } - } - - @Nullable - private Map<String, Sha256Digest> verifySignedHashes(final String signifyDigest) { - final byte[] publicKeyBytes = Base64.decode(MODULE_PUBLIC_KEY_BASE64, Base64.DEFAULT); - - if (publicKeyBytes == null || publicKeyBytes.length != 32 + 10 || publicKeyBytes[0] != 'E' || publicKeyBytes[1] != 'd') - return null; - - final String[] lines = signifyDigest.split("\n", 3); - if (lines.length != 3) - return null; - if (!lines[0].startsWith("untrusted comment: ")) - return null; - - final byte[] signatureBytes = Base64.decode(lines[1], Base64.DEFAULT); - if (signatureBytes == null || signatureBytes.length != 64 + 10) - return null; - for (int i = 0; i < 10; ++i) { - if (signatureBytes[i] != publicKeyBytes[i]) - return null; - } - - try { - EdDSAParameterSpec parameterSpec = EdDSANamedCurveTable.getByName(EdDSANamedCurveTable.ED_25519); - Signature signature = new EdDSAEngine(MessageDigest.getInstance(parameterSpec.getHashAlgorithm())); - byte[] rawPublicKeyBytes = new byte[32]; - System.arraycopy(publicKeyBytes, 10, rawPublicKeyBytes, 0, 32); - signature.initVerify(new EdDSAPublicKey(new EdDSAPublicKeySpec(rawPublicKeyBytes, parameterSpec))); - signature.update(lines[2].getBytes(StandardCharsets.UTF_8)); - if (!signature.verify(signatureBytes, 10, 64)) - return null; - } catch (final Exception ignored) { - return null; - } - - Map<String, Sha256Digest> hashes = new HashMap<>(); - for (final String line : lines[2].split("\n")) { - final String[] components = line.split(" ", 2); - if (components.length != 2) - return null; - try { - hashes.put(components[1], new Sha256Digest(components[0])); - } catch (final Exception ignored) { - return null; - } - } - return hashes; - } - public Integer download() throws IOException, RootShellException, NoSuchAlgorithmException { final List<String> output = new ArrayList<>(); rootShell.run(output, "sha256sum /proc/version|cut -d ' ' -f 1"); if (output.size() != 1 || output.get(0).length() != 64) throw new InvalidParameterException("Invalid sha256 of /proc/version"); final String moduleName = String.format(MODULE_NAME, output.get(0)); - HttpURLConnection connection = (HttpURLConnection)new URL(MODULE_LIST_URL).openConnection(); + HttpURLConnection connection = (HttpURLConnection) new URL(MODULE_LIST_URL).openConnection(); connection.setRequestProperty("User-Agent", userAgent); connection.connect(); if (connection.getResponseCode() != HttpURLConnection.HTTP_OK) @@ -150,7 +82,7 @@ public class ModuleLoader { throw new InvalidParameterException("The signature did not verify or invalid hash list format"); if (!modules.containsKey(moduleName)) return OsConstants.ENOENT; - connection = (HttpURLConnection)new URL(String.format(MODULE_URL, moduleName)).openConnection(); + connection = (HttpURLConnection) new URL(String.format(MODULE_URL, moduleName)).openConnection(); connection.setRequestProperty("User-Agent", userAgent); connection.connect(); if (connection.getResponseCode() != HttpURLConnection.HTTP_OK) @@ -185,4 +117,72 @@ public class ModuleLoader { } return OsConstants.EXIT_SUCCESS; } + + public void loadModule() throws IOException, RootShellException { + rootShell.run(null, String.format("insmod \"%s/wireguard-$(sha256sum /proc/version|cut -d ' ' -f 1).ko\"", moduleDir.getAbsolutePath())); + } + + public boolean moduleMightExist() { + return moduleDir.exists() && moduleDir.isDirectory(); + } + + @Nullable + private Map<String, Sha256Digest> verifySignedHashes(final String signifyDigest) { + final byte[] publicKeyBytes = Base64.decode(MODULE_PUBLIC_KEY_BASE64, Base64.DEFAULT); + + if (publicKeyBytes == null || publicKeyBytes.length != 32 + 10 || publicKeyBytes[0] != 'E' || publicKeyBytes[1] != 'd') + return null; + + final String[] lines = signifyDigest.split("\n", 3); + if (lines.length != 3) + return null; + if (!lines[0].startsWith("untrusted comment: ")) + return null; + + final byte[] signatureBytes = Base64.decode(lines[1], Base64.DEFAULT); + if (signatureBytes == null || signatureBytes.length != 64 + 10) + return null; + for (int i = 0; i < 10; ++i) { + if (signatureBytes[i] != publicKeyBytes[i]) + return null; + } + + try { + EdDSAParameterSpec parameterSpec = EdDSANamedCurveTable.getByName(EdDSANamedCurveTable.ED_25519); + Signature signature = new EdDSAEngine(MessageDigest.getInstance(parameterSpec.getHashAlgorithm())); + byte[] rawPublicKeyBytes = new byte[32]; + System.arraycopy(publicKeyBytes, 10, rawPublicKeyBytes, 0, 32); + signature.initVerify(new EdDSAPublicKey(new EdDSAPublicKeySpec(rawPublicKeyBytes, parameterSpec))); + signature.update(lines[2].getBytes(StandardCharsets.UTF_8)); + if (!signature.verify(signatureBytes, 10, 64)) + return null; + } catch (final Exception ignored) { + return null; + } + + Map<String, Sha256Digest> hashes = new HashMap<>(); + for (final String line : lines[2].split("\n")) { + final String[] components = line.split(" ", 2); + if (components.length != 2) + return null; + try { + hashes.put(components[1], new Sha256Digest(components[0])); + } catch (final Exception ignored) { + return null; + } + } + return hashes; + } + + private static final class Sha256Digest { + private byte[] bytes; + + private Sha256Digest(final String hex) { + if (hex.length() != 64) + throw new InvalidParameterException("SHA256 hashes must be 32 bytes long"); + bytes = new byte[32]; + for (int i = 0; i < 32; ++i) + bytes[i] = (byte) Integer.parseInt(hex.substring(i * 2, i * 2 + 2), 16); + } + } } diff --git a/tunnel/src/main/java/com/wireguard/android/util/RootShell.java b/tunnel/src/main/java/com/wireguard/android/util/RootShell.java index 9f941815..160ba12f 100644 --- a/tunnel/src/main/java/com/wireguard/android/util/RootShell.java +++ b/tunnel/src/main/java/com/wireguard/android/util/RootShell.java @@ -187,28 +187,32 @@ public class RootShell { } 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) { + private final Reason reason; + public RootShellException(final Reason reason, final Object... format) { this.reason = reason; this.format = format; } - public boolean isIORelated() { - return reason != Reason.NO_ROOT_ACCESS; + + public Object[] getFormat() { + return format; } + public Reason getReason() { return reason; } - public Object[] getFormat() { - return format; + + public boolean isIORelated() { + return reason != Reason.NO_ROOT_ACCESS; + } + + 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 } } } diff --git a/tunnel/src/main/java/com/wireguard/android/util/ToolsInstaller.java b/tunnel/src/main/java/com/wireguard/android/util/ToolsInstaller.java index f12e755f..f3565c1e 100644 --- a/tunnel/src/main/java/com/wireguard/android/util/ToolsInstaller.java +++ b/tunnel/src/main/java/com/wireguard/android/util/ToolsInstaller.java @@ -40,9 +40,9 @@ public final class ToolsInstaller { 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(); + private final RootShell rootShell; @Nullable private Boolean areToolsAvailable; @Nullable private Boolean installAsMagiskModule; @@ -107,6 +107,29 @@ public final class ToolsInstaller { } } + 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; + } + 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."); @@ -161,29 +184,6 @@ public final class ToolsInstaller { } } - 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) { diff --git a/tunnel/src/main/java/com/wireguard/config/InetAddresses.java b/tunnel/src/main/java/com/wireguard/config/InetAddresses.java index 9b0ab965..573c522d 100644 --- a/tunnel/src/main/java/com/wireguard/config/InetAddresses.java +++ b/tunnel/src/main/java/com/wireguard/config/InetAddresses.java @@ -35,7 +35,8 @@ public final class InetAddresses { PARSER_METHOD = m; } - private InetAddresses() { } + private InetAddresses() { + } /** * Parses a numeric IPv4 or IPv6 address without performing any DNS lookups. diff --git a/tunnel/src/main/java/com/wireguard/config/ParseException.java b/tunnel/src/main/java/com/wireguard/config/ParseException.java index f4da7ccd..289f1120 100644 --- a/tunnel/src/main/java/com/wireguard/config/ParseException.java +++ b/tunnel/src/main/java/com/wireguard/config/ParseException.java @@ -10,6 +10,7 @@ import com.wireguard.util.NonNullForAll; import androidx.annotation.Nullable; /** + * */ @NonNullForAll public class ParseException extends Exception { diff --git a/tunnel/src/main/java/com/wireguard/crypto/Key.java b/tunnel/src/main/java/com/wireguard/crypto/Key.java index fe03fa2d..c11688d5 100644 --- a/tunnel/src/main/java/com/wireguard/crypto/Key.java +++ b/tunnel/src/main/java/com/wireguard/crypto/Key.java @@ -204,6 +204,16 @@ public final class Key { return new Key(publicKey); } + @Override + public boolean equals(final Object obj) { + if (obj == this) + return true; + if (obj == null || obj.getClass() != getClass()) + return false; + final Key other = (Key) obj; + return MessageDigest.isEqual(key, other.key); + } + /** * Returns the key as an array of bytes. * @@ -214,6 +224,14 @@ public final class Key { return Arrays.copyOf(key, key.length); } + @Override + public int hashCode() { + int ret = 0; + for (int i = 0; i < key.length / 4; ++i) + ret ^= (key[i * 4 + 0] >> 0) + (key[i * 4 + 1] >> 8) + (key[i * 4 + 2] >> 16) + (key[i * 4 + 3] >> 24); + return ret; + } + /** * Encodes the key to base64. * @@ -250,24 +268,6 @@ public final class Key { return new String(output); } - @Override - public int hashCode() { - int ret = 0; - for (int i = 0; i < key.length / 4; ++i) - ret ^= (key[i * 4 + 0] >> 0) + (key[i * 4 + 1] >> 8) + (key[i * 4 + 2] >> 16) + (key[i * 4 + 3] >> 24); - return ret; - } - - @Override - public boolean equals(final Object obj) { - if (obj == this) - return true; - if (obj == null || obj.getClass() != getClass()) - return false; - final Key other = (Key) obj; - return MessageDigest.isEqual(key, other.key); - } - /** * The supported formats for encoding a WireGuard key. */ diff --git a/ui/src/main/java/com/wireguard/android/Application.java b/ui/src/main/java/com/wireguard/android/Application.java index 52d5e1ea..6cf4da36 100644 --- a/ui/src/main/java/com/wireguard/android/Application.java +++ b/ui/src/main/java/com/wireguard/android/Application.java @@ -37,17 +37,16 @@ import java9.util.concurrent.CompletableFuture; @NonNullForAll public class Application extends android.app.Application implements SharedPreferences.OnSharedPreferenceChangeListener { - private static final String TAG = "WireGuard/" + Application.class.getSimpleName(); public static final String USER_AGENT = String.format(Locale.ENGLISH, "WireGuard/%s (Android %d; %s; %s; %s %s; %s)", BuildConfig.VERSION_NAME, Build.VERSION.SDK_INT, Build.SUPPORTED_ABIS.length > 0 ? Build.SUPPORTED_ABIS[0] : "unknown ABI", Build.BOARD, Build.MANUFACTURER, Build.MODEL, Build.FINGERPRINT); - + private static final String TAG = "WireGuard/" + Application.class.getSimpleName(); @SuppressWarnings("NullableProblems") private static WeakReference<Application> weakSelf; private final CompletableFuture<Backend> futureBackend = new CompletableFuture<>(); @SuppressWarnings("NullableProblems") private AsyncWorker asyncWorker; @Nullable private Backend backend; + @SuppressWarnings("NullableProblems") private ModuleLoader moduleLoader; @SuppressWarnings("NullableProblems") private RootShell rootShell; @SuppressWarnings("NullableProblems") private SharedPreferences sharedPreferences; @SuppressWarnings("NullableProblems") private ToolsInstaller toolsInstaller; - @SuppressWarnings("NullableProblems") private ModuleLoader moduleLoader; @SuppressWarnings("NullableProblems") private TunnelManager tunnelManager; public Application() { @@ -102,6 +101,10 @@ public class Application extends android.app.Application implements SharedPrefer return get().futureBackend; } + public static ModuleLoader getModuleLoader() { + return get().moduleLoader; + } + public static RootShell getRootShell() { return get().rootShell; } @@ -114,10 +117,6 @@ public class Application extends android.app.Application implements SharedPrefer return get().toolsInstaller; } - public static ModuleLoader getModuleLoader() { - return get().moduleLoader; - } - public static TunnelManager getTunnelManager() { return get().tunnelManager; } @@ -168,14 +167,14 @@ public class Application extends android.app.Application implements SharedPrefer } @Override - public void onTerminate() { - sharedPreferences.unregisterOnSharedPreferenceChangeListener(this); - super.onTerminate(); + public void onSharedPreferenceChanged(final SharedPreferences sharedPreferences, final String key) { + if ("multiple_tunnels".equals(key) && backend != null && backend instanceof WgQuickBackend) + ((WgQuickBackend) backend).setMultipleTunnels(sharedPreferences.getBoolean(key, false)); } @Override - public void onSharedPreferenceChanged(final SharedPreferences sharedPreferences, final String key) { - if ("multiple_tunnels".equals(key) && backend != null && backend instanceof WgQuickBackend) - ((WgQuickBackend)backend).setMultipleTunnels(sharedPreferences.getBoolean(key, false)); + public void onTerminate() { + sharedPreferences.unregisterOnSharedPreferenceChangeListener(this); + super.onTerminate(); } } diff --git a/ui/src/main/java/com/wireguard/android/fragment/TunnelDetailFragment.java b/ui/src/main/java/com/wireguard/android/fragment/TunnelDetailFragment.java index 762efc5d..e2adecc3 100644 --- a/ui/src/main/java/com/wireguard/android/fragment/TunnelDetailFragment.java +++ b/ui/src/main/java/com/wireguard/android/fragment/TunnelDetailFragment.java @@ -34,8 +34,20 @@ import androidx.databinding.DataBindingUtil; @NonNullForAll public class TunnelDetailFragment extends BaseFragment { @Nullable private TunnelDetailFragmentBinding binding; - @Nullable private Timer timer; @Nullable private State lastState = State.TOGGLE; + @Nullable private Timer timer; + + private String formatBytes(final long bytes) { + if (bytes < 1024) + return requireContext().getString(R.string.transfer_bytes, bytes); + else if (bytes < 1024 * 1024) + return requireContext().getString(R.string.transfer_kibibytes, bytes / 1024.0); + else if (bytes < 1024 * 1024 * 1024) + return requireContext().getString(R.string.transfer_mibibytes, bytes / (1024.0 * 1024.0)); + else if (bytes < 1024 * 1024 * 1024 * 1024) + return requireContext().getString(R.string.transfer_gibibytes, bytes / (1024.0 * 1024.0 * 1024.0)); + return requireContext().getString(R.string.transfer_tibibytes, bytes / (1024.0 * 1024.0 * 1024.0) / 1024.0); + } @Override public void onCreate(@Nullable final Bundle savedInstanceState) { @@ -49,27 +61,6 @@ public class TunnelDetailFragment extends BaseFragment { } @Override - public void onStop() { - super.onStop(); - if (timer != null) { - timer.cancel(); - timer = null; - } - } - - @Override - public void onResume() { - super.onResume(); - timer = new Timer(); - timer.scheduleAtFixedRate(new TimerTask() { - @Override - public void run() { - updateStats(); - } - }, 0, 1000); - } - - @Override public View onCreateView(final LayoutInflater inflater, @Nullable final ViewGroup container, @Nullable final Bundle savedInstanceState) { super.onCreateView(inflater, container, savedInstanceState); @@ -87,6 +78,18 @@ public class TunnelDetailFragment extends BaseFragment { } @Override + public void onResume() { + super.onResume(); + timer = new Timer(); + timer.scheduleAtFixedRate(new TimerTask() { + @Override + public void run() { + updateStats(); + } + }, 0, 1000); + } + + @Override public void onSelectedTunnelChanged(@Nullable final ObservableTunnel oldTunnel, @Nullable final ObservableTunnel newTunnel) { if (binding == null) return; @@ -100,6 +103,15 @@ public class TunnelDetailFragment extends BaseFragment { } @Override + public void onStop() { + super.onStop(); + if (timer != null) { + timer.cancel(); + timer = null; + } + } + + @Override public void onViewStateRestored(@Nullable final Bundle savedInstanceState) { if (binding == null) { return; @@ -110,18 +122,6 @@ public class TunnelDetailFragment extends BaseFragment { super.onViewStateRestored(savedInstanceState); } - private String formatBytes(final long bytes) { - if (bytes < 1024) - return requireContext().getString(R.string.transfer_bytes, bytes); - else if (bytes < 1024*1024) - return requireContext().getString(R.string.transfer_kibibytes, bytes/1024.0); - else if (bytes < 1024*1024*1024) - return requireContext().getString(R.string.transfer_mibibytes, bytes/(1024.0*1024.0)); - else if (bytes < 1024*1024*1024*1024) - return requireContext().getString(R.string.transfer_gibibytes, bytes/(1024.0*1024.0*1024.0)); - return requireContext().getString(R.string.transfer_tibibytes, bytes/(1024.0*1024.0*1024.0)/1024.0); - } - private void updateStats() { if (binding == null || !isResumed()) return; diff --git a/ui/src/main/java/com/wireguard/android/fragment/TunnelListFragment.java b/ui/src/main/java/com/wireguard/android/fragment/TunnelListFragment.java index 995a2d59..7ed82f1f 100644 --- a/ui/src/main/java/com/wireguard/android/fragment/TunnelListFragment.java +++ b/ui/src/main/java/com/wireguard/android/fragment/TunnelListFragment.java @@ -262,14 +262,6 @@ public class TunnelListFragment extends BaseFragment { }); } - private void showSnackbar(final CharSequence message) { - if (binding != null) { - final Snackbar snackbar = Snackbar.make(binding.mainContainer, message, Snackbar.LENGTH_LONG); - snackbar.setAnchorView(binding.createFab); - snackbar.show(); - } - } - private void onTunnelDeletionFinished(final Integer count, @Nullable final Throwable throwable) { final String message; if (throwable == null) { @@ -337,6 +329,14 @@ public class TunnelListFragment extends BaseFragment { }); } + private void showSnackbar(final CharSequence message) { + if (binding != null) { + final Snackbar snackbar = Snackbar.make(binding.mainContainer, message, Snackbar.LENGTH_LONG); + snackbar.setAnchorView(binding.createFab); + snackbar.show(); + } + } + private MultiselectableRelativeLayout viewForTunnel(final ObservableTunnel tunnel, final List tunnels) { return (MultiselectableRelativeLayout) binding.tunnelList.findViewHolderForAdapterPosition(tunnels.indexOf(tunnel)).itemView; } diff --git a/ui/src/main/java/com/wireguard/android/model/ObservableTunnel.java b/ui/src/main/java/com/wireguard/android/model/ObservableTunnel.java index 3a9fa229..b0f8387b 100644 --- a/ui/src/main/java/com/wireguard/android/model/ObservableTunnel.java +++ b/ui/src/main/java/com/wireguard/android/model/ObservableTunnel.java @@ -27,12 +27,12 @@ import java9.util.concurrent.CompletionStage; public class ObservableTunnel extends BaseObservable implements Keyed<String>, Tunnel { private final TunnelManager manager; @Nullable private Config config; - private State state; private String name; + private State state; @Nullable private Statistics statistics; ObservableTunnel(final TunnelManager manager, final String name, - @Nullable final Config config, final State state) { + @Nullable final Config config, final State state) { this.name = name; this.manager = manager; this.config = config; @@ -103,6 +103,11 @@ public class ObservableTunnel extends BaseObservable implements Keyed<String>, T return name; } + @Override + public void onStateChange(final State newState) { + onStateChanged(newState); + } + State onStateChanged(final State state) { if (state != State.UP) onStatisticsChanged(null); @@ -111,11 +116,6 @@ public class ObservableTunnel extends BaseObservable implements Keyed<String>, T return state; } - @Override - public void onStateChange(final State newState) { - onStateChanged(newState); - } - @Nullable Statistics onStatisticsChanged(@Nullable final Statistics statistics) { this.statistics = statistics; diff --git a/ui/src/main/java/com/wireguard/android/preference/LogExporterPreference.java b/ui/src/main/java/com/wireguard/android/preference/LogExporterPreference.java index 43494aaa..32b3e33c 100644 --- a/ui/src/main/java/com/wireguard/android/preference/LogExporterPreference.java +++ b/ui/src/main/java/com/wireguard/android/preference/LogExporterPreference.java @@ -47,8 +47,7 @@ public class LogExporterPreference extends Preference { final Process process = Runtime.getRuntime().exec(new String[]{ "logcat", "-b", "all", "-d", "-v", "threadtime", "*:V"}); try (final BufferedReader stdout = new BufferedReader(new InputStreamReader(process.getInputStream())); - final BufferedReader stderr = new BufferedReader(new InputStreamReader(process.getErrorStream()))) - { + final BufferedReader stderr = new BufferedReader(new InputStreamReader(process.getErrorStream()))) { String line; while ((line = stdout.readLine()) != null) { outputFile.getOutputStream().write(line.getBytes()); diff --git a/ui/src/main/java/com/wireguard/android/preference/VersionPreference.java b/ui/src/main/java/com/wireguard/android/preference/VersionPreference.java index b07df3e5..47266fa8 100644 --- a/ui/src/main/java/com/wireguard/android/preference/VersionPreference.java +++ b/ui/src/main/java/com/wireguard/android/preference/VersionPreference.java @@ -28,14 +28,6 @@ import androidx.preference.Preference; public class VersionPreference extends Preference { @Nullable private String versionSummary; - private String getBackendPrettyName(final Context context, final Backend backend) { - if (backend instanceof GoBackend) - return context.getString(R.string.type_name_kernel_module); - if (backend instanceof WgQuickBackend) - return context.getString(R.string.type_name_go_userspace); - return ""; - } - public VersionPreference(final Context context, final AttributeSet attrs) { super(context, attrs); @@ -50,6 +42,14 @@ public class VersionPreference extends Preference { }); } + private String getBackendPrettyName(final Context context, final Backend backend) { + if (backend instanceof GoBackend) + return context.getString(R.string.type_name_kernel_module); + if (backend instanceof WgQuickBackend) + return context.getString(R.string.type_name_go_userspace); + return ""; + } + @Nullable @Override public CharSequence getSummary() { diff --git a/ui/src/main/java/com/wireguard/android/util/DownloadsFileSaver.java b/ui/src/main/java/com/wireguard/android/util/DownloadsFileSaver.java index b4772ef4..c1c667f5 100644 --- a/ui/src/main/java/com/wireguard/android/util/DownloadsFileSaver.java +++ b/ui/src/main/java/com/wireguard/android/util/DownloadsFileSaver.java @@ -26,30 +26,6 @@ import java.io.OutputStream; @NonNullForAll public class DownloadsFileSaver { - public static class DownloadsFile { - private Context context; - private OutputStream outputStream; - private String fileName; - private Uri uri; - - private DownloadsFile(final Context context, final OutputStream outputStream, final String fileName, final Uri uri) { - this.context = context; - this.outputStream = outputStream; - this.fileName = fileName; - this.uri = uri; - } - - public OutputStream getOutputStream() { return outputStream; } - public String getFileName() { return fileName; } - - public void delete() { - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) - context.getContentResolver().delete(uri, null, null); - else - new File(fileName).delete(); - } - } - public static DownloadsFile save(final Context context, final String name, final String mimeType, final boolean overwriteExisting) throws Exception { if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) { final ContentResolver contentResolver = context.getContentResolver(); @@ -89,12 +65,40 @@ public class DownloadsFileSaver { } return new DownloadsFile(context, contentStream, path, contentUri); } else { - @SuppressWarnings("deprecation") - final File path = Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DOWNLOADS); + @SuppressWarnings("deprecation") final File path = Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DOWNLOADS); final File file = new File(path, name); if (!path.isDirectory() && !path.mkdirs()) throw new IOException(context.getString(R.string.create_output_dir_error)); return new DownloadsFile(context, new FileOutputStream(file), file.getAbsolutePath(), null); } } + + public static class DownloadsFile { + private Context context; + private String fileName; + private OutputStream outputStream; + private Uri uri; + + private DownloadsFile(final Context context, final OutputStream outputStream, final String fileName, final Uri uri) { + this.context = context; + this.outputStream = outputStream; + this.fileName = fileName; + this.uri = uri; + } + + public void delete() { + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) + context.getContentResolver().delete(uri, null, null); + else + new File(fileName).delete(); + } + + public String getFileName() { + return fileName; + } + + public OutputStream getOutputStream() { + return outputStream; + } + } } diff --git a/ui/src/main/java/com/wireguard/android/util/ErrorMessages.java b/ui/src/main/java/com/wireguard/android/util/ErrorMessages.java index accc7ef7..e266cfed 100644 --- a/ui/src/main/java/com/wireguard/android/util/ErrorMessages.java +++ b/ui/src/main/java/com/wireguard/android/util/ErrorMessages.java @@ -51,14 +51,6 @@ public final class ErrorMessages { BackendException.Reason.TUN_CREATION_ERROR, R.string.tun_create_error, BackendException.Reason.GO_ACTIVATION_ERROR_CODE, R.string.tunnel_on_error )); - private static final Map<RootShellException.Reason, Integer> RSE_REASON_MAP = new EnumMap<>(Maps.of( - RootShellException.Reason.NO_ROOT_ACCESS, R.string.error_root, - RootShellException.Reason.SHELL_MARKER_COUNT_ERROR, R.string.shell_marker_count_error, - RootShellException.Reason.SHELL_EXIT_STATUS_READ_ERROR, R.string.shell_exit_status_read_error, - RootShellException.Reason.SHELL_START_ERROR, R.string.shell_start_error, - RootShellException.Reason.CREATE_BIN_DIR_ERROR, R.string.create_bin_dir_error, - RootShellException.Reason.CREATE_TEMP_DIR_ERROR, R.string.create_temp_dir_error - )); private static final Map<Format, Integer> KFE_FORMAT_MAP = new EnumMap<>(Maps.of( Format.BASE64, R.string.key_length_explanation_base64, Format.BINARY, R.string.key_length_explanation_binary, @@ -74,6 +66,14 @@ public final class ErrorMessages { InetNetwork.class, R.string.parse_error_inet_network, Integer.class, R.string.parse_error_integer ); + private static final Map<RootShellException.Reason, Integer> RSE_REASON_MAP = new EnumMap<>(Maps.of( + RootShellException.Reason.NO_ROOT_ACCESS, R.string.error_root, + RootShellException.Reason.SHELL_MARKER_COUNT_ERROR, R.string.shell_marker_count_error, + RootShellException.Reason.SHELL_EXIT_STATUS_READ_ERROR, R.string.shell_exit_status_read_error, + RootShellException.Reason.SHELL_START_ERROR, R.string.shell_start_error, + RootShellException.Reason.CREATE_BIN_DIR_ERROR, R.string.create_bin_dir_error, + RootShellException.Reason.CREATE_TEMP_DIR_ERROR, R.string.create_temp_dir_error + )); private ErrorMessages() { // Prevent instantiation |