From 9ba4d7429b5298b3b8c1facfd5bf9deb946858be Mon Sep 17 00:00:00 2001 From: Wenchao Tong Date: Thu, 26 Feb 2015 18:13:07 -0800 Subject: [PATCH] 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 --- core/java/android/net/NetworkStats.java | 160 ++++++++++++++++++++++++ 1 file changed, 160 insertions(+) diff --git a/core/java/android/net/NetworkStats.java b/core/java/android/net/NetworkStats.java index 2afe578b6b..0766253e63 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; + } }