Skip to content

Commit 487ef34

Browse files
committed
tunnel: export latest handshake stat
Signed-off-by: Jason A. Donenfeld <[email protected]>
1 parent 979570f commit 487ef34

File tree

3 files changed

+74
-42
lines changed

3 files changed

+74
-42
lines changed

tunnel/src/main/java/com/wireguard/android/backend/GoBackend.java

Lines changed: 21 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@
2424
import com.wireguard.util.NonNullForAll;
2525

2626
import java.net.InetAddress;
27+
import java.time.Instant;
2728
import java.util.Collections;
2829
import java.util.Set;
2930
import java.util.concurrent.ExecutionException;
@@ -125,12 +126,14 @@ public Statistics getStatistics(final Tunnel tunnel) {
125126
Key key = null;
126127
long rx = 0;
127128
long tx = 0;
129+
long latestHandshakeMSec = 0;
128130
for (final String line : config.split("\\n")) {
129131
if (line.startsWith("public_key=")) {
130132
if (key != null)
131-
stats.add(key, rx, tx);
133+
stats.add(key, rx, tx, latestHandshakeMSec);
132134
rx = 0;
133135
tx = 0;
136+
latestHandshakeMSec = 0;
134137
try {
135138
key = Key.fromHex(line.substring(11));
136139
} catch (final KeyFormatException ignored) {
@@ -152,10 +155,26 @@ public Statistics getStatistics(final Tunnel tunnel) {
152155
} catch (final NumberFormatException ignored) {
153156
tx = 0;
154157
}
158+
} else if (line.startsWith("last_handshake_time_sec=")) {
159+
if (key == null)
160+
continue;
161+
try {
162+
latestHandshakeMSec += Long.parseLong(line.substring(24)) * 1000;
163+
} catch (final NumberFormatException ignored) {
164+
latestHandshakeMSec = 0;
165+
}
166+
} else if (line.startsWith("last_handshake_time_nsec=")) {
167+
if (key == null)
168+
continue;
169+
try {
170+
latestHandshakeMSec += Long.parseLong(line.substring(25)) / 1000000;
171+
} catch (final NumberFormatException ignored) {
172+
latestHandshakeMSec = 0;
173+
}
155174
}
156175
}
157176
if (key != null)
158-
stats.add(key, rx, tx);
177+
stats.add(key, rx, tx, latestHandshakeMSec);
159178
return stats;
160179
}
161180

tunnel/src/main/java/com/wireguard/android/backend/Statistics.java

Lines changed: 49 additions & 37 deletions
Original file line numberDiff line numberDiff line change
@@ -6,36 +6,65 @@
66
package com.wireguard.android.backend;
77

88
import android.os.SystemClock;
9-
import android.util.Pair;
109

1110
import com.wireguard.crypto.Key;
1211
import com.wireguard.util.NonNullForAll;
1312

1413
import java.util.HashMap;
1514
import java.util.Map;
15+
import java.util.Objects;
16+
17+
import androidx.annotation.Nullable;
1618

1719
/**
1820
* Class representing transfer statistics for a {@link Tunnel} instance.
1921
*/
2022
@NonNullForAll
2123
public class Statistics {
22-
private final Map<Key, Pair<Long, Long>> peerBytes = new HashMap<>();
24+
25+
// TODO: switch to Java Record class once R8 supports desugaring those.
26+
public final class PeerStats {
27+
public final long rxBytes, txBytes, latestHandshakeEpochMillis;
28+
29+
PeerStats(final long rxBytes, final long txBytes, final long latestHandshakeEpochMillis) {
30+
this.rxBytes = rxBytes;
31+
this.txBytes = txBytes;
32+
this.latestHandshakeEpochMillis = latestHandshakeEpochMillis;
33+
}
34+
35+
@Override public boolean equals(final Object o) {
36+
if (this == o)
37+
return true;
38+
if (o == null || getClass() != o.getClass())
39+
return false;
40+
final PeerStats stats = (PeerStats) o;
41+
return rxBytes == stats.rxBytes && txBytes == stats.txBytes && latestHandshakeEpochMillis == stats.latestHandshakeEpochMillis;
42+
}
43+
44+
@Override public int hashCode() {
45+
return Objects.hash(rxBytes, txBytes, latestHandshakeEpochMillis);
46+
}
47+
}
48+
49+
private final Map<Key, PeerStats> stats = new HashMap<>();
2350
private long lastTouched = SystemClock.elapsedRealtime();
2451

2552
Statistics() {
2653
}
2754

2855
/**
29-
* Add a peer and its current data usage to the internal map.
56+
* Add a peer and its current stats to the internal map.
3057
*
31-
* @param key A WireGuard public key bound to a particular peer
32-
* @param rx The received traffic for the {@link com.wireguard.config.Peer} referenced by
33-
* the provided {@link Key}. This value is in bytes
34-
* @param tx The transmitted traffic for the {@link com.wireguard.config.Peer} referenced by
35-
* the provided {@link Key}. This value is in bytes.
58+
* @param key A WireGuard public key bound to a particular peer
59+
* @param rxBytes The received traffic for the {@link com.wireguard.config.Peer} referenced by
60+
* the provided {@link Key}. This value is in bytes
61+
* @param txBytes The transmitted traffic for the {@link com.wireguard.config.Peer} referenced by
62+
* the provided {@link Key}. This value is in bytes.
63+
* @param latestHandshake The timestamp of the latest handshake for the {@link com.wireguard.config.Peer}
64+
* referenced by the provided {@link Key}. The value is in epoch milliseconds.
3665
*/
37-
void add(final Key key, final long rx, final long tx) {
38-
peerBytes.put(key, Pair.create(rx, tx));
66+
void add(final Key key, final long rxBytes, final long txBytes, final long latestHandshake) {
67+
stats.put(key, new PeerStats(rxBytes, txBytes, latestHandshake));
3968
lastTouched = SystemClock.elapsedRealtime();
4069
}
4170

@@ -49,31 +78,14 @@ public boolean isStale() {
4978
}
5079

5180
/**
52-
* Get the received traffic (in bytes) for the {@link com.wireguard.config.Peer} referenced by
53-
* the provided {@link Key}
54-
*
55-
* @param peer A {@link Key} representing a {@link com.wireguard.config.Peer}.
56-
* @return a long representing the number of bytes received by this peer.
57-
*/
58-
public long peerRx(final Key peer) {
59-
final Pair<Long, Long> rxTx = peerBytes.get(peer);
60-
if (rxTx == null)
61-
return 0;
62-
return rxTx.first;
63-
}
64-
65-
/**
66-
* Get the transmitted traffic (in bytes) for the {@link com.wireguard.config.Peer} referenced by
67-
* the provided {@link Key}
81+
* Get the statistics for the {@link com.wireguard.config.Peer} referenced by the provided {@link Key}
6882
*
6983
* @param peer A {@link Key} representing a {@link com.wireguard.config.Peer}.
70-
* @return a long representing the number of bytes transmitted by this peer.
84+
* @return a {@link PeerStats} representing various statistics about this peer.
7185
*/
72-
public long peerTx(final Key peer) {
73-
final Pair<Long, Long> rxTx = peerBytes.get(peer);
74-
if (rxTx == null)
75-
return 0;
76-
return rxTx.second;
86+
@Nullable
87+
public PeerStats peer(final Key peer) {
88+
return this.stats.get(peer);
7789
}
7890

7991
/**
@@ -83,7 +95,7 @@ public long peerTx(final Key peer) {
8395
* {@link com.wireguard.config.Peer}s
8496
*/
8597
public Key[] peers() {
86-
return peerBytes.keySet().toArray(new Key[0]);
98+
return stats.keySet().toArray(new Key[0]);
8799
}
88100

89101
/**
@@ -93,8 +105,8 @@ public Key[] peers() {
93105
*/
94106
public long totalRx() {
95107
long rx = 0;
96-
for (final Pair<Long, Long> val : peerBytes.values()) {
97-
rx += val.first;
108+
for (final PeerStats val : stats.values()) {
109+
rx += val.rxBytes;
98110
}
99111
return rx;
100112
}
@@ -106,8 +118,8 @@ public long totalRx() {
106118
*/
107119
public long totalTx() {
108120
long tx = 0;
109-
for (final Pair<Long, Long> val : peerBytes.values()) {
110-
tx += val.second;
121+
for (final PeerStats val : stats.values()) {
122+
tx += val.txBytes;
111123
}
112124
return tx;
113125
}

tunnel/src/main/java/com/wireguard/android/backend/WgQuickBackend.java

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@
2020
import java.io.File;
2121
import java.io.FileOutputStream;
2222
import java.nio.charset.StandardCharsets;
23+
import java.time.Instant;
2324
import java.util.ArrayList;
2425
import java.util.Collection;
2526
import java.util.Collections;
@@ -83,17 +84,17 @@ public Statistics getStatistics(final Tunnel tunnel) {
8384
final Statistics stats = new Statistics();
8485
final Collection<String> output = new ArrayList<>();
8586
try {
86-
if (rootShell.run(output, String.format("wg show '%s' transfer", tunnel.getName())) != 0)
87+
if (rootShell.run(output, String.format("wg show '%s' dump", tunnel.getName())) != 0)
8788
return stats;
8889
} catch (final Exception ignored) {
8990
return stats;
9091
}
9192
for (final String line : output) {
9293
final String[] parts = line.split("\\t");
93-
if (parts.length != 3)
94+
if (parts.length != 8)
9495
continue;
9596
try {
96-
stats.add(Key.fromBase64(parts[0]), Long.parseLong(parts[1]), Long.parseLong(parts[2]));
97+
stats.add(Key.fromBase64(parts[0]), Long.parseLong(parts[5]), Long.parseLong(parts[6]), Long.parseLong(parts[4]) * 1000);
9798
} catch (final Exception ignored) {
9899
}
99100
}

0 commit comments

Comments
 (0)