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 7569b5fa..84e32eb2 100644 --- a/app/src/main/java/com/wireguard/android/backend/Backend.java +++ b/app/src/main/java/com/wireguard/android/backend/Backend.java @@ -41,13 +41,6 @@ public interface Backend { */ Statistics getStatistics(Tunnel tunnel) throws Exception; - /** - * Determine type name of underlying backend. - * - * @return Type name - */ - String getTypePrettyName(); - /** * Determine version of underlying backend. * diff --git a/app/src/main/java/com/wireguard/android/backend/BackendException.java b/app/src/main/java/com/wireguard/android/backend/BackendException.java new file mode 100644 index 00000000..2994e02a --- /dev/null +++ b/app/src/main/java/com/wireguard/android/backend/BackendException.java @@ -0,0 +1,31 @@ +/* + * 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 index 6bbf5a3b..00c1d217 100644 --- a/app/src/main/java/com/wireguard/android/backend/GoBackend.java +++ b/app/src/main/java/com/wireguard/android/backend/GoBackend.java @@ -14,10 +14,8 @@ import androidx.annotation.Nullable; import androidx.collection.ArraySet; import android.util.Log; -import com.wireguard.android.Application; -import com.wireguard.android.R; +import com.wireguard.android.backend.BackendException.Reason; import com.wireguard.android.backend.Tunnel.State; -import com.wireguard.android.util.ExceptionLoggers; import com.wireguard.android.util.SharedLibraryLoader; import com.wireguard.config.Config; import com.wireguard.config.InetNetwork; @@ -28,7 +26,6 @@ import com.wireguard.crypto.KeyFormatException; import java.net.InetAddress; import java.util.Collections; import java.util.HashSet; -import java.util.Objects; import java.util.Set; import java.util.concurrent.TimeUnit; import java.util.concurrent.TimeoutException; @@ -130,11 +127,6 @@ public final class GoBackend implements Backend { return stats; } - @Override - public String getTypePrettyName() { - return context.getString(R.string.type_name_go_userspace); - } - @Override public String getVersion() { return wgVersion(); @@ -171,10 +163,11 @@ public final class GoBackend implements Backend { Log.i(TAG, "Bringing tunnel " + tunnel.getName() + " " + state); if (state == State.UP) { - Objects.requireNonNull(config, context.getString(R.string.no_config_error)); + if (config == null) + throw new BackendException(Reason.TUNNEL_MISSING_CONFIG); if (VpnService.prepare(context) != null) - throw new Exception(context.getString(R.string.vpn_not_authorized_error)); + throw new BackendException(Reason.VPN_NOT_AUTHORIZED); final VpnService service; if (!vpnService.isDone()) @@ -183,7 +176,9 @@ public final class GoBackend implements Backend { try { service = vpnService.get(2, TimeUnit.SECONDS); } catch (final TimeoutException e) { - throw new Exception(context.getString(R.string.vpn_start_error), e); + final Exception be = new BackendException(Reason.UNABLE_TO_START_VPN); + be.initCause(e); + throw be; } service.setOwner(this); @@ -225,12 +220,12 @@ public final class GoBackend implements Backend { builder.setBlocking(true); try (final ParcelFileDescriptor tun = builder.establish()) { if (tun == null) - throw new Exception(context.getString(R.string.tun_create_error)); + 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 Exception(context.getString(R.string.tunnel_on_error, currentTunnelHandle)); + throw new BackendException(Reason.GO_ACTIVATION_ERROR_CODE, currentTunnelHandle); currentTunnel = tunnel; currentConfig = config; 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 e04e6658..5769da66 100644 --- a/app/src/main/java/com/wireguard/android/backend/WgQuickBackend.java +++ b/app/src/main/java/com/wireguard/android/backend/WgQuickBackend.java @@ -5,12 +5,13 @@ package com.wireguard.android.backend; -import android.content.Context; import androidx.annotation.Nullable; + +import android.content.Context; import android.util.Log; import com.wireguard.android.Application; -import com.wireguard.android.R; +import com.wireguard.android.backend.BackendException.Reason; import com.wireguard.android.backend.Tunnel.State; import com.wireguard.config.Config; import com.wireguard.crypto.Key; @@ -40,13 +41,11 @@ public final class WgQuickBackend implements Backend { private static final String TAG = "WireGuard/" + WgQuickBackend.class.getSimpleName(); private final File localTemporaryDir; - private final Context context; private final Map runningConfigs = new HashMap<>(); private final Set notifiers = new HashSet<>(); public WgQuickBackend(final Context context) { localTemporaryDir = new File(context.getCacheDir(), "tmp"); - this.context = context; } @Override @@ -92,17 +91,12 @@ public final class WgQuickBackend implements Backend { return stats; } - @Override - public String getTypePrettyName() { - return context.getString(R.string.type_name_kernel_module); - } - @Override public String getVersion() throws Exception { final List output = new ArrayList<>(); if (Application.getRootShell() .run(output, "cat /sys/module/wireguard/version") != 0 || output.isEmpty()) - throw new Exception(context.getString(R.string.module_version_error)); + throw new BackendException(Reason.UNKNOWN_KERNEL_MODULE_NAME); return output.get(0); } @@ -150,7 +144,7 @@ public final class WgQuickBackend implements Backend { // noinspection ResultOfMethodCallIgnored tempFile.delete(); if (result != 0) - throw new Exception(context.getString(R.string.tunnel_config_error, result)); + throw new BackendException(Reason.WG_QUICK_CONFIG_ERROR_CODE, result); if (state == State.UP) runningConfigs.put(tunnel, config); diff --git a/app/src/main/java/com/wireguard/android/preference/VersionPreference.java b/app/src/main/java/com/wireguard/android/preference/VersionPreference.java index a0a5d1ff..7e95a8ae 100644 --- a/app/src/main/java/com/wireguard/android/preference/VersionPreference.java +++ b/app/src/main/java/com/wireguard/android/preference/VersionPreference.java @@ -16,21 +16,32 @@ import android.util.AttributeSet; import com.wireguard.android.Application; import com.wireguard.android.BuildConfig; import com.wireguard.android.R; +import com.wireguard.android.backend.Backend; +import com.wireguard.android.backend.GoBackend; +import com.wireguard.android.backend.WgQuickBackend; import java.util.Locale; 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); Application.getBackendAsync().thenAccept(backend -> { - versionSummary = getContext().getString(R.string.version_summary_checking, backend.getTypePrettyName().toLowerCase(Locale.ENGLISH)); + versionSummary = getContext().getString(R.string.version_summary_checking, getBackendPrettyName(context, backend).toLowerCase(Locale.ENGLISH)); Application.getAsyncWorker().supplyAsync(backend::getVersion).whenComplete((version, exception) -> { versionSummary = exception == null - ? getContext().getString(R.string.version_summary, backend.getTypePrettyName(), version) - : getContext().getString(R.string.version_summary_unknown, backend.getTypePrettyName().toLowerCase(Locale.ENGLISH)); + ? getContext().getString(R.string.version_summary, getBackendPrettyName(context, backend), version) + : getContext().getString(R.string.version_summary_unknown, getBackendPrettyName(context, backend).toLowerCase(Locale.ENGLISH)); notifyChanged(); }); }); diff --git a/app/src/main/java/com/wireguard/android/util/ErrorMessages.java b/app/src/main/java/com/wireguard/android/util/ErrorMessages.java index 7f926541..59d513f4 100644 --- a/app/src/main/java/com/wireguard/android/util/ErrorMessages.java +++ b/app/src/main/java/com/wireguard/android/util/ErrorMessages.java @@ -12,9 +12,9 @@ import androidx.annotation.Nullable; import com.wireguard.android.Application; import com.wireguard.android.R; +import com.wireguard.android.backend.BackendException; import com.wireguard.config.BadConfigException; import com.wireguard.config.BadConfigException.Location; -import com.wireguard.config.BadConfigException.Reason; import com.wireguard.config.InetEndpoint; import com.wireguard.config.InetNetwork; import com.wireguard.config.ParseException; @@ -29,16 +29,25 @@ import java.util.Map; import java9.util.Maps; public final class ErrorMessages { - private static final Map BCE_REASON_MAP = new EnumMap<>(Maps.of( - Reason.INVALID_KEY, R.string.bad_config_reason_invalid_key, - Reason.INVALID_NUMBER, R.string.bad_config_reason_invalid_number, - Reason.INVALID_VALUE, R.string.bad_config_reason_invalid_value, - Reason.MISSING_ATTRIBUTE, R.string.bad_config_reason_missing_attribute, - Reason.MISSING_SECTION, R.string.bad_config_reason_missing_section, - Reason.MISSING_VALUE, R.string.bad_config_reason_missing_value, - Reason.SYNTAX_ERROR, R.string.bad_config_reason_syntax_error, - Reason.UNKNOWN_ATTRIBUTE, R.string.bad_config_reason_unknown_attribute, - Reason.UNKNOWN_SECTION, R.string.bad_config_reason_unknown_section + private static final Map BCE_REASON_MAP = new EnumMap<>(Maps.of( + BadConfigException.Reason.INVALID_KEY, R.string.bad_config_reason_invalid_key, + BadConfigException.Reason.INVALID_NUMBER, R.string.bad_config_reason_invalid_number, + BadConfigException.Reason.INVALID_VALUE, R.string.bad_config_reason_invalid_value, + BadConfigException.Reason.MISSING_ATTRIBUTE, R.string.bad_config_reason_missing_attribute, + BadConfigException.Reason.MISSING_SECTION, R.string.bad_config_reason_missing_section, + BadConfigException.Reason.MISSING_VALUE, R.string.bad_config_reason_missing_value, + BadConfigException.Reason.SYNTAX_ERROR, R.string.bad_config_reason_syntax_error, + BadConfigException.Reason.UNKNOWN_ATTRIBUTE, R.string.bad_config_reason_unknown_attribute, + BadConfigException.Reason.UNKNOWN_SECTION, R.string.bad_config_reason_unknown_section + )); + private static final Map BE_REASON_MAP = new EnumMap<>(Maps.of( + BackendException.Reason.UNKNOWN_KERNEL_MODULE_NAME, R.string.module_version_error, + BackendException.Reason.WG_QUICK_CONFIG_ERROR_CODE, R.string.tunnel_config_error, + BackendException.Reason.TUNNEL_MISSING_CONFIG, R.string.no_config_error, + BackendException.Reason.VPN_NOT_AUTHORIZED, R.string.vpn_not_authorized_error, + BackendException.Reason.UNABLE_TO_START_VPN, R.string.vpn_start_error, + 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 KFE_FORMAT_MAP = new EnumMap<>(Maps.of( Format.BASE64, R.string.key_length_explanation_base64, @@ -77,6 +86,9 @@ public final class ErrorMessages { bce.getLocation().getName()); final String explanation = getBadConfigExceptionExplanation(resources, bce); message = resources.getString(R.string.bad_config_error, reason, context) + explanation; + } else if (rootCause instanceof BackendException) { + final BackendException be = (BackendException) rootCause; + message = resources.getString(BE_REASON_MAP.get(be.getReason()), be.getFormat()); } else if (rootCause.getMessage() != null) { message = rootCause.getMessage(); } else { @@ -123,7 +135,7 @@ public final class ErrorMessages { private static Throwable rootCause(final Throwable throwable) { Throwable cause = throwable; while (cause.getCause() != null) { - if (cause instanceof BadConfigException) + if (cause instanceof BadConfigException || cause instanceof BackendException) break; final Throwable nextCause = cause.getCause(); if (nextCause instanceof RemoteException)