Signed-off-by: Jason A. Donenfeld <Jason@zx2c4.com>master
@@ -41,13 +41,6 @@ public interface Backend { | |||||
*/ | */ | ||||
Statistics getStatistics(Tunnel tunnel) throws Exception; | Statistics getStatistics(Tunnel tunnel) throws Exception; | ||||
/** | |||||
* Determine type name of underlying backend. | |||||
* | |||||
* @return Type name | |||||
*/ | |||||
String getTypePrettyName(); | |||||
/** | /** | ||||
* Determine version of underlying backend. | * Determine version of underlying backend. | ||||
* | * | ||||
@@ -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; | |||||
} | |||||
} |
@@ -14,10 +14,8 @@ import androidx.annotation.Nullable; | |||||
import androidx.collection.ArraySet; | import androidx.collection.ArraySet; | ||||
import android.util.Log; | 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.backend.Tunnel.State; | ||||
import com.wireguard.android.util.ExceptionLoggers; | |||||
import com.wireguard.android.util.SharedLibraryLoader; | import com.wireguard.android.util.SharedLibraryLoader; | ||||
import com.wireguard.config.Config; | import com.wireguard.config.Config; | ||||
import com.wireguard.config.InetNetwork; | import com.wireguard.config.InetNetwork; | ||||
@@ -28,7 +26,6 @@ import com.wireguard.crypto.KeyFormatException; | |||||
import java.net.InetAddress; | import java.net.InetAddress; | ||||
import java.util.Collections; | import java.util.Collections; | ||||
import java.util.HashSet; | import java.util.HashSet; | ||||
import java.util.Objects; | |||||
import java.util.Set; | import java.util.Set; | ||||
import java.util.concurrent.TimeUnit; | import java.util.concurrent.TimeUnit; | ||||
import java.util.concurrent.TimeoutException; | import java.util.concurrent.TimeoutException; | ||||
@@ -130,11 +127,6 @@ public final class GoBackend implements Backend { | |||||
return stats; | return stats; | ||||
} | } | ||||
@Override | |||||
public String getTypePrettyName() { | |||||
return context.getString(R.string.type_name_go_userspace); | |||||
} | |||||
@Override | @Override | ||||
public String getVersion() { | public String getVersion() { | ||||
return wgVersion(); | return wgVersion(); | ||||
@@ -171,10 +163,11 @@ public final class GoBackend implements Backend { | |||||
Log.i(TAG, "Bringing tunnel " + tunnel.getName() + " " + state); | Log.i(TAG, "Bringing tunnel " + tunnel.getName() + " " + state); | ||||
if (state == State.UP) { | 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) | 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; | final VpnService service; | ||||
if (!vpnService.isDone()) | if (!vpnService.isDone()) | ||||
@@ -183,7 +176,9 @@ public final class GoBackend implements Backend { | |||||
try { | try { | ||||
service = vpnService.get(2, TimeUnit.SECONDS); | service = vpnService.get(2, TimeUnit.SECONDS); | ||||
} catch (final TimeoutException e) { | } 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); | service.setOwner(this); | ||||
@@ -225,12 +220,12 @@ public final class GoBackend implements Backend { | |||||
builder.setBlocking(true); | builder.setBlocking(true); | ||||
try (final ParcelFileDescriptor tun = builder.establish()) { | try (final ParcelFileDescriptor tun = builder.establish()) { | ||||
if (tun == null) | 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()); | Log.d(TAG, "Go backend v" + wgVersion()); | ||||
currentTunnelHandle = wgTurnOn(tunnel.getName(), tun.detachFd(), goConfig); | currentTunnelHandle = wgTurnOn(tunnel.getName(), tun.detachFd(), goConfig); | ||||
} | } | ||||
if (currentTunnelHandle < 0) | 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; | currentTunnel = tunnel; | ||||
currentConfig = config; | currentConfig = config; | ||||
@@ -5,12 +5,13 @@ | |||||
package com.wireguard.android.backend; | package com.wireguard.android.backend; | ||||
import android.content.Context; | |||||
import androidx.annotation.Nullable; | import androidx.annotation.Nullable; | ||||
import android.content.Context; | |||||
import android.util.Log; | import android.util.Log; | ||||
import com.wireguard.android.Application; | 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.backend.Tunnel.State; | ||||
import com.wireguard.config.Config; | import com.wireguard.config.Config; | ||||
import com.wireguard.crypto.Key; | 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 static final String TAG = "WireGuard/" + WgQuickBackend.class.getSimpleName(); | ||||
private final File localTemporaryDir; | private final File localTemporaryDir; | ||||
private final Context context; | |||||
private final Map<Tunnel, Config> runningConfigs = new HashMap<>(); | private final Map<Tunnel, Config> runningConfigs = new HashMap<>(); | ||||
private final Set<TunnelStateChangeNotificationReceiver> notifiers = new HashSet<>(); | private final Set<TunnelStateChangeNotificationReceiver> notifiers = new HashSet<>(); | ||||
public WgQuickBackend(final Context context) { | public WgQuickBackend(final Context context) { | ||||
localTemporaryDir = new File(context.getCacheDir(), "tmp"); | localTemporaryDir = new File(context.getCacheDir(), "tmp"); | ||||
this.context = context; | |||||
} | } | ||||
@Override | @Override | ||||
@@ -92,17 +91,12 @@ public final class WgQuickBackend implements Backend { | |||||
return stats; | return stats; | ||||
} | } | ||||
@Override | |||||
public String getTypePrettyName() { | |||||
return context.getString(R.string.type_name_kernel_module); | |||||
} | |||||
@Override | @Override | ||||
public String getVersion() throws Exception { | public String getVersion() throws Exception { | ||||
final List<String> output = new ArrayList<>(); | final List<String> output = new ArrayList<>(); | ||||
if (Application.getRootShell() | if (Application.getRootShell() | ||||
.run(output, "cat /sys/module/wireguard/version") != 0 || output.isEmpty()) | .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); | return output.get(0); | ||||
} | } | ||||
@@ -150,7 +144,7 @@ public final class WgQuickBackend implements Backend { | |||||
// noinspection ResultOfMethodCallIgnored | // noinspection ResultOfMethodCallIgnored | ||||
tempFile.delete(); | tempFile.delete(); | ||||
if (result != 0) | 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) | if (state == State.UP) | ||||
runningConfigs.put(tunnel, config); | runningConfigs.put(tunnel, config); | ||||
@@ -16,21 +16,32 @@ import android.util.AttributeSet; | |||||
import com.wireguard.android.Application; | import com.wireguard.android.Application; | ||||
import com.wireguard.android.BuildConfig; | import com.wireguard.android.BuildConfig; | ||||
import com.wireguard.android.R; | 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; | import java.util.Locale; | ||||
public class VersionPreference extends Preference { | public class VersionPreference extends Preference { | ||||
@Nullable private String versionSummary; | @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) { | public VersionPreference(final Context context, final AttributeSet attrs) { | ||||
super(context, attrs); | super(context, attrs); | ||||
Application.getBackendAsync().thenAccept(backend -> { | 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) -> { | Application.getAsyncWorker().supplyAsync(backend::getVersion).whenComplete((version, exception) -> { | ||||
versionSummary = exception == null | 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(); | notifyChanged(); | ||||
}); | }); | ||||
}); | }); | ||||
@@ -12,9 +12,9 @@ import androidx.annotation.Nullable; | |||||
import com.wireguard.android.Application; | import com.wireguard.android.Application; | ||||
import com.wireguard.android.R; | import com.wireguard.android.R; | ||||
import com.wireguard.android.backend.BackendException; | |||||
import com.wireguard.config.BadConfigException; | import com.wireguard.config.BadConfigException; | ||||
import com.wireguard.config.BadConfigException.Location; | import com.wireguard.config.BadConfigException.Location; | ||||
import com.wireguard.config.BadConfigException.Reason; | |||||
import com.wireguard.config.InetEndpoint; | import com.wireguard.config.InetEndpoint; | ||||
import com.wireguard.config.InetNetwork; | import com.wireguard.config.InetNetwork; | ||||
import com.wireguard.config.ParseException; | import com.wireguard.config.ParseException; | ||||
@@ -29,16 +29,25 @@ import java.util.Map; | |||||
import java9.util.Maps; | import java9.util.Maps; | ||||
public final class ErrorMessages { | public final class ErrorMessages { | ||||
private static final Map<Reason, Integer> 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<BadConfigException.Reason, Integer> 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<BackendException.Reason, Integer> 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<Format, Integer> KFE_FORMAT_MAP = new EnumMap<>(Maps.of( | private static final Map<Format, Integer> KFE_FORMAT_MAP = new EnumMap<>(Maps.of( | ||||
Format.BASE64, R.string.key_length_explanation_base64, | Format.BASE64, R.string.key_length_explanation_base64, | ||||
@@ -77,6 +86,9 @@ public final class ErrorMessages { | |||||
bce.getLocation().getName()); | bce.getLocation().getName()); | ||||
final String explanation = getBadConfigExceptionExplanation(resources, bce); | final String explanation = getBadConfigExceptionExplanation(resources, bce); | ||||
message = resources.getString(R.string.bad_config_error, reason, context) + explanation; | 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) { | } else if (rootCause.getMessage() != null) { | ||||
message = rootCause.getMessage(); | message = rootCause.getMessage(); | ||||
} else { | } else { | ||||
@@ -123,7 +135,7 @@ public final class ErrorMessages { | |||||
private static Throwable rootCause(final Throwable throwable) { | private static Throwable rootCause(final Throwable throwable) { | ||||
Throwable cause = throwable; | Throwable cause = throwable; | ||||
while (cause.getCause() != null) { | while (cause.getCause() != null) { | ||||
if (cause instanceof BadConfigException) | |||||
if (cause instanceof BadConfigException || cause instanceof BackendException) | |||||
break; | break; | ||||
final Throwable nextCause = cause.getCause(); | final Throwable nextCause = cause.getCause(); | ||||
if (nextCause instanceof RemoteException) | if (nextCause instanceof RemoteException) | ||||