Merge ab/7633965
Bug: 169893837
Merged-In: I3ef19b77bc33546a3e80bca75532d017b4712054
Change-Id: I595fb801f4519177825f3fdc0021fb874a36aa31
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
diff --git a/core/java/android/net/nsd/NsdManager.java b/core/java/android/net/nsd/NsdManager.java
index 5a25cfc..ae8d010 100644
--- a/core/java/android/net/nsd/NsdManager.java
+++ b/core/java/android/net/nsd/NsdManager.java
@@ -23,6 +23,9 @@
import android.annotation.SdkConstant;
import android.annotation.SdkConstant.SdkConstantType;
import android.annotation.SystemService;
+import android.app.compat.CompatChanges;
+import android.compat.annotation.ChangeId;
+import android.compat.annotation.EnabledSince;
import android.content.Context;
import android.os.Handler;
import android.os.HandlerThread;
@@ -126,6 +129,24 @@
private static final boolean DBG = false;
/**
+ * When enabled, apps targeting < Android 12 are considered legacy for
+ * the NSD native daemon.
+ * The platform will only keep the daemon running as long as there are
+ * any legacy apps connected.
+ *
+ * After Android 12, directly communicate with native daemon might not
+ * work since the native damon won't always stay alive.
+ * Use the NSD APIs from NsdManager as the replacement is recommended.
+ * An another alternative could be bundling your own mdns solutions instead of
+ * depending on the system mdns native daemon.
+ *
+ * @hide
+ */
+ @ChangeId
+ @EnabledSince(targetSdkVersion = android.os.Build.VERSION_CODES.S)
+ public static final long RUN_NATIVE_NSD_ONLY_IF_LEGACY_APPS = 191844585L;
+
+ /**
* Broadcast intent action to indicate whether network service discovery is
* enabled or disabled. An extra {@link #EXTRA_NSD_STATE} provides the state
* information as int.
@@ -203,6 +224,9 @@
public static final int DAEMON_CLEANUP = BASE + 21;
/** @hide */
+ public static final int DAEMON_STARTUP = BASE + 22;
+
+ /** @hide */
public static final int ENABLE = BASE + 24;
/** @hide */
public static final int DISABLE = BASE + 25;
@@ -232,6 +256,8 @@
EVENT_NAMES.put(RESOLVE_SERVICE, "RESOLVE_SERVICE");
EVENT_NAMES.put(RESOLVE_SERVICE_FAILED, "RESOLVE_SERVICE_FAILED");
EVENT_NAMES.put(RESOLVE_SERVICE_SUCCEEDED, "RESOLVE_SERVICE_SUCCEEDED");
+ EVENT_NAMES.put(DAEMON_CLEANUP, "DAEMON_CLEANUP");
+ EVENT_NAMES.put(DAEMON_STARTUP, "DAEMON_STARTUP");
EVENT_NAMES.put(ENABLE, "ENABLE");
EVENT_NAMES.put(DISABLE, "DISABLE");
EVENT_NAMES.put(NATIVE_DAEMON_EVENT, "NATIVE_DAEMON_EVENT");
@@ -494,6 +520,12 @@
} catch (InterruptedException e) {
fatal("Interrupted wait at init");
}
+ if (CompatChanges.isChangeEnabled(RUN_NATIVE_NSD_ONLY_IF_LEGACY_APPS)) {
+ return;
+ }
+ // Only proactively start the daemon if the target SDK < S, otherwise the internal service
+ // would automatically start/stop the native daemon as needed.
+ mAsyncChannel.sendMessage(DAEMON_STARTUP);
}
private static void fatal(String msg) {
diff --git a/services/core/java/com/android/server/NsdService.java b/services/core/java/com/android/server/NsdService.java
index a481a6a..c9608a5 100644
--- a/services/core/java/com/android/server/NsdService.java
+++ b/services/core/java/com/android/server/NsdService.java
@@ -61,7 +61,7 @@
private static final String MDNS_TAG = "mDnsConnector";
private static final boolean DBG = true;
- private static final long CLEANUP_DELAY_MS = 3000;
+ private static final long CLEANUP_DELAY_MS = 10000;
private final Context mContext;
private final NsdSettings mNsdSettings;
@@ -82,6 +82,8 @@
private static final int INVALID_ID = 0;
private int mUniqueId = 1;
+ // The count of the connected legacy clients.
+ private int mLegacyClientCount = 0;
private class NsdStateMachine extends StateMachine {
@@ -94,19 +96,27 @@
return NsdManager.nameOf(what);
}
- void maybeStartDaemon() {
+ private void maybeStartDaemon() {
mDaemon.maybeStart();
maybeScheduleStop();
}
- void maybeScheduleStop() {
- if (!isAnyRequestActive()) {
- cancelStop();
- sendMessageDelayed(NsdManager.DAEMON_CLEANUP, mCleanupDelayMs);
+ private boolean isAnyRequestActive() {
+ return mIdToClientInfoMap.size() != 0;
+ }
+
+ private void scheduleStop() {
+ sendMessageDelayed(NsdManager.DAEMON_CLEANUP, mCleanupDelayMs);
+ }
+ private void maybeScheduleStop() {
+ // The native daemon should stay alive and can't be cleanup
+ // if any legacy client connected.
+ if (!isAnyRequestActive() && mLegacyClientCount == 0) {
+ scheduleStop();
}
}
- void cancelStop() {
+ private void cancelStop() {
this.removeMessages(NsdManager.DAEMON_CLEANUP);
}
@@ -164,11 +174,16 @@
if (DBG) Slog.d(TAG, "Client connection lost with reason: " + msg.arg1);
break;
}
+
cInfo = mClients.get(msg.replyTo);
if (cInfo != null) {
cInfo.expungeAllRequests();
mClients.remove(msg.replyTo);
+ if (cInfo.isLegacy()) {
+ mLegacyClientCount -= 1;
+ }
}
+ maybeScheduleStop();
break;
case AsyncChannel.CMD_CHANNEL_FULL_CONNECTION:
AsyncChannel ac = new AsyncChannel();
@@ -197,6 +212,17 @@
case NsdManager.DAEMON_CLEANUP:
mDaemon.maybeStop();
break;
+ // This event should be only sent by the legacy (target SDK < S) clients.
+ // Mark the sending client as legacy.
+ case NsdManager.DAEMON_STARTUP:
+ cInfo = mClients.get(msg.replyTo);
+ if (cInfo != null) {
+ cancelStop();
+ cInfo.setLegacy();
+ mLegacyClientCount += 1;
+ maybeStartDaemon();
+ }
+ break;
case NsdManager.NATIVE_DAEMON_EVENT:
default:
Slog.e(TAG, "Unhandled " + msg);
@@ -235,7 +261,7 @@
public void exit() {
// TODO: it is incorrect to stop the daemon without expunging all requests
// and sending error callbacks to clients.
- maybeScheduleStop();
+ scheduleStop();
}
private boolean requestLimitReached(ClientInfo clientInfo) {
@@ -271,9 +297,6 @@
return NOT_HANDLED;
case AsyncChannel.CMD_CHANNEL_DISCONNECTED:
return NOT_HANDLED;
- }
-
- switch (msg.what) {
case NsdManager.DISABLE:
//TODO: cleanup clients
transitionTo(mDisabledState);
@@ -531,10 +554,6 @@
}
}
- private boolean isAnyRequestActive() {
- return mIdToClientInfoMap.size() != 0;
- }
-
private String unescape(String s) {
StringBuilder sb = new StringBuilder(s.length());
for (int i = 0; i < s.length(); ++i) {
@@ -859,6 +878,9 @@
/* A map from client id to the type of the request we had received */
private final SparseIntArray mClientRequests = new SparseIntArray();
+ // The target SDK of this client < Build.VERSION_CODES.S
+ private boolean mIsLegacy = false;
+
private ClientInfo(AsyncChannel c, Messenger m) {
mChannel = c;
mMessenger = m;
@@ -871,6 +893,7 @@
sb.append("mChannel ").append(mChannel).append("\n");
sb.append("mMessenger ").append(mMessenger).append("\n");
sb.append("mResolvedService ").append(mResolvedService).append("\n");
+ sb.append("mIsLegacy ").append(mIsLegacy).append("\n");
for(int i = 0; i< mClientIds.size(); i++) {
int clientID = mClientIds.keyAt(i);
sb.append("clientId ").append(clientID).
@@ -880,6 +903,14 @@
return sb.toString();
}
+ private boolean isLegacy() {
+ return mIsLegacy;
+ }
+
+ private void setLegacy() {
+ mIsLegacy = true;
+ }
+
// Remove any pending requests from the global map when we get rid of a client,
// and send cancellations to the daemon.
private void expungeAllRequests() {
@@ -907,7 +938,6 @@
}
mClientIds.clear();
mClientRequests.clear();
- mNsdStateMachine.maybeScheduleStop();
}
// mClientIds is a sparse array of listener id -> mDnsClient id. For a given mDnsClient id,
diff --git a/services/core/java/com/android/server/net/NetworkStatsService.java b/services/core/java/com/android/server/net/NetworkStatsService.java
index 4ee867b..097b071 100644
--- a/services/core/java/com/android/server/net/NetworkStatsService.java
+++ b/services/core/java/com/android/server/net/NetworkStatsService.java
@@ -289,8 +289,7 @@
private String mActiveIface;
/** Set of any ifaces associated with mobile networks since boot. */
- @GuardedBy("mStatsLock")
- private String[] mMobileIfaces = new String[0];
+ private volatile String[] mMobileIfaces = new String[0];
/** Set of all ifaces currently used by traffic that does not explicitly specify a Network. */
@GuardedBy("mStatsLock")
@@ -935,7 +934,12 @@
@Override
public String[] getMobileIfaces() {
- return mMobileIfaces;
+ // TODO (b/192758557): Remove debug log.
+ if (ArrayUtils.contains(mMobileIfaces, null)) {
+ throw new NullPointerException(
+ "null element in mMobileIfaces: " + Arrays.toString(mMobileIfaces));
+ }
+ return mMobileIfaces.clone();
}
@Override
@@ -1084,7 +1088,8 @@
}
@Override
- public long getIfaceStats(String iface, int type) {
+ public long getIfaceStats(@NonNull String iface, int type) {
+ Objects.requireNonNull(iface);
long nativeIfaceStats = nativeGetIfaceStat(iface, type, checkBpfStatsEnable());
if (nativeIfaceStats == -1) {
return nativeIfaceStats;
@@ -1382,7 +1387,12 @@
}
}
- mMobileIfaces = mobileIfaces.toArray(new String[mobileIfaces.size()]);
+ mMobileIfaces = mobileIfaces.toArray(new String[0]);
+ // TODO (b/192758557): Remove debug log.
+ if (ArrayUtils.contains(mMobileIfaces, null)) {
+ throw new NullPointerException(
+ "null element in mMobileIfaces: " + Arrays.toString(mMobileIfaces));
+ }
}
private static int getSubIdForMobile(@NonNull NetworkStateSnapshot state) {