mirror of
https://github.com/amnezia-vpn/amneziawg-android.git
synced 2026-07-03 14:05:13 +02:00
Compare commits
46 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| b67fa3a38c | |||
| 8b0123042f | |||
| 16890a659e | |||
| d40ac7f89d | |||
| 2694f48b87 | |||
| 0f91aeb2d3 | |||
| bc0111f895 | |||
| f8a3e9b332 | |||
| db9397fd3e | |||
| 20717ff128 | |||
| a532a88585 | |||
| 0b077bd523 | |||
| e008efcf97 | |||
| 7cf676f9bd | |||
| b83538d08d | |||
| 3c31c340d8 | |||
| 59620456ee | |||
| e42bd29382 | |||
| 18dbc21f96 | |||
| 68d871c47c | |||
| a45a219e5f | |||
| 49788240aa | |||
| 52166500fd | |||
| 3c8fef2655 | |||
| 21af2f2f62 | |||
| 6d01296e8b | |||
| 749efcde21 | |||
| 3af2420da9 | |||
| d0d24f4554 | |||
| 96b44c1771 | |||
| 4a1d07b364 | |||
| 7fbe5349a2 | |||
| d8bad72fd6 | |||
| 927b32c99f | |||
| 27b691bef6 | |||
| d2b9de740d | |||
| eb45b4b1bd | |||
| c545b5e65f | |||
| 992b6486a1 | |||
| 421b1f889b | |||
| 1ee1368e18 | |||
| 1a6a8789c1 | |||
| b5c155db1b | |||
| 6c5b46eadd | |||
| 5abbab2635 | |||
| e5766094f4 |
+1
-1
@@ -6,7 +6,7 @@
|
||||
/captures/
|
||||
/local.properties
|
||||
.DS_Store
|
||||
.externalNativeBuild/
|
||||
.cxx/
|
||||
Thumbs.db
|
||||
build/
|
||||
*.apk
|
||||
|
||||
Generated
-38
@@ -29,19 +29,10 @@
|
||||
<emptyLine />
|
||||
</value>
|
||||
</option>
|
||||
<option name="RIGHT_MARGIN" value="100" />
|
||||
<AndroidXmlCodeStyleSettings>
|
||||
<option name="USE_CUSTOM_SETTINGS" value="true" />
|
||||
</AndroidXmlCodeStyleSettings>
|
||||
<JavaCodeStyleSettings>
|
||||
<option name="GENERATE_FINAL_LOCALS" value="true" />
|
||||
<option name="GENERATE_FINAL_PARAMETERS" value="true" />
|
||||
<option name="INSERT_INNER_CLASS_IMPORTS" value="true" />
|
||||
<option name="CLASS_COUNT_TO_USE_IMPORT_ON_DEMAND" value="99" />
|
||||
<option name="NAMES_COUNT_TO_USE_IMPORT_ON_DEMAND" value="99" />
|
||||
<option name="PACKAGES_TO_USE_IMPORT_ON_DEMAND">
|
||||
<value />
|
||||
</option>
|
||||
<option name="IMPORT_LAYOUT_TABLE">
|
||||
<value>
|
||||
<package name="android" withSubpackages="true" static="false" />
|
||||
@@ -65,35 +56,7 @@
|
||||
</value>
|
||||
</option>
|
||||
</JavaCodeStyleSettings>
|
||||
<Objective-C-extensions>
|
||||
<file>
|
||||
<option name="com.jetbrains.cidr.lang.util.OCDeclarationKind" value="Import" />
|
||||
<option name="com.jetbrains.cidr.lang.util.OCDeclarationKind" value="Macro" />
|
||||
<option name="com.jetbrains.cidr.lang.util.OCDeclarationKind" value="Typedef" />
|
||||
<option name="com.jetbrains.cidr.lang.util.OCDeclarationKind" value="Enum" />
|
||||
<option name="com.jetbrains.cidr.lang.util.OCDeclarationKind" value="Constant" />
|
||||
<option name="com.jetbrains.cidr.lang.util.OCDeclarationKind" value="Global" />
|
||||
<option name="com.jetbrains.cidr.lang.util.OCDeclarationKind" value="Struct" />
|
||||
<option name="com.jetbrains.cidr.lang.util.OCDeclarationKind" value="FunctionPredecl" />
|
||||
<option name="com.jetbrains.cidr.lang.util.OCDeclarationKind" value="Function" />
|
||||
</file>
|
||||
<class>
|
||||
<option name="com.jetbrains.cidr.lang.util.OCDeclarationKind" value="Property" />
|
||||
<option name="com.jetbrains.cidr.lang.util.OCDeclarationKind" value="Synthesize" />
|
||||
<option name="com.jetbrains.cidr.lang.util.OCDeclarationKind" value="InitMethod" />
|
||||
<option name="com.jetbrains.cidr.lang.util.OCDeclarationKind" value="StaticMethod" />
|
||||
<option name="com.jetbrains.cidr.lang.util.OCDeclarationKind" value="InstanceMethod" />
|
||||
<option name="com.jetbrains.cidr.lang.util.OCDeclarationKind" value="DeallocMethod" />
|
||||
</class>
|
||||
<extensions>
|
||||
<pair source="cpp" header="h" fileNamingConvention="NONE" />
|
||||
<pair source="c" header="h" fileNamingConvention="NONE" />
|
||||
</extensions>
|
||||
</Objective-C-extensions>
|
||||
<XML>
|
||||
<option name="XML_KEEP_LINE_BREAKS" value="false" />
|
||||
<option name="XML_ALIGN_ATTRIBUTES" value="false" />
|
||||
<option name="XML_SPACE_INSIDE_EMPTY_TAG" value="true" />
|
||||
<option name="XML_LEGACY_SETTINGS_IMPORTED" value="true" />
|
||||
</XML>
|
||||
<codeStyleSettings language="JAVA">
|
||||
@@ -385,7 +348,6 @@
|
||||
</arrangement>
|
||||
</codeStyleSettings>
|
||||
<codeStyleSettings language="XML">
|
||||
<option name="FORCE_REARRANGE_MODE" value="1" />
|
||||
<indentOptions>
|
||||
<option name="CONTINUATION_INDENT_SIZE" value="4" />
|
||||
</indentOptions>
|
||||
|
||||
@@ -7,9 +7,7 @@ This is an Android GUI for [WireGuard](https://www.wireguard.com/). It [opportun
|
||||
## Building
|
||||
|
||||
```
|
||||
$ git clone https://git.zx2c4.com/wireguard-android
|
||||
$ git clone --recurse-submodules https://git.zx2c4.com/wireguard-android
|
||||
$ cd wireguard-android
|
||||
$ git submodule init
|
||||
$ git submodule update
|
||||
$ ./gradlew assembleRelease
|
||||
```
|
||||
|
||||
+16
-11
@@ -6,19 +6,19 @@ apply from: 'nonnull.gradle'
|
||||
final def keystorePropertiesFile = rootProject.file("keystore.properties")
|
||||
|
||||
android {
|
||||
buildToolsVersion '28.0.3'
|
||||
buildToolsVersion '29.0.2'
|
||||
compileOptions {
|
||||
sourceCompatibility JavaVersion.VERSION_1_8
|
||||
targetCompatibility JavaVersion.VERSION_1_8
|
||||
}
|
||||
compileSdkVersion 28
|
||||
compileSdkVersion 29
|
||||
dataBinding.enabled true
|
||||
defaultConfig {
|
||||
applicationId 'com.wireguard.android'
|
||||
minSdkVersion 21
|
||||
targetSdkVersion 28
|
||||
versionCode 450
|
||||
versionName '0.0.20190605'
|
||||
targetSdkVersion 29
|
||||
versionCode 459
|
||||
versionName '0.0.20191120'
|
||||
buildConfigField 'int', 'MIN_SDK_VERSION', "$minSdkVersion.apiLevel"
|
||||
}
|
||||
// If the keystore file exists
|
||||
@@ -67,16 +67,20 @@ android {
|
||||
}
|
||||
|
||||
ext {
|
||||
annotationsVersion = '1.0.1'
|
||||
appcompatVersion = '1.0.2'
|
||||
annotationsVersion = '1.1.0'
|
||||
appcompatVersion = '1.1.0'
|
||||
cardviewVersion = '1.0.0'
|
||||
databindingVersion = '3.3.1'
|
||||
databindingVersion = '3.5.0'
|
||||
materialComponentsVersion = '1.0.0'
|
||||
jsr305Version = '3.0.2'
|
||||
preferenceVersion = '1.0.0'
|
||||
streamsupportVersion = '1.7.0'
|
||||
threetenabpVersion = '1.1.1'
|
||||
preferenceVersion = '1.1.0'
|
||||
streamsupportVersion = '1.7.1'
|
||||
threetenabpVersion = '1.2.1'
|
||||
// ZXING switched minSdk to 24 so we cannot upgrade to 4.0.2 without following suit.
|
||||
// If you choose to upgrade to minSDK 24 then you should also disable Jetifier from
|
||||
// gradle.properties.
|
||||
zxingEmbeddedVersion = '3.6.0'
|
||||
eddsaVersion = '0.3.0'
|
||||
}
|
||||
|
||||
dependencies {
|
||||
@@ -91,6 +95,7 @@ dependencies {
|
||||
implementation "com.journeyapps:zxing-android-embedded:$zxingEmbeddedVersion"
|
||||
implementation "net.sourceforge.streamsupport:android-retrofuture:$streamsupportVersion"
|
||||
implementation "net.sourceforge.streamsupport:android-retrostreams:$streamsupportVersion"
|
||||
implementation "net.i2p.crypto:eddsa:$eddsaVersion"
|
||||
}
|
||||
|
||||
tasks.withType(JavaCompile) {
|
||||
|
||||
Vendored
-2
@@ -1,5 +1,3 @@
|
||||
# Squelch all warnings, they're harmless but ProGuard
|
||||
# escalates them as errors.
|
||||
-dontwarn sun.misc.Unsafe
|
||||
# We're OSS anyway and who doesn't love a readable log
|
||||
-dontobfuscate
|
||||
|
||||
@@ -19,7 +19,6 @@
|
||||
<application
|
||||
android:name=".Application"
|
||||
android:allowBackup="false"
|
||||
android:extractNativeLibs="true"
|
||||
android:icon="@mipmap/ic_launcher"
|
||||
android:label="@string/app_name"
|
||||
android:roundIcon="@mipmap/ic_launcher_round"
|
||||
|
||||
@@ -12,7 +12,9 @@ import android.os.AsyncTask;
|
||||
import android.os.Build;
|
||||
import android.os.Handler;
|
||||
import android.os.Looper;
|
||||
import android.preference.PreferenceManager;
|
||||
import android.util.Log;
|
||||
|
||||
import androidx.preference.PreferenceManager;
|
||||
import androidx.annotation.Nullable;
|
||||
import androidx.appcompat.app.AppCompatDelegate;
|
||||
|
||||
@@ -22,15 +24,26 @@ import com.wireguard.android.backend.WgQuickBackend;
|
||||
import com.wireguard.android.configStore.FileConfigStore;
|
||||
import com.wireguard.android.model.TunnelManager;
|
||||
import com.wireguard.android.util.AsyncWorker;
|
||||
import com.wireguard.android.util.ModuleLoader;
|
||||
import com.wireguard.android.util.RootShell;
|
||||
import com.wireguard.android.util.ToolsInstaller;
|
||||
|
||||
import java.io.File;
|
||||
import java.lang.ref.WeakReference;
|
||||
import java.util.Locale;
|
||||
|
||||
import java9.util.concurrent.CompletableFuture;
|
||||
|
||||
public class Application extends android.app.Application {
|
||||
private static final String TAG = "WireGuard/" + Application.class.getSimpleName();
|
||||
public static final String USER_AGENT;
|
||||
|
||||
static {
|
||||
String preferredAbi = "unknown ABI";
|
||||
if (Build.SUPPORTED_ABIS.length > 0)
|
||||
preferredAbi = Build.SUPPORTED_ABIS[0];
|
||||
USER_AGENT = String.format(Locale.ENGLISH, "WireGuard/%s (Android %d; %s; %s; %s %s; %s)", BuildConfig.VERSION_NAME, Build.VERSION.SDK_INT, preferredAbi, Build.BOARD, Build.MANUFACTURER, Build.MODEL, Build.FINGERPRINT);
|
||||
}
|
||||
|
||||
@SuppressWarnings("NullableProblems") private static WeakReference<Application> weakSelf;
|
||||
private final CompletableFuture<Backend> futureBackend = new CompletableFuture<>();
|
||||
@SuppressWarnings("NullableProblems") private AsyncWorker asyncWorker;
|
||||
@@ -38,6 +51,7 @@ public class Application extends android.app.Application {
|
||||
@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() {
|
||||
@@ -57,9 +71,19 @@ public class Application extends android.app.Application {
|
||||
synchronized (app.futureBackend) {
|
||||
if (app.backend == null) {
|
||||
Backend backend = null;
|
||||
if (new File("/sys/module/wireguard").exists()) {
|
||||
boolean didStartRootShell = false;
|
||||
if (!app.moduleLoader.isModuleLoaded() && app.moduleLoader.moduleMightExist()) {
|
||||
try {
|
||||
app.rootShell.start();
|
||||
didStartRootShell = true;
|
||||
app.moduleLoader.loadModule();
|
||||
} catch (final Exception ignored) {
|
||||
}
|
||||
}
|
||||
if (app.moduleLoader.isModuleLoaded()) {
|
||||
try {
|
||||
if (!didStartRootShell)
|
||||
app.rootShell.start();
|
||||
backend = new WgQuickBackend(app.getApplicationContext());
|
||||
} catch (final Exception ignored) {
|
||||
}
|
||||
@@ -87,6 +111,9 @@ public class Application extends android.app.Application {
|
||||
public static ToolsInstaller getToolsInstaller() {
|
||||
return get().toolsInstaller;
|
||||
}
|
||||
public static ModuleLoader getModuleLoader() {
|
||||
return get().moduleLoader;
|
||||
}
|
||||
|
||||
public static TunnelManager getTunnelManager() {
|
||||
return get().tunnelManager;
|
||||
@@ -108,16 +135,22 @@ public class Application extends android.app.Application {
|
||||
|
||||
@Override
|
||||
public void onCreate() {
|
||||
Log.i(TAG, USER_AGENT);
|
||||
super.onCreate();
|
||||
|
||||
asyncWorker = new AsyncWorker(AsyncTask.SERIAL_EXECUTOR, new Handler(Looper.getMainLooper()));
|
||||
rootShell = new RootShell(getApplicationContext());
|
||||
toolsInstaller = new ToolsInstaller(getApplicationContext());
|
||||
moduleLoader = new ModuleLoader(getApplicationContext());
|
||||
|
||||
sharedPreferences = PreferenceManager.getDefaultSharedPreferences(getApplicationContext());
|
||||
AppCompatDelegate.setDefaultNightMode(
|
||||
sharedPreferences.getBoolean("dark_theme", false) ?
|
||||
AppCompatDelegate.MODE_NIGHT_YES : AppCompatDelegate.MODE_NIGHT_NO);
|
||||
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.Q) {
|
||||
AppCompatDelegate.setDefaultNightMode(
|
||||
sharedPreferences.getBoolean("dark_theme", false) ?
|
||||
AppCompatDelegate.MODE_NIGHT_YES : AppCompatDelegate.MODE_NIGHT_NO);
|
||||
} else {
|
||||
AppCompatDelegate.setDefaultNightMode(AppCompatDelegate.MODE_NIGHT_FOLLOW_SYSTEM);
|
||||
}
|
||||
|
||||
tunnelManager = new TunnelManager(new FileConfigStore(getApplicationContext()));
|
||||
tunnelManager.onCreate();
|
||||
|
||||
@@ -6,6 +6,7 @@
|
||||
package com.wireguard.android.activity;
|
||||
|
||||
import android.content.pm.PackageManager;
|
||||
import android.os.Build;
|
||||
import android.os.Bundle;
|
||||
import androidx.annotation.Nullable;
|
||||
import androidx.core.app.ActivityCompat;
|
||||
@@ -91,13 +92,16 @@ public class SettingsActivity extends ThemeChangeAwareActivity {
|
||||
@Override
|
||||
public void onCreatePreferences(final Bundle savedInstanceState, final String key) {
|
||||
addPreferencesFromResource(R.xml.preferences);
|
||||
final PreferenceScreen screen = getPreferenceScreen();
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q)
|
||||
screen.removePreference(getPreferenceManager().findPreference("dark_theme"));
|
||||
|
||||
final Preference wgQuickOnlyPrefs[] = {
|
||||
getPreferenceManager().findPreference("tools_installer"),
|
||||
getPreferenceManager().findPreference("restore_on_boot")
|
||||
};
|
||||
for (final Preference pref : wgQuickOnlyPrefs)
|
||||
pref.setVisible(false);
|
||||
final PreferenceScreen screen = getPreferenceScreen();
|
||||
Application.getBackendAsync().thenAccept(backend -> {
|
||||
for (final Preference pref : wgQuickOnlyPrefs) {
|
||||
if (backend instanceof WgQuickBackend)
|
||||
@@ -106,6 +110,19 @@ public class SettingsActivity extends ThemeChangeAwareActivity {
|
||||
screen.removePreference(pref);
|
||||
}
|
||||
});
|
||||
|
||||
final Preference moduleInstaller = getPreferenceManager().findPreference("module_downloader");
|
||||
moduleInstaller.setVisible(false);
|
||||
if (Application.getModuleLoader().isModuleLoaded()) {
|
||||
screen.removePreference(moduleInstaller);
|
||||
} else {
|
||||
Application.getAsyncWorker().runAsync(Application.getRootShell()::start).whenComplete((v, e) -> {
|
||||
if (e == null)
|
||||
moduleInstaller.setVisible(true);
|
||||
else
|
||||
screen.removePreference(moduleInstaller);
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -7,6 +7,7 @@ package com.wireguard.android.activity;
|
||||
|
||||
import android.content.SharedPreferences;
|
||||
import android.content.res.Resources;
|
||||
import android.os.Build;
|
||||
import android.os.Bundle;
|
||||
import androidx.annotation.Nullable;
|
||||
import androidx.appcompat.app.AppCompatActivity;
|
||||
@@ -55,12 +56,14 @@ public abstract class ThemeChangeAwareActivity extends AppCompatActivity impleme
|
||||
@Override
|
||||
protected void onCreate(@Nullable final Bundle savedInstanceState) {
|
||||
super.onCreate(savedInstanceState);
|
||||
Application.getSharedPreferences().registerOnSharedPreferenceChangeListener(this);
|
||||
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.Q)
|
||||
Application.getSharedPreferences().registerOnSharedPreferenceChangeListener(this);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onDestroy() {
|
||||
Application.getSharedPreferences().unregisterOnSharedPreferenceChangeListener(this);
|
||||
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.Q)
|
||||
Application.getSharedPreferences().unregisterOnSharedPreferenceChangeListener(this);
|
||||
super.onDestroy();
|
||||
}
|
||||
|
||||
|
||||
@@ -24,6 +24,8 @@ import com.wireguard.android.util.SharedLibraryLoader;
|
||||
import com.wireguard.config.Config;
|
||||
import com.wireguard.config.InetNetwork;
|
||||
import com.wireguard.config.Peer;
|
||||
import com.wireguard.crypto.Key;
|
||||
import com.wireguard.crypto.KeyFormatException;
|
||||
|
||||
import java.net.InetAddress;
|
||||
import java.util.Collections;
|
||||
@@ -47,6 +49,8 @@ public final class GoBackend implements Backend {
|
||||
this.context = context;
|
||||
}
|
||||
|
||||
private static native String wgGetConfig(int handle);
|
||||
|
||||
private static native int wgGetSocketV4(int handle);
|
||||
|
||||
private static native int wgGetSocketV6(int handle);
|
||||
@@ -90,7 +94,45 @@ public final class GoBackend implements Backend {
|
||||
|
||||
@Override
|
||||
public Statistics getStatistics(final Tunnel tunnel) {
|
||||
return new Statistics();
|
||||
final Statistics stats = new Statistics();
|
||||
if (tunnel != currentTunnel) {
|
||||
return stats;
|
||||
}
|
||||
final String config = wgGetConfig(currentTunnelHandle);
|
||||
Key key = null;
|
||||
long rx = 0, tx = 0;
|
||||
for (final String line : config.split("\\n")) {
|
||||
if (line.startsWith("public_key=")) {
|
||||
if (key != null)
|
||||
stats.add(key, rx, tx);
|
||||
rx = 0;
|
||||
tx = 0;
|
||||
try {
|
||||
key = Key.fromHex(line.substring(11));
|
||||
} catch (final KeyFormatException ignored) {
|
||||
key = null;
|
||||
}
|
||||
} else if (line.startsWith("rx_bytes=")) {
|
||||
if (key == null)
|
||||
continue;
|
||||
try {
|
||||
rx = Long.parseLong(line.substring(9));
|
||||
} catch (final NumberFormatException ignored) {
|
||||
rx = 0;
|
||||
}
|
||||
} else if (line.startsWith("tx_bytes=")) {
|
||||
if (key == null)
|
||||
continue;
|
||||
try {
|
||||
tx = Long.parseLong(line.substring(9));
|
||||
} catch (final NumberFormatException ignored) {
|
||||
tx = 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (key != null)
|
||||
stats.add(key, rx, tx);
|
||||
return stats;
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
@@ -15,11 +15,13 @@ import com.wireguard.android.model.Tunnel;
|
||||
import com.wireguard.android.model.Tunnel.State;
|
||||
import com.wireguard.android.model.Tunnel.Statistics;
|
||||
import com.wireguard.config.Config;
|
||||
import com.wireguard.crypto.Key;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.FileOutputStream;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.Locale;
|
||||
@@ -83,7 +85,24 @@ public final class WgQuickBackend implements Backend {
|
||||
|
||||
@Override
|
||||
public Statistics getStatistics(final Tunnel tunnel) {
|
||||
return new Statistics();
|
||||
final Statistics stats = new Statistics();
|
||||
final Collection<String> output = new ArrayList<>();
|
||||
try {
|
||||
if (Application.getRootShell().run(output, String.format("wg show '%s' transfer", tunnel.getName())) != 0)
|
||||
return stats;
|
||||
} catch (final Exception ignored) {
|
||||
return stats;
|
||||
}
|
||||
for (final String line : output) {
|
||||
final String[] parts = line.split("\\t");
|
||||
if (parts.length != 3)
|
||||
continue;
|
||||
try {
|
||||
stats.add(Key.fromBase64(parts[0]), Long.parseLong(parts[1]), Long.parseLong(parts[2]));
|
||||
} catch (final Exception ignored) {
|
||||
}
|
||||
}
|
||||
return stats;
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
@@ -7,6 +7,8 @@ package com.wireguard.android.fragment;
|
||||
|
||||
import android.os.Bundle;
|
||||
import androidx.annotation.Nullable;
|
||||
import androidx.databinding.DataBindingUtil;
|
||||
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.Menu;
|
||||
import android.view.MenuInflater;
|
||||
@@ -15,7 +17,13 @@ import android.view.ViewGroup;
|
||||
|
||||
import com.wireguard.android.R;
|
||||
import com.wireguard.android.databinding.TunnelDetailFragmentBinding;
|
||||
import com.wireguard.android.databinding.TunnelDetailPeerBinding;
|
||||
import com.wireguard.android.model.Tunnel;
|
||||
import com.wireguard.android.model.Tunnel.State;
|
||||
import com.wireguard.crypto.Key;
|
||||
|
||||
import java.util.Timer;
|
||||
import java.util.TimerTask;
|
||||
|
||||
/**
|
||||
* Fragment that shows details about a specific tunnel.
|
||||
@@ -23,6 +31,20 @@ import com.wireguard.android.model.Tunnel;
|
||||
|
||||
public class TunnelDetailFragment extends BaseFragment {
|
||||
@Nullable private TunnelDetailFragmentBinding binding;
|
||||
@Nullable private Timer timer;
|
||||
@Nullable private State lastState = State.TOGGLE;
|
||||
|
||||
private static class StatsTimerTask extends TimerTask {
|
||||
final TunnelDetailFragment tdf;
|
||||
private StatsTimerTask(final TunnelDetailFragment tdf) {
|
||||
this.tdf = tdf;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void run() {
|
||||
tdf.updateStats();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onCreate(@Nullable final Bundle savedInstanceState) {
|
||||
@@ -35,6 +57,22 @@ public class TunnelDetailFragment extends BaseFragment {
|
||||
inflater.inflate(R.menu.tunnel_detail, menu);
|
||||
}
|
||||
|
||||
@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 StatsTimerTask(this), 0, 1000);
|
||||
}
|
||||
|
||||
@Override
|
||||
public View onCreateView(final LayoutInflater inflater, @Nullable final ViewGroup container,
|
||||
@Nullable final Bundle savedInstanceState) {
|
||||
@@ -59,6 +97,8 @@ public class TunnelDetailFragment extends BaseFragment {
|
||||
binding.setConfig(null);
|
||||
else
|
||||
newTunnel.getConfigAsync().thenAccept(binding::setConfig);
|
||||
lastState = State.TOGGLE;
|
||||
updateStats();
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -72,4 +112,52 @@ public class TunnelDetailFragment extends BaseFragment {
|
||||
super.onViewStateRestored(savedInstanceState);
|
||||
}
|
||||
|
||||
private String formatBytes(final long bytes) {
|
||||
if (bytes < 1024)
|
||||
return getContext().getString(R.string.transfer_bytes, bytes);
|
||||
else if (bytes < 1024*1024)
|
||||
return getContext().getString(R.string.transfer_kibibytes, bytes/1024.0);
|
||||
else if (bytes < 1024*1024*1024)
|
||||
return getContext().getString(R.string.transfer_mibibytes, bytes/(1024.0*1024.0));
|
||||
else if (bytes < 1024*1024*1024*1024)
|
||||
return getContext().getString(R.string.transfer_gibibytes, bytes/(1024.0*1024.0*1024.0));
|
||||
return getContext().getString(R.string.transfer_tibibytes, bytes/(1024.0*1024.0*1024.0)/1024.0);
|
||||
}
|
||||
|
||||
private void updateStats() {
|
||||
if (binding == null || !isResumed())
|
||||
return;
|
||||
final State state = binding.getTunnel().getState();
|
||||
if (state != State.UP && lastState == state)
|
||||
return;
|
||||
lastState = state;
|
||||
binding.getTunnel().getStatisticsAsync().whenComplete((statistics, throwable) -> {
|
||||
if (throwable != null) {
|
||||
for (int i = 0; i < binding.peersLayout.getChildCount(); ++i) {
|
||||
final TunnelDetailPeerBinding peer = DataBindingUtil.getBinding(binding.peersLayout.getChildAt(i));
|
||||
if (peer == null)
|
||||
continue;
|
||||
peer.transferLabel.setVisibility(View.GONE);
|
||||
peer.transferText.setVisibility(View.GONE);
|
||||
}
|
||||
return;
|
||||
}
|
||||
for (int i = 0; i < binding.peersLayout.getChildCount(); ++i) {
|
||||
final TunnelDetailPeerBinding peer = DataBindingUtil.getBinding(binding.peersLayout.getChildAt(i));
|
||||
if (peer == null)
|
||||
continue;
|
||||
final Key publicKey = peer.getItem().getPublicKey();
|
||||
final long rx = statistics.peerRx(publicKey);
|
||||
final long tx = statistics.peerTx(publicKey);
|
||||
if (rx == 0 && tx == 0) {
|
||||
peer.transferLabel.setVisibility(View.GONE);
|
||||
peer.transferText.setVisibility(View.GONE);
|
||||
continue;
|
||||
}
|
||||
peer.transferText.setText(getContext().getString(R.string.transfer_rx_tx, formatBytes(rx), formatBytes(tx)));
|
||||
peer.transferLabel.setVisibility(View.VISIBLE);
|
||||
peer.transferText.setVisibility(View.VISIBLE);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@@ -5,6 +5,9 @@
|
||||
|
||||
package com.wireguard.android.model;
|
||||
|
||||
import android.os.SystemClock;
|
||||
import android.util.Pair;
|
||||
|
||||
import androidx.databinding.BaseObservable;
|
||||
import androidx.databinding.Bindable;
|
||||
import androidx.annotation.Nullable;
|
||||
@@ -12,8 +15,11 @@ import androidx.annotation.Nullable;
|
||||
import com.wireguard.android.BR;
|
||||
import com.wireguard.android.util.ExceptionLoggers;
|
||||
import com.wireguard.config.Config;
|
||||
import com.wireguard.crypto.Key;
|
||||
import com.wireguard.util.Keyed;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
import java9.util.concurrent.CompletableFuture;
|
||||
@@ -85,15 +91,13 @@ public class Tunnel extends BaseObservable implements Keyed<String> {
|
||||
@Bindable
|
||||
@Nullable
|
||||
public Statistics getStatistics() {
|
||||
// FIXME: Check age of statistics.
|
||||
if (statistics == null)
|
||||
if (statistics == null || statistics.isStale())
|
||||
TunnelManager.getTunnelStatistics(this).whenComplete(ExceptionLoggers.E);
|
||||
return statistics;
|
||||
}
|
||||
|
||||
public CompletionStage<Statistics> getStatisticsAsync() {
|
||||
// FIXME: Check age of statistics.
|
||||
if (statistics == null)
|
||||
if (statistics == null || statistics.isStale())
|
||||
return TunnelManager.getTunnelStatistics(this);
|
||||
return CompletableFuture.completedFuture(statistics);
|
||||
}
|
||||
@@ -154,5 +158,48 @@ public class Tunnel extends BaseObservable implements Keyed<String> {
|
||||
}
|
||||
|
||||
public static class Statistics extends BaseObservable {
|
||||
private long lastTouched = SystemClock.elapsedRealtime();
|
||||
private final Map<Key, Pair<Long, Long>> peerBytes = new HashMap<>();
|
||||
|
||||
public void add(final Key key, final long rx, final long tx) {
|
||||
peerBytes.put(key, Pair.create(rx, tx));
|
||||
lastTouched = SystemClock.elapsedRealtime();
|
||||
}
|
||||
|
||||
private boolean isStale() {
|
||||
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;
|
||||
return peerBytes.get(peer).first;
|
||||
}
|
||||
|
||||
public long peerTx(final Key peer) {
|
||||
if (!peerBytes.containsKey(peer))
|
||||
return 0;
|
||||
return peerBytes.get(peer).second;
|
||||
}
|
||||
|
||||
public long totalRx() {
|
||||
long rx = 0;
|
||||
for (final Pair<Long, Long> val : peerBytes.values()) {
|
||||
rx += val.first;
|
||||
}
|
||||
return rx;
|
||||
}
|
||||
|
||||
public long totalTx() {
|
||||
long tx = 0;
|
||||
for (final Pair<Long, Long> val : peerBytes.values()) {
|
||||
tx += val.second;
|
||||
}
|
||||
return tx;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -8,22 +8,21 @@ package com.wireguard.android.preference;
|
||||
import android.Manifest;
|
||||
import android.content.Context;
|
||||
import android.content.pm.PackageManager;
|
||||
import android.os.Environment;
|
||||
import androidx.annotation.Nullable;
|
||||
import com.google.android.material.snackbar.Snackbar;
|
||||
import androidx.preference.Preference;
|
||||
|
||||
import android.util.AttributeSet;
|
||||
import android.util.Log;
|
||||
|
||||
import com.wireguard.android.Application;
|
||||
import com.wireguard.android.R;
|
||||
import com.wireguard.android.util.DownloadsFileSaver;
|
||||
import com.wireguard.android.util.DownloadsFileSaver.DownloadsFile;
|
||||
import com.wireguard.android.util.ErrorMessages;
|
||||
import com.wireguard.android.util.FragmentUtils;
|
||||
|
||||
import java.io.BufferedReader;
|
||||
import java.io.File;
|
||||
import java.io.FileOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStreamReader;
|
||||
|
||||
/**
|
||||
@@ -41,36 +40,33 @@ public class LogExporterPreference extends Preference {
|
||||
|
||||
private void exportLog() {
|
||||
Application.getAsyncWorker().supplyAsync(() -> {
|
||||
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(
|
||||
getContext().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.
|
||||
*/
|
||||
new FileOutputStream(file).close();
|
||||
|
||||
DownloadsFile outputFile = DownloadsFileSaver.save(getContext(), "wireguard-log.txt", "text/plain", true);
|
||||
try {
|
||||
final Process process = Runtime.getRuntime().exec(new String[]{
|
||||
"logcat", "-b", "all", "-d", "-v", "threadtime", "-f", file.getAbsolutePath(), "*:V"});
|
||||
if (process.waitFor() != 0) {
|
||||
try (final BufferedReader reader = new BufferedReader(new InputStreamReader(process.getErrorStream()))) {
|
||||
"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())))
|
||||
{
|
||||
String line;
|
||||
while ((line = stdout.readLine()) != null) {
|
||||
outputFile.getOutputStream().write(line.getBytes());
|
||||
outputFile.getOutputStream().write('\n');
|
||||
}
|
||||
outputFile.getOutputStream().close();
|
||||
stdout.close();
|
||||
if (process.waitFor() != 0) {
|
||||
final StringBuilder errors = new StringBuilder();
|
||||
errors.append("Unable to run logcat: ");
|
||||
String line;
|
||||
while ((line = reader.readLine()) != null)
|
||||
errors.append(R.string.logcat_error);
|
||||
while ((line = stderr.readLine()) != null)
|
||||
errors.append(line);
|
||||
throw new Exception(errors.toString());
|
||||
}
|
||||
}
|
||||
} catch (final Exception e) {
|
||||
// noinspection ResultOfMethodCallIgnored
|
||||
file.delete();
|
||||
outputFile.delete();
|
||||
throw e;
|
||||
}
|
||||
return file.getAbsolutePath();
|
||||
return outputFile.getFileName();
|
||||
}).whenComplete(this::exportLogComplete);
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,91 @@
|
||||
/*
|
||||
* Copyright © 2019 WireGuard LLC. All Rights Reserved.
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
package com.wireguard.android.preference;
|
||||
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.system.OsConstants;
|
||||
import android.util.AttributeSet;
|
||||
import android.widget.Toast;
|
||||
|
||||
import com.wireguard.android.Application;
|
||||
import com.wireguard.android.R;
|
||||
import com.wireguard.android.util.ModuleLoader;
|
||||
import com.wireguard.android.util.ToolsInstaller;
|
||||
|
||||
import androidx.annotation.Nullable;
|
||||
import androidx.preference.Preference;
|
||||
|
||||
public class ModuleDownloaderPreference extends Preference {
|
||||
private State state = State.INITIAL;
|
||||
|
||||
public ModuleDownloaderPreference(final Context context, final AttributeSet attrs) {
|
||||
super(context, attrs);
|
||||
}
|
||||
|
||||
@Override
|
||||
public CharSequence getSummary() {
|
||||
return getContext().getString(state.messageResourceId);
|
||||
}
|
||||
|
||||
@Override
|
||||
public CharSequence getTitle() {
|
||||
return getContext().getString(R.string.module_installer_title);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onClick() {
|
||||
setState(State.WORKING);
|
||||
Application.getAsyncWorker().supplyAsync(Application.getModuleLoader()::download).whenComplete(this::onDownloadResult);
|
||||
}
|
||||
|
||||
private void onDownloadResult(final Integer result, @Nullable final Throwable throwable) {
|
||||
if (throwable != null) {
|
||||
setState(State.FAILURE);
|
||||
Toast.makeText(getContext(), throwable.getMessage(), Toast.LENGTH_LONG).show();
|
||||
} else if (result == OsConstants.ENOENT)
|
||||
setState(State.NOTFOUND);
|
||||
else if (result == OsConstants.EXIT_SUCCESS) {
|
||||
setState(State.SUCCESS);
|
||||
Application.getAsyncWorker().runAsync(() -> {
|
||||
Thread.sleep(1000 * 5);
|
||||
Intent i = getContext().getPackageManager().getLaunchIntentForPackage(getContext().getPackageName());
|
||||
if (i == null)
|
||||
return;
|
||||
i.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP);
|
||||
i.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
|
||||
Application.get().startActivity(i);
|
||||
System.exit(0);
|
||||
});
|
||||
} else
|
||||
setState(State.FAILURE);
|
||||
}
|
||||
|
||||
private void setState(final State state) {
|
||||
if (this.state == state)
|
||||
return;
|
||||
this.state = state;
|
||||
if (isEnabled() != state.shouldEnableView)
|
||||
setEnabled(state.shouldEnableView);
|
||||
notifyChanged();
|
||||
}
|
||||
|
||||
private enum State {
|
||||
INITIAL(R.string.module_installer_initial, true),
|
||||
FAILURE(R.string.module_installer_error, true),
|
||||
WORKING(R.string.module_installer_working, false),
|
||||
SUCCESS(R.string.module_installer_success, false),
|
||||
NOTFOUND(R.string.module_installer_not_found, false);
|
||||
|
||||
private final int messageResourceId;
|
||||
private final boolean shouldEnableView;
|
||||
|
||||
State(final int messageResourceId, final boolean shouldEnableView) {
|
||||
this.messageResourceId = messageResourceId;
|
||||
this.shouldEnableView = shouldEnableView;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -8,7 +8,6 @@ package com.wireguard.android.preference;
|
||||
import android.Manifest;
|
||||
import android.content.Context;
|
||||
import android.content.pm.PackageManager;
|
||||
import android.os.Environment;
|
||||
import androidx.annotation.Nullable;
|
||||
import com.google.android.material.snackbar.Snackbar;
|
||||
import androidx.preference.Preference;
|
||||
@@ -18,13 +17,12 @@ import android.util.Log;
|
||||
import com.wireguard.android.Application;
|
||||
import com.wireguard.android.R;
|
||||
import com.wireguard.android.model.Tunnel;
|
||||
import com.wireguard.android.util.DownloadsFileSaver;
|
||||
import com.wireguard.android.util.DownloadsFileSaver.DownloadsFile;
|
||||
import com.wireguard.android.util.ErrorMessages;
|
||||
import com.wireguard.android.util.FragmentUtils;
|
||||
import com.wireguard.config.Config;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.FileOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
@@ -63,12 +61,8 @@ public class ZipExporterPreference extends Preference {
|
||||
.whenComplete((ignored1, exception) -> Application.getAsyncWorker().supplyAsync(() -> {
|
||||
if (exception != null)
|
||||
throw exception;
|
||||
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(
|
||||
getContext().getString(R.string.create_output_dir_error));
|
||||
try (ZipOutputStream zip = new ZipOutputStream(new FileOutputStream(file))) {
|
||||
DownloadsFile outputFile = DownloadsFileSaver.save(getContext(), "wireguard-export.zip", "application/zip", true);
|
||||
try (ZipOutputStream zip = new ZipOutputStream(outputFile.getOutputStream())) {
|
||||
for (int i = 0; i < futureConfigs.size(); ++i) {
|
||||
zip.putNextEntry(new ZipEntry(tunnels.get(i).getName() + ".conf"));
|
||||
zip.write(futureConfigs.get(i).getNow(null).
|
||||
@@ -76,11 +70,10 @@ public class ZipExporterPreference extends Preference {
|
||||
}
|
||||
zip.closeEntry();
|
||||
} catch (final Exception e) {
|
||||
// noinspection ResultOfMethodCallIgnored
|
||||
file.delete();
|
||||
outputFile.delete();
|
||||
throw e;
|
||||
}
|
||||
return file.getAbsolutePath();
|
||||
return outputFile.getFileName();
|
||||
}).whenComplete(this::exportZipComplete));
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,98 @@
|
||||
/*
|
||||
* Copyright © 2019 WireGuard LLC. All Rights Reserved.
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
package com.wireguard.android.util;
|
||||
|
||||
import android.content.ContentResolver;
|
||||
import android.content.ContentValues;
|
||||
import android.content.Context;
|
||||
import android.database.Cursor;
|
||||
import android.net.Uri;
|
||||
import android.os.Build;
|
||||
import android.os.Environment;
|
||||
import android.provider.MediaStore;
|
||||
import android.provider.MediaStore.MediaColumns;
|
||||
|
||||
import com.wireguard.android.R;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.FileOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.OutputStream;
|
||||
|
||||
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();
|
||||
if (overwriteExisting)
|
||||
contentResolver.delete(MediaStore.Downloads.EXTERNAL_CONTENT_URI, String.format("%s = ?", MediaColumns.DISPLAY_NAME), new String[]{name});
|
||||
final ContentValues contentValues = new ContentValues();
|
||||
contentValues.put(MediaColumns.DISPLAY_NAME, name);
|
||||
contentValues.put(MediaColumns.MIME_TYPE, mimeType);
|
||||
final Uri contentUri = contentResolver.insert(MediaStore.Downloads.EXTERNAL_CONTENT_URI, contentValues);
|
||||
if (contentUri == null)
|
||||
throw new IOException(context.getString(R.string.create_downloads_file_error));
|
||||
final OutputStream contentStream = contentResolver.openOutputStream(contentUri);
|
||||
if (contentStream == null)
|
||||
throw new IOException(context.getString(R.string.create_downloads_file_error));
|
||||
@SuppressWarnings("deprecation")
|
||||
Cursor cursor = contentResolver.query(contentUri, new String[]{MediaColumns.DATA}, null, null, null);
|
||||
String path = null;
|
||||
if (cursor != null) {
|
||||
try {
|
||||
if (cursor.moveToFirst())
|
||||
path = cursor.getString(0);
|
||||
} finally {
|
||||
cursor.close();
|
||||
}
|
||||
}
|
||||
if (path == null) {
|
||||
path = "Download/";
|
||||
cursor = contentResolver.query(contentUri, new String[]{MediaColumns.DISPLAY_NAME}, null, null, null);
|
||||
if (cursor != null) {
|
||||
try {
|
||||
if (cursor.moveToFirst())
|
||||
path += cursor.getString(0);
|
||||
} finally {
|
||||
cursor.close();
|
||||
}
|
||||
}
|
||||
}
|
||||
return new DownloadsFile(context, contentStream, path, contentUri);
|
||||
} else {
|
||||
@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);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -18,8 +18,8 @@ public final class FragmentUtils {
|
||||
public static SettingsActivity getPrefActivity(final Preference preference) {
|
||||
final Context context = preference.getContext();
|
||||
if (context instanceof ContextThemeWrapper) {
|
||||
if (((ContextThemeWrapper) context).getBaseContext() instanceof SettingsActivity) {
|
||||
return ((SettingsActivity) ((ContextThemeWrapper) context).getBaseContext());
|
||||
if (context instanceof SettingsActivity) {
|
||||
return ((SettingsActivity) context);
|
||||
}
|
||||
}
|
||||
return null;
|
||||
|
||||
@@ -0,0 +1,183 @@
|
||||
/*
|
||||
* Copyright © 2019 WireGuard LLC. All Rights Reserved.
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
package com.wireguard.android.util;
|
||||
|
||||
import android.content.Context;
|
||||
import android.system.OsConstants;
|
||||
import android.util.Base64;
|
||||
|
||||
import com.wireguard.android.Application;
|
||||
import com.wireguard.android.util.RootShell.NoRootException;
|
||||
|
||||
import net.i2p.crypto.eddsa.EdDSAEngine;
|
||||
import net.i2p.crypto.eddsa.EdDSAPublicKey;
|
||||
import net.i2p.crypto.eddsa.spec.EdDSANamedCurveTable;
|
||||
import net.i2p.crypto.eddsa.spec.EdDSAParameterSpec;
|
||||
import net.i2p.crypto.eddsa.spec.EdDSAPublicKeySpec;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.FileOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.net.HttpURLConnection;
|
||||
import java.net.URL;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.security.InvalidParameterException;
|
||||
import java.security.MessageDigest;
|
||||
import java.security.NoSuchAlgorithmException;
|
||||
import java.security.Signature;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
import javax.annotation.Nullable;
|
||||
|
||||
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 File moduleDir;
|
||||
private final File tmpDir;
|
||||
|
||||
public ModuleLoader(final Context context) {
|
||||
moduleDir = new File(context.getCacheDir(), "kmod");
|
||||
tmpDir = new File(context.getCacheDir(), "tmp");
|
||||
}
|
||||
|
||||
public boolean moduleMightExist() {
|
||||
return moduleDir.exists() && moduleDir.isDirectory();
|
||||
}
|
||||
|
||||
public void loadModule() throws IOException, NoRootException {
|
||||
Application.getRootShell().run(null, String.format("insmod \"%s/wireguard-$(sha256sum /proc/version|cut -d ' ' -f 1).ko\"", moduleDir.getAbsolutePath()));
|
||||
}
|
||||
|
||||
public 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, NoRootException, NoSuchAlgorithmException {
|
||||
final List<String> output = new ArrayList<>();
|
||||
Application.getRootShell().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();
|
||||
connection.setRequestProperty("User-Agent", Application.USER_AGENT);
|
||||
connection.connect();
|
||||
if (connection.getResponseCode() != HttpURLConnection.HTTP_OK)
|
||||
throw new IOException("Hash list could not be found");
|
||||
byte[] input = new byte[1024 * 1024 * 3 /* 3MiB */];
|
||||
int len;
|
||||
try (final InputStream inputStream = connection.getInputStream()) {
|
||||
len = inputStream.read(input);
|
||||
}
|
||||
if (len <= 0)
|
||||
throw new IOException("Hash list was empty");
|
||||
final Map<String, Sha256Digest> modules = verifySignedHashes(new String(input, 0, len, StandardCharsets.UTF_8));
|
||||
if (modules == null)
|
||||
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.setRequestProperty("User-Agent", Application.USER_AGENT);
|
||||
connection.connect();
|
||||
if (connection.getResponseCode() != HttpURLConnection.HTTP_OK)
|
||||
throw new IOException("Module file could not be found, despite being on hash list");
|
||||
|
||||
tmpDir.mkdirs();
|
||||
moduleDir.mkdir();
|
||||
File tempFile = null;
|
||||
try {
|
||||
tempFile = File.createTempFile("UNVERIFIED-", null, tmpDir);
|
||||
MessageDigest digest = MessageDigest.getInstance("SHA-256");
|
||||
try (final InputStream inputStream = connection.getInputStream();
|
||||
final FileOutputStream outputStream = new FileOutputStream(tempFile)) {
|
||||
int total = 0;
|
||||
while ((len = inputStream.read(input)) > 0) {
|
||||
total += len;
|
||||
if (total > 1024 * 1024 * 15 /* 15 MiB */)
|
||||
throw new IOException("File too big");
|
||||
outputStream.write(input, 0, len);
|
||||
digest.update(input, 0, len);
|
||||
}
|
||||
outputStream.getFD().sync();
|
||||
}
|
||||
if (!Arrays.equals(digest.digest(), modules.get(moduleName).bytes))
|
||||
throw new IOException("Incorrect file hash");
|
||||
|
||||
if (!tempFile.renameTo(new File(moduleDir, moduleName)))
|
||||
throw new IOException("Unable to rename to final destination");
|
||||
} finally {
|
||||
if (tempFile != null)
|
||||
tempFile.delete();
|
||||
}
|
||||
return OsConstants.EXIT_SUCCESS;
|
||||
}
|
||||
}
|
||||
@@ -43,9 +43,8 @@ public class RootShell {
|
||||
|
||||
public RootShell(final Context context) {
|
||||
deviceNotRootedMessage = context.getString(R.string.error_root);
|
||||
final File cacheDir = context.getCacheDir();
|
||||
localBinaryDir = new File(cacheDir, "bin");
|
||||
localTemporaryDir = new File(cacheDir, "tmp");
|
||||
localBinaryDir = new File(context.getCodeCacheDir(), "bin");
|
||||
localTemporaryDir = new File(context.getCacheDir(), "tmp");
|
||||
preamble = String.format("export CALLING_PACKAGE=%s PATH=\"%s:$PATH\" TMPDIR='%s'; id -u\n",
|
||||
BuildConfig.APPLICATION_ID, localBinaryDir, localTemporaryDir);
|
||||
this.context = context;
|
||||
|
||||
@@ -13,6 +13,9 @@ import java.io.File;
|
||||
import java.io.FileOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collection;
|
||||
import java.util.HashSet;
|
||||
import java.util.zip.ZipEntry;
|
||||
import java.util.zip.ZipFile;
|
||||
|
||||
@@ -22,6 +25,43 @@ public final class SharedLibraryLoader {
|
||||
private SharedLibraryLoader() {
|
||||
}
|
||||
|
||||
public static boolean extractLibrary(final Context context, final String libName, final File destination) throws IOException {
|
||||
final Collection<String> apks = new HashSet<>();
|
||||
if (context.getApplicationInfo().sourceDir != null)
|
||||
apks.add(context.getApplicationInfo().sourceDir);
|
||||
if (context.getApplicationInfo().splitSourceDirs != null)
|
||||
apks.addAll(Arrays.asList(context.getApplicationInfo().splitSourceDirs));
|
||||
|
||||
for (final String abi : Build.SUPPORTED_ABIS) {
|
||||
for (final String apk : apks) {
|
||||
final ZipFile zipFile;
|
||||
try {
|
||||
zipFile = new ZipFile(new File(apk), ZipFile.OPEN_READ);
|
||||
} catch (final IOException e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
|
||||
final String mappedLibName = System.mapLibraryName(libName);
|
||||
final byte[] buffer = new byte[1024 * 32];
|
||||
final String libZipPath = "lib" + File.separatorChar + abi + File.separatorChar + mappedLibName;
|
||||
final ZipEntry zipEntry = zipFile.getEntry(libZipPath);
|
||||
if (zipEntry == null)
|
||||
continue;
|
||||
Log.d(TAG, "Extracting apk:/" + libZipPath + " to " + destination.getAbsolutePath());
|
||||
try (final FileOutputStream out = new FileOutputStream(destination);
|
||||
final InputStream in = zipFile.getInputStream(zipEntry)) {
|
||||
int len;
|
||||
while ((len = in.read(buffer)) != -1) {
|
||||
out.write(buffer, 0, len);
|
||||
}
|
||||
out.getFD().sync();
|
||||
}
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
public static void loadSharedLibrary(final Context context, final String libName) {
|
||||
Throwable noAbiException;
|
||||
try {
|
||||
@@ -31,42 +71,20 @@ public final class SharedLibraryLoader {
|
||||
Log.d(TAG, "Failed to load library normally, so attempting to extract from apk", e);
|
||||
noAbiException = e;
|
||||
}
|
||||
|
||||
final ZipFile zipFile;
|
||||
File f = null;
|
||||
try {
|
||||
zipFile = new ZipFile(new File(context.getApplicationInfo().sourceDir), ZipFile.OPEN_READ);
|
||||
} catch (final IOException e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
|
||||
final String mappedLibName = System.mapLibraryName(libName);
|
||||
final byte[] buffer = new byte[1024 * 32];
|
||||
for (final String abi : Build.SUPPORTED_ABIS) {
|
||||
final String libZipPath = "lib" + File.separatorChar + abi + File.separatorChar + mappedLibName;
|
||||
final ZipEntry zipEntry = zipFile.getEntry(libZipPath);
|
||||
if (zipEntry == null)
|
||||
continue;
|
||||
File f = null;
|
||||
try {
|
||||
f = File.createTempFile("lib", ".so", context.getCacheDir());
|
||||
Log.d(TAG, "Extracting apk:/" + libZipPath + " to " + f.getAbsolutePath() + " and loading");
|
||||
try (final FileOutputStream out = new FileOutputStream(f);
|
||||
final InputStream in = zipFile.getInputStream(zipEntry)) {
|
||||
int len;
|
||||
while ((len = in.read(buffer)) != -1) {
|
||||
out.write(buffer, 0, len);
|
||||
}
|
||||
}
|
||||
f = File.createTempFile("lib", ".so", context.getCodeCacheDir());
|
||||
if (extractLibrary(context, libName, f)) {
|
||||
System.load(f.getAbsolutePath());
|
||||
return;
|
||||
} catch (final Exception e) {
|
||||
Log.d(TAG, "Failed to load library apk:/" + libZipPath, e);
|
||||
noAbiException = e;
|
||||
} finally {
|
||||
if (f != null)
|
||||
// noinspection ResultOfMethodCallIgnored
|
||||
f.delete();
|
||||
}
|
||||
} catch (final Exception e) {
|
||||
Log.d(TAG, "Failed to load library apk:/" + libName, e);
|
||||
noAbiException = e;
|
||||
} finally {
|
||||
if (f != null)
|
||||
// noinspection ResultOfMethodCallIgnored
|
||||
f.delete();
|
||||
}
|
||||
if (noAbiException instanceof RuntimeException)
|
||||
throw (RuntimeException) noAbiException;
|
||||
|
||||
@@ -31,10 +31,7 @@ public final class ToolsInstaller {
|
||||
public static final int NO = 0x2;
|
||||
public static final int SYSTEM = 0x8;
|
||||
public static final int YES = 0x1;
|
||||
private static final String[][] EXECUTABLES = {
|
||||
{"libwg.so", "wg"},
|
||||
{"libwg-quick.so", "wg-quick"},
|
||||
};
|
||||
private static final String[] EXECUTABLES = {"wg", "wg-quick"};
|
||||
private static final File[] INSTALL_DIRS = {
|
||||
new File("/system/xbin"),
|
||||
new File("/system/bin"),
|
||||
@@ -45,13 +42,11 @@ public final class ToolsInstaller {
|
||||
private final Context context;
|
||||
private final File localBinaryDir;
|
||||
private final Object lock = new Object();
|
||||
private final File nativeLibraryDir;
|
||||
@Nullable private Boolean areToolsAvailable;
|
||||
@Nullable private Boolean installAsMagiskModule;
|
||||
|
||||
public ToolsInstaller(final Context context) {
|
||||
localBinaryDir = new File(context.getCacheDir(), "bin");
|
||||
nativeLibraryDir = new File(context.getApplicationInfo().nativeLibraryDir);
|
||||
localBinaryDir = new File(context.getCodeCacheDir(), "bin");
|
||||
this.context = context;
|
||||
}
|
||||
|
||||
@@ -72,10 +67,10 @@ public final class ToolsInstaller {
|
||||
if (INSTALL_DIR == null)
|
||||
return ERROR;
|
||||
final StringBuilder script = new StringBuilder();
|
||||
for (final String[] names : EXECUTABLES) {
|
||||
for (final String name : EXECUTABLES) {
|
||||
script.append(String.format("cmp -s '%s' '%s' && ",
|
||||
new File(nativeLibraryDir, names[0]),
|
||||
new File(INSTALL_DIR, names[1])));
|
||||
new File(localBinaryDir, name).getAbsolutePath(),
|
||||
new File(INSTALL_DIR, name).getAbsolutePath()));
|
||||
}
|
||||
script.append("exit ").append(OsConstants.EALREADY).append(';');
|
||||
try {
|
||||
@@ -89,18 +84,15 @@ public final class ToolsInstaller {
|
||||
}
|
||||
}
|
||||
|
||||
public void ensureToolsAvailable() throws FileNotFoundException, NoRootException {
|
||||
public void ensureToolsAvailable() throws FileNotFoundException {
|
||||
synchronized (lock) {
|
||||
if (areToolsAvailable == null) {
|
||||
final int ret = symlink();
|
||||
if (ret == OsConstants.EALREADY) {
|
||||
Log.d(TAG, "Tools were already symlinked into our private binary dir");
|
||||
try {
|
||||
Log.d(TAG, extract() ? "Tools are now extracted into our private binary dir" :
|
||||
"Tools were already extracted into our private binary dir");
|
||||
areToolsAvailable = true;
|
||||
} else if (ret == OsConstants.EXIT_SUCCESS) {
|
||||
Log.d(TAG, "Tools are now symlinked into our private binary dir");
|
||||
areToolsAvailable = true;
|
||||
} else {
|
||||
Log.e(TAG, "For some reason, wg and wg-quick are not available at all");
|
||||
} catch (final IOException e) {
|
||||
Log.e(TAG, "The wg and wg-quick tools are not available", e);
|
||||
areToolsAvailable = false;
|
||||
}
|
||||
}
|
||||
@@ -110,21 +102,22 @@ public final class ToolsInstaller {
|
||||
}
|
||||
}
|
||||
|
||||
public int install() throws NoRootException {
|
||||
public int install() throws NoRootException, IOException {
|
||||
return willInstallAsMagiskModule() ? installMagisk() : installSystem();
|
||||
}
|
||||
|
||||
private int installMagisk() throws NoRootException {
|
||||
private int installMagisk() throws NoRootException, IOException {
|
||||
extract();
|
||||
final StringBuilder script = new StringBuilder("set -ex; ");
|
||||
|
||||
script.append("trap 'rm -rf /sbin/.magisk/img/wireguard' INT TERM EXIT; ");
|
||||
script.append(String.format("rm -rf /sbin/.magisk/img/wireguard/; mkdir -p /sbin/.magisk/img/wireguard%s; ", INSTALL_DIR));
|
||||
script.append(String.format("printf 'name=WireGuard Command Line Tools\nversion=%s\nversionCode=%s\nauthor=zx2c4\ndescription=Command line tools for WireGuard\nminMagisk=1500\n' > /sbin/.magisk/img/wireguard/module.prop; ", BuildConfig.VERSION_NAME, BuildConfig.VERSION_CODE));
|
||||
script.append("touch /sbin/.magisk/img/wireguard/auto_mount; ");
|
||||
for (final String[] names : EXECUTABLES) {
|
||||
final File destination = new File("/sbin/.magisk/img/wireguard" + INSTALL_DIR, names[1]);
|
||||
for (final String name : EXECUTABLES) {
|
||||
final File destination = new File("/sbin/.magisk/img/wireguard" + INSTALL_DIR, name);
|
||||
script.append(String.format("cp '%s' '%s'; chmod 755 '%s'; chcon 'u:object_r:system_file:s0' '%s' || true; ",
|
||||
new File(nativeLibraryDir, names[0]), destination, destination, destination));
|
||||
new File(localBinaryDir, name), destination, destination, destination));
|
||||
}
|
||||
script.append("trap - INT TERM EXIT;");
|
||||
|
||||
@@ -135,15 +128,16 @@ public final class ToolsInstaller {
|
||||
}
|
||||
}
|
||||
|
||||
private int installSystem() throws NoRootException {
|
||||
private int installSystem() throws NoRootException, IOException {
|
||||
if (INSTALL_DIR == null)
|
||||
return OsConstants.ENOENT;
|
||||
extract();
|
||||
final StringBuilder script = new StringBuilder("set -ex; ");
|
||||
script.append("trap 'mount -o ro,remount /system' EXIT; mount -o rw,remount /system; ");
|
||||
for (final String[] names : EXECUTABLES) {
|
||||
final File destination = new File(INSTALL_DIR, names[1]);
|
||||
for (final String name : EXECUTABLES) {
|
||||
final File destination = new File(INSTALL_DIR, name);
|
||||
script.append(String.format("cp '%s' '%s'; chmod 755 '%s'; restorecon '%s' || true; ",
|
||||
new File(nativeLibraryDir, names[0]), destination, destination, destination));
|
||||
new File(localBinaryDir, name), destination, destination, destination));
|
||||
}
|
||||
try {
|
||||
return Application.getRootShell().run(null, script.toString()) == 0 ? YES | SYSTEM : ERROR;
|
||||
@@ -152,27 +146,27 @@ public final class ToolsInstaller {
|
||||
}
|
||||
}
|
||||
|
||||
public int symlink() throws NoRootException {
|
||||
final StringBuilder script = new StringBuilder("set -x; ");
|
||||
for (final String[] names : EXECUTABLES) {
|
||||
script.append(String.format("test '%s' -ef '%s' && ",
|
||||
new File(nativeLibraryDir, names[0]),
|
||||
new File(localBinaryDir, names[1])));
|
||||
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();
|
||||
}
|
||||
script.append("exit ").append(OsConstants.EALREADY).append("; set -e; ");
|
||||
|
||||
for (final String[] names : EXECUTABLES) {
|
||||
script.append(String.format("ln -fns '%s' '%s'; ",
|
||||
new File(nativeLibraryDir, names[0]),
|
||||
new File(localBinaryDir, names[1])));
|
||||
}
|
||||
script.append("exit ").append(OsConstants.EXIT_SUCCESS).append(';');
|
||||
|
||||
try {
|
||||
return Application.getRootShell().run(null, script.toString());
|
||||
} catch (final IOException ignored) {
|
||||
return OsConstants.EXIT_FAILURE;
|
||||
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() {
|
||||
|
||||
@@ -124,6 +124,7 @@ public class SlashDrawable extends Drawable {
|
||||
return mDrawable.getIntrinsicWidth();
|
||||
}
|
||||
|
||||
@SuppressWarnings("deprecation")
|
||||
@Override
|
||||
public int getOpacity() {
|
||||
return PixelFormat.OPAQUE;
|
||||
|
||||
@@ -5,31 +5,35 @@
|
||||
|
||||
package com.wireguard.config;
|
||||
|
||||
import java.lang.reflect.InvocationTargetException;
|
||||
import android.os.Build;
|
||||
import java.lang.reflect.Method;
|
||||
import java.net.Inet4Address;
|
||||
import java.net.Inet6Address;
|
||||
import java.net.InetAddress;
|
||||
import java.net.UnknownHostException;
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
import javax.annotation.Nullable;
|
||||
|
||||
/**
|
||||
* Utility methods for creating instances of {@link InetAddress}.
|
||||
*/
|
||||
public final class InetAddresses {
|
||||
private static final Method PARSER_METHOD;
|
||||
@Nullable private static final Method PARSER_METHOD;
|
||||
private static final Pattern WONT_TOUCH_RESOLVER = Pattern.compile("^(((([0-9A-Fa-f]{1,4}:){7}([0-9A-Fa-f]{1,4}|:))|(([0-9A-Fa-f]{1,4}:){6}(:[0-9A-Fa-f]{1,4}|((25[0-5]|2[0-4]\\d|1\\d\\d|[1-9]?\\d)(\\.(25[0-5]|2[0-4]\\d|1\\d\\d|[1-9]?\\d)){3})|:))|(([0-9A-Fa-f]{1,4}:){5}(((:[0-9A-Fa-f]{1,4}){1,2})|:((25[0-5]|2[0-4]\\d|1\\d\\d|[1-9]?\\d)(\\.(25[0-5]|2[0-4]\\d|1\\d\\d|[1-9]?\\d)){3})|:))|(([0-9A-Fa-f]{1,4}:){4}(((:[0-9A-Fa-f]{1,4}){1,3})|((:[0-9A-Fa-f]{1,4})?:((25[0-5]|2[0-4]\\d|1\\d\\d|[1-9]?\\d)(\\.(25[0-5]|2[0-4]\\d|1\\d\\d|[1-9]?\\d)){3}))|:))|(([0-9A-Fa-f]{1,4}:){3}(((:[0-9A-Fa-f]{1,4}){1,4})|((:[0-9A-Fa-f]{1,4}){0,2}:((25[0-5]|2[0-4]\\d|1\\d\\d|[1-9]?\\d)(\\.(25[0-5]|2[0-4]\\d|1\\d\\d|[1-9]?\\d)){3}))|:))|(([0-9A-Fa-f]{1,4}:){2}(((:[0-9A-Fa-f]{1,4}){1,5})|((:[0-9A-Fa-f]{1,4}){0,3}:((25[0-5]|2[0-4]\\d|1\\d\\d|[1-9]?\\d)(\\.(25[0-5]|2[0-4]\\d|1\\d\\d|[1-9]?\\d)){3}))|:))|(([0-9A-Fa-f]{1,4}:){1}(((:[0-9A-Fa-f]{1,4}){1,6})|((:[0-9A-Fa-f]{1,4}){0,4}:((25[0-5]|2[0-4]\\d|1\\d\\d|[1-9]?\\d)(\\.(25[0-5]|2[0-4]\\d|1\\d\\d|[1-9]?\\d)){3}))|:))|(:(((:[0-9A-Fa-f]{1,4}){1,7})|((:[0-9A-Fa-f]{1,4}){0,5}:((25[0-5]|2[0-4]\\d|1\\d\\d|[1-9]?\\d)(\\.(25[0-5]|2[0-4]\\d|1\\d\\d|[1-9]?\\d)){3}))|:)))(%.+)?)|((?:(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\\.){3}(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?))$");
|
||||
|
||||
static {
|
||||
Method m = null;
|
||||
try {
|
||||
// This method is only present on Android.
|
||||
// noinspection JavaReflectionMemberAccess
|
||||
PARSER_METHOD = InetAddress.class.getMethod("parseNumericAddress", String.class);
|
||||
} catch (final NoSuchMethodException e) {
|
||||
throw new RuntimeException(e);
|
||||
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.Q)
|
||||
// noinspection JavaReflectionMemberAccess
|
||||
m = InetAddress.class.getMethod("parseNumericAddress", String.class);
|
||||
} catch (final Exception ignored) {
|
||||
}
|
||||
PARSER_METHOD = m;
|
||||
}
|
||||
|
||||
private InetAddresses() {
|
||||
// Prevent instantiation.
|
||||
}
|
||||
private InetAddresses() { }
|
||||
|
||||
/**
|
||||
* Parses a numeric IPv4 or IPv6 address without performing any DNS lookups.
|
||||
@@ -41,14 +45,28 @@ public final class InetAddresses {
|
||||
if (address.isEmpty())
|
||||
throw new ParseException(InetAddress.class, address, "Empty address");
|
||||
try {
|
||||
return (InetAddress) PARSER_METHOD.invoke(null, address);
|
||||
} catch (final IllegalAccessException | InvocationTargetException e) {
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q)
|
||||
return android.net.InetAddresses.parseNumericAddress(address);
|
||||
else if (PARSER_METHOD != null)
|
||||
return (InetAddress) PARSER_METHOD.invoke(null, address);
|
||||
else
|
||||
throw new NoSuchMethodException("parseNumericAddress");
|
||||
} catch (final IllegalArgumentException e) {
|
||||
throw new ParseException(InetAddress.class, address, e);
|
||||
} catch (final Exception e) {
|
||||
final Throwable cause = e.getCause();
|
||||
// Re-throw parsing exceptions with the original type, as callers might try to catch
|
||||
// them. On the other hand, callers cannot be expected to handle reflection failures.
|
||||
if (cause instanceof IllegalArgumentException)
|
||||
throw new ParseException(InetAddress.class, address, cause);
|
||||
throw new RuntimeException(e);
|
||||
try {
|
||||
if (WONT_TOUCH_RESOLVER.matcher(address).matches())
|
||||
return InetAddress.getByName(address);
|
||||
else
|
||||
throw new ParseException(InetAddress.class, address, "Not an IP address");
|
||||
} catch (final UnknownHostException f) {
|
||||
throw new ParseException(InetAddress.class, address, f);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -7,6 +7,7 @@ package com.wireguard.crypto;
|
||||
|
||||
import com.wireguard.crypto.KeyFormatException.Type;
|
||||
|
||||
import java.security.MessageDigest;
|
||||
import java.security.SecureRandom;
|
||||
import java.util.Arrays;
|
||||
|
||||
@@ -247,6 +248,24 @@ 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.
|
||||
*/
|
||||
|
||||
@@ -125,6 +125,7 @@
|
||||
</androidx.cardview.widget.CardView>
|
||||
|
||||
<LinearLayout
|
||||
android:id="@+id/peers_layout"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginBottom="4dp"
|
||||
|
||||
@@ -89,6 +89,24 @@
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_below="@+id/endpoint_label"
|
||||
android:text="@{item.endpoint}" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/transfer_label"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_below="@+id/endpoint_text"
|
||||
android:layout_marginTop="8dp"
|
||||
android:labelFor="@+id/transfer_text"
|
||||
android:text="@string/transfer"
|
||||
android:visibility="gone" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/transfer_text"
|
||||
style="?android:attr/textAppearanceMedium"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_below="@+id/transfer_label"
|
||||
android:visibility="gone" />
|
||||
</RelativeLayout>
|
||||
</androidx.cardview.widget.CardView>
|
||||
</layout>
|
||||
|
||||
@@ -59,6 +59,7 @@
|
||||
<string name="create_from_file">Create from file or archive</string>
|
||||
<string name="create_from_qr_code">Create from QR code</string>
|
||||
<string name="create_output_dir_error">Cannot create output directory</string>
|
||||
<string name="create_downloads_file_error">Cannot create file in downloads directory</string>
|
||||
<string name="create_temp_dir_error">Cannot create local temporary directory</string>
|
||||
<string name="create_tunnel">Create Tunnel</string>
|
||||
<string name="dark_theme_summary_off">Currently using light (day) theme</string>
|
||||
@@ -94,7 +95,14 @@
|
||||
<string name="log_export_success">Saved to “%s”</string>
|
||||
<string name="log_export_summary">Log file will be saved to downloads folder</string>
|
||||
<string name="log_export_title">Export log file</string>
|
||||
<string name="logcat_error">Unable to run logcat: </string>
|
||||
<string name="module_version_error">Unable to determine kernel module version</string>
|
||||
<string name="module_installer_not_found">No modules are available for your device</string>
|
||||
<string name="module_installer_initial">The experimental kernel module can improve performance</string>
|
||||
<string name="module_installer_success">Success. The application will restart in 5 seconds</string>
|
||||
<string name="module_installer_title">Download and install kernel module</string>
|
||||
<string name="module_installer_working">Downloading and installing…</string>
|
||||
<string name="module_installer_error">Something went wrong. Please try again</string>
|
||||
<string name="mtu">MTU</string>
|
||||
<string name="multiple_tunnels_error">Only one userspace tunnel can run at a time</string>
|
||||
<string name="name">Name</string>
|
||||
@@ -136,6 +144,13 @@
|
||||
<string name="tools_installer_title">Install command line tools</string>
|
||||
<string name="tools_installer_working">Installing wg and wg-quick</string>
|
||||
<string name="tools_unavailable_error">Required tools unavailable</string>
|
||||
<string name="transfer">Transfer</string>
|
||||
<string name="transfer_rx_tx">rx: %s, tx: %s</string>
|
||||
<string name="transfer_bytes">%d B</string>
|
||||
<string name="transfer_kibibytes">%.2f KiB</string>
|
||||
<string name="transfer_mibibytes">%.2f MiB</string>
|
||||
<string name="transfer_gibibytes">%.2f GiB</string>
|
||||
<string name="transfer_tibibytes">%.2f TiB</string>
|
||||
<string name="tun_create_error">Unable to create tun device</string>
|
||||
<string name="tunnel_config_error">Unable to configure tunnel (wg-quick returned %d)</string>
|
||||
<string name="tunnel_create_error">Unable to create tunnel: %s</string>
|
||||
|
||||
@@ -6,6 +6,7 @@
|
||||
android:key="restore_on_boot"
|
||||
android:summary="@string/restore_on_boot_summary"
|
||||
android:title="@string/restore_on_boot_title" />
|
||||
<com.wireguard.android.preference.ModuleDownloaderPreference android:key="module_downloader" />
|
||||
<com.wireguard.android.preference.ToolsInstallerPreference android:key="tools_installer" />
|
||||
<com.wireguard.android.preference.ZipExporterPreference />
|
||||
<com.wireguard.android.preference.LogExporterPreference />
|
||||
|
||||
@@ -9,7 +9,8 @@ set(CMAKE_RUNTIME_OUTPUT_DIRECTORY "${CMAKE_LIBRARY_OUTPUT_DIRECTORY}")
|
||||
set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} -fuse-ld=gold")
|
||||
|
||||
add_executable(libwg-quick.so wireguard/src/tools/wg-quick/android.c ndk-compat/compat.c)
|
||||
target_compile_options(libwg-quick.so PUBLIC -O3 -std=gnu11 -Wall -pedantic -Wno-missing-field-initializers -include ${CMAKE_CURRENT_SOURCE_DIR}/ndk-compat/compat.h -DWG_PACKAGE_NAME=\"${ANDROID_PACKAGE_NAME}\")
|
||||
target_compile_options(libwg-quick.so PUBLIC -O3 -std=gnu11 -Wall -include ${CMAKE_CURRENT_SOURCE_DIR}/ndk-compat/compat.h -DWG_PACKAGE_NAME=\"${ANDROID_PACKAGE_NAME}\")
|
||||
target_link_libraries(libwg-quick.so -ldl)
|
||||
|
||||
file(GLOB WG_SOURCES wireguard/src/tools/*.c libmnl/src/*.c ndk-compat/compat.c)
|
||||
add_executable(libwg.so ${WG_SOURCES})
|
||||
|
||||
+10
-24
@@ -20,30 +20,16 @@ export GOARCH := $(NDK_GO_ARCH_MAP_$(ANDROID_ARCH_NAME))
|
||||
export GOOS := android
|
||||
export CGO_ENABLED := 1
|
||||
|
||||
DESIRED_GO_VERSION := 1.13.3
|
||||
|
||||
default: $(DESTDIR)/libwg-go.so
|
||||
|
||||
GOBUILDARCH := $(NDK_GO_ARCH_MAP_$(shell uname -m))
|
||||
GOBUILDOS := $(shell uname -s | tr '[:upper:]' '[:lower:]')
|
||||
GOBUILDVERSION := 1.12.5
|
||||
GOBUILDTARBALL := https://dl.google.com/go/go$(GOBUILDVERSION).$(GOBUILDOS)-$(GOBUILDARCH).tar.gz
|
||||
GOBUILDVERSION_NEEDED := go version go$(GOBUILDVERSION) $(GOBUILDOS)/$(GOBUILDARCH)
|
||||
export GOROOT := $(BUILDDIR)/goroot
|
||||
export GOPATH := $(BUILDDIR)/gopath
|
||||
export PATH := $(GOROOT)/bin:$(PATH)
|
||||
GOBUILDVERSION_CURRENT := $(shell $(GOROOT)/bin/go version 2>/dev/null)
|
||||
ifneq ($(GOBUILDVERSION_NEEDED),$(GOBUILDVERSION_CURRENT))
|
||||
$(shell rm -f $(GOROOT)/bin/go)
|
||||
endif
|
||||
$(GOROOT)/bin/go:
|
||||
rm -rf "$(GOROOT)"
|
||||
mkdir -p "$(GOROOT)"
|
||||
curl "$(GOBUILDTARBALL)" | tar -C "$(GOROOT)" --strip-components=1 -xzf - || { rm -rf "$(GOROOT)"; exit 1; }
|
||||
patch -p1 -f -N -r- -d "$(GOROOT)" < goruntime-boottime-over-monotonic.diff || { rm -rf "$(GOROOT)"; exit 1; }
|
||||
$(BUILDDIR)/go-$(DESIRED_GO_VERSION)/.prepared:
|
||||
mkdir -p "$(dir $@)"
|
||||
curl "https://dl.google.com/go/go$(DESIRED_GO_VERSION).$(shell uname -s | tr '[:upper:]' '[:lower:]')-$(NDK_GO_ARCH_MAP_$(shell uname -m)).tar.gz" | tar -C "$(dir $@)" --strip-components=1 -xzf -
|
||||
patch -p1 -f -N -r- -d "$(dir $@)" < goruntime-boottime-over-monotonic.diff
|
||||
touch "$@"
|
||||
|
||||
$(shell test "$$(cat $(BUILDDIR)/.gobuildversion 2>/dev/null)" = "$(GOBUILDVERSION_CURRENT)" || rm -f "$(DESTDIR)/libwg-go.so")
|
||||
|
||||
$(DESTDIR)/libwg-go.so: $(GOROOT)/bin/go
|
||||
go get -tags linux || { chmod -fR +w "$(GOPATH)/pkg/mod"; rm -rf "$(GOPATH)/pkg/mod"; exit 1; }
|
||||
chmod -fR +w "$(GOPATH)/pkg/mod"
|
||||
go build -tags linux -ldflags="-X main.socketDirectory=/data/data/$(ANDROID_PACKAGE_NAME)/cache/wireguard" -v -o "$@" -buildmode c-shared
|
||||
go version > $(BUILDDIR)/.gobuildversion
|
||||
$(DESTDIR)/libwg-go.so: export PATH := $(BUILDDIR)/go-$(DESIRED_GO_VERSION)/bin/:$(PATH)
|
||||
$(DESTDIR)/libwg-go.so: $(BUILDDIR)/go-$(DESIRED_GO_VERSION)/.prepared go.mod
|
||||
go build -tags linux -ldflags="-X golang.zx2c4.com/wireguard/ipc.socketDirectory=/data/data/$(ANDROID_PACKAGE_NAME)/cache/wireguard" -v -trimpath -o "$@" -buildmode c-shared
|
||||
|
||||
@@ -15,6 +15,7 @@ import (
|
||||
"golang.zx2c4.com/wireguard/device"
|
||||
"golang.zx2c4.com/wireguard/ipc"
|
||||
"golang.zx2c4.com/wireguard/tun"
|
||||
"bytes"
|
||||
"log"
|
||||
"math"
|
||||
"net"
|
||||
@@ -168,6 +169,22 @@ func wgGetSocketV6(tunnelHandle int32) int32 {
|
||||
return int32(fd)
|
||||
}
|
||||
|
||||
//export wgGetConfig
|
||||
func wgGetConfig(tunnelHandle int32) *C.char {
|
||||
handle, ok := tunnelHandles[tunnelHandle]
|
||||
if !ok {
|
||||
return nil
|
||||
}
|
||||
settings := new(bytes.Buffer)
|
||||
writer := bufio.NewWriter(settings)
|
||||
err := handle.device.IpcGetOperation(writer)
|
||||
if err != nil {
|
||||
return nil
|
||||
}
|
||||
writer.Flush()
|
||||
return C.CString(settings.String())
|
||||
}
|
||||
|
||||
//export wgVersion
|
||||
func wgVersion() *C.char {
|
||||
return C.CString(device.WireGuardGoVersion)
|
||||
|
||||
@@ -1,8 +1,10 @@
|
||||
module golang.zx2c4.com/wireguard/android
|
||||
|
||||
go 1.12
|
||||
go 1.13
|
||||
|
||||
require (
|
||||
golang.org/x/sys v0.0.0-20190522044717-8097e1b27ff5
|
||||
golang.zx2c4.com/wireguard v0.0.20190518-0.20190605110920-108c37a05639
|
||||
golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550 // indirect
|
||||
golang.org/x/net v0.0.0-20191014212845-da9a3fd4c582 // indirect
|
||||
golang.org/x/sys v0.0.0-20191018095205-727590c5006e
|
||||
golang.zx2c4.com/wireguard v0.0.20191013-0.20191017134306-ae492d1b3599
|
||||
)
|
||||
|
||||
@@ -1,11 +1,19 @@
|
||||
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
|
||||
golang.org/x/crypto v0.0.0-20190513172903-22d7a77e9e5f/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
|
||||
golang.org/x/crypto v0.0.0-20191002192127-34f69633bfdc/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
|
||||
golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550 h1:ObdrDkeb4kJdCP557AjRjq69pTHfNouLtWZG7j9rPN8=
|
||||
golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
|
||||
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
|
||||
golang.org/x/net v0.0.0-20190522155817-f3200d17e092/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks=
|
||||
golang.org/x/net v0.0.0-20191003171128-d98b1b443823/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||
golang.org/x/net v0.0.0-20191014212845-da9a3fd4c582 h1:p9xBe/w/OzkeYVKm234g55gMdD1nSIooTir5kV11kfA=
|
||||
golang.org/x/net v0.0.0-20191014212845-da9a3fd4c582/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20190516110030-61b9204099cb/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20190522044717-8097e1b27ff5/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20191003212358-c178f38b412c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20191018095205-727590c5006e h1:ZtoklVMHQy6BFRHkbG6JzK+S6rX82//Yeok1vMlizfQ=
|
||||
golang.org/x/sys v0.0.0-20191018095205-727590c5006e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||
golang.zx2c4.com/wireguard v0.0.20190518-0.20190605110920-108c37a05639 h1:AoX5+g0OBk+KqTTMVl4YPFZ9ioU2tPW1YwtzaVWdORA=
|
||||
golang.zx2c4.com/wireguard v0.0.20190518-0.20190605110920-108c37a05639/go.mod h1:8X7vp4RrsvM83bde6vQ94DsL4ZpjUViVUym8aa8zGhs=
|
||||
golang.org/x/text v0.3.2 h1:tW2bmiBqwgJj/UpqtC8EpXEZVYOwU0yG4iWbprSVAcs=
|
||||
golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk=
|
||||
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||
golang.zx2c4.com/wireguard v0.0.20191013-0.20191017134306-ae492d1b3599 h1:fHOjo1GyHRI2q/LcFB9NiFTqXB7WRiu2idShqqTwrZ4=
|
||||
golang.zx2c4.com/wireguard v0.0.20191013-0.20191017134306-ae492d1b3599/go.mod h1:P2HsVp8SKwZEufsnezXZA4GRX/T49/HlU7DGuelXsU4=
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
From 6998a7866b8428fd49c31506795c9f8154826d13 Mon Sep 17 00:00:00 2001
|
||||
From b19623e7673a4d6743745382d5d38751b64e011d Mon Sep 17 00:00:00 2001
|
||||
From: "Jason A. Donenfeld" <Jason@zx2c4.com>
|
||||
Date: Wed, 27 Feb 2019 05:05:44 +0100
|
||||
Subject: [PATCH] runtime: use CLOCK_BOOTTIME in nanotime on Linux
|
||||
@@ -28,7 +28,7 @@ Change-Id: I7b2a6ca0c5bc5fce57ec0eeafe7b68270b429321
|
||||
8 files changed, 11 insertions(+), 11 deletions(-)
|
||||
|
||||
diff --git a/src/runtime/sys_linux_386.s b/src/runtime/sys_linux_386.s
|
||||
index 40b55a67eb..6105780ff4 100644
|
||||
index 72c43bd9da..daadfe32a9 100644
|
||||
--- a/src/runtime/sys_linux_386.s
|
||||
+++ b/src/runtime/sys_linux_386.s
|
||||
@@ -288,13 +288,13 @@ noswitch:
|
||||
@@ -48,7 +48,7 @@ index 40b55a67eb..6105780ff4 100644
|
||||
INVOKE_SYSCALL
|
||||
|
||||
diff --git a/src/runtime/sys_linux_amd64.s b/src/runtime/sys_linux_amd64.s
|
||||
index b709f77060..18a1f1d43a 100644
|
||||
index 5c300f553d..e4a6f12ec6 100644
|
||||
--- a/src/runtime/sys_linux_amd64.s
|
||||
+++ b/src/runtime/sys_linux_amd64.s
|
||||
@@ -261,7 +261,7 @@ noswitch:
|
||||
@@ -61,7 +61,7 @@ index b709f77060..18a1f1d43a 100644
|
||||
CALL AX
|
||||
MOVQ 0(SP), AX // sec
|
||||
diff --git a/src/runtime/sys_linux_arm.s b/src/runtime/sys_linux_arm.s
|
||||
index 43a58335c8..8b93635f4f 100644
|
||||
index 9c7398451c..61b6cd91f6 100644
|
||||
--- a/src/runtime/sys_linux_arm.s
|
||||
+++ b/src/runtime/sys_linux_arm.s
|
||||
@@ -11,7 +11,7 @@
|
||||
@@ -83,7 +83,7 @@ index 43a58335c8..8b93635f4f 100644
|
||||
MOVW runtime·vdsoClockgettimeSym(SB), R11
|
||||
CMP $0, R11
|
||||
diff --git a/src/runtime/sys_linux_arm64.s b/src/runtime/sys_linux_arm64.s
|
||||
index 8b344be8f8..daf142fba0 100644
|
||||
index 2835b6ca1c..346ca9cfce 100644
|
||||
--- a/src/runtime/sys_linux_arm64.s
|
||||
+++ b/src/runtime/sys_linux_arm64.s
|
||||
@@ -13,7 +13,7 @@
|
||||
@@ -105,7 +105,7 @@ index 8b344be8f8..daf142fba0 100644
|
||||
CBZ R2, fallback
|
||||
BL (R2)
|
||||
diff --git a/src/runtime/sys_linux_mips64x.s b/src/runtime/sys_linux_mips64x.s
|
||||
index c45703d228..9842b596e7 100644
|
||||
index 33ed1050c2..59a5be179c 100644
|
||||
--- a/src/runtime/sys_linux_mips64x.s
|
||||
+++ b/src/runtime/sys_linux_mips64x.s
|
||||
@@ -189,7 +189,7 @@ TEXT runtime·walltime(SB),NOSPLIT,$16
|
||||
@@ -118,10 +118,10 @@ index c45703d228..9842b596e7 100644
|
||||
MOVV $SYS_clock_gettime, R2
|
||||
SYSCALL
|
||||
diff --git a/src/runtime/sys_linux_mipsx.s b/src/runtime/sys_linux_mipsx.s
|
||||
index f362b0f3f1..5ab866e047 100644
|
||||
index 6e539fbc6f..55b2bf7156 100644
|
||||
--- a/src/runtime/sys_linux_mipsx.s
|
||||
+++ b/src/runtime/sys_linux_mipsx.s
|
||||
@@ -193,7 +193,7 @@ TEXT runtime·walltime(SB),NOSPLIT,$8-12
|
||||
@@ -194,7 +194,7 @@ TEXT runtime·walltime(SB),NOSPLIT,$8-12
|
||||
RET
|
||||
|
||||
TEXT runtime·nanotime(SB),NOSPLIT,$8-8
|
||||
@@ -131,7 +131,7 @@ index f362b0f3f1..5ab866e047 100644
|
||||
MOVW $SYS_clock_gettime, R2
|
||||
SYSCALL
|
||||
diff --git a/src/runtime/sys_linux_ppc64x.s b/src/runtime/sys_linux_ppc64x.s
|
||||
index 6835f434de..46618b2d91 100644
|
||||
index 13d23156bd..f67e5062aa 100644
|
||||
--- a/src/runtime/sys_linux_ppc64x.s
|
||||
+++ b/src/runtime/sys_linux_ppc64x.s
|
||||
@@ -204,7 +204,7 @@ fallback:
|
||||
@@ -144,7 +144,7 @@ index 6835f434de..46618b2d91 100644
|
||||
MOVD R1, R15 // R15 is unchanged by C code
|
||||
MOVD g_m(g), R21 // R21 = m
|
||||
diff --git a/src/runtime/sys_linux_s390x.s b/src/runtime/sys_linux_s390x.s
|
||||
index c79ceea751..44e3258e60 100644
|
||||
index 58b36dff0a..cb92e9a402 100644
|
||||
--- a/src/runtime/sys_linux_s390x.s
|
||||
+++ b/src/runtime/sys_linux_s390x.s
|
||||
@@ -180,7 +180,7 @@ TEXT runtime·walltime(SB),NOSPLIT,$16
|
||||
@@ -157,5 +157,5 @@ index c79ceea751..44e3258e60 100644
|
||||
MOVW $SYS_clock_gettime, R1
|
||||
SYSCALL
|
||||
--
|
||||
2.20.1
|
||||
2.23.0
|
||||
|
||||
|
||||
@@ -12,6 +12,7 @@ extern int wgTurnOn(struct go_string ifname, int tun_fd, struct go_string settin
|
||||
extern void wgTurnOff(int handle);
|
||||
extern int wgGetSocketV4(int handle);
|
||||
extern int wgGetSocketV6(int handle);
|
||||
extern char *wgGetConfig(int handle);
|
||||
extern char *wgVersion();
|
||||
|
||||
JNIEXPORT jint JNICALL Java_com_wireguard_android_backend_GoBackend_wgTurnOn(JNIEnv *env, jclass c, jstring ifname, jint tun_fd, jstring settings)
|
||||
@@ -47,6 +48,17 @@ JNIEXPORT jint JNICALL Java_com_wireguard_android_backend_GoBackend_wgGetSocketV
|
||||
return wgGetSocketV6(handle);
|
||||
}
|
||||
|
||||
JNIEXPORT jstring JNICALL Java_com_wireguard_android_backend_GoBackend_wgGetConfig(JNIEnv *env, jclass c, jint handle)
|
||||
{
|
||||
jstring ret;
|
||||
char *config = wgGetConfig(handle);
|
||||
if (!config)
|
||||
return NULL;
|
||||
ret = (*env)->NewStringUTF(env, config);
|
||||
free(config);
|
||||
return ret;
|
||||
}
|
||||
|
||||
JNIEXPORT jstring JNICALL Java_com_wireguard_android_backend_GoBackend_wgVersion(JNIEnv *env, jclass c)
|
||||
{
|
||||
jstring ret;
|
||||
|
||||
+1
-1
Submodule app/tools/wireguard updated: 40eaf20e04...0b27b1d315
+8
-1
@@ -7,7 +7,7 @@ allprojects {
|
||||
|
||||
buildscript {
|
||||
dependencies {
|
||||
classpath 'com.android.tools.build:gradle:3.3.1'
|
||||
classpath 'com.android.tools.build:gradle:3.5.1'
|
||||
}
|
||||
repositories {
|
||||
google()
|
||||
@@ -18,3 +18,10 @@ buildscript {
|
||||
task clean(type: Delete) {
|
||||
delete rootProject.buildDir
|
||||
}
|
||||
|
||||
tasks {
|
||||
wrapper {
|
||||
gradleVersion = "5.6.2"
|
||||
distributionType = Wrapper.DistributionType.ALL
|
||||
}
|
||||
}
|
||||
|
||||
Vendored
BIN
Binary file not shown.
+1
-2
@@ -1,6 +1,5 @@
|
||||
#Sun Feb 17 03:37:07 CET 2019
|
||||
distributionBase=GRADLE_USER_HOME
|
||||
distributionPath=wrapper/dists
|
||||
distributionUrl=https\://services.gradle.org/distributions/gradle-5.6.2-all.zip
|
||||
zipStoreBase=GRADLE_USER_HOME
|
||||
zipStorePath=wrapper/dists
|
||||
distributionUrl=https\://services.gradle.org/distributions/gradle-4.10.1-all.zip
|
||||
|
||||
@@ -1,5 +1,21 @@
|
||||
#!/usr/bin/env sh
|
||||
|
||||
#
|
||||
# Copyright 2015 the original author or authors.
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||
# you may not use this file except in compliance with the License.
|
||||
# You may obtain a copy of the License at
|
||||
#
|
||||
# https://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
#
|
||||
|
||||
##############################################################################
|
||||
##
|
||||
## Gradle start up script for UN*X
|
||||
@@ -28,7 +44,7 @@ APP_NAME="Gradle"
|
||||
APP_BASE_NAME=`basename "$0"`
|
||||
|
||||
# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
|
||||
DEFAULT_JVM_OPTS=""
|
||||
DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"'
|
||||
|
||||
# Use the maximum available, or set MAX_FD != -1 to use that value.
|
||||
MAX_FD="maximum"
|
||||
@@ -109,8 +125,8 @@ if $darwin; then
|
||||
GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\""
|
||||
fi
|
||||
|
||||
# For Cygwin, switch paths to Windows format before running java
|
||||
if $cygwin ; then
|
||||
# For Cygwin or MSYS, switch paths to Windows format before running java
|
||||
if [ "$cygwin" = "true" -o "$msys" = "true" ] ; then
|
||||
APP_HOME=`cygpath --path --mixed "$APP_HOME"`
|
||||
CLASSPATH=`cygpath --path --mixed "$CLASSPATH"`
|
||||
JAVACMD=`cygpath --unix "$JAVACMD"`
|
||||
|
||||
Reference in New Issue
Block a user