Add support for app data accounting for in-kernel dataplanes
This change ensures that app data accounting works correctly within the
confines of in-kernel dataplanes, as used by platform VPNs and the VCN.
Notably, the VCN MUST NOT specify the IMSI, as that would lead to double
counting of the interface statistics.
Bug: 175853498
Bug: 190620024
Test: atest NetworkStatsTest FrameworksVcnTests
Change-Id: I768907cd3dd2028c7040cddd81fc71a5ce69bbdb
diff --git a/core/java/android/net/NetworkStats.java b/core/java/android/net/NetworkStats.java
index 6ccbab7..288b06e 100644
--- a/core/java/android/net/NetworkStats.java
+++ b/core/java/android/net/NetworkStats.java
@@ -24,6 +24,7 @@
import android.os.Build;
import android.os.Parcel;
import android.os.Parcelable;
+import android.os.Process;
import android.os.SystemClock;
import android.util.SparseBooleanArray;
@@ -1487,8 +1488,31 @@
continue;
}
- if (recycle.uid == tunUid) {
- // Add up traffic through tunUid's underlying interfaces.
+ if (tunUid == Process.SYSTEM_UID) {
+ // Kernel-based VPN or VCN, traffic sent by apps on the VPN/VCN network
+ //
+ // Since the data is not UID-accounted on underlying networks, just use VPN/VCN
+ // network usage as ground truth. Encrypted traffic on the underlying networks will
+ // never be processed here because encrypted traffic on the underlying interfaces
+ // is not present in UID stats, and this method is only called on UID stats.
+ if (tunIface.equals(recycle.iface)) {
+ tunIfaceTotal.add(recycle);
+ underlyingIfacesTotal.add(recycle);
+
+ // In steady state, there should always be one network, but edge cases may
+ // result in the network being null (network lost), and thus no underlying
+ // ifaces is possible.
+ if (perInterfaceTotal.length > 0) {
+ // While platform VPNs and VCNs have exactly one underlying network, that
+ // network may have multiple interfaces (eg for 464xlat). This layer does
+ // not have the required information to identify which of the interfaces
+ // were used. Select "any" of the interfaces. Since overhead is already
+ // lost, this number is an approximation anyways.
+ perInterfaceTotal[0].add(recycle);
+ }
+ }
+ } else if (recycle.uid == tunUid) {
+ // VpnService VPN, traffic sent by the VPN app over underlying networks
for (int j = 0; j < underlyingIfaces.size(); j++) {
if (Objects.equals(underlyingIfaces.get(j), recycle.iface)) {
perInterfaceTotal[j].add(recycle);
@@ -1497,7 +1521,7 @@
}
}
} else if (tunIface.equals(recycle.iface)) {
- // Add up all tunIface traffic excluding traffic from the vpn app itself.
+ // VpnService VPN; traffic sent by apps on the VPN network
tunIfaceTotal.add(recycle);
}
}
@@ -1532,9 +1556,13 @@
// Consider only entries that go onto the VPN interface.
continue;
}
- if (uid[i] == tunUid) {
+
+ if (uid[i] == tunUid && tunUid != Process.SYSTEM_UID) {
// Exclude VPN app from the redistribution, as it can choose to create packet
// streams by writing to itself.
+ //
+ // However, for platform VPNs, do not exclude the system's usage of the VPN network,
+ // since it is never local-only, and never double counted
continue;
}
tmpEntry.uid = uid[i];
@@ -1641,6 +1669,12 @@
int tunUid,
@NonNull List<String> underlyingIfaces,
@NonNull Entry[] moved) {
+ if (tunUid == Process.SYSTEM_UID) {
+ // No traffic recorded on a per-UID basis for in-kernel VPN/VCNs over underlying
+ // networks; thus no traffic to deduct.
+ return;
+ }
+
for (int i = 0; i < underlyingIfaces.size(); i++) {
moved[i].uid = tunUid;
// Add debug info