aboutsummaryrefslogtreecommitdiffstatshomepage
diff options
context:
space:
mode:
authorJason A. Donenfeld <Jason@zx2c4.com>2019-11-20 11:44:34 +0100
committerJason A. Donenfeld <Jason@zx2c4.com>2019-11-20 12:44:54 +0100
commit8b0123042f54d843301fbef458b249fcf12466d2 (patch)
treeb3e7bd9f143568666d21014eafe544affb0a89b7
parentModuleLoader: sync file before renaming (diff)
downloadwireguard-android-8b0123042f54d843301fbef458b249fcf12466d2.tar.xz
wireguard-android-8b0123042f54d843301fbef458b249fcf12466d2.zip
Implement statistics
Signed-off-by: Jason A. Donenfeld <Jason@zx2c4.com>
-rw-r--r--app/src/main/java/com/wireguard/android/backend/GoBackend.java44
-rw-r--r--app/src/main/java/com/wireguard/android/backend/WgQuickBackend.java21
-rw-r--r--app/src/main/java/com/wireguard/android/fragment/TunnelDetailFragment.java88
-rw-r--r--app/src/main/java/com/wireguard/android/model/Tunnel.java55
-rw-r--r--app/src/main/java/com/wireguard/crypto/Key.java19
-rw-r--r--app/src/main/res/layout/tunnel_detail_fragment.xml1
-rw-r--r--app/src/main/res/layout/tunnel_detail_peer.xml18
-rw-r--r--app/src/main/res/values/strings.xml7
-rw-r--r--app/tools/libwg-go/api-android.go17
-rw-r--r--app/tools/libwg-go/jni.c12
-rw-r--r--build.gradle2
11 files changed, 277 insertions, 7 deletions
diff --git a/app/src/main/java/com/wireguard/android/backend/GoBackend.java b/app/src/main/java/com/wireguard/android/backend/GoBackend.java
index e85f2b0d..3e8e1ec9 100644
--- a/app/src/main/java/com/wireguard/android/backend/GoBackend.java
+++ b/app/src/main/java/com/wireguard/android/backend/GoBackend.java
@@ -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
diff --git a/app/src/main/java/com/wireguard/android/backend/WgQuickBackend.java b/app/src/main/java/com/wireguard/android/backend/WgQuickBackend.java
index 71427d8a..99b90af2 100644
--- a/app/src/main/java/com/wireguard/android/backend/WgQuickBackend.java
+++ b/app/src/main/java/com/wireguard/android/backend/WgQuickBackend.java
@@ -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
diff --git a/app/src/main/java/com/wireguard/android/fragment/TunnelDetailFragment.java b/app/src/main/java/com/wireguard/android/fragment/TunnelDetailFragment.java
index 8d2be476..f28a7b67 100644
--- a/app/src/main/java/com/wireguard/android/fragment/TunnelDetailFragment.java
+++ b/app/src/main/java/com/wireguard/android/fragment/TunnelDetailFragment.java
@@ -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) {
@@ -36,6 +58,22 @@ public class TunnelDetailFragment extends BaseFragment {
}
@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) {
super.onCreateView(inflater, container, 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);
+ }
+ });
+ }
}
diff --git a/app/src/main/java/com/wireguard/android/model/Tunnel.java b/app/src/main/java/com/wireguard/android/model/Tunnel.java
index 49e78a22..87b607d0 100644
--- a/app/src/main/java/com/wireguard/android/model/Tunnel.java
+++ b/app/src/main/java/com/wireguard/android/model/Tunnel.java
@@ -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;
+ }
}
}
diff --git a/app/src/main/java/com/wireguard/crypto/Key.java b/app/src/main/java/com/wireguard/crypto/Key.java
index f743ddd2..6648a5f3 100644
--- a/app/src/main/java/com/wireguard/crypto/Key.java
+++ b/app/src/main/java/com/wireguard/crypto/Key.java
@@ -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.
*/
diff --git a/app/src/main/res/layout/tunnel_detail_fragment.xml b/app/src/main/res/layout/tunnel_detail_fragment.xml
index 34332c2f..23056ecf 100644
--- a/app/src/main/res/layout/tunnel_detail_fragment.xml
+++ b/app/src/main/res/layout/tunnel_detail_fragment.xml
@@ -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"
diff --git a/app/src/main/res/layout/tunnel_detail_peer.xml b/app/src/main/res/layout/tunnel_detail_peer.xml
index f528a8df..0f7ae336 100644
--- a/app/src/main/res/layout/tunnel_detail_peer.xml
+++ b/app/src/main/res/layout/tunnel_detail_peer.xml
@@ -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>
diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml
index 33470706..336cd4ef 100644
--- a/app/src/main/res/values/strings.xml
+++ b/app/src/main/res/values/strings.xml
@@ -144,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>
diff --git a/app/tools/libwg-go/api-android.go b/app/tools/libwg-go/api-android.go
index 7e951b9c..7a393cae 100644
--- a/app/tools/libwg-go/api-android.go
+++ b/app/tools/libwg-go/api-android.go
@@ -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)
diff --git a/app/tools/libwg-go/jni.c b/app/tools/libwg-go/jni.c
index f6229a49..3f877d47 100644
--- a/app/tools/libwg-go/jni.c
+++ b/app/tools/libwg-go/jni.c
@@ -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;
diff --git a/build.gradle b/build.gradle
index e22f92cf..aa7a4692 100644
--- a/build.gradle
+++ b/build.gradle
@@ -7,7 +7,7 @@ allprojects {
buildscript {
dependencies {
- classpath 'com.android.tools.build:gradle:3.5.0'
+ classpath 'com.android.tools.build:gradle:3.5.1'
}
repositories {
google()