diff --git a/core/java/android/net/LinkProperties.java b/core/java/android/net/LinkProperties.java index 8be5cf8bd19e4..47b74ab4d23f4 100644 --- a/core/java/android/net/LinkProperties.java +++ b/core/java/android/net/LinkProperties.java @@ -16,6 +16,7 @@ package android.net; +import android.annotation.NonNull; import android.net.ProxyInfo; import android.os.Parcelable; import android.os.Parcel; @@ -24,7 +25,6 @@ import android.text.TextUtils; import java.net.InetAddress; import java.net.Inet4Address; import java.net.Inet6Address; - import java.net.UnknownHostException; import java.util.ArrayList; import java.util.Collection; @@ -480,10 +480,13 @@ public final class LinkProperties implements Parcelable { * Returns all the links stacked on top of this link. * @hide */ - public List getStackedLinks() { + public @NonNull List getStackedLinks() { + if (mStackedLinks.isEmpty()) { + return Collections.EMPTY_LIST; + } List stacked = new ArrayList(); for (LinkProperties link : mStackedLinks.values()) { - stacked.add(new LinkProperties(link)); + stacked.add(new LinkProperties(link)); } return Collections.unmodifiableList(stacked); } diff --git a/core/java/com/android/internal/net/NetworkStatsFactory.java b/core/java/com/android/internal/net/NetworkStatsFactory.java index e2a2b1eb2a5d3..506114bd01f3d 100644 --- a/core/java/com/android/internal/net/NetworkStatsFactory.java +++ b/core/java/com/android/internal/net/NetworkStatsFactory.java @@ -25,17 +25,20 @@ import static com.android.server.NetworkManagementSocketTagger.kernelToTag; import android.net.NetworkStats; import android.os.StrictMode; import android.os.SystemClock; +import android.util.ArrayMap; +import com.android.internal.annotations.GuardedBy; import com.android.internal.annotations.VisibleForTesting; import com.android.internal.util.ArrayUtils; import com.android.internal.util.ProcFileReader; +import libcore.io.IoUtils; + import java.io.File; import java.io.FileInputStream; import java.io.IOException; import java.net.ProtocolException; - -import libcore.io.IoUtils; +import java.util.Objects; /** * Creates {@link NetworkStats} instances by parsing various {@code /proc/} @@ -54,6 +57,19 @@ public class NetworkStatsFactory { /** Path to {@code /proc/net/xt_qtaguid/stats}. */ private final File mStatsXtUid; + @GuardedBy("sStackedIfaces") + private static final ArrayMap sStackedIfaces = new ArrayMap<>(); + + public static void noteStackedIface(String stackedIface, String baseIface) { + synchronized (sStackedIfaces) { + if (baseIface != null) { + sStackedIfaces.put(stackedIface, baseIface); + } else { + sStackedIfaces.remove(stackedIface); + } + } + } + public NetworkStatsFactory() { this(new File("/proc/")); } @@ -171,8 +187,54 @@ public class NetworkStatsFactory { } public NetworkStats readNetworkStatsDetail(int limitUid, String[] limitIfaces, int limitTag, - NetworkStats lastStats) - throws IOException { + NetworkStats lastStats) throws IOException { + final NetworkStats stats = readNetworkStatsDetailInternal(limitUid, limitIfaces, limitTag, + lastStats); + + synchronized (sStackedIfaces) { + // Sigh, xt_qtaguid ends up double-counting tx traffic going through + // clatd interfaces, so we need to subtract it here. + final int size = sStackedIfaces.size(); + for (int i = 0; i < size; i++) { + final String stackedIface = sStackedIfaces.keyAt(i); + final String baseIface = sStackedIfaces.valueAt(i); + + // Count up the tx traffic and subtract from root UID on the + // base interface. + NetworkStats.Entry adjust = new NetworkStats.Entry(baseIface, 0, 0, 0, 0L, 0L, 0L, + 0L, 0L); + NetworkStats.Entry entry = null; + for (int j = 0; j < stats.size(); j++) { + entry = stats.getValues(j, entry); + if (Objects.equals(entry.iface, stackedIface)) { + adjust.txBytes -= entry.txBytes; + adjust.txPackets -= entry.txPackets; + } + } + stats.combineValues(adjust); + } + } + + // Double sigh, all rx traffic on clat needs to be tweaked to + // account for the dropped IPv6 header size post-unwrap. + NetworkStats.Entry entry = null; + for (int i = 0; i < stats.size(); i++) { + entry = stats.getValues(i, entry); + if (entry.iface != null && entry.iface.startsWith("clat")) { + // Delta between IPv4 header (20b) and IPv6 header (40b) + entry.rxBytes = entry.rxPackets * 20; + entry.rxPackets = 0; + entry.txBytes = 0; + entry.txPackets = 0; + stats.combineValues(entry); + } + } + + return stats; + } + + private NetworkStats readNetworkStatsDetailInternal(int limitUid, String[] limitIfaces, + int limitTag, NetworkStats lastStats) throws IOException { if (USE_NATIVE_PARSING) { final NetworkStats stats; if (lastStats != null) { diff --git a/services/core/java/com/android/server/ConnectivityService.java b/services/core/java/com/android/server/ConnectivityService.java index f9baccdaeed87..08cb7914b792f 100644 --- a/services/core/java/com/android/server/ConnectivityService.java +++ b/services/core/java/com/android/server/ConnectivityService.java @@ -119,7 +119,9 @@ import android.util.Xml; import com.android.internal.R; import com.android.internal.annotations.GuardedBy; +import com.android.internal.app.IBatteryStats; import com.android.internal.net.LegacyVpnInfo; +import com.android.internal.net.NetworkStatsFactory; import com.android.internal.net.VpnConfig; import com.android.internal.net.VpnProfile; import com.android.internal.telephony.DctConstants; @@ -4476,12 +4478,23 @@ public class ConnectivityService extends IConnectivityManager.Stub { // TODO - read the tcp buffer size config string from somewhere // updateNetworkSettings(); } - // notify battery stats service about this network + + // Notify battery stats service about this network, both the normal + // interface and any stacked links. try { - BatteryStatsService.getService().noteNetworkInterfaceType( - newNetwork.linkProperties.getInterfaceName(), - newNetwork.networkInfo.getType()); - } catch (RemoteException e) { } + final IBatteryStats bs = BatteryStatsService.getService(); + final int type = newNetwork.networkInfo.getType(); + + final String baseIface = newNetwork.linkProperties.getInterfaceName(); + bs.noteNetworkInterfaceType(baseIface, type); + for (LinkProperties stacked : newNetwork.linkProperties.getStackedLinks()) { + final String stackedIface = stacked.getInterfaceName(); + bs.noteNetworkInterfaceType(stackedIface, type); + NetworkStatsFactory.noteStackedIface(stackedIface, baseIface); + } + } catch (RemoteException ignored) { + } + notifyNetworkCallbacks(newNetwork, ConnectivityManager.CALLBACK_AVAILABLE); } else { if (DBG && newNetwork.networkRequests.size() != 0) { diff --git a/services/core/java/com/android/server/net/NetworkPolicyManagerService.java b/services/core/java/com/android/server/net/NetworkPolicyManagerService.java index 0da1fa4ecdcd2..ad2bb926a4322 100644 --- a/services/core/java/com/android/server/net/NetworkPolicyManagerService.java +++ b/services/core/java/com/android/server/net/NetworkPolicyManagerService.java @@ -38,8 +38,8 @@ import static android.net.NetworkPolicy.LIMIT_DISABLED; import static android.net.NetworkPolicy.SNOOZE_NEVER; import static android.net.NetworkPolicy.WARNING_DISABLED; import static android.net.NetworkPolicyManager.EXTRA_NETWORK_TEMPLATE; -import static android.net.NetworkPolicyManager.POLICY_NONE; import static android.net.NetworkPolicyManager.POLICY_ALLOW_BACKGROUND_BATTERY_SAVE; +import static android.net.NetworkPolicyManager.POLICY_NONE; import static android.net.NetworkPolicyManager.POLICY_REJECT_METERED_BACKGROUND; import static android.net.NetworkPolicyManager.RULE_ALLOW_ALL; import static android.net.NetworkPolicyManager.RULE_REJECT_METERED; @@ -96,6 +96,7 @@ import android.net.INetworkManagementEventObserver; import android.net.INetworkPolicyListener; import android.net.INetworkPolicyManager; import android.net.INetworkStatsService; +import android.net.LinkProperties; import android.net.NetworkIdentity; import android.net.NetworkInfo; import android.net.NetworkPolicy; @@ -127,6 +128,7 @@ import android.util.ArraySet; import android.util.AtomicFile; import android.util.Log; import android.util.NtpTrustedTime; +import android.util.Pair; import android.util.Slog; import android.util.SparseArray; import android.util.SparseBooleanArray; @@ -142,6 +144,8 @@ import com.android.server.LocalServices; import com.android.server.SystemConfig; import com.google.android.collect.Lists; +import libcore.io.IoUtils; + import org.xmlpull.v1.XmlPullParser; import org.xmlpull.v1.XmlPullParserException; import org.xmlpull.v1.XmlSerializer; @@ -158,8 +162,6 @@ import java.util.Arrays; import java.util.List; import java.util.Objects; -import libcore.io.IoUtils; - /** * Service that maintains low-level network policy rules, using * {@link NetworkStatsService} statistics to drive those rules. @@ -1049,36 +1051,44 @@ public class NetworkPolicyManagerService extends INetworkPolicyManager.Stub { // use over those networks can have a cost associated with it). final boolean powerSave = mRestrictPower && !mRestrictBackground; - // first, derive identity for all connected networks, which can be used - // to match against templates. - final ArrayMap networks = new ArrayMap(states.length); + // First, generate identities of all connected networks so we can + // quickly compare them against all defined policies below. + final ArrayList> connIdents = new ArrayList<>(states.length); final ArraySet connIfaces = new ArraySet(states.length); for (NetworkState state : states) { - // stash identity and iface away for later use if (state.networkInfo.isConnected()) { - final String iface = state.linkProperties.getInterfaceName(); final NetworkIdentity ident = NetworkIdentity.buildNetworkIdentity(mContext, state); - networks.put(ident, iface); + + final String baseIface = state.linkProperties.getInterfaceName(); + connIdents.add(Pair.create(baseIface, ident)); if (powerSave) { - connIfaces.add(iface); + connIfaces.add(baseIface); + } + + // Stacked interfaces are considered to have same identity as + // their parent network. + final List stackedLinks = state.linkProperties.getStackedLinks(); + for (LinkProperties stackedLink : stackedLinks) { + final String stackedIface = stackedLink.getInterfaceName(); + connIdents.add(Pair.create(stackedIface, ident)); + if (powerSave) { + connIfaces.add(stackedIface); + } } } } - // build list of rules and ifaces to enforce them against + // Apply policies against all connected interfaces found above mNetworkRules.clear(); final ArrayList ifaceList = Lists.newArrayList(); - for (int i = mNetworkPolicy.size()-1; i >= 0; i--) { + for (int i = mNetworkPolicy.size() - 1; i >= 0; i--) { final NetworkPolicy policy = mNetworkPolicy.valueAt(i); - // collect all active ifaces that match this template ifaceList.clear(); - for (int j = networks.size()-1; j >= 0; j--) { - final NetworkIdentity ident = networks.keyAt(j); - if (policy.template.matches(ident)) { - final String iface = networks.valueAt(j); - ifaceList.add(iface); + for (int j = connIdents.size() - 1; j >= 0; j--) { + final Pair ident = connIdents.get(j); + if (policy.template.matches(ident.second)) { + ifaceList.add(ident.first); } } @@ -1787,6 +1797,8 @@ public class NetworkPolicyManagerService extends INetworkPolicyManager.Stub { } fout.decreaseIndent(); + fout.print("Metered ifaces: "); fout.println(String.valueOf(mMeteredIfaces)); + fout.println("Policy for UIDs:"); fout.increaseIndent(); int size = mUidPolicy.size(); diff --git a/services/core/java/com/android/server/net/NetworkStatsService.java b/services/core/java/com/android/server/net/NetworkStatsService.java index 271e9e9b22c71..e35ca468cdbd5 100644 --- a/services/core/java/com/android/server/net/NetworkStatsService.java +++ b/services/core/java/com/android/server/net/NetworkStatsService.java @@ -62,8 +62,6 @@ import static android.text.format.DateUtils.DAY_IN_MILLIS; import static android.text.format.DateUtils.HOUR_IN_MILLIS; import static android.text.format.DateUtils.MINUTE_IN_MILLIS; import static android.text.format.DateUtils.SECOND_IN_MILLIS; -import static com.android.internal.util.ArrayUtils.appendElement; -import static com.android.internal.util.ArrayUtils.contains; import static com.android.internal.util.Preconditions.checkNotNull; import static com.android.server.NetworkManagementService.LIMIT_GLOBAL_ALERT; import static com.android.server.NetworkManagementSocketTagger.resetKernelUidStats; @@ -107,6 +105,8 @@ import android.provider.Settings; import android.provider.Settings.Global; import android.telephony.PhoneStateListener; import android.telephony.TelephonyManager; +import android.util.ArrayMap; +import android.util.ArraySet; import android.util.EventLog; import android.util.Log; import android.util.MathUtils; @@ -121,14 +121,12 @@ import com.android.internal.util.FileRotator; import com.android.internal.util.IndentingPrintWriter; import com.android.server.EventLogTags; import com.android.server.connectivity.Tethering; -import com.google.android.collect.Maps; import java.io.File; import java.io.FileDescriptor; import java.io.IOException; import java.io.PrintWriter; import java.util.Arrays; -import java.util.HashMap; import java.util.HashSet; import java.util.List; @@ -215,7 +213,9 @@ public class NetworkStatsService extends INetworkStatsService.Stub { private final Object mStatsLock = new Object(); /** Set of currently active ifaces. */ - private HashMap mActiveIfaces = Maps.newHashMap(); + private final ArrayMap mActiveIfaces = new ArrayMap<>(); + /** Set of currently active ifaces for UID stats. */ + private final ArrayMap mActiveUidIfaces = new ArrayMap<>(); /** Current default active iface. */ private String mActiveIface; /** Set of any ifaces associated with mobile networks since boot. */ @@ -883,30 +883,52 @@ public class NetworkStatsService extends INetworkStatsService.Stub { mActiveIface = activeLink != null ? activeLink.getInterfaceName() : null; - // rebuild active interfaces based on connected networks + // Rebuild active interfaces based on connected networks mActiveIfaces.clear(); + mActiveUidIfaces.clear(); + final ArraySet mobileIfaces = new ArraySet<>(); for (NetworkState state : states) { if (state.networkInfo.isConnected()) { - // collect networks under their parent interfaces - final String iface = state.linkProperties.getInterfaceName(); + final boolean isMobile = isNetworkTypeMobile(state.networkInfo.getType()); + final NetworkIdentity ident = NetworkIdentity.buildNetworkIdentity(mContext, state); - NetworkIdentitySet ident = mActiveIfaces.get(iface); - if (ident == null) { - ident = new NetworkIdentitySet(); - mActiveIfaces.put(iface, ident); + // Traffic occurring on the base interface is always counted for + // both total usage and UID details. + final String baseIface = state.linkProperties.getInterfaceName(); + findOrCreateNetworkIdentitySet(mActiveIfaces, baseIface).add(ident); + findOrCreateNetworkIdentitySet(mActiveUidIfaces, baseIface).add(ident); + if (isMobile) { + mobileIfaces.add(baseIface); } - ident.add(NetworkIdentity.buildNetworkIdentity(mContext, state)); - - // remember any ifaces associated with mobile networks - if (isNetworkTypeMobile(state.networkInfo.getType()) && iface != null) { - if (!contains(mMobileIfaces, iface)) { - mMobileIfaces = appendElement(String.class, mMobileIfaces, iface); + // Traffic occurring on stacked interfaces is usually clatd, + // which is already accounted against its final egress interface + // by the kernel. Thus, we only need to collect stacked + // interface stats at the UID level. + final List stackedLinks = state.linkProperties.getStackedLinks(); + for (LinkProperties stackedLink : stackedLinks) { + final String stackedIface = stackedLink.getInterfaceName(); + findOrCreateNetworkIdentitySet(mActiveUidIfaces, stackedIface).add(ident); + if (isMobile) { + mobileIfaces.add(stackedIface); } } } } + + mobileIfaces.remove(null); + mMobileIfaces = mobileIfaces.toArray(new String[mobileIfaces.size()]); + } + + private static NetworkIdentitySet findOrCreateNetworkIdentitySet( + ArrayMap map, K key) { + NetworkIdentitySet ident = map.get(key); + if (ident == null) { + ident = new NetworkIdentitySet(); + map.put(key, ident); + } + return ident; } /** @@ -926,8 +948,8 @@ public class NetworkStatsService extends INetworkStatsService.Stub { mDevRecorder.recordSnapshotLocked(devSnapshot, mActiveIfaces, currentTime); mXtRecorder.recordSnapshotLocked(xtSnapshot, mActiveIfaces, currentTime); - mUidRecorder.recordSnapshotLocked(uidSnapshot, mActiveIfaces, currentTime); - mUidTagRecorder.recordSnapshotLocked(uidSnapshot, mActiveIfaces, currentTime); + mUidRecorder.recordSnapshotLocked(uidSnapshot, mActiveUidIfaces, currentTime); + mUidTagRecorder.recordSnapshotLocked(uidSnapshot, mActiveUidIfaces, currentTime); } catch (IllegalStateException e) { Slog.w(TAG, "problem reading network stats: " + e); @@ -980,8 +1002,8 @@ public class NetworkStatsService extends INetworkStatsService.Stub { mDevRecorder.recordSnapshotLocked(devSnapshot, mActiveIfaces, currentTime); mXtRecorder.recordSnapshotLocked(xtSnapshot, mActiveIfaces, currentTime); - mUidRecorder.recordSnapshotLocked(uidSnapshot, mActiveIfaces, currentTime); - mUidTagRecorder.recordSnapshotLocked(uidSnapshot, mActiveIfaces, currentTime); + mUidRecorder.recordSnapshotLocked(uidSnapshot, mActiveUidIfaces, currentTime); + mUidTagRecorder.recordSnapshotLocked(uidSnapshot, mActiveUidIfaces, currentTime); } catch (IllegalStateException e) { Log.wtf(TAG, "problem reading network stats", e); @@ -1136,10 +1158,19 @@ public class NetworkStatsService extends INetworkStatsService.Stub { pw.println("Active interfaces:"); pw.increaseIndent(); - for (String iface : mActiveIfaces.keySet()) { - final NetworkIdentitySet ident = mActiveIfaces.get(iface); - pw.print("iface="); pw.print(iface); - pw.print(" ident="); pw.println(ident.toString()); + for (int i = 0; i < mActiveIfaces.size(); i++) { + pw.printPair("iface", mActiveIfaces.keyAt(i)); + pw.printPair("ident", mActiveIfaces.valueAt(i)); + pw.println(); + } + pw.decreaseIndent(); + + pw.println("Active UID interfaces:"); + pw.increaseIndent(); + for (int i = 0; i < mActiveUidIfaces.size(); i++) { + pw.printPair("iface", mActiveUidIfaces.keyAt(i)); + pw.printPair("ident", mActiveUidIfaces.valueAt(i)); + pw.println(); } pw.decreaseIndent();