Signed-off-by: Jason A. Donenfeld <Jason@zx2c4.com>master
@@ -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. | |||
* | |||
@@ -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 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; | |||
@@ -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<Tunnel, Config> runningConfigs = new HashMap<>(); | |||
private final Set<TunnelStateChangeNotificationReceiver> 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<String> 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); | |||
@@ -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(); | |||
}); | |||
}); | |||
@@ -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<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( | |||
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) | |||