@@ -57,7 +57,7 @@ public interface Backend { | |||
* | |||
* @return Type name | |||
*/ | |||
String getTypeName(); | |||
String getTypePrettyName(); | |||
/** | |||
* Determine version of underlying backend. | |||
@@ -14,6 +14,7 @@ import android.support.v4.util.ArraySet; | |||
import android.util.Log; | |||
import com.wireguard.android.Application; | |||
import com.wireguard.android.R; | |||
import com.wireguard.android.activity.MainActivity; | |||
import com.wireguard.android.model.Tunnel; | |||
import com.wireguard.android.model.Tunnel.State; | |||
@@ -26,6 +27,7 @@ import com.wireguard.config.Peer; | |||
import java.net.InetAddress; | |||
import java.util.Collections; | |||
import java.util.Locale; | |||
import java.util.Objects; | |||
import java.util.Set; | |||
import java.util.concurrent.TimeUnit; | |||
@@ -93,8 +95,8 @@ public final class GoBackend implements Backend { | |||
} | |||
@Override | |||
public String getTypeName() { | |||
return "Go userspace"; | |||
public String getTypePrettyName() { | |||
return context.getResources().getString(R.string.type_name_go_userspace); | |||
} | |||
@Override | |||
@@ -110,7 +112,7 @@ public final class GoBackend implements Backend { | |||
if (state == originalState) | |||
return originalState; | |||
if (state == State.UP && currentTunnel != null) | |||
throw new IllegalStateException("Only one userspace tunnel can run at a time"); | |||
throw new IllegalStateException(context.getResources().getString(R.string.multiple_tunnels_error)); | |||
Log.d(TAG, "Changing tunnel " + tunnel.getName() + " to state " + state); | |||
setStateInternal(tunnel, tunnel.getConfig(), state); | |||
return getState(tunnel); | |||
@@ -122,10 +124,10 @@ public final class GoBackend implements Backend { | |||
if (state == State.UP) { | |||
Log.i(TAG, "Bringing tunnel up"); | |||
Objects.requireNonNull(config, "Trying to bring up a tunnel with no config"); | |||
Objects.requireNonNull(config, context.getResources().getString(R.string.no_config_error)); | |||
if (VpnService.prepare(context) != null) | |||
throw new Exception("VPN service not authorized by user"); | |||
throw new Exception(context.getResources().getString(R.string.vpn_not_authed_error)); | |||
final VpnService service; | |||
if (!vpnService.isDone()) | |||
@@ -134,7 +136,7 @@ public final class GoBackend implements Backend { | |||
try { | |||
service = vpnService.get(2, TimeUnit.SECONDS); | |||
} catch (final TimeoutException e) { | |||
throw new Exception("Unable to start Android VPN service", e); | |||
throw new Exception(context.getResources().getString(R.string.vpn_start_error), e); | |||
} | |||
if (currentTunnelHandle != -1) { | |||
@@ -172,12 +174,12 @@ public final class GoBackend implements Backend { | |||
builder.setBlocking(true); | |||
try (final ParcelFileDescriptor tun = builder.establish()) { | |||
if (tun == null) | |||
throw new Exception("Unable to create tun device"); | |||
throw new Exception(context.getResources().getString(R.string.tun_create_error)); | |||
Log.d(TAG, "Go backend v" + wgVersion()); | |||
currentTunnelHandle = wgTurnOn(tunnel.getName(), tun.detachFd(), goConfig); | |||
} | |||
if (currentTunnelHandle < 0) | |||
throw new Exception("Unable to turn tunnel on (wgTurnOn return " + currentTunnelHandle + ')'); | |||
throw new Exception(String.format(Locale.getDefault(), context.getResources().getString(R.string.tunnel_on_error), currentTunnelHandle)); | |||
currentTunnel = tunnel; | |||
@@ -10,6 +10,7 @@ import android.support.annotation.Nullable; | |||
import android.util.Log; | |||
import com.wireguard.android.Application; | |||
import com.wireguard.android.R; | |||
import com.wireguard.android.model.Tunnel; | |||
import com.wireguard.android.model.Tunnel.State; | |||
import com.wireguard.android.model.Tunnel.Statistics; | |||
@@ -36,9 +37,11 @@ public final class WgQuickBackend implements Backend { | |||
private static final String TAG = "WireGuard/" + WgQuickBackend.class.getSimpleName(); | |||
private final File localTemporaryDir; | |||
private final Context context; | |||
public WgQuickBackend(final Context context) { | |||
localTemporaryDir = new File(context.getCacheDir(), "tmp"); | |||
this.context = context; | |||
} | |||
@Override | |||
@@ -84,8 +87,8 @@ public final class WgQuickBackend implements Backend { | |||
} | |||
@Override | |||
public String getTypeName() { | |||
return "Kernel module"; | |||
public String getTypePrettyName() { | |||
return context.getResources().getString(R.string.type_name_kernel_module); | |||
} | |||
@Override | |||
@@ -93,7 +96,7 @@ public final class WgQuickBackend implements Backend { | |||
final List<String> output = new ArrayList<>(); | |||
if (Application.getRootShell() | |||
.run(output, "cat /sys/module/wireguard/version") != 0 || output.isEmpty()) | |||
throw new Exception("Unable to determine kernel module version"); | |||
throw new Exception(context.getResources().getString(R.string.module_version_error)); | |||
return output.get(0); | |||
} | |||
@@ -125,6 +128,6 @@ public final class WgQuickBackend implements Backend { | |||
// noinspection ResultOfMethodCallIgnored | |||
tempFile.delete(); | |||
if (result != 0) | |||
throw new Exception("Unable to configure tunnel (wg-quick returned " + result + ')'); | |||
throw new Exception(context.getResources().getString(R.string.tunnel_config_error)); | |||
} | |||
} |
@@ -8,6 +8,7 @@ package com.wireguard.android.configStore; | |||
import android.content.Context; | |||
import android.util.Log; | |||
import com.wireguard.android.R; | |||
import com.wireguard.config.Config; | |||
import com.wireguard.config.ParseException; | |||
@@ -17,6 +18,7 @@ import java.io.FileNotFoundException; | |||
import java.io.FileOutputStream; | |||
import java.io.IOException; | |||
import java.nio.charset.StandardCharsets; | |||
import java.util.Locale; | |||
import java.util.Set; | |||
import java9.util.stream.Collectors; | |||
@@ -40,7 +42,8 @@ public final class FileConfigStore implements ConfigStore { | |||
Log.d(TAG, "Creating configuration for tunnel " + name); | |||
final File file = fileFor(name); | |||
if (!file.createNewFile()) | |||
throw new IOException("Configuration file " + file.getName() + " already exists"); | |||
throw new IOException(String.format(Locale.getDefault(), | |||
context.getResources().getString(R.string.config_file_exists_error), file.getName())); | |||
try (final FileOutputStream stream = new FileOutputStream(file, false)) { | |||
stream.write(config.toWgQuickString().getBytes(StandardCharsets.UTF_8)); | |||
} | |||
@@ -52,7 +55,8 @@ public final class FileConfigStore implements ConfigStore { | |||
Log.d(TAG, "Deleting configuration for tunnel " + name); | |||
final File file = fileFor(name); | |||
if (!file.delete()) | |||
throw new IOException("Cannot delete configuration file " + file.getName()); | |||
throw new IOException(String.format(Locale.getDefault(), | |||
context.getResources().getString(R.string.config_delete_error), file.getName())); | |||
} | |||
@Override | |||
@@ -80,11 +84,13 @@ public final class FileConfigStore implements ConfigStore { | |||
final File file = fileFor(name); | |||
final File replacementFile = fileFor(replacement); | |||
if (!replacementFile.createNewFile()) | |||
throw new IOException("Configuration for " + replacement + " already exists"); | |||
throw new IOException(String.format(Locale.getDefault(), | |||
context.getResources().getString(R.string.config_exists_error), replacement)); | |||
if (!file.renameTo(replacementFile)) { | |||
if (!replacementFile.delete()) | |||
Log.w(TAG, "Couldn't delete marker file for new name " + replacement); | |||
throw new IOException("Cannot rename configuration file " + file.getName()); | |||
throw new IOException(String.format(Locale.getDefault(), | |||
context.getResources().getString(R.string.config_rename_error), file.getName())); | |||
} | |||
} | |||
@@ -93,7 +99,8 @@ public final class FileConfigStore implements ConfigStore { | |||
Log.d(TAG, "Saving configuration for tunnel " + name); | |||
final File file = fileFor(name); | |||
if (!file.isFile()) | |||
throw new FileNotFoundException("Configuration file " + file.getName() + " not found"); | |||
throw new FileNotFoundException(String.format(Locale.getDefault(), | |||
context.getResources().getString(R.string.config_not_found_error), file.getName())); | |||
try (final FileOutputStream stream = new FileOutputStream(file, false)) { | |||
stream.write(config.toWgQuickString().getBytes(StandardCharsets.UTF_8)); | |||
} | |||
@@ -24,6 +24,7 @@ import com.wireguard.config.ParseException; | |||
import java.io.ByteArrayInputStream; | |||
import java.io.IOException; | |||
import java.nio.charset.StandardCharsets; | |||
import java.util.Locale; | |||
import java.util.Objects; | |||
public class ConfigNamingDialogFragment extends DialogFragment { | |||
@@ -68,7 +69,8 @@ public class ConfigNamingDialogFragment extends DialogFragment { | |||
try { | |||
config = Config.parse(new ByteArrayInputStream(getArguments().getString(KEY_CONFIG_TEXT).getBytes(StandardCharsets.UTF_8))); | |||
} catch (final IOException | ParseException exception) { | |||
throw new RuntimeException("Invalid config passed to " + getClass().getSimpleName(), exception); | |||
throw new RuntimeException(String.format(Locale.getDefault(), | |||
getResources().getString(R.string.invalid_config_error), getClass().getSimpleName()), exception); | |||
} | |||
} | |||
@@ -115,14 +115,15 @@ public class TunnelListFragment extends BaseFragment { | |||
int idx = name.lastIndexOf('/'); | |||
if (idx >= 0) { | |||
if (idx >= name.length() - 1) | |||
throw new IllegalArgumentException("Illegal file name: " + name); | |||
throw new IllegalArgumentException(String.format(Locale.getDefault(), | |||
getResources().getString(R.string.illegal_filename_error), name)); | |||
name = name.substring(idx + 1); | |||
} | |||
boolean isZip = name.toLowerCase(Locale.ENGLISH).endsWith(".zip"); | |||
if (name.toLowerCase(Locale.ENGLISH).endsWith(".conf")) | |||
name = name.substring(0, name.length() - ".conf".length()); | |||
else if (!isZip) | |||
throw new IllegalArgumentException("File must be .conf or .zip"); | |||
throw new IllegalArgumentException(getResources().getString(R.string.bad_extension_error)); | |||
if (isZip) { | |||
try (ZipInputStream zip = new ZipInputStream(contentResolver.openInputStream(uri)); | |||
@@ -161,7 +162,7 @@ public class TunnelListFragment extends BaseFragment { | |||
if (throwables.size() == 1) | |||
throw throwables.get(0); | |||
else if (throwables.isEmpty()) | |||
throw new IllegalArgumentException("No configurations found"); | |||
throw new IllegalArgumentException(getResources().getString(R.string.no_configs_error)); | |||
} | |||
return CompletableFuture.allOf(futureTunnels.toArray(new CompletableFuture[futureTunnels.size()])); | |||
@@ -44,7 +44,8 @@ public class LogExporterPreference extends Preference { | |||
final File path = Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DOWNLOADS); | |||
final File file = new File(path, "wireguard-log.txt"); | |||
if (!path.isDirectory() && !path.mkdirs()) | |||
throw new IOException("Cannot create output directory"); | |||
throw new IOException( | |||
getContext().getResources().getString(R.string.create_output_dir_error)); | |||
/* We would like to simply run `builder.redirectOutput(file);`, but this is API 26. | |||
* Instead we have to do this dance, since logcat appends. | |||
@@ -26,11 +26,11 @@ public class VersionPreference extends Preference { | |||
super(context, attrs); | |||
Application.getBackendAsync().thenAccept(backend -> { | |||
versionSummary = getContext().getString(R.string.version_summary_checking, backend.getTypeName().toLowerCase(Locale.ENGLISH)); | |||
versionSummary = getContext().getString(R.string.version_summary_checking, backend.getTypePrettyName().toLowerCase(Locale.ENGLISH)); | |||
Application.getAsyncWorker().supplyAsync(backend::getVersion).whenComplete((version, exception) -> { | |||
versionSummary = exception == null | |||
? getContext().getString(R.string.version_summary, backend.getTypeName(), version) | |||
: getContext().getString(R.string.version_summary_unknown, backend.getTypeName().toLowerCase(Locale.ENGLISH)); | |||
? getContext().getString(R.string.version_summary, backend.getTypePrettyName(), version) | |||
: getContext().getString(R.string.version_summary_unknown, backend.getTypePrettyName().toLowerCase(Locale.ENGLISH)); | |||
notifyChanged(); | |||
}); | |||
}); | |||
@@ -55,7 +55,8 @@ public class ZipExporterPreference extends Preference { | |||
for (final Tunnel tunnel : tunnels) | |||
futureConfigs.add(tunnel.getConfigAsync().toCompletableFuture()); | |||
if (futureConfigs.isEmpty()) { | |||
exportZipComplete(null, new IllegalArgumentException("No tunnels exist")); | |||
exportZipComplete(null, new IllegalArgumentException( | |||
getContext().getResources().getString(R.string.no_tunnels_error))); | |||
return; | |||
} | |||
CompletableFuture.allOf(futureConfigs.toArray(new CompletableFuture[futureConfigs.size()])) | |||
@@ -65,7 +66,8 @@ public class ZipExporterPreference extends Preference { | |||
final File path = Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DOWNLOADS); | |||
final File file = new File(path, "wireguard-export.zip"); | |||
if (!path.isDirectory() && !path.mkdirs()) | |||
throw new IOException("Cannot create output directory"); | |||
throw new IOException( | |||
getContext().getResources().getString(R.string.create_output_dir_error)); | |||
try (ZipOutputStream zip = new ZipOutputStream(new FileOutputStream(file))) { | |||
for (int i = 0; i < futureConfigs.size(); ++i) { | |||
zip.putNextEntry(new ZipEntry(tunnels.get(i).getName() + ".conf")); | |||
@@ -20,6 +20,7 @@ import java.io.InputStreamReader; | |||
import java.io.OutputStreamWriter; | |||
import java.nio.charset.StandardCharsets; | |||
import java.util.Collection; | |||
import java.util.Locale; | |||
import java.util.UUID; | |||
/** | |||
@@ -30,6 +31,7 @@ public class RootShell { | |||
private static final String SU = "su"; | |||
private static final String TAG = "WireGuard/" + RootShell.class.getSimpleName(); | |||
private final Context context; | |||
private final String deviceNotRootedMessage; | |||
private final File localBinaryDir; | |||
private final File localTemporaryDir; | |||
@@ -47,6 +49,7 @@ public class RootShell { | |||
localTemporaryDir = new File(cacheDir, "tmp"); | |||
preamble = String.format("export CALLING_PACKAGE=%s PATH=\"%s:$PATH\" TMPDIR='%s'; id -u\n", | |||
BuildConfig.APPLICATION_ID, localBinaryDir, localTemporaryDir); | |||
this.context = context; | |||
} | |||
private static boolean isExecutableInPath(final String name) { | |||
@@ -121,9 +124,10 @@ public class RootShell { | |||
} | |||
} | |||
if (markersSeen != 4) | |||
throw new IOException("Expected 4 markers, received " + markersSeen); | |||
throw new IOException(String.format(Locale.getDefault(), | |||
context.getResources().getString(R.string.marker_count_error), markersSeen)); | |||
if (errnoStdout != errnoStderr) | |||
throw new IOException("Unable to read exit status"); | |||
throw new IOException(context.getResources().getString(R.string.exit_status_read_error)); | |||
Log.v(TAG, "exit: " + errnoStdout); | |||
return errnoStdout; | |||
} | |||
@@ -136,9 +140,9 @@ public class RootShell { | |||
if (isRunning()) | |||
return; | |||
if (!localBinaryDir.isDirectory() && !localBinaryDir.mkdirs()) | |||
throw new FileNotFoundException("Could not create local binary directory"); | |||
throw new FileNotFoundException(context.getResources().getString(R.string.create_bin_dir_error)); | |||
if (!localTemporaryDir.isDirectory() && !localTemporaryDir.mkdirs()) | |||
throw new FileNotFoundException("Could not create local temporary directory"); | |||
throw new FileNotFoundException(context.getResources().getString(R.string.create_temp_dir_error)); | |||
try { | |||
final ProcessBuilder builder = new ProcessBuilder().command(SU); | |||
builder.environment().put("LC_ALL", "C"); | |||
@@ -168,7 +172,8 @@ public class RootShell { | |||
if (line.contains("Permission denied")) | |||
throw new NoRootException(deviceNotRootedMessage); | |||
} | |||
throw new IOException("Shell failed to start: " + process.exitValue()); | |||
throw new IOException(String.format(Locale.getDefault(), | |||
context.getResources().getString(R.string.shell_start_error), process.exitValue())); | |||
} | |||
} catch (final IOException | NoRootException e) { | |||
stop(); | |||
@@ -12,6 +12,7 @@ import android.util.Log; | |||
import com.wireguard.android.Application; | |||
import com.wireguard.android.BuildConfig; | |||
import com.wireguard.android.R; | |||
import com.wireguard.android.util.RootShell.NoRootException; | |||
import java.io.File; | |||
@@ -41,6 +42,7 @@ public final class ToolsInstaller { | |||
@Nullable private static final File INSTALL_DIR = getInstallDir(); | |||
private static final String TAG = "WireGuard/" + ToolsInstaller.class.getSimpleName(); | |||
private final Context context; | |||
private final File localBinaryDir; | |||
private final Object lock = new Object(); | |||
private final File nativeLibraryDir; | |||
@@ -50,6 +52,7 @@ public final class ToolsInstaller { | |||
public ToolsInstaller(final Context context) { | |||
localBinaryDir = new File(context.getCacheDir(), "bin"); | |||
nativeLibraryDir = new File(context.getApplicationInfo().nativeLibraryDir); | |||
this.context = context; | |||
} | |||
@Nullable | |||
@@ -102,7 +105,8 @@ public final class ToolsInstaller { | |||
} | |||
} | |||
if (!areToolsAvailable) | |||
throw new FileNotFoundException("Required tools unavailable"); | |||
throw new FileNotFoundException( | |||
context.getResources().getString(R.string.tools_unavailable_error)); | |||
} | |||
} | |||
@@ -218,7 +218,7 @@ public class FloatingActionsMenu extends ViewGroup { | |||
attr.recycle(); | |||
if (mLabelsStyle != 0 && expandsHorizontally()) { | |||
throw new IllegalStateException("Action labels in horizontal expand orientation is not supported."); | |||
throw new IllegalStateException(getResources().getString(R.string.horizontal_expand_error)); | |||
} | |||
createAddButton(context); | |||
@@ -114,4 +114,34 @@ | |||
<string name="zip_export_error">Unable to export tunnels: %s</string> | |||
<string name="zip_export_success">Saved to %s</string> | |||
<string name="zip_export_summary">Zip file will be saved to downloads folder</string> | |||
<string name="vpn_not_authed_error">VPN service not authorized by user</string> | |||
<string name="vpn_start_error">Unable to start Android VPN service</string> | |||
<string name="no_config_error">Trying to bring up a tunnel with no config</string> | |||
<string name="tun_create_error">Unable to create tun device</string> | |||
<string name="tunnel_on_error">Unable to turn tunnel on (wgTurnOn returned %i)</string> | |||
<string name="tunnel_config_error">Unable to configure tunnel (wg-quick returned %i)</string> | |||
<string name="module_version_error">Unable to determine kernel module version</string> | |||
<string name="config_file_exists_error">Configuration file %s already exists</string> | |||
<string name="config_exists_error">Configuration for %s already exists</string> | |||
<string name="config_delete_error">Cannot delete configuration file %s</string> | |||
<string name="config_rename_error">Cannot rename configuration file %s</string> | |||
<string name="config_not_found_error">Configuration file %s not found</string> | |||
<string name="no_tunnels_error">No tunnels exist</string> | |||
<string name="create_output_dir_error">Cannot create output directory</string> | |||
<string name="illegal_filename_error">Illegal file name %s</string> | |||
<string name="bad_extension_error">File must be .conf or .zip</string> | |||
<string name="no_configs_error">No configurations found</string> | |||
<string name="invalid_config_error">Invalid config passed to %s</string> | |||
<string name="multiple_tunnels_error">Only one userspace tunnel can run at a time</string> | |||
<string name="horizontal_expand_error">Action labels in horizontal expand orientation is not supported.</string> | |||
<string name="marker_count_error">Expected 4 markers, received %i</string> | |||
<string name="exit_status_read_error">Unable to read exit status</string> | |||
<string name="create_bin_dir_error">Could not create local binary directory</string> | |||
<string name="create_temp_dir_error">Could not create local temporary directory</string> | |||
<string name="shell_start_error">Shell failed to start: %i</string> | |||
<string name="tools_unavailable_error">Required tools unavailable</string> | |||
<string name="type_name_kernel_module">Kernel module</string> | |||
<string name="type_name_go_userspace">Go userspace</string> | |||
</resources> |