NetworkStatsFactory: fix double accounting on IPv6 only networks

For 464xlat scenarios on IPv6 networks, the clatd interface setup
introduces double counting of apps ipv4 traffic. NetworkStatsFactory was
accounting for this on the tx path, but not on the rx path. Also it did
not accounted for the 20 bytes added by the IPv6 header.

This patch subtract correctly the rx and tx traffic from the root uid on
the underlying interface, and also adds correctly the 20 bytes cost per
packet on the stacked interface for 464xlat traffic.

Test: added several new unit tests, based on synthetic data and real
      data also.
Bug: 33681750
Change-Id: I4867fe181938d94b5594b3d88896a3c4e01d895c
Merged-In: I2675643b220acbc6110179fa937d4c313b6f5e32

(cherry picked from commit e1bb3a14d1ee1aba2f92e5bccf659774699eaffe)
diff --git a/core/java/com/android/internal/net/NetworkStatsFactory.java b/core/java/com/android/internal/net/NetworkStatsFactory.java
index 506114b..2fb3532 100644
--- a/core/java/com/android/internal/net/NetworkStatsFactory.java
+++ b/core/java/com/android/internal/net/NetworkStatsFactory.java
@@ -50,6 +50,11 @@
     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/xt_qtaguid/iface_stat_all}. */
     private final File mStatsXtIfaceAll;
     /** Path to {@code /proc/net/xt_qtaguid/iface_stat_fmt}. */
@@ -57,6 +62,7 @@
     /** Path to {@code /proc/net/xt_qtaguid/stats}. */
     private final File mStatsXtUid;
 
+    // TODO: to improve testability and avoid global state, do not use a static variable.
     @GuardedBy("sStackedIfaces")
     private static final ArrayMap<String, String> sStackedIfaces = new ArrayMap<>();
 
@@ -124,9 +130,7 @@
                 stats.addValues(entry);
                 reader.finishLine();
             }
-        } catch (NullPointerException e) {
-            throw new ProtocolException("problem parsing stats", e);
-        } catch (NumberFormatException e) {
+        } catch (NullPointerException|NumberFormatException e) {
             throw new ProtocolException("problem parsing stats", e);
         } finally {
             IoUtils.closeQuietly(reader);
@@ -171,9 +175,7 @@
                 stats.addValues(entry);
                 reader.finishLine();
             }
-        } catch (NullPointerException e) {
-            throw new ProtocolException("problem parsing stats", e);
-        } catch (NumberFormatException e) {
+        } catch (NullPointerException|NumberFormatException e) {
             throw new ProtocolException("problem parsing stats", e);
         } finally {
             IoUtils.closeQuietly(reader);
@@ -188,26 +190,32 @@
 
     public NetworkStats readNetworkStatsDetail(int limitUid, String[] limitIfaces, int limitTag,
             NetworkStats lastStats) throws IOException {
-        final NetworkStats stats = readNetworkStatsDetailInternal(limitUid, limitIfaces, limitTag,
-                lastStats);
+        final NetworkStats stats =
+              readNetworkStatsDetailInternal(limitUid, limitIfaces, limitTag, lastStats);
+        NetworkStats.Entry entry = null; // for recycling
 
         synchronized (sStackedIfaces) {
-            // Sigh, xt_qtaguid ends up double-counting tx traffic going through
-            // clatd interfaces, so we need to subtract it here.
+            // 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).
             final int size = sStackedIfaces.size();
             for (int i = 0; i < size; i++) {
                 final String stackedIface = sStackedIfaces.keyAt(i);
                 final String baseIface = sStackedIfaces.valueAt(i);
+                if (!stackedIface.startsWith(CLATD_INTERFACE_PREFIX)) {
+                    continue;
+                }
 
-                // 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;
+                NetworkStats.Entry adjust =
+                    new NetworkStats.Entry(baseIface, 0, 0, 0, 0L, 0L, 0L, 0L, 0L);
                 for (int j = 0; j < stats.size(); j++) {
                     entry = stats.getValues(j, entry);
                     if (Objects.equals(entry.iface, stackedIface)) {
-                        adjust.txBytes -= entry.txBytes;
+                        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;
                     }
                 }
@@ -215,19 +223,20 @@
             }
         }
 
-        // 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 464xlat traffic, xt_qtaguid only counts the bytes of the inner 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).
         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);
+            if (entry.iface == null || !entry.iface.startsWith(CLATD_INTERFACE_PREFIX)) {
+                continue;
             }
+            entry.rxBytes = entry.rxPackets * IPV4V6_HEADER_DELTA;
+            entry.txBytes = entry.txPackets * IPV4V6_HEADER_DELTA;
+            entry.rxPackets = 0;
+            entry.txPackets = 0;
+            stats.combineValues(entry);
         }
 
         return stats;
@@ -305,9 +314,7 @@
 
                 reader.finishLine();
             }
-        } catch (NullPointerException e) {
-            throw new ProtocolException("problem parsing idx " + idx, e);
-        } catch (NumberFormatException e) {
+        } catch (NullPointerException|NumberFormatException e) {
             throw new ProtocolException("problem parsing idx " + idx, e);
         } finally {
             IoUtils.closeQuietly(reader);