summaryrefslogtreecommitdiffstats
path: root/core
diff options
context:
space:
mode:
authorWenchao Tong <tongwenchao@google.com>2015-02-26 18:13:07 -0800
committerWenchao Tong <tongwenchao@google.com>2015-03-09 11:38:10 -0700
commit21377c3af8ab683abac3e43006bce74498a37c9c (patch)
tree46a16652991f5ca4ebbb21d1acefcc42ea7c3cd2 /core
parent5d8a69f2774d5843704f18f6ff608edc55051e9d (diff)
downloadframeworks_base-21377c3af8ab683abac3e43006bce74498a37c9c.zip
frameworks_base-21377c3af8ab683abac3e43006bce74498a37c9c.tar.gz
frameworks_base-21377c3af8ab683abac3e43006bce74498a37c9c.tar.bz2
NetworkStats to support VPN accounting.
Create a new method to migrate underlying network traffic from VPN app to other apps. Bug: 19536273 Change-Id: I3434cad361592e26b01225edf8012f7b16afc98f
Diffstat (limited to 'core')
-rw-r--r--core/java/android/net/NetworkStats.java160
-rw-r--r--core/tests/coretests/src/android/net/NetworkStatsTest.java67
2 files changed, 227 insertions, 0 deletions
diff --git a/core/java/android/net/NetworkStats.java b/core/java/android/net/NetworkStats.java
index 2afe578..0766253 100644
--- a/core/java/android/net/NetworkStats.java
+++ b/core/java/android/net/NetworkStats.java
@@ -19,6 +19,7 @@ package android.net;
import android.os.Parcel;
import android.os.Parcelable;
import android.os.SystemClock;
+import android.util.Slog;
import android.util.SparseBooleanArray;
import com.android.internal.annotations.VisibleForTesting;
@@ -42,6 +43,7 @@ import java.util.Objects;
* @hide
*/
public class NetworkStats implements Parcelable {
+ private static final String TAG = "NetworkStats";
/** {@link #iface} value when interface details unavailable. */
public static final String IFACE_ALL = null;
/** {@link #uid} value when UID details unavailable. */
@@ -783,4 +785,162 @@ public class NetworkStats implements Parcelable {
public void foundNonMonotonic(
NetworkStats left, int leftIndex, NetworkStats right, int rightIndex, C cookie);
}
+
+ /**
+ * VPN accounting. Move some VPN's underlying traffic to other UIDs that use tun0 iface.
+ *
+ * This method should only be called on delta NetworkStats. Do not call this method on a
+ * snapshot {@link NetworkStats} object because the tunUid and/or the underlyingIface may
+ * change over time.
+ *
+ * This method performs adjustments for one active VPN package and one VPN iface at a time.
+ *
+ * It is possible for the VPN software to use multiple underlying networks. This method
+ * only migrates traffic for the primary underlying network.
+ *
+ * @param tunUid uid of the VPN application
+ * @param tunIface iface of the vpn tunnel
+ * @param underlyingIface the primary underlying network iface used by the VPN application
+ * @return true if it successfully adjusts the accounting for VPN, false otherwise
+ */
+ public boolean migrateTun(int tunUid, String tunIface, String underlyingIface) {
+ Entry tunIfaceTotal = new Entry();
+ Entry underlyingIfaceTotal = new Entry();
+
+ tunAdjustmentInit(tunUid, tunIface, underlyingIface, tunIfaceTotal, underlyingIfaceTotal);
+
+ // If tunIface < underlyingIface, it leaves the overhead traffic in the VPN app.
+ // If tunIface > underlyingIface, the VPN app doesn't get credit for data compression.
+ // Negative stats should be avoided.
+ Entry pool = tunGetPool(tunIfaceTotal, underlyingIfaceTotal);
+ if (pool.isEmpty()) {
+ return true;
+ }
+ Entry moved = addTrafficToApplications(tunIface, underlyingIface, tunIfaceTotal, pool);
+ deductTrafficFromVpnApp(tunUid, underlyingIface, moved);
+
+ if (!moved.isEmpty()) {
+ Slog.wtf(TAG, "Failed to deduct underlying network traffic from VPN package. Moved="
+ + moved);
+ return false;
+ }
+ return true;
+ }
+
+ /**
+ * Initializes the data used by the migrateTun() method.
+ *
+ * This is the first pass iteration which does the following work:
+ * (1) Adds up all the traffic through tun0.
+ * (2) Adds up all the traffic through the tunUid's underlyingIface
+ * (both foreground and background).
+ */
+ private void tunAdjustmentInit(int tunUid, String tunIface, String underlyingIface,
+ Entry tunIfaceTotal, Entry underlyingIfaceTotal) {
+ Entry recycle = new Entry();
+ for (int i = 0; i < size; i++) {
+ getValues(i, recycle);
+ if (recycle.uid == UID_ALL) {
+ throw new IllegalStateException(
+ "Cannot adjust VPN accounting on an iface aggregated NetworkStats.");
+ }
+
+ if (recycle.uid == tunUid && recycle.tag == TAG_NONE
+ && Objects.equals(underlyingIface, recycle.iface)) {
+ underlyingIfaceTotal.add(recycle);
+ }
+
+ if (recycle.tag == TAG_NONE && Objects.equals(tunIface, recycle.iface)) {
+ // Add up all tunIface traffic.
+ tunIfaceTotal.add(recycle);
+ }
+ }
+ }
+
+ private static Entry tunGetPool(Entry tunIfaceTotal, Entry underlyingIfaceTotal) {
+ Entry pool = new Entry();
+ pool.rxBytes = Math.min(tunIfaceTotal.rxBytes, underlyingIfaceTotal.rxBytes);
+ pool.rxPackets = Math.min(tunIfaceTotal.rxPackets, underlyingIfaceTotal.rxPackets);
+ pool.txBytes = Math.min(tunIfaceTotal.txBytes, underlyingIfaceTotal.txBytes);
+ pool.txPackets = Math.min(tunIfaceTotal.txPackets, underlyingIfaceTotal.txPackets);
+ pool.operations = Math.min(tunIfaceTotal.operations, underlyingIfaceTotal.operations);
+ return pool;
+ }
+
+ private Entry addTrafficToApplications(String tunIface, String underlyingIface,
+ Entry tunIfaceTotal, Entry pool) {
+ Entry moved = new Entry();
+ Entry tmpEntry = new Entry();
+ tmpEntry.iface = underlyingIface;
+ for (int i = 0; i < size; i++) {
+ if (Objects.equals(iface[i], tunIface)) {
+ if (tunIfaceTotal.rxBytes > 0) {
+ tmpEntry.rxBytes = pool.rxBytes * rxBytes[i] / tunIfaceTotal.rxBytes;
+ } else {
+ tmpEntry.rxBytes = 0;
+ }
+ if (tunIfaceTotal.rxPackets > 0) {
+ tmpEntry.rxPackets = pool.rxPackets * rxPackets[i] / tunIfaceTotal.rxPackets;
+ } else {
+ tmpEntry.rxPackets = 0;
+ }
+ if (tunIfaceTotal.txBytes > 0) {
+ tmpEntry.txBytes = pool.txBytes * txBytes[i] / tunIfaceTotal.txBytes;
+ } else {
+ tmpEntry.txBytes = 0;
+ }
+ if (tunIfaceTotal.txPackets > 0) {
+ tmpEntry.txPackets = pool.txPackets * txPackets[i] / tunIfaceTotal.txPackets;
+ } else {
+ tmpEntry.txPackets = 0;
+ }
+ if (tunIfaceTotal.operations > 0) {
+ tmpEntry.operations =
+ pool.operations * operations[i] / tunIfaceTotal.operations;
+ } else {
+ tmpEntry.operations = 0;
+ }
+ tmpEntry.uid = uid[i];
+ tmpEntry.tag = tag[i];
+ tmpEntry.set = set[i];
+ combineValues(tmpEntry);
+ if (tag[i] == TAG_NONE) {
+ moved.add(tmpEntry);
+ }
+ }
+ }
+ return moved;
+ }
+
+ private void deductTrafficFromVpnApp(int tunUid, String underlyingIface, Entry moved) {
+ // Caveat: if the vpn software uses tag, the total tagged traffic may be greater than
+ // the TAG_NONE traffic.
+ int idxVpnBackground = findIndex(underlyingIface, tunUid, SET_DEFAULT, TAG_NONE);
+ if (idxVpnBackground != -1) {
+ tunSubtract(idxVpnBackground, this, moved);
+ }
+
+ int idxVpnForeground = findIndex(underlyingIface, tunUid, SET_FOREGROUND, TAG_NONE);
+ if (idxVpnForeground != -1) {
+ tunSubtract(idxVpnForeground, this, moved);
+ }
+ }
+
+ private static void tunSubtract(int i, NetworkStats left, Entry right) {
+ long rxBytes = Math.min(left.rxBytes[i], right.rxBytes);
+ left.rxBytes[i] -= rxBytes;
+ right.rxBytes -= rxBytes;
+
+ long rxPackets = Math.min(left.rxPackets[i], right.rxPackets);
+ left.rxPackets[i] -= rxPackets;
+ right.rxPackets -= rxPackets;
+
+ long txBytes = Math.min(left.txBytes[i], right.txBytes);
+ left.txBytes[i] -= txBytes;
+ right.txBytes -= txBytes;
+
+ long txPackets = Math.min(left.txPackets[i], right.txPackets);
+ left.txPackets[i] -= txPackets;
+ right.txPackets -= txPackets;
+ }
}
diff --git a/core/tests/coretests/src/android/net/NetworkStatsTest.java b/core/tests/coretests/src/android/net/NetworkStatsTest.java
index 9ee4e20..fd922a2 100644
--- a/core/tests/coretests/src/android/net/NetworkStatsTest.java
+++ b/core/tests/coretests/src/android/net/NetworkStatsTest.java
@@ -320,6 +320,73 @@ public class NetworkStatsTest extends TestCase {
red.combineAllValues(blue);
}
+ public void testMigrateTun() throws Exception {
+ final int tunUid = 10030;
+ final String tunIface = "tun0";
+ final String underlyingIface = "wlan0";
+ final int testTag1 = 8888;
+ NetworkStats delta = new NetworkStats(TEST_START, 17)
+ .addValues(tunIface, 10100, SET_DEFAULT, TAG_NONE, 39605L, 46L, 12259L, 55L, 0L)
+ .addValues(tunIface, 10100, SET_FOREGROUND, TAG_NONE, 0L, 0L, 0L, 0L, 0L)
+ .addValues(tunIface, 10120, SET_DEFAULT, TAG_NONE, 72667L, 197L, 43909L, 241L, 0L)
+ .addValues(tunIface, 10120, SET_FOREGROUND, TAG_NONE, 9297L, 17L, 4128L, 21L, 0L)
+ // VPN package also uses some traffic through unprotected network.
+ .addValues(tunIface, tunUid, SET_DEFAULT, TAG_NONE, 4983L, 10L, 1801L, 12L, 0L)
+ .addValues(tunIface, tunUid, SET_FOREGROUND, TAG_NONE, 0L, 0L, 0L, 0L, 0L)
+ // Tag entries
+ .addValues(tunIface, 10120, SET_DEFAULT, testTag1, 21691L, 41L, 13820L, 51L, 0L)
+ .addValues(tunIface, 10120, SET_FOREGROUND, testTag1, 1281L, 2L, 665L, 2L, 0L)
+ // Irrelevant entries
+ .addValues(TEST_IFACE, 10100, SET_DEFAULT, TAG_NONE, 1685L, 5L, 2070L, 6L, 0L)
+ // Underlying Iface entries
+ .addValues(underlyingIface, 10100, SET_DEFAULT, TAG_NONE, 5178L, 8L, 2139L, 11L, 0L)
+ .addValues(underlyingIface, 10100, SET_FOREGROUND, TAG_NONE, 0L, 0L, 0L, 0L, 0L)
+ .addValues(underlyingIface, tunUid, SET_DEFAULT, TAG_NONE, 149873L, 287L,
+ 59217L /* smaller than sum(tun0) */, 299L /* smaller than sum(tun0) */, 0L)
+ .addValues(underlyingIface, tunUid, SET_FOREGROUND, TAG_NONE, 0L, 0L, 0L, 0L, 0L);
+
+ assertTrue(delta.migrateTun(tunUid, tunIface, underlyingIface));
+ assertEquals(17, delta.size());
+
+ // tunIface and TEST_IFACE entries are not changed.
+ assertValues(delta, 0, tunIface, 10100, SET_DEFAULT, TAG_NONE,
+ 39605L, 46L, 12259L, 55L, 0L);
+ assertValues(delta, 1, tunIface, 10100, SET_FOREGROUND, TAG_NONE, 0L, 0L, 0L, 0L, 0L);
+ assertValues(delta, 2, tunIface, 10120, SET_DEFAULT, TAG_NONE,
+ 72667L, 197L, 43909L, 241L, 0L);
+ assertValues(delta, 3, tunIface, 10120, SET_FOREGROUND, TAG_NONE,
+ 9297L, 17L, 4128L, 21L, 0L);
+ assertValues(delta, 4, tunIface, tunUid, SET_DEFAULT, TAG_NONE,
+ 4983L, 10L, 1801L, 12L, 0L);
+ assertValues(delta, 5, tunIface, tunUid, SET_FOREGROUND, TAG_NONE, 0L, 0L, 0L, 0L, 0L);
+ assertValues(delta, 6, tunIface, 10120, SET_DEFAULT, testTag1,
+ 21691L, 41L, 13820L, 51L, 0L);
+ assertValues(delta, 7, tunIface, 10120, SET_FOREGROUND, testTag1, 1281L, 2L, 665L, 2L, 0L);
+ assertValues(delta, 8, TEST_IFACE, 10100, SET_DEFAULT, TAG_NONE, 1685L, 5L, 2070L, 6L, 0L);
+
+ // Existing underlying Iface entries are updated
+ assertValues(delta, 9, underlyingIface, 10100, SET_DEFAULT, TAG_NONE,
+ 44783L, 54L, 13829L, 60L, 0L);
+ assertValues(delta, 10, underlyingIface, 10100, SET_FOREGROUND, TAG_NONE,
+ 0L, 0L, 0L, 0L, 0L);
+
+ // VPN underlying Iface entries are updated
+ assertValues(delta, 11, underlyingIface, tunUid, SET_DEFAULT, TAG_NONE,
+ 28304L, 27L, 1719L, 12L, 0L);
+ assertValues(delta, 12, underlyingIface, tunUid, SET_FOREGROUND, TAG_NONE,
+ 0L, 0L, 0L, 0L, 0L);
+
+ // New entries are added for new application's underlying Iface traffic
+ assertValues(delta, 13, underlyingIface, 10120, SET_DEFAULT, TAG_NONE,
+ 72667L, 197L, 41872l, 219L, 0L);
+ assertValues(delta, 14, underlyingIface, 10120, SET_FOREGROUND, TAG_NONE,
+ 9297L, 17L, 3936, 19L, 0L);
+ assertValues(delta, 15, underlyingIface, 10120, SET_DEFAULT, testTag1,
+ 21691L, 41L, 13179L, 46L, 0L);
+ assertValues(delta, 16, underlyingIface, 10120, SET_FOREGROUND, testTag1,
+ 1281L, 2L, 634L, 1L, 0L);
+ }
+
private static void assertValues(NetworkStats stats, int index, String iface, int uid, int set,
int tag, long rxBytes, long rxPackets, long txBytes, long txPackets, long operations) {
final NetworkStats.Entry entry = stats.getValues(index, null);