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

Commit 83088dcd authored by Remi NGUYEN VAN's avatar Remi NGUYEN VAN Committed by Gerrit Code Review
Browse files

Merge "Fix network usage stats on 464xlat tethered."

parents 18aa649b 9fb55e4f
Loading
Loading
Loading
Loading
+75 −0
Original line number Diff line number Diff line
@@ -31,6 +31,7 @@ import java.io.CharArrayWriter;
import java.io.PrintWriter;
import java.util.Arrays;
import java.util.HashSet;
import java.util.Map;
import java.util.Objects;

/**
@@ -97,6 +98,11 @@ public class NetworkStats implements Parcelable {
    /** Denotes a request for stats at the interface and UID level. */
    public static final int STATS_PER_UID = 1;

    private static final String CLATD_INTERFACE_PREFIX = "v4-";
    // Delta between IPv4 header (20b) and IPv6 header (40b).
    // Used for correct stats accounting on clatd interfaces.
    private static final int IPV4V6_HEADER_DELTA = 20;

    // TODO: move fields to "mVariable" notation

    /**
@@ -751,6 +757,75 @@ public class NetworkStats implements Parcelable {
        return result;
    }

    /**
     * Calculate and apply adjustments to captured statistics for 464xlat traffic counted twice.
     *
     * <p>This mutates both base and stacked traffic stats, to account respectively for
     * double-counted traffic and IPv4/IPv6 header size difference.
     *
     * <p>For 464xlat traffic, xt_qtaguid sees every IPv4 packet twice, once as a native IPv4
     * packet on the stacked interface, and once as translated to an IPv6 packet on the
     * base interface. For correct stats accounting on the base interface, every 464xlat
     * packet needs to be subtracted from the root UID on the base interface both for tx
     * and rx traffic (http://b/12249687, http:/b/33681750).
     *
     * <p>This method will behave fine if {@code stackedIfaces} is an non-synchronized but add-only
     * {@code ConcurrentHashMap}
     * @param baseTraffic Traffic on the base interfaces. Will be mutated.
     * @param stackedTraffic Stats with traffic stacked on top of our ifaces. Will also be mutated.
     * @param stackedIfaces Mapping ipv6if -> ipv4if interface where traffic is counted on both.
     */
    public static void apply464xlatAdjustments(NetworkStats baseTraffic,
            NetworkStats stackedTraffic, Map<String, String> stackedIfaces) {
        // Total 464xlat traffic to subtract from uid 0 on all base interfaces.
        // stackedIfaces may grow afterwards, but NetworkStats will just be resized automatically.
        final NetworkStats adjustments = new NetworkStats(0, stackedIfaces.size());

        // For recycling
        Entry entry = null;
        Entry adjust = new NetworkStats.Entry(IFACE_ALL, 0, 0, 0, 0, 0, 0, 0L, 0L, 0L, 0L, 0L);

        for (int i = 0; i < stackedTraffic.size; i++) {
            entry = stackedTraffic.getValues(i, entry);
            if (entry.iface == null || !entry.iface.startsWith(CLATD_INTERFACE_PREFIX)) {
                continue;
            }
            final String baseIface = stackedIfaces.get(entry.iface);
            if (baseIface == null) {
                continue;
            }
            // Subtract any 464lat traffic seen for the root UID on the current base interface.
            adjust.iface = baseIface;
            adjust.rxBytes = -(entry.rxBytes + entry.rxPackets * IPV4V6_HEADER_DELTA);
            adjust.txBytes = -(entry.txBytes + entry.txPackets * IPV4V6_HEADER_DELTA);
            adjust.rxPackets = -entry.rxPackets;
            adjust.txPackets = -entry.txPackets;
            adjustments.combineValues(adjust);

            // For 464xlat traffic, xt_qtaguid only counts the bytes of the native IPv4 packet sent
            // on the stacked interface with prefix "v4-" and drops the IPv6 header size after
            // unwrapping. To account correctly for on-the-wire traffic, add the 20 additional bytes
            // difference for all packets (http://b/12249687, http:/b/33681750).
            entry.rxBytes += entry.rxPackets * IPV4V6_HEADER_DELTA;
            entry.txBytes += entry.txPackets * IPV4V6_HEADER_DELTA;
            stackedTraffic.setValues(i, entry);
        }

        baseTraffic.combineAllValues(adjustments);
    }

    /**
     * Calculate and apply adjustments to captured statistics for 464xlat traffic counted twice.
     *
     * <p>This mutates the object this method is called on. Equivalent to calling
     * {@link #apply464xlatAdjustments(NetworkStats, NetworkStats, Map)} with {@code this} as
     * base and stacked traffic.
     * @param stackedIfaces Mapping ipv6if -> ipv4if interface where traffic is counted on both.
     */
    public void apply464xlatAdjustments(Map<String, String> stackedIfaces) {
        apply464xlatAdjustments(this, this, stackedIfaces);
    }

    /**
     * Return total statistics grouped by {@link #iface}; doesn't mutate the
     * original structure.
+13 −47
Original line number Diff line number Diff line
@@ -54,11 +54,6 @@ public class NetworkStatsFactory {
    private static final boolean USE_NATIVE_PARSING = true;
    private static final boolean SANITY_CHECK_NATIVE = false;

    private static final String CLATD_INTERFACE_PREFIX = "v4-";
    // Delta between IPv4 header (20b) and IPv6 header (40b).
    // Used for correct stats accounting on clatd interfaces.
    private static final int IPV4V6_HEADER_DELTA = 20;

    /** Path to {@code /proc/net/dev}. */
    private final File mStatsIfaceDev;
    /** Path to {@code /proc/net/xt_qtaguid/iface_stat_all}. */
@@ -94,7 +89,7 @@ public class NetworkStatsFactory {
     * {@link #noteStackedIface(String, String)}, but only interfaces noted before this method
     * is called are guaranteed to be included.
     */
    public static String[] augmentWithStackedInterfacesLocked(@Nullable String[] requiredIfaces) {
    public static String[] augmentWithStackedInterfaces(@Nullable String[] requiredIfaces) {
        if (requiredIfaces == NetworkStats.INTERFACES_ALL) {
            return null;
        }
@@ -116,6 +111,15 @@ public class NetworkStatsFactory {
        return relatedIfaces.toArray(outArray);
    }

    /**
     * Applies 464xlat adjustments with ifaces noted with {@link #noteStackedIface(String, String)}.
     * @see NetworkStats#apply464xlatAdjustments(NetworkStats, NetworkStats, Map)
     */
    public static void apply464xlatAdjustments(NetworkStats baseTraffic,
            NetworkStats stackedTraffic) {
        NetworkStats.apply464xlatAdjustments(baseTraffic, stackedTraffic, sStackedIfaces);
    }

    @VisibleForTesting
    public static void clearStackedIfaces() {
        sStackedIfaces.clear();
@@ -287,48 +291,10 @@ public class NetworkStatsFactory {
            NetworkStats lastStats) throws IOException {
        final NetworkStats stats =
              readNetworkStatsDetailInternal(limitUid, limitIfaces, limitTag, lastStats);
        // Total 464xlat traffic to subtract from uid 0 on all base interfaces.
        // sStackedIfaces may grow afterwards, but NetworkStats will just be resized automatically.
        final NetworkStats adjustments = new NetworkStats(0, sStackedIfaces.size());

        NetworkStats.Entry entry = null; // For recycling

        // For 464xlat traffic, xt_qtaguid sees every IPv4 packet twice, once as a native IPv4
        // packet on the stacked interface, and once as translated to an IPv6 packet on the
        // base interface. For correct stats accounting on the base interface, every 464xlat
        // packet needs to be subtracted from the root UID on the base interface both for tx
        // and rx traffic (http://b/12249687, http:/b/33681750).
        for (int i = 0; i < stats.size(); i++) {
            entry = stats.getValues(i, entry);
            if (entry.iface == null || !entry.iface.startsWith(CLATD_INTERFACE_PREFIX)) {
                continue;
            }
            final String baseIface = sStackedIfaces.get(entry.iface);
            if (baseIface == null) {
                continue;
            }

            NetworkStats.Entry adjust =
                    new NetworkStats.Entry(baseIface, 0, 0, 0, 0, 0, 0, 0L, 0L, 0L, 0L, 0L);
            // Subtract any 464lat traffic seen for the root UID on the current base interface.
            adjust.rxBytes -= (entry.rxBytes + entry.rxPackets * IPV4V6_HEADER_DELTA);
            adjust.txBytes -= (entry.txBytes + entry.txPackets * IPV4V6_HEADER_DELTA);
            adjust.rxPackets -= entry.rxPackets;
            adjust.txPackets -= entry.txPackets;
            adjustments.combineValues(adjust);

            // For 464xlat traffic, xt_qtaguid only counts the bytes of the native IPv4 packet sent
            // on the stacked interface with prefix "v4-" and drops the IPv6 header size after
            // unwrapping. To account correctly for on-the-wire traffic, add the 20 additional bytes
            // difference for all packets (http://b/12249687, http:/b/33681750).
            entry.rxBytes = entry.rxPackets * IPV4V6_HEADER_DELTA;
            entry.txBytes = entry.txPackets * IPV4V6_HEADER_DELTA;
            entry.rxPackets = 0;
            entry.txPackets = 0;
            stats.combineValues(entry);
        }

        stats.combineAllValues(adjustments);

        // No locking here: apply464xlatAdjustments behaves fine with an add-only ConcurrentHashMap.
        // TODO: remove this and only apply adjustments in NetworkStatsService.
        stats.apply464xlatAdjustments(sStackedIfaces);

        return stats;
    }
+4 −4
Original line number Diff line number Diff line
@@ -146,7 +146,6 @@ import java.io.PrintWriter;
import java.util.Arrays;
import java.util.HashSet;
import java.util.List;
import java.util.Map;

/**
 * Collect and persist detailed network statistics, and provide this data to
@@ -756,7 +755,7 @@ public class NetworkStatsService extends INetworkStatsService.Stub {
    public NetworkStats getDetailedUidStats(String[] requiredIfaces) {
        try {
            final String[] ifacesToQuery =
                    NetworkStatsFactory.augmentWithStackedInterfacesLocked(requiredIfaces);
                    NetworkStatsFactory.augmentWithStackedInterfaces(requiredIfaces);
            return getNetworkStatsUidDetail(ifacesToQuery);
        } catch (RemoteException e) {
            Log.wtf(TAG, "Error compiling UID stats", e);
@@ -1490,12 +1489,14 @@ public class NetworkStatsService extends INetworkStatsService.Stub {
    private NetworkStats getNetworkStatsUidDetail(String[] ifaces)
            throws RemoteException {

        // TODO: remove 464xlat adjustments from NetworkStatsFactory and apply all at once here.
        final NetworkStats uidSnapshot = mNetworkManager.getNetworkStatsUidDetail(UID_ALL,
                ifaces);

        // fold tethering stats and operations into uid snapshot
        final NetworkStats tetherSnapshot = getNetworkStatsTethering(STATS_PER_UID);
        tetherSnapshot.filter(UID_ALL, ifaces, TAG_ALL);
        NetworkStatsFactory.apply464xlatAdjustments(uidSnapshot, tetherSnapshot);
        uidSnapshot.combineAllValues(tetherSnapshot);

        final TelephonyManager telephonyManager = (TelephonyManager) mContext.getSystemService(
@@ -1505,13 +1506,12 @@ public class NetworkStatsService extends INetworkStatsService.Stub {
        final NetworkStats vtStats = telephonyManager.getVtDataUsage(STATS_PER_UID);
        if (vtStats != null) {
            vtStats.filter(UID_ALL, ifaces, TAG_ALL);
            NetworkStatsFactory.apply464xlatAdjustments(uidSnapshot, vtStats);
            uidSnapshot.combineAllValues(vtStats);
        }

        uidSnapshot.combineAllValues(mUidOperations);

        // TODO: apply tethering & VC 464xlat adjustments here

        return uidSnapshot;
    }

+84 −0
Original line number Diff line number Diff line
@@ -39,8 +39,10 @@ import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertTrue;
import static org.junit.Assert.fail;

import android.os.Process;
import android.support.test.runner.AndroidJUnit4;
import android.support.test.filters.SmallTest;
import android.util.ArrayMap;

import com.google.android.collect.Sets;

@@ -773,6 +775,88 @@ public class NetworkStatsTest {
        assertEquals(entry2, stats.getValues(1, null));
    }

    @Test
    public void testApply464xlatAdjustments() {
        final String v4Iface = "v4-wlan0";
        final String baseIface = "wlan0";
        final String otherIface = "other";
        final int appUid = 10001;
        final int rootUid = Process.ROOT_UID;
        ArrayMap<String, String> stackedIface = new ArrayMap<>();
        stackedIface.put(v4Iface, baseIface);

        NetworkStats.Entry otherEntry = new NetworkStats.Entry(
                otherIface, appUid, SET_DEFAULT, TAG_NONE,
                2600  /* rxBytes */,
                2 /* rxPackets */,
                3800 /* txBytes */,
                3 /* txPackets */,
                0 /* operations */);

        NetworkStats stats = new NetworkStats(TEST_START, 3)
                .addValues(v4Iface, appUid, SET_DEFAULT, TAG_NONE,
                        30501490  /* rxBytes */,
                        22401 /* rxPackets */,
                        876235 /* txBytes */,
                        13805 /* txPackets */,
                        0 /* operations */)
                .addValues(baseIface, rootUid, SET_DEFAULT, TAG_NONE,
                        31113087,
                        22588,
                        1169942,
                        13902,
                        0)
                .addValues(otherEntry);

        stats.apply464xlatAdjustments(stackedIface);

        assertEquals(3, stats.size());
        assertValues(stats, 0, v4Iface, appUid, SET_DEFAULT, TAG_NONE,
                METERED_NO, ROAMING_NO, DEFAULT_NETWORK_NO,
                30949510,
                22401,
                1152335,
                13805,
                0);
        assertValues(stats, 1, baseIface, 0, SET_DEFAULT, TAG_NONE,
                METERED_NO, ROAMING_NO, DEFAULT_NETWORK_NO,
                163577,
                187,
                17607,
                97,
                0);
        assertEquals(otherEntry, stats.getValues(2, null));
    }

    @Test
    public void testApply464xlatAdjustments_noStackedIface() {
        NetworkStats.Entry firstEntry = new NetworkStats.Entry(
                "if1", 10002, SET_DEFAULT, TAG_NONE,
                2600  /* rxBytes */,
                2 /* rxPackets */,
                3800 /* txBytes */,
                3 /* txPackets */,
                0 /* operations */);
        NetworkStats.Entry secondEntry = new NetworkStats.Entry(
                "if2", 10002, SET_DEFAULT, TAG_NONE,
                5000  /* rxBytes */,
                3 /* rxPackets */,
                6000 /* txBytes */,
                4 /* txPackets */,
                0 /* operations */);

        NetworkStats stats = new NetworkStats(TEST_START, 2)
                .addValues(firstEntry)
                .addValues(secondEntry);

        // Empty map: no adjustment
        stats.apply464xlatAdjustments(new ArrayMap<>());

        assertEquals(2, stats.size());
        assertEquals(firstEntry, stats.getValues(0, null));
        assertEquals(secondEntry, stats.getValues(1, null));
    }

    private static void assertContains(NetworkStats stats,  String iface, int uid, int set,
            int tag, int metered, int roaming, int defaultNetwork, long rxBytes, long rxPackets,
            long txBytes, long txPackets, long operations) {