Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit eb2c2c79 authored by Jeff Sharkey's avatar Jeff Sharkey
Browse files

Hack and ship: NetworkStats edition.

Some devices use clatd for catching raw IPv4 traffic when running on
a pure-IPv6 carrier network.  In those situations, the per-UID
stats are accounted against the clat iface, so framework users need
to combine both the "base" and "stacked" iface usage together.

This also means that policy rules (like restricting background data
or battery saver) need to apply to the stacked ifaces.

Finally, we need to massage stats data slightly:

-- Currently xt_qtaguid double-counts the clatd traffic *leaving*
the device; both against the original UID on the clat iface, and
against UID 0 on the final egress interface.

-- All clatd traffic *arriving* at the device is missing the extra
IPv6 packet header overhead when accounted against the final UID.

Bug: 12249687, 15459248, 16296564
Change-Id: I0ee59d96831f52782de7a980e4cce9b061902fff
parent fdd78e06
Loading
Loading
Loading
Loading
+6 −3
Original line number Diff line number Diff line
@@ -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,7 +480,10 @@ public final class LinkProperties implements Parcelable {
     * Returns all the links stacked on top of this link.
     * @hide
     */
    public List<LinkProperties> getStackedLinks() {
    public @NonNull List<LinkProperties> getStackedLinks() {
        if (mStackedLinks.isEmpty()) {
            return Collections.EMPTY_LIST;
        }
        List<LinkProperties> stacked = new ArrayList<LinkProperties>();
        for (LinkProperties link : mStackedLinks.values()) {
            stacked.add(new LinkProperties(link));
+66 −4
Original line number Diff line number Diff line
@@ -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<String, String> 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) {
+18 −5
Original line number Diff line number Diff line
@@ -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) {
+31 −19
Original line number Diff line number Diff line
@@ -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<NetworkIdentity, String> networks = new ArrayMap<NetworkIdentity,
                String>(states.length);
        // First, generate identities of all connected networks so we can
        // quickly compare them against all defined policies below.
        final ArrayList<Pair<String, NetworkIdentity>> connIdents = new ArrayList<>(states.length);
        final ArraySet<String> connIfaces = new ArraySet<String>(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<LinkProperties> 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<String> ifaceList = Lists.newArrayList();
        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<String, NetworkIdentity> 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();
+58 −27
Original line number Diff line number Diff line
@@ -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<String, NetworkIdentitySet> mActiveIfaces = Maps.newHashMap();
    private final ArrayMap<String, NetworkIdentitySet> mActiveIfaces = new ArrayMap<>();
    /** Set of currently active ifaces for UID stats. */
    private final ArrayMap<String, NetworkIdentitySet> 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<String> 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<LinkProperties> 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 <K> NetworkIdentitySet findOrCreateNetworkIdentitySet(
            ArrayMap<K, NetworkIdentitySet> 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();