Merge ab/7633965

Bug: 169893837
Merged-In: I3ef19b77bc33546a3e80bca75532d017b4712054
Change-Id: I595fb801f4519177825f3fdc0021fb874a36aa31
This commit is contained in:
The Android Open Source Project
2021-08-12 12:03:34 -07:00
committed by Xin Li
4 changed files with 131 additions and 25 deletions

View File

@@ -24,6 +24,7 @@ import android.compat.annotation.UnsupportedAppUsage;
import android.os.Build; import android.os.Build;
import android.os.Parcel; import android.os.Parcel;
import android.os.Parcelable; import android.os.Parcelable;
import android.os.Process;
import android.os.SystemClock; import android.os.SystemClock;
import android.util.SparseBooleanArray; import android.util.SparseBooleanArray;
@@ -1487,8 +1488,31 @@ public final class NetworkStats implements Parcelable {
continue; continue;
} }
if (recycle.uid == tunUid) { if (tunUid == Process.SYSTEM_UID) {
// Add up traffic through tunUid's underlying interfaces. // 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++) { for (int j = 0; j < underlyingIfaces.size(); j++) {
if (Objects.equals(underlyingIfaces.get(j), recycle.iface)) { if (Objects.equals(underlyingIfaces.get(j), recycle.iface)) {
perInterfaceTotal[j].add(recycle); perInterfaceTotal[j].add(recycle);
@@ -1497,7 +1521,7 @@ public final class NetworkStats implements Parcelable {
} }
} }
} else if (tunIface.equals(recycle.iface)) { } 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); tunIfaceTotal.add(recycle);
} }
} }
@@ -1532,9 +1556,13 @@ public final class NetworkStats implements Parcelable {
// Consider only entries that go onto the VPN interface. // Consider only entries that go onto the VPN interface.
continue; 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 // Exclude VPN app from the redistribution, as it can choose to create packet
// streams by writing to itself. // 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; continue;
} }
tmpEntry.uid = uid[i]; tmpEntry.uid = uid[i];
@@ -1641,6 +1669,12 @@ public final class NetworkStats implements Parcelable {
int tunUid, int tunUid,
@NonNull List<String> underlyingIfaces, @NonNull List<String> underlyingIfaces,
@NonNull Entry[] moved) { @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++) { for (int i = 0; i < underlyingIfaces.size(); i++) {
moved[i].uid = tunUid; moved[i].uid = tunUid;
// Add debug info // Add debug info

View File

@@ -23,6 +23,9 @@ import static com.android.internal.util.Preconditions.checkStringNotEmpty;
import android.annotation.SdkConstant; import android.annotation.SdkConstant;
import android.annotation.SdkConstant.SdkConstantType; import android.annotation.SdkConstant.SdkConstantType;
import android.annotation.SystemService; 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.content.Context;
import android.os.Handler; import android.os.Handler;
import android.os.HandlerThread; import android.os.HandlerThread;
@@ -125,6 +128,24 @@ public final class NsdManager {
private static final String TAG = NsdManager.class.getSimpleName(); private static final String TAG = NsdManager.class.getSimpleName();
private static final boolean DBG = false; 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 * Broadcast intent action to indicate whether network service discovery is
* enabled or disabled. An extra {@link #EXTRA_NSD_STATE} provides the state * enabled or disabled. An extra {@link #EXTRA_NSD_STATE} provides the state
@@ -202,6 +223,9 @@ public final class NsdManager {
/** @hide */ /** @hide */
public static final int DAEMON_CLEANUP = BASE + 21; public static final int DAEMON_CLEANUP = BASE + 21;
/** @hide */
public static final int DAEMON_STARTUP = BASE + 22;
/** @hide */ /** @hide */
public static final int ENABLE = BASE + 24; public static final int ENABLE = BASE + 24;
/** @hide */ /** @hide */
@@ -232,6 +256,8 @@ public final class NsdManager {
EVENT_NAMES.put(RESOLVE_SERVICE, "RESOLVE_SERVICE"); EVENT_NAMES.put(RESOLVE_SERVICE, "RESOLVE_SERVICE");
EVENT_NAMES.put(RESOLVE_SERVICE_FAILED, "RESOLVE_SERVICE_FAILED"); EVENT_NAMES.put(RESOLVE_SERVICE_FAILED, "RESOLVE_SERVICE_FAILED");
EVENT_NAMES.put(RESOLVE_SERVICE_SUCCEEDED, "RESOLVE_SERVICE_SUCCEEDED"); 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(ENABLE, "ENABLE");
EVENT_NAMES.put(DISABLE, "DISABLE"); EVENT_NAMES.put(DISABLE, "DISABLE");
EVENT_NAMES.put(NATIVE_DAEMON_EVENT, "NATIVE_DAEMON_EVENT"); EVENT_NAMES.put(NATIVE_DAEMON_EVENT, "NATIVE_DAEMON_EVENT");
@@ -494,6 +520,12 @@ public final class NsdManager {
} catch (InterruptedException e) { } catch (InterruptedException e) {
fatal("Interrupted wait at init"); 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) { private static void fatal(String msg) {

View File

@@ -61,7 +61,7 @@ public class NsdService extends INsdManager.Stub {
private static final String MDNS_TAG = "mDnsConnector"; private static final String MDNS_TAG = "mDnsConnector";
private static final boolean DBG = true; 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 Context mContext;
private final NsdSettings mNsdSettings; private final NsdSettings mNsdSettings;
@@ -82,6 +82,8 @@ public class NsdService extends INsdManager.Stub {
private static final int INVALID_ID = 0; private static final int INVALID_ID = 0;
private int mUniqueId = 1; private int mUniqueId = 1;
// The count of the connected legacy clients.
private int mLegacyClientCount = 0;
private class NsdStateMachine extends StateMachine { private class NsdStateMachine extends StateMachine {
@@ -94,19 +96,27 @@ public class NsdService extends INsdManager.Stub {
return NsdManager.nameOf(what); return NsdManager.nameOf(what);
} }
void maybeStartDaemon() { private void maybeStartDaemon() {
mDaemon.maybeStart(); mDaemon.maybeStart();
maybeScheduleStop(); maybeScheduleStop();
} }
void maybeScheduleStop() { private boolean isAnyRequestActive() {
if (!isAnyRequestActive()) { return mIdToClientInfoMap.size() != 0;
cancelStop(); }
sendMessageDelayed(NsdManager.DAEMON_CLEANUP, mCleanupDelayMs);
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); this.removeMessages(NsdManager.DAEMON_CLEANUP);
} }
@@ -164,11 +174,16 @@ public class NsdService extends INsdManager.Stub {
if (DBG) Slog.d(TAG, "Client connection lost with reason: " + msg.arg1); if (DBG) Slog.d(TAG, "Client connection lost with reason: " + msg.arg1);
break; break;
} }
cInfo = mClients.get(msg.replyTo); cInfo = mClients.get(msg.replyTo);
if (cInfo != null) { if (cInfo != null) {
cInfo.expungeAllRequests(); cInfo.expungeAllRequests();
mClients.remove(msg.replyTo); mClients.remove(msg.replyTo);
if (cInfo.isLegacy()) {
mLegacyClientCount -= 1;
}
} }
maybeScheduleStop();
break; break;
case AsyncChannel.CMD_CHANNEL_FULL_CONNECTION: case AsyncChannel.CMD_CHANNEL_FULL_CONNECTION:
AsyncChannel ac = new AsyncChannel(); AsyncChannel ac = new AsyncChannel();
@@ -197,6 +212,17 @@ public class NsdService extends INsdManager.Stub {
case NsdManager.DAEMON_CLEANUP: case NsdManager.DAEMON_CLEANUP:
mDaemon.maybeStop(); mDaemon.maybeStop();
break; 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: case NsdManager.NATIVE_DAEMON_EVENT:
default: default:
Slog.e(TAG, "Unhandled " + msg); Slog.e(TAG, "Unhandled " + msg);
@@ -235,7 +261,7 @@ public class NsdService extends INsdManager.Stub {
public void exit() { public void exit() {
// TODO: it is incorrect to stop the daemon without expunging all requests // TODO: it is incorrect to stop the daemon without expunging all requests
// and sending error callbacks to clients. // and sending error callbacks to clients.
maybeScheduleStop(); scheduleStop();
} }
private boolean requestLimitReached(ClientInfo clientInfo) { private boolean requestLimitReached(ClientInfo clientInfo) {
@@ -271,9 +297,6 @@ public class NsdService extends INsdManager.Stub {
return NOT_HANDLED; return NOT_HANDLED;
case AsyncChannel.CMD_CHANNEL_DISCONNECTED: case AsyncChannel.CMD_CHANNEL_DISCONNECTED:
return NOT_HANDLED; return NOT_HANDLED;
}
switch (msg.what) {
case NsdManager.DISABLE: case NsdManager.DISABLE:
//TODO: cleanup clients //TODO: cleanup clients
transitionTo(mDisabledState); transitionTo(mDisabledState);
@@ -531,10 +554,6 @@ public class NsdService extends INsdManager.Stub {
} }
} }
private boolean isAnyRequestActive() {
return mIdToClientInfoMap.size() != 0;
}
private String unescape(String s) { private String unescape(String s) {
StringBuilder sb = new StringBuilder(s.length()); StringBuilder sb = new StringBuilder(s.length());
for (int i = 0; i < s.length(); ++i) { for (int i = 0; i < s.length(); ++i) {
@@ -859,6 +878,9 @@ public class NsdService extends INsdManager.Stub {
/* A map from client id to the type of the request we had received */ /* A map from client id to the type of the request we had received */
private final SparseIntArray mClientRequests = new SparseIntArray(); 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) { private ClientInfo(AsyncChannel c, Messenger m) {
mChannel = c; mChannel = c;
mMessenger = m; mMessenger = m;
@@ -871,6 +893,7 @@ public class NsdService extends INsdManager.Stub {
sb.append("mChannel ").append(mChannel).append("\n"); sb.append("mChannel ").append(mChannel).append("\n");
sb.append("mMessenger ").append(mMessenger).append("\n"); sb.append("mMessenger ").append(mMessenger).append("\n");
sb.append("mResolvedService ").append(mResolvedService).append("\n"); sb.append("mResolvedService ").append(mResolvedService).append("\n");
sb.append("mIsLegacy ").append(mIsLegacy).append("\n");
for(int i = 0; i< mClientIds.size(); i++) { for(int i = 0; i< mClientIds.size(); i++) {
int clientID = mClientIds.keyAt(i); int clientID = mClientIds.keyAt(i);
sb.append("clientId ").append(clientID). sb.append("clientId ").append(clientID).
@@ -880,6 +903,14 @@ public class NsdService extends INsdManager.Stub {
return sb.toString(); 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, // Remove any pending requests from the global map when we get rid of a client,
// and send cancellations to the daemon. // and send cancellations to the daemon.
private void expungeAllRequests() { private void expungeAllRequests() {
@@ -907,7 +938,6 @@ public class NsdService extends INsdManager.Stub {
} }
mClientIds.clear(); mClientIds.clear();
mClientRequests.clear(); mClientRequests.clear();
mNsdStateMachine.maybeScheduleStop();
} }
// mClientIds is a sparse array of listener id -> mDnsClient id. For a given mDnsClient id, // mClientIds is a sparse array of listener id -> mDnsClient id. For a given mDnsClient id,

View File

@@ -289,8 +289,7 @@ public class NetworkStatsService extends INetworkStatsService.Stub {
private String mActiveIface; private String mActiveIface;
/** Set of any ifaces associated with mobile networks since boot. */ /** Set of any ifaces associated with mobile networks since boot. */
@GuardedBy("mStatsLock") private volatile String[] mMobileIfaces = new String[0];
private String[] mMobileIfaces = new String[0];
/** Set of all ifaces currently used by traffic that does not explicitly specify a Network. */ /** Set of all ifaces currently used by traffic that does not explicitly specify a Network. */
@GuardedBy("mStatsLock") @GuardedBy("mStatsLock")
@@ -935,7 +934,12 @@ public class NetworkStatsService extends INetworkStatsService.Stub {
@Override @Override
public String[] getMobileIfaces() { 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 @Override
@@ -1084,7 +1088,8 @@ public class NetworkStatsService extends INetworkStatsService.Stub {
} }
@Override @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()); long nativeIfaceStats = nativeGetIfaceStat(iface, type, checkBpfStatsEnable());
if (nativeIfaceStats == -1) { if (nativeIfaceStats == -1) {
return nativeIfaceStats; return nativeIfaceStats;
@@ -1382,7 +1387,12 @@ public class NetworkStatsService extends INetworkStatsService.Stub {
} }
} }
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) { private static int getSubIdForMobile(@NonNull NetworkStateSnapshot state) {