Merge "Take the multicast lock on mDNS usage"
This commit is contained in:
@@ -17,6 +17,8 @@
|
||||
package com.android.server;
|
||||
|
||||
import static android.net.ConnectivityManager.NETID_UNSET;
|
||||
import static android.net.NetworkCapabilities.TRANSPORT_VPN;
|
||||
import static android.net.NetworkCapabilities.TRANSPORT_WIFI;
|
||||
import static android.net.nsd.NsdManager.MDNS_DISCOVERY_MANAGER_EVENT;
|
||||
import static android.net.nsd.NsdManager.MDNS_SERVICE_EVENT;
|
||||
import static android.net.nsd.NsdManager.RESOLVE_SERVICE_SUCCEEDED;
|
||||
@@ -27,6 +29,7 @@ import static com.android.server.connectivity.mdns.MdnsRecord.MAX_LABEL_LENGTH;
|
||||
|
||||
import android.annotation.NonNull;
|
||||
import android.annotation.Nullable;
|
||||
import android.app.ActivityManager;
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.net.ConnectivityManager;
|
||||
@@ -45,6 +48,7 @@ import android.net.nsd.INsdServiceConnector;
|
||||
import android.net.nsd.MDnsManager;
|
||||
import android.net.nsd.NsdManager;
|
||||
import android.net.nsd.NsdServiceInfo;
|
||||
import android.net.wifi.WifiManager;
|
||||
import android.os.Binder;
|
||||
import android.os.Handler;
|
||||
import android.os.HandlerThread;
|
||||
@@ -53,7 +57,9 @@ import android.os.Looper;
|
||||
import android.os.Message;
|
||||
import android.os.RemoteException;
|
||||
import android.os.UserHandle;
|
||||
import android.provider.DeviceConfig;
|
||||
import android.text.TextUtils;
|
||||
import android.util.ArraySet;
|
||||
import android.util.Log;
|
||||
import android.util.Pair;
|
||||
import android.util.SparseArray;
|
||||
@@ -62,6 +68,7 @@ import com.android.internal.annotations.VisibleForTesting;
|
||||
import com.android.internal.util.IndentingPrintWriter;
|
||||
import com.android.internal.util.State;
|
||||
import com.android.internal.util.StateMachine;
|
||||
import com.android.net.module.util.CollectionUtils;
|
||||
import com.android.net.module.util.DeviceConfigUtils;
|
||||
import com.android.net.module.util.InetAddressUtils;
|
||||
import com.android.net.module.util.PermissionUtils;
|
||||
@@ -69,6 +76,7 @@ import com.android.net.module.util.SharedLog;
|
||||
import com.android.server.connectivity.mdns.ExecutorProvider;
|
||||
import com.android.server.connectivity.mdns.MdnsAdvertiser;
|
||||
import com.android.server.connectivity.mdns.MdnsDiscoveryManager;
|
||||
import com.android.server.connectivity.mdns.MdnsInterfaceSocket;
|
||||
import com.android.server.connectivity.mdns.MdnsMultinetworkSocketClient;
|
||||
import com.android.server.connectivity.mdns.MdnsSearchOptions;
|
||||
import com.android.server.connectivity.mdns.MdnsServiceBrowserListener;
|
||||
@@ -141,6 +149,14 @@ public class NsdService extends INsdManager.Stub {
|
||||
"mdns_advertiser_allowlist_";
|
||||
private static final String MDNS_ALLOWLIST_FLAG_SUFFIX = "_version";
|
||||
|
||||
@VisibleForTesting
|
||||
static final String MDNS_CONFIG_RUNNING_APP_ACTIVE_IMPORTANCE_CUTOFF =
|
||||
"mdns_config_running_app_active_importance_cutoff";
|
||||
@VisibleForTesting
|
||||
static final int DEFAULT_RUNNING_APP_ACTIVE_IMPORTANCE_CUTOFF =
|
||||
ActivityManager.RunningAppProcessInfo.IMPORTANCE_FOREGROUND;
|
||||
private final int mRunningAppActiveImportanceCutoff;
|
||||
|
||||
public static final boolean DBG = Log.isLoggable(TAG, Log.DEBUG);
|
||||
private static final long CLEANUP_DELAY_MS = 10000;
|
||||
private static final int IFACE_IDX_ANY = 0;
|
||||
@@ -175,6 +191,16 @@ public class NsdService extends INsdManager.Stub {
|
||||
/* A map from unique id to client info */
|
||||
private final SparseArray<ClientInfo> mIdToClientInfoMap= new SparseArray<>();
|
||||
|
||||
// Note this is not final to avoid depending on the Wi-Fi service starting before NsdService
|
||||
@Nullable
|
||||
private WifiManager.MulticastLock mHeldMulticastLock;
|
||||
// Fulfilled network requests that require the Wi-Fi lock: key is the obtained Network
|
||||
// (non-null), value is the requested Network (nullable)
|
||||
@NonNull
|
||||
private final ArraySet<Network> mWifiLockRequiredNetworks = new ArraySet<>();
|
||||
@NonNull
|
||||
private final ArraySet<Integer> mRunningAppActiveUids = new ArraySet<>();
|
||||
|
||||
private final long mCleanupDelayMs;
|
||||
|
||||
private static final int INVALID_ID = 0;
|
||||
@@ -299,6 +325,104 @@ public class NsdService extends INsdManager.Stub {
|
||||
}
|
||||
}
|
||||
|
||||
private class SocketRequestMonitor implements MdnsSocketProvider.SocketRequestMonitor {
|
||||
@Override
|
||||
public void onSocketRequestFulfilled(@Nullable Network socketNetwork,
|
||||
@NonNull MdnsInterfaceSocket socket, @NonNull int[] transports) {
|
||||
// The network may be null for Wi-Fi SoftAp interfaces (tethering), but there is no APF
|
||||
// filtering on such interfaces, so taking the multicast lock is not necessary to
|
||||
// disable APF filtering of multicast.
|
||||
if (socketNetwork == null
|
||||
|| !CollectionUtils.contains(transports, TRANSPORT_WIFI)
|
||||
|| CollectionUtils.contains(transports, TRANSPORT_VPN)) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (mWifiLockRequiredNetworks.add(socketNetwork)) {
|
||||
updateMulticastLock();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onSocketDestroyed(@Nullable Network socketNetwork,
|
||||
@NonNull MdnsInterfaceSocket socket) {
|
||||
if (mWifiLockRequiredNetworks.remove(socketNetwork)) {
|
||||
updateMulticastLock();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private class UidImportanceListener implements ActivityManager.OnUidImportanceListener {
|
||||
private final Handler mHandler;
|
||||
|
||||
private UidImportanceListener(Handler handler) {
|
||||
mHandler = handler;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onUidImportance(int uid, int importance) {
|
||||
mHandler.post(() -> handleUidImportanceChanged(uid, importance));
|
||||
}
|
||||
}
|
||||
|
||||
private void handleUidImportanceChanged(int uid, int importance) {
|
||||
// Lower importance values are more "important"
|
||||
final boolean modified = importance <= mRunningAppActiveImportanceCutoff
|
||||
? mRunningAppActiveUids.add(uid)
|
||||
: mRunningAppActiveUids.remove(uid);
|
||||
if (modified) {
|
||||
updateMulticastLock();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Take or release the lock based on updated internal state.
|
||||
*
|
||||
* This determines whether the lock needs to be held based on
|
||||
* {@link #mWifiLockRequiredNetworks}, {@link #mRunningAppActiveUids} and
|
||||
* {@link ClientInfo#mClientRequests}, so it must be called after any of the these have been
|
||||
* updated.
|
||||
*/
|
||||
private void updateMulticastLock() {
|
||||
final int needsLockUid = getMulticastLockNeededUid();
|
||||
if (needsLockUid >= 0 && mHeldMulticastLock == null) {
|
||||
final WifiManager wm = mContext.getSystemService(WifiManager.class);
|
||||
if (wm == null) {
|
||||
Log.wtf(TAG, "Got a TRANSPORT_WIFI network without WifiManager");
|
||||
return;
|
||||
}
|
||||
mHeldMulticastLock = wm.createMulticastLock(TAG);
|
||||
mHeldMulticastLock.acquire();
|
||||
mServiceLogs.log("Taking multicast lock for uid " + needsLockUid);
|
||||
} else if (needsLockUid < 0 && mHeldMulticastLock != null) {
|
||||
mHeldMulticastLock.release();
|
||||
mHeldMulticastLock = null;
|
||||
mServiceLogs.log("Released multicast lock");
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @return The UID of an app requiring the multicast lock, or -1 if none.
|
||||
*/
|
||||
private int getMulticastLockNeededUid() {
|
||||
if (mWifiLockRequiredNetworks.size() == 0) {
|
||||
// Return early if NSD is not active, or not on any relevant network
|
||||
return -1;
|
||||
}
|
||||
for (int i = 0; i < mIdToClientInfoMap.size(); i++) {
|
||||
final ClientInfo clientInfo = mIdToClientInfoMap.valueAt(i);
|
||||
if (!mRunningAppActiveUids.contains(clientInfo.mUid)) {
|
||||
// Ignore non-active UIDs
|
||||
continue;
|
||||
}
|
||||
|
||||
if (clientInfo.hasAnyJavaBackendRequestForNetworks(mWifiLockRequiredNetworks)) {
|
||||
return clientInfo.mUid;
|
||||
}
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
|
||||
/**
|
||||
* Data class of mdns service callback information.
|
||||
*/
|
||||
@@ -404,7 +528,7 @@ public class NsdService extends INsdManager.Stub {
|
||||
try {
|
||||
cb.asBinder().linkToDeath(arg.connector, 0);
|
||||
final String tag = "Client" + arg.uid + "-" + mClientNumberId++;
|
||||
cInfo = new ClientInfo(cb, arg.useJavaBackend,
|
||||
cInfo = new ClientInfo(cb, arg.uid, arg.useJavaBackend,
|
||||
mServiceLogs.forSubComponent(tag));
|
||||
mClients.put(arg.connector, cInfo);
|
||||
} catch (RemoteException e) {
|
||||
@@ -529,9 +653,11 @@ public class NsdService extends INsdManager.Stub {
|
||||
}
|
||||
|
||||
private void storeAdvertiserRequestMap(int clientId, int globalId,
|
||||
ClientInfo clientInfo) {
|
||||
clientInfo.mClientRequests.put(clientId, new AdvertiserClientRequest(globalId));
|
||||
ClientInfo clientInfo, @Nullable Network requestedNetwork) {
|
||||
clientInfo.mClientRequests.put(clientId,
|
||||
new AdvertiserClientRequest(globalId, requestedNetwork));
|
||||
mIdToClientInfoMap.put(globalId, clientInfo);
|
||||
updateMulticastLock();
|
||||
}
|
||||
|
||||
private void removeRequestMap(int clientId, int globalId, ClientInfo clientInfo) {
|
||||
@@ -544,14 +670,17 @@ public class NsdService extends INsdManager.Stub {
|
||||
maybeScheduleStop();
|
||||
} else {
|
||||
maybeStopMonitoringSocketsIfNoActiveRequest();
|
||||
updateMulticastLock();
|
||||
}
|
||||
}
|
||||
|
||||
private void storeDiscoveryManagerRequestMap(int clientId, int globalId,
|
||||
MdnsListener listener, ClientInfo clientInfo) {
|
||||
MdnsListener listener, ClientInfo clientInfo,
|
||||
@Nullable Network requestedNetwork) {
|
||||
clientInfo.mClientRequests.put(clientId,
|
||||
new DiscoveryManagerRequest(globalId, listener));
|
||||
new DiscoveryManagerRequest(globalId, listener, requestedNetwork));
|
||||
mIdToClientInfoMap.put(globalId, clientInfo);
|
||||
updateMulticastLock();
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -628,7 +757,8 @@ public class NsdService extends INsdManager.Stub {
|
||||
}
|
||||
mMdnsDiscoveryManager.registerListener(
|
||||
listenServiceType, listener, optionsBuilder.build());
|
||||
storeDiscoveryManagerRequestMap(clientId, id, listener, clientInfo);
|
||||
storeDiscoveryManagerRequestMap(clientId, id, listener, clientInfo,
|
||||
info.getNetwork());
|
||||
clientInfo.onDiscoverServicesStarted(clientId, info);
|
||||
clientInfo.log("Register a DiscoveryListener " + id
|
||||
+ " for service type:" + listenServiceType);
|
||||
@@ -728,7 +858,8 @@ public class NsdService extends INsdManager.Stub {
|
||||
// Name._subtype._sub._type._tcp, which is incorrect
|
||||
// (it should be Name._type._tcp).
|
||||
mAdvertiser.addService(id, serviceInfo, typeSubtype.second);
|
||||
storeAdvertiserRequestMap(clientId, id, clientInfo);
|
||||
storeAdvertiserRequestMap(clientId, id, clientInfo,
|
||||
serviceInfo.getNetwork());
|
||||
} else {
|
||||
maybeStartDaemon();
|
||||
if (registerService(id, serviceInfo)) {
|
||||
@@ -818,7 +949,8 @@ public class NsdService extends INsdManager.Stub {
|
||||
.build();
|
||||
mMdnsDiscoveryManager.registerListener(
|
||||
resolveServiceType, listener, options);
|
||||
storeDiscoveryManagerRequestMap(clientId, id, listener, clientInfo);
|
||||
storeDiscoveryManagerRequestMap(clientId, id, listener, clientInfo,
|
||||
info.getNetwork());
|
||||
clientInfo.log("Register a ResolutionListener " + id
|
||||
+ " for service type:" + resolveServiceType);
|
||||
} else {
|
||||
@@ -912,7 +1044,8 @@ public class NsdService extends INsdManager.Stub {
|
||||
.build();
|
||||
mMdnsDiscoveryManager.registerListener(
|
||||
resolveServiceType, listener, options);
|
||||
storeDiscoveryManagerRequestMap(clientId, id, listener, clientInfo);
|
||||
storeDiscoveryManagerRequestMap(clientId, id, listener, clientInfo,
|
||||
info.getNetwork());
|
||||
clientInfo.log("Register a ServiceInfoListener " + id
|
||||
+ " for service type:" + resolveServiceType);
|
||||
break;
|
||||
@@ -1389,10 +1522,20 @@ public class NsdService extends INsdManager.Stub {
|
||||
mDeps = deps;
|
||||
|
||||
mMdnsSocketProvider = deps.makeMdnsSocketProvider(ctx, handler.getLooper(),
|
||||
LOGGER.forSubComponent("MdnsSocketProvider"));
|
||||
LOGGER.forSubComponent("MdnsSocketProvider"), new SocketRequestMonitor());
|
||||
// Netlink monitor starts on boot, and intentionally never stopped, to ensure that all
|
||||
// address events are received.
|
||||
handler.post(mMdnsSocketProvider::startNetLinkMonitor);
|
||||
|
||||
// NsdService is started after ActivityManager (startOtherServices in SystemServer, vs.
|
||||
// startBootstrapServices).
|
||||
mRunningAppActiveImportanceCutoff = mDeps.getDeviceConfigInt(
|
||||
MDNS_CONFIG_RUNNING_APP_ACTIVE_IMPORTANCE_CUTOFF,
|
||||
DEFAULT_RUNNING_APP_ACTIVE_IMPORTANCE_CUTOFF);
|
||||
final ActivityManager am = ctx.getSystemService(ActivityManager.class);
|
||||
am.addOnUidImportanceListener(new UidImportanceListener(handler),
|
||||
mRunningAppActiveImportanceCutoff);
|
||||
|
||||
mMdnsSocketClient =
|
||||
new MdnsMultinetworkSocketClient(handler.getLooper(), mMdnsSocketProvider);
|
||||
mMdnsDiscoveryManager = deps.makeMdnsDiscoveryManager(new ExecutorProvider(),
|
||||
@@ -1471,8 +1614,23 @@ public class NsdService extends INsdManager.Stub {
|
||||
* @see MdnsSocketProvider
|
||||
*/
|
||||
public MdnsSocketProvider makeMdnsSocketProvider(@NonNull Context context,
|
||||
@NonNull Looper looper, @NonNull SharedLog sharedLog) {
|
||||
return new MdnsSocketProvider(context, looper, sharedLog);
|
||||
@NonNull Looper looper, @NonNull SharedLog sharedLog,
|
||||
@NonNull MdnsSocketProvider.SocketRequestMonitor socketCreationCallback) {
|
||||
return new MdnsSocketProvider(context, looper, sharedLog, socketCreationCallback);
|
||||
}
|
||||
|
||||
/**
|
||||
* @see DeviceConfig#getInt(String, String, int)
|
||||
*/
|
||||
public int getDeviceConfigInt(@NonNull String config, int defaultValue) {
|
||||
return DeviceConfig.getInt(NAMESPACE_TETHERING, config, defaultValue);
|
||||
}
|
||||
|
||||
/**
|
||||
* @see Binder#getCallingUid()
|
||||
*/
|
||||
public int getCallingUid() {
|
||||
return Binder.getCallingUid();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1626,7 +1784,7 @@ public class NsdService extends INsdManager.Stub {
|
||||
final INsdServiceConnector connector = new NsdServiceConnector();
|
||||
mNsdStateMachine.sendMessage(mNsdStateMachine.obtainMessage(NsdManager.REGISTER_CLIENT,
|
||||
new ConnectorArgs((NsdServiceConnector) connector, cb, useJavaBackend,
|
||||
Binder.getCallingUid())));
|
||||
mDeps.getCallingUid())));
|
||||
return connector;
|
||||
}
|
||||
|
||||
@@ -1857,18 +2015,34 @@ public class NsdService extends INsdManager.Stub {
|
||||
}
|
||||
}
|
||||
|
||||
private static class AdvertiserClientRequest extends ClientRequest {
|
||||
private AdvertiserClientRequest(int globalId) {
|
||||
private abstract static class JavaBackendClientRequest extends ClientRequest {
|
||||
@Nullable
|
||||
private final Network mRequestedNetwork;
|
||||
|
||||
private JavaBackendClientRequest(int globalId, @Nullable Network requestedNetwork) {
|
||||
super(globalId);
|
||||
mRequestedNetwork = requestedNetwork;
|
||||
}
|
||||
|
||||
@Nullable
|
||||
public Network getRequestedNetwork() {
|
||||
return mRequestedNetwork;
|
||||
}
|
||||
}
|
||||
|
||||
private static class DiscoveryManagerRequest extends ClientRequest {
|
||||
private static class AdvertiserClientRequest extends JavaBackendClientRequest {
|
||||
private AdvertiserClientRequest(int globalId, @Nullable Network requestedNetwork) {
|
||||
super(globalId, requestedNetwork);
|
||||
}
|
||||
}
|
||||
|
||||
private static class DiscoveryManagerRequest extends JavaBackendClientRequest {
|
||||
@NonNull
|
||||
private final MdnsListener mListener;
|
||||
|
||||
private DiscoveryManagerRequest(int globalId, @NonNull MdnsListener listener) {
|
||||
super(globalId);
|
||||
private DiscoveryManagerRequest(int globalId, @NonNull MdnsListener listener,
|
||||
@Nullable Network requestedNetwork) {
|
||||
super(globalId, requestedNetwork);
|
||||
mListener = listener;
|
||||
}
|
||||
}
|
||||
@@ -1886,13 +2060,16 @@ public class NsdService extends INsdManager.Stub {
|
||||
|
||||
// The target SDK of this client < Build.VERSION_CODES.S
|
||||
private boolean mIsPreSClient = false;
|
||||
private final int mUid;
|
||||
// The flag of using java backend if the client's target SDK >= U
|
||||
private final boolean mUseJavaBackend;
|
||||
// Store client logs
|
||||
private final SharedLog mClientLogs;
|
||||
|
||||
private ClientInfo(INsdManagerCallback cb, boolean useJavaBackend, SharedLog sharedLog) {
|
||||
private ClientInfo(INsdManagerCallback cb, int uid, boolean useJavaBackend,
|
||||
SharedLog sharedLog) {
|
||||
mCb = cb;
|
||||
mUid = uid;
|
||||
mUseJavaBackend = useJavaBackend;
|
||||
mClientLogs = sharedLog;
|
||||
mClientLogs.log("New client. useJavaBackend=" + useJavaBackend);
|
||||
@@ -1903,6 +2080,8 @@ public class NsdService extends INsdManager.Stub {
|
||||
StringBuilder sb = new StringBuilder();
|
||||
sb.append("mResolvedService ").append(mResolvedService).append("\n");
|
||||
sb.append("mIsLegacy ").append(mIsPreSClient).append("\n");
|
||||
sb.append("mUseJavaBackend ").append(mUseJavaBackend).append("\n");
|
||||
sb.append("mUid ").append(mUid).append("\n");
|
||||
for (int i = 0; i < mClientRequests.size(); i++) {
|
||||
int clientID = mClientRequests.keyAt(i);
|
||||
sb.append("clientId ")
|
||||
@@ -1974,6 +2153,26 @@ public class NsdService extends INsdManager.Stub {
|
||||
}
|
||||
}
|
||||
mClientRequests.clear();
|
||||
updateMulticastLock();
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns true if this client has any Java backend request that requests one of the given
|
||||
* networks.
|
||||
*/
|
||||
boolean hasAnyJavaBackendRequestForNetworks(@NonNull ArraySet<Network> networks) {
|
||||
for (int i = 0; i < mClientRequests.size(); i++) {
|
||||
final ClientRequest req = mClientRequests.valueAt(i);
|
||||
if (!(req instanceof JavaBackendClientRequest)) {
|
||||
continue;
|
||||
}
|
||||
final Network reqNetwork = ((JavaBackendClientRequest) mClientRequests.valueAt(i))
|
||||
.getRequestedNetwork();
|
||||
if (MdnsUtils.isAnyNetworkMatched(reqNetwork, networks)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
// mClientRequests is a sparse array of listener id -> ClientRequest. For a given
|
||||
|
||||
@@ -97,6 +97,8 @@ public class MdnsSocketProvider {
|
||||
// the netlink monitor is never stop and the old states must be kept.
|
||||
private final SparseArray<LinkProperties> mIfaceIdxToLinkProperties = new SparseArray<>();
|
||||
private final byte[] mPacketReadBuffer = new byte[READ_BUFFER_SIZE];
|
||||
@NonNull
|
||||
private final SocketRequestMonitor mSocketRequestMonitor;
|
||||
private boolean mMonitoringSockets = false;
|
||||
private boolean mRequestStop = false;
|
||||
private String mWifiP2pTetherInterface = null;
|
||||
@@ -155,17 +157,20 @@ public class MdnsSocketProvider {
|
||||
}
|
||||
|
||||
public MdnsSocketProvider(@NonNull Context context, @NonNull Looper looper,
|
||||
@NonNull SharedLog sharedLog) {
|
||||
this(context, looper, new Dependencies(), sharedLog);
|
||||
@NonNull SharedLog sharedLog,
|
||||
@NonNull SocketRequestMonitor socketRequestMonitor) {
|
||||
this(context, looper, new Dependencies(), sharedLog, socketRequestMonitor);
|
||||
}
|
||||
|
||||
MdnsSocketProvider(@NonNull Context context, @NonNull Looper looper,
|
||||
@NonNull Dependencies deps, @NonNull SharedLog sharedLog) {
|
||||
@NonNull Dependencies deps, @NonNull SharedLog sharedLog,
|
||||
@NonNull SocketRequestMonitor socketRequestMonitor) {
|
||||
mContext = context;
|
||||
mLooper = looper;
|
||||
mHandler = new Handler(looper);
|
||||
mDependencies = deps;
|
||||
mSharedLog = sharedLog;
|
||||
mSocketRequestMonitor = socketRequestMonitor;
|
||||
mNetworkCallback = new NetworkCallback() {
|
||||
@Override
|
||||
public void onLost(Network network) {
|
||||
@@ -312,10 +317,12 @@ public class MdnsSocketProvider {
|
||||
private static class SocketInfo {
|
||||
final MdnsInterfaceSocket mSocket;
|
||||
final List<LinkAddress> mAddresses;
|
||||
final int[] mTransports;
|
||||
|
||||
SocketInfo(MdnsInterfaceSocket socket, List<LinkAddress> addresses) {
|
||||
SocketInfo(MdnsInterfaceSocket socket, List<LinkAddress> addresses, int[] transports) {
|
||||
mSocket = socket;
|
||||
mAddresses = new ArrayList<>(addresses);
|
||||
mTransports = transports;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -498,9 +505,13 @@ public class MdnsSocketProvider {
|
||||
if (networkKey == LOCAL_NET) {
|
||||
transports = new int[0];
|
||||
} else {
|
||||
transports = mActiveNetworksTransports.get(((NetworkAsKey) networkKey).mNetwork);
|
||||
if (transports == null) {
|
||||
final int[] knownTransports =
|
||||
mActiveNetworksTransports.get(((NetworkAsKey) networkKey).mNetwork);
|
||||
if (knownTransports != null) {
|
||||
transports = knownTransports;
|
||||
} else {
|
||||
Log.wtf(TAG, "transports is missing for key: " + networkKey);
|
||||
transports = new int[0];
|
||||
}
|
||||
}
|
||||
if (networkInterface == null || !isMdnsCapableInterface(networkInterface, transports)) {
|
||||
@@ -512,21 +523,22 @@ public class MdnsSocketProvider {
|
||||
networkInterface.getNetworkInterface(), MdnsConstants.MDNS_PORT, mLooper,
|
||||
mPacketReadBuffer);
|
||||
final List<LinkAddress> addresses = lp.getLinkAddresses();
|
||||
// TODO: technically transport types are mutable, although generally not in ways that
|
||||
// would meaningfully impact the logic using it here. Consider updating logic to
|
||||
// support transports being added/removed.
|
||||
final SocketInfo socketInfo = new SocketInfo(socket, addresses, transports);
|
||||
if (networkKey == LOCAL_NET) {
|
||||
mTetherInterfaceSockets.put(interfaceName, new SocketInfo(socket, addresses));
|
||||
mTetherInterfaceSockets.put(interfaceName, socketInfo);
|
||||
} else {
|
||||
mNetworkSockets.put(((NetworkAsKey) networkKey).mNetwork,
|
||||
new SocketInfo(socket, addresses));
|
||||
mNetworkSockets.put(((NetworkAsKey) networkKey).mNetwork, socketInfo);
|
||||
}
|
||||
// Try to join IPv4/IPv6 group.
|
||||
socket.joinGroup(addresses);
|
||||
|
||||
// Notify the listeners which need this socket.
|
||||
if (networkKey == LOCAL_NET) {
|
||||
notifySocketCreated(null /* network */, socket, addresses);
|
||||
} else {
|
||||
notifySocketCreated(((NetworkAsKey) networkKey).mNetwork, socket, addresses);
|
||||
}
|
||||
final Network network =
|
||||
networkKey == LOCAL_NET ? null : ((NetworkAsKey) networkKey).mNetwork;
|
||||
notifySocketCreated(network, socketInfo);
|
||||
} catch (IOException e) {
|
||||
mSharedLog.e("Create socket failed ifName:" + interfaceName, e);
|
||||
}
|
||||
@@ -568,6 +580,7 @@ public class MdnsSocketProvider {
|
||||
|
||||
socketInfo.mSocket.destroy();
|
||||
notifyInterfaceDestroyed(network, socketInfo.mSocket);
|
||||
mSocketRequestMonitor.onSocketDestroyed(network, socketInfo.mSocket);
|
||||
mSharedLog.log("Remove socket on net:" + network);
|
||||
}
|
||||
|
||||
@@ -576,15 +589,18 @@ public class MdnsSocketProvider {
|
||||
if (socketInfo == null) return;
|
||||
socketInfo.mSocket.destroy();
|
||||
notifyInterfaceDestroyed(null /* network */, socketInfo.mSocket);
|
||||
mSocketRequestMonitor.onSocketDestroyed(null /* network */, socketInfo.mSocket);
|
||||
mSharedLog.log("Remove socket on ifName:" + interfaceName);
|
||||
}
|
||||
|
||||
private void notifySocketCreated(Network network, MdnsInterfaceSocket socket,
|
||||
List<LinkAddress> addresses) {
|
||||
private void notifySocketCreated(Network network, SocketInfo socketInfo) {
|
||||
for (int i = 0; i < mCallbacksToRequestedNetworks.size(); i++) {
|
||||
final Network requestedNetwork = mCallbacksToRequestedNetworks.valueAt(i);
|
||||
if (isNetworkMatched(requestedNetwork, network)) {
|
||||
mCallbacksToRequestedNetworks.keyAt(i).onSocketCreated(network, socket, addresses);
|
||||
mCallbacksToRequestedNetworks.keyAt(i).onSocketCreated(network, socketInfo.mSocket,
|
||||
socketInfo.mAddresses);
|
||||
mSocketRequestMonitor.onSocketRequestFulfilled(network, socketInfo.mSocket,
|
||||
socketInfo.mTransports);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -622,6 +638,8 @@ public class MdnsSocketProvider {
|
||||
} else {
|
||||
// Notify the socket for requested network.
|
||||
cb.onSocketCreated(network, socketInfo.mSocket, socketInfo.mAddresses);
|
||||
mSocketRequestMonitor.onSocketRequestFulfilled(network, socketInfo.mSocket,
|
||||
socketInfo.mTransports);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -636,6 +654,8 @@ public class MdnsSocketProvider {
|
||||
// Notify the socket for requested network.
|
||||
cb.onSocketCreated(
|
||||
null /* network */, socketInfo.mSocket, socketInfo.mAddresses);
|
||||
mSocketRequestMonitor.onSocketRequestFulfilled(null /* socketNetwork */,
|
||||
socketInfo.mSocket, socketInfo.mTransports);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -690,6 +710,7 @@ public class MdnsSocketProvider {
|
||||
if (matchRequestedNetwork(network)) continue;
|
||||
final SocketInfo info = mNetworkSockets.removeAt(i);
|
||||
info.mSocket.destroy();
|
||||
mSocketRequestMonitor.onSocketDestroyed(network, info.mSocket);
|
||||
mSharedLog.log("Remove socket on net:" + network + " after unrequestSocket");
|
||||
}
|
||||
|
||||
@@ -699,6 +720,7 @@ public class MdnsSocketProvider {
|
||||
for (int i = mTetherInterfaceSockets.size() - 1; i >= 0; i--) {
|
||||
final SocketInfo info = mTetherInterfaceSockets.valueAt(i);
|
||||
info.mSocket.destroy();
|
||||
mSocketRequestMonitor.onSocketDestroyed(null /* network */, info.mSocket);
|
||||
mSharedLog.log("Remove socket on ifName:" + mTetherInterfaceSockets.keyAt(i)
|
||||
+ " after unrequestSocket");
|
||||
}
|
||||
@@ -709,19 +731,61 @@ public class MdnsSocketProvider {
|
||||
}
|
||||
|
||||
|
||||
/*** Callbacks for listening socket changes */
|
||||
/**
|
||||
* Callback used to register socket requests.
|
||||
*/
|
||||
public interface SocketCallback {
|
||||
/*** Notify the socket is created */
|
||||
/**
|
||||
* Notify the socket was created for the registered request.
|
||||
*
|
||||
* This may be called immediately when the request is registered with an existing socket,
|
||||
* if it had been created previously for other requests.
|
||||
*/
|
||||
default void onSocketCreated(@Nullable Network network, @NonNull MdnsInterfaceSocket socket,
|
||||
@NonNull List<LinkAddress> addresses) {}
|
||||
/*** Notify the interface is destroyed */
|
||||
|
||||
/**
|
||||
* Notify that the interface was destroyed, so the provided socket cannot be used anymore.
|
||||
*
|
||||
* This indicates that although the socket was still requested, it had to be destroyed.
|
||||
*/
|
||||
default void onInterfaceDestroyed(@Nullable Network network,
|
||||
@NonNull MdnsInterfaceSocket socket) {}
|
||||
/*** Notify the addresses is changed on the network */
|
||||
|
||||
/**
|
||||
* Notify the interface addresses have changed for the network.
|
||||
*/
|
||||
default void onAddressesChanged(@Nullable Network network,
|
||||
@NonNull MdnsInterfaceSocket socket, @NonNull List<LinkAddress> addresses) {}
|
||||
}
|
||||
|
||||
/**
|
||||
* Global callback indicating when sockets are created or destroyed for requests.
|
||||
*/
|
||||
public interface SocketRequestMonitor {
|
||||
/**
|
||||
* Indicates that the socket was used to fulfill the request of one requester.
|
||||
*
|
||||
* There is always at most one socket created for each interface. The interface is available
|
||||
* in {@link MdnsInterfaceSocket#getInterface()}.
|
||||
* @param socketNetwork The network of the socket interface, if any.
|
||||
* @param socket The socket that was provided to a requester.
|
||||
* @param transports Array of TRANSPORT_* from {@link NetworkCapabilities}. Empty if the
|
||||
* interface is not part of a network with known transports.
|
||||
*/
|
||||
default void onSocketRequestFulfilled(@Nullable Network socketNetwork,
|
||||
@NonNull MdnsInterfaceSocket socket, @NonNull int[] transports) {}
|
||||
|
||||
/**
|
||||
* Indicates that a previously created socket was destroyed.
|
||||
*
|
||||
* @param socketNetwork The network of the socket interface, if any.
|
||||
* @param socket The destroyed socket.
|
||||
*/
|
||||
default void onSocketDestroyed(@Nullable Network socketNetwork,
|
||||
@NonNull MdnsInterfaceSocket socket) {}
|
||||
}
|
||||
|
||||
private interface NetworkKey {
|
||||
}
|
||||
|
||||
|
||||
@@ -20,6 +20,7 @@ import android.annotation.NonNull;
|
||||
import android.annotation.Nullable;
|
||||
import android.net.Network;
|
||||
import android.os.Handler;
|
||||
import android.util.ArraySet;
|
||||
|
||||
import com.android.server.connectivity.mdns.MdnsConstants;
|
||||
import com.android.server.connectivity.mdns.MdnsRecord;
|
||||
@@ -129,12 +130,21 @@ public class MdnsUtils {
|
||||
return false;
|
||||
}
|
||||
|
||||
/*** Check whether the target network is matched current network */
|
||||
/*** Check whether the target network matches the current network */
|
||||
public static boolean isNetworkMatched(@Nullable Network targetNetwork,
|
||||
@Nullable Network currentNetwork) {
|
||||
return targetNetwork == null || targetNetwork.equals(currentNetwork);
|
||||
}
|
||||
|
||||
/*** Check whether the target network matches any of the current networks */
|
||||
public static boolean isAnyNetworkMatched(@Nullable Network targetNetwork,
|
||||
ArraySet<Network> currentNetworks) {
|
||||
if (targetNetwork == null) {
|
||||
return !currentNetworks.isEmpty();
|
||||
}
|
||||
return currentNetworks.contains(targetNetwork);
|
||||
}
|
||||
|
||||
/**
|
||||
* Truncate a service name to up to maxLength UTF-8 bytes.
|
||||
*/
|
||||
|
||||
@@ -16,13 +16,21 @@
|
||||
|
||||
package com.android.server;
|
||||
|
||||
import static android.app.ActivityManager.RunningAppProcessInfo.IMPORTANCE_CACHED;
|
||||
import static android.app.ActivityManager.RunningAppProcessInfo.IMPORTANCE_FOREGROUND;
|
||||
import static android.app.ActivityManager.RunningAppProcessInfo.IMPORTANCE_GONE;
|
||||
import static android.app.ActivityManager.RunningAppProcessInfo.IMPORTANCE_VISIBLE;
|
||||
import static android.net.InetAddresses.parseNumericAddress;
|
||||
import static android.net.NetworkCapabilities.TRANSPORT_ETHERNET;
|
||||
import static android.net.NetworkCapabilities.TRANSPORT_VPN;
|
||||
import static android.net.NetworkCapabilities.TRANSPORT_WIFI;
|
||||
import static android.net.connectivity.ConnectivityCompatChanges.ENABLE_PLATFORM_MDNS_BACKEND;
|
||||
import static android.net.connectivity.ConnectivityCompatChanges.RUN_NATIVE_NSD_ONLY_IF_LEGACY_APPS_T_AND_LATER;
|
||||
import static android.net.nsd.NsdManager.FAILURE_BAD_PARAMETERS;
|
||||
import static android.net.nsd.NsdManager.FAILURE_INTERNAL_ERROR;
|
||||
import static android.net.nsd.NsdManager.FAILURE_OPERATION_NOT_RUNNING;
|
||||
|
||||
import static com.android.server.NsdService.DEFAULT_RUNNING_APP_ACTIVE_IMPORTANCE_CUTOFF;
|
||||
import static com.android.server.NsdService.parseTypeAndSubtype;
|
||||
import static com.android.testutils.ContextUtils.mockService;
|
||||
|
||||
@@ -42,6 +50,7 @@ import static org.mockito.Mockito.any;
|
||||
import static org.mockito.Mockito.doCallRealMethod;
|
||||
import static org.mockito.Mockito.doReturn;
|
||||
import static org.mockito.Mockito.eq;
|
||||
import static org.mockito.Mockito.inOrder;
|
||||
import static org.mockito.Mockito.mock;
|
||||
import static org.mockito.Mockito.never;
|
||||
import static org.mockito.Mockito.reset;
|
||||
@@ -51,6 +60,8 @@ import static org.mockito.Mockito.verify;
|
||||
import static org.mockito.Mockito.verifyNoMoreInteractions;
|
||||
import static org.mockito.Mockito.when;
|
||||
|
||||
import android.app.ActivityManager;
|
||||
import android.app.ActivityManager.OnUidImportanceListener;
|
||||
import android.compat.testing.PlatformCompatChangeRule;
|
||||
import android.content.ContentResolver;
|
||||
import android.content.Context;
|
||||
@@ -68,7 +79,9 @@ import android.net.nsd.NsdManager;
|
||||
import android.net.nsd.NsdManager.DiscoveryListener;
|
||||
import android.net.nsd.NsdManager.RegistrationListener;
|
||||
import android.net.nsd.NsdManager.ResolveListener;
|
||||
import android.net.nsd.NsdManager.ServiceInfoCallback;
|
||||
import android.net.nsd.NsdServiceInfo;
|
||||
import android.net.wifi.WifiManager;
|
||||
import android.os.Binder;
|
||||
import android.os.Build;
|
||||
import android.os.Handler;
|
||||
@@ -85,10 +98,12 @@ import androidx.test.filters.SmallTest;
|
||||
import com.android.server.NsdService.Dependencies;
|
||||
import com.android.server.connectivity.mdns.MdnsAdvertiser;
|
||||
import com.android.server.connectivity.mdns.MdnsDiscoveryManager;
|
||||
import com.android.server.connectivity.mdns.MdnsInterfaceSocket;
|
||||
import com.android.server.connectivity.mdns.MdnsSearchOptions;
|
||||
import com.android.server.connectivity.mdns.MdnsServiceBrowserListener;
|
||||
import com.android.server.connectivity.mdns.MdnsServiceInfo;
|
||||
import com.android.server.connectivity.mdns.MdnsSocketProvider;
|
||||
import com.android.server.connectivity.mdns.MdnsSocketProvider.SocketRequestMonitor;
|
||||
import com.android.testutils.DevSdkIgnoreRule;
|
||||
import com.android.testutils.DevSdkIgnoreRunner;
|
||||
import com.android.testutils.HandlerUtils;
|
||||
@@ -101,6 +116,7 @@ import org.junit.rules.TestRule;
|
||||
import org.junit.runner.RunWith;
|
||||
import org.mockito.AdditionalAnswers;
|
||||
import org.mockito.ArgumentCaptor;
|
||||
import org.mockito.InOrder;
|
||||
import org.mockito.Mock;
|
||||
import org.mockito.MockitoAnnotations;
|
||||
|
||||
@@ -145,6 +161,11 @@ public class NsdServiceTest {
|
||||
@Mock MdnsDiscoveryManager mDiscoveryManager;
|
||||
@Mock MdnsAdvertiser mAdvertiser;
|
||||
@Mock MdnsSocketProvider mSocketProvider;
|
||||
@Mock WifiManager mWifiManager;
|
||||
@Mock WifiManager.MulticastLock mMulticastLock;
|
||||
@Mock ActivityManager mActivityManager;
|
||||
SocketRequestMonitor mSocketRequestMonitor;
|
||||
OnUidImportanceListener mUidImportanceListener;
|
||||
HandlerThread mThread;
|
||||
TestHandler mHandler;
|
||||
NsdService mService;
|
||||
@@ -167,9 +188,13 @@ public class NsdServiceTest {
|
||||
mHandler = new TestHandler(mThread.getLooper());
|
||||
when(mContext.getContentResolver()).thenReturn(mResolver);
|
||||
mockService(mContext, MDnsManager.class, MDnsManager.MDNS_SERVICE, mMockMDnsM);
|
||||
mockService(mContext, WifiManager.class, Context.WIFI_SERVICE, mWifiManager);
|
||||
mockService(mContext, ActivityManager.class, Context.ACTIVITY_SERVICE, mActivityManager);
|
||||
if (mContext.getSystemService(MDnsManager.class) == null) {
|
||||
// Test is using mockito-extended
|
||||
doCallRealMethod().when(mContext).getSystemService(MDnsManager.class);
|
||||
doCallRealMethod().when(mContext).getSystemService(WifiManager.class);
|
||||
doCallRealMethod().when(mContext).getSystemService(ActivityManager.class);
|
||||
}
|
||||
doReturn(true).when(mMockMDnsM).registerService(
|
||||
anyInt(), anyString(), anyString(), anyInt(), any(), anyInt());
|
||||
@@ -180,9 +205,21 @@ public class NsdServiceTest {
|
||||
doReturn(false).when(mDeps).isMdnsDiscoveryManagerEnabled(any(Context.class));
|
||||
doReturn(mDiscoveryManager).when(mDeps)
|
||||
.makeMdnsDiscoveryManager(any(), any(), any());
|
||||
doReturn(mSocketProvider).when(mDeps).makeMdnsSocketProvider(any(), any(), any());
|
||||
doReturn(mMulticastLock).when(mWifiManager).createMulticastLock(any());
|
||||
doReturn(mSocketProvider).when(mDeps).makeMdnsSocketProvider(any(), any(), any(), any());
|
||||
doReturn(DEFAULT_RUNNING_APP_ACTIVE_IMPORTANCE_CUTOFF).when(mDeps).getDeviceConfigInt(
|
||||
eq(NsdService.MDNS_CONFIG_RUNNING_APP_ACTIVE_IMPORTANCE_CUTOFF), anyInt());
|
||||
doReturn(mAdvertiser).when(mDeps).makeMdnsAdvertiser(any(), any(), any(), any());
|
||||
mService = makeService();
|
||||
final ArgumentCaptor<SocketRequestMonitor> cbMonitorCaptor =
|
||||
ArgumentCaptor.forClass(SocketRequestMonitor.class);
|
||||
verify(mDeps).makeMdnsSocketProvider(any(), any(), any(), cbMonitorCaptor.capture());
|
||||
mSocketRequestMonitor = cbMonitorCaptor.getValue();
|
||||
|
||||
final ArgumentCaptor<OnUidImportanceListener> uidListenerCaptor =
|
||||
ArgumentCaptor.forClass(OnUidImportanceListener.class);
|
||||
verify(mActivityManager).addOnUidImportanceListener(uidListenerCaptor.capture(), anyInt());
|
||||
mUidImportanceListener = uidListenerCaptor.getValue();
|
||||
}
|
||||
|
||||
@After
|
||||
@@ -738,8 +775,8 @@ public class NsdServiceTest {
|
||||
public void testRegisterAndUnregisterServiceInfoCallback() {
|
||||
final NsdManager client = connectClient(mService);
|
||||
final NsdServiceInfo request = new NsdServiceInfo(SERVICE_NAME, SERVICE_TYPE);
|
||||
final NsdManager.ServiceInfoCallback serviceInfoCallback = mock(
|
||||
NsdManager.ServiceInfoCallback.class);
|
||||
final ServiceInfoCallback serviceInfoCallback = mock(
|
||||
ServiceInfoCallback.class);
|
||||
final String serviceTypeWithLocalDomain = SERVICE_TYPE + ".local";
|
||||
final Network network = new Network(999);
|
||||
request.setNetwork(network);
|
||||
@@ -813,8 +850,8 @@ public class NsdServiceTest {
|
||||
final NsdManager client = connectClient(mService);
|
||||
final String invalidServiceType = "a_service";
|
||||
final NsdServiceInfo request = new NsdServiceInfo(SERVICE_NAME, invalidServiceType);
|
||||
final NsdManager.ServiceInfoCallback serviceInfoCallback = mock(
|
||||
NsdManager.ServiceInfoCallback.class);
|
||||
final ServiceInfoCallback serviceInfoCallback = mock(
|
||||
ServiceInfoCallback.class);
|
||||
client.registerServiceInfoCallback(request, Runnable::run, serviceInfoCallback);
|
||||
waitForIdle();
|
||||
|
||||
@@ -826,8 +863,8 @@ public class NsdServiceTest {
|
||||
@Test
|
||||
public void testUnregisterNotRegisteredCallback() {
|
||||
final NsdManager client = connectClient(mService);
|
||||
final NsdManager.ServiceInfoCallback serviceInfoCallback = mock(
|
||||
NsdManager.ServiceInfoCallback.class);
|
||||
final ServiceInfoCallback serviceInfoCallback = mock(
|
||||
ServiceInfoCallback.class);
|
||||
|
||||
assertThrows(IllegalArgumentException.class, () ->
|
||||
client.unregisterServiceInfoCallback(serviceInfoCallback));
|
||||
@@ -1336,6 +1373,194 @@ public class NsdServiceTest {
|
||||
verify(mDiscoveryManager, times(2)).registerListener(anyString(), any(), any());
|
||||
}
|
||||
|
||||
@Test
|
||||
@EnableCompatChanges(ENABLE_PLATFORM_MDNS_BACKEND)
|
||||
public void testTakeMulticastLockOnBehalfOfClient_ForWifiNetworksOnly() {
|
||||
// Test on one client in the foreground
|
||||
mUidImportanceListener.onUidImportance(123, IMPORTANCE_FOREGROUND);
|
||||
doReturn(123).when(mDeps).getCallingUid();
|
||||
final NsdManager client = connectClient(mService);
|
||||
|
||||
final NsdServiceInfo regInfo = new NsdServiceInfo(SERVICE_NAME, SERVICE_TYPE);
|
||||
regInfo.setHostAddresses(List.of(parseNumericAddress("192.0.2.123")));
|
||||
regInfo.setPort(12345);
|
||||
// File a request for all networks
|
||||
regInfo.setNetwork(null);
|
||||
|
||||
final RegistrationListener regListener = mock(RegistrationListener.class);
|
||||
client.registerService(regInfo, NsdManager.PROTOCOL_DNS_SD, Runnable::run, regListener);
|
||||
waitForIdle();
|
||||
verify(mSocketProvider).startMonitoringSockets();
|
||||
verify(mAdvertiser).addService(anyInt(), any(), any());
|
||||
|
||||
final Network wifiNetwork1 = new Network(123);
|
||||
final Network wifiNetwork2 = new Network(124);
|
||||
final Network ethernetNetwork = new Network(125);
|
||||
|
||||
final MdnsInterfaceSocket wifiNetworkSocket1 = mock(MdnsInterfaceSocket.class);
|
||||
final MdnsInterfaceSocket wifiNetworkSocket2 = mock(MdnsInterfaceSocket.class);
|
||||
final MdnsInterfaceSocket ethernetNetworkSocket = mock(MdnsInterfaceSocket.class);
|
||||
|
||||
// Nothing happens for networks with no transports, no Wi-Fi transport, or VPN transport
|
||||
mHandler.post(() -> {
|
||||
mSocketRequestMonitor.onSocketRequestFulfilled(
|
||||
new Network(125), mock(MdnsInterfaceSocket.class), new int[0]);
|
||||
mSocketRequestMonitor.onSocketRequestFulfilled(
|
||||
ethernetNetwork, ethernetNetworkSocket,
|
||||
new int[] { TRANSPORT_ETHERNET });
|
||||
mSocketRequestMonitor.onSocketRequestFulfilled(
|
||||
new Network(127), mock(MdnsInterfaceSocket.class),
|
||||
new int[] { TRANSPORT_WIFI, TRANSPORT_VPN });
|
||||
});
|
||||
waitForIdle();
|
||||
verify(mWifiManager, never()).createMulticastLock(any());
|
||||
|
||||
// First Wi-Fi network
|
||||
mHandler.post(() -> mSocketRequestMonitor.onSocketRequestFulfilled(
|
||||
wifiNetwork1, wifiNetworkSocket1, new int[] { TRANSPORT_WIFI }));
|
||||
waitForIdle();
|
||||
verify(mWifiManager).createMulticastLock(any());
|
||||
verify(mMulticastLock).acquire();
|
||||
|
||||
// Second Wi-Fi network
|
||||
mHandler.post(() -> mSocketRequestMonitor.onSocketRequestFulfilled(
|
||||
wifiNetwork2, wifiNetworkSocket2, new int[] { TRANSPORT_WIFI }));
|
||||
waitForIdle();
|
||||
verifyNoMoreInteractions(mMulticastLock);
|
||||
|
||||
// One Wi-Fi network becomes unused, nothing happens
|
||||
mHandler.post(() -> mSocketRequestMonitor.onSocketDestroyed(
|
||||
wifiNetwork1, wifiNetworkSocket1));
|
||||
waitForIdle();
|
||||
verifyNoMoreInteractions(mMulticastLock);
|
||||
|
||||
// Ethernet network becomes unused, still nothing
|
||||
mHandler.post(() -> mSocketRequestMonitor.onSocketDestroyed(
|
||||
ethernetNetwork, ethernetNetworkSocket));
|
||||
waitForIdle();
|
||||
verifyNoMoreInteractions(mMulticastLock);
|
||||
|
||||
// The second Wi-Fi network becomes unused, the lock is released
|
||||
mHandler.post(() -> mSocketRequestMonitor.onSocketDestroyed(
|
||||
wifiNetwork2, wifiNetworkSocket2));
|
||||
waitForIdle();
|
||||
verify(mMulticastLock).release();
|
||||
}
|
||||
|
||||
@Test
|
||||
@EnableCompatChanges(ENABLE_PLATFORM_MDNS_BACKEND)
|
||||
public void testTakeMulticastLockOnBehalfOfClient_ForForegroundAppsOnly() {
|
||||
final int uid1 = 12;
|
||||
final int uid2 = 34;
|
||||
final int uid3 = 56;
|
||||
final int uid4 = 78;
|
||||
final InOrder lockOrder = inOrder(mMulticastLock);
|
||||
// Connect one client without any foreground info
|
||||
doReturn(uid1).when(mDeps).getCallingUid();
|
||||
final NsdManager client1 = connectClient(mService);
|
||||
|
||||
// Connect client2 as visible, but not foreground
|
||||
mUidImportanceListener.onUidImportance(uid2, IMPORTANCE_VISIBLE);
|
||||
waitForIdle();
|
||||
doReturn(uid2).when(mDeps).getCallingUid();
|
||||
final NsdManager client2 = connectClient(mService);
|
||||
|
||||
// Connect client3, client4 as foreground
|
||||
mUidImportanceListener.onUidImportance(uid3, IMPORTANCE_FOREGROUND);
|
||||
waitForIdle();
|
||||
doReturn(uid3).when(mDeps).getCallingUid();
|
||||
final NsdManager client3 = connectClient(mService);
|
||||
|
||||
mUidImportanceListener.onUidImportance(uid4, IMPORTANCE_FOREGROUND);
|
||||
waitForIdle();
|
||||
doReturn(uid4).when(mDeps).getCallingUid();
|
||||
final NsdManager client4 = connectClient(mService);
|
||||
|
||||
// First client advertises on any network
|
||||
final NsdServiceInfo regInfo = new NsdServiceInfo(SERVICE_NAME, SERVICE_TYPE);
|
||||
regInfo.setHostAddresses(List.of(parseNumericAddress("192.0.2.123")));
|
||||
regInfo.setPort(12345);
|
||||
regInfo.setNetwork(null);
|
||||
final RegistrationListener regListener = mock(RegistrationListener.class);
|
||||
client1.registerService(regInfo, NsdManager.PROTOCOL_DNS_SD, Runnable::run, regListener);
|
||||
waitForIdle();
|
||||
|
||||
final MdnsInterfaceSocket wifiSocket = mock(MdnsInterfaceSocket.class);
|
||||
final Network wifiNetwork = new Network(123);
|
||||
|
||||
final MdnsInterfaceSocket ethSocket = mock(MdnsInterfaceSocket.class);
|
||||
final Network ethNetwork = new Network(234);
|
||||
|
||||
mHandler.post(() -> {
|
||||
mSocketRequestMonitor.onSocketRequestFulfilled(
|
||||
wifiNetwork, wifiSocket, new int[] { TRANSPORT_WIFI });
|
||||
mSocketRequestMonitor.onSocketRequestFulfilled(
|
||||
ethNetwork, ethSocket, new int[] { TRANSPORT_ETHERNET });
|
||||
});
|
||||
waitForIdle();
|
||||
|
||||
// No multicast lock since client1 has no foreground info
|
||||
lockOrder.verifyNoMoreInteractions();
|
||||
|
||||
// Second client discovers specifically on the Wi-Fi network
|
||||
final DiscoveryListener discListener = mock(DiscoveryListener.class);
|
||||
client2.discoverServices(SERVICE_TYPE, NsdManager.PROTOCOL_DNS_SD, wifiNetwork,
|
||||
Runnable::run, discListener);
|
||||
waitForIdle();
|
||||
mHandler.post(() -> mSocketRequestMonitor.onSocketRequestFulfilled(
|
||||
wifiNetwork, wifiSocket, new int[] { TRANSPORT_WIFI }));
|
||||
waitForIdle();
|
||||
// No multicast lock since client2 is not visible enough
|
||||
lockOrder.verifyNoMoreInteractions();
|
||||
|
||||
// Third client registers a callback on all networks
|
||||
final NsdServiceInfo cbInfo = new NsdServiceInfo(SERVICE_NAME, SERVICE_TYPE);
|
||||
cbInfo.setNetwork(null);
|
||||
final ServiceInfoCallback infoCb = mock(ServiceInfoCallback.class);
|
||||
client3.registerServiceInfoCallback(cbInfo, Runnable::run, infoCb);
|
||||
waitForIdle();
|
||||
mHandler.post(() -> {
|
||||
mSocketRequestMonitor.onSocketRequestFulfilled(
|
||||
wifiNetwork, wifiSocket, new int[] { TRANSPORT_WIFI });
|
||||
mSocketRequestMonitor.onSocketRequestFulfilled(
|
||||
ethNetwork, ethSocket, new int[] { TRANSPORT_ETHERNET });
|
||||
});
|
||||
waitForIdle();
|
||||
|
||||
// Multicast lock is taken for third client
|
||||
lockOrder.verify(mMulticastLock).acquire();
|
||||
|
||||
// Client3 goes to the background
|
||||
mUidImportanceListener.onUidImportance(uid3, IMPORTANCE_CACHED);
|
||||
waitForIdle();
|
||||
lockOrder.verify(mMulticastLock).release();
|
||||
|
||||
// client4 resolves on a different network
|
||||
final ResolveListener resolveListener = mock(ResolveListener.class);
|
||||
final NsdServiceInfo resolveInfo = new NsdServiceInfo(SERVICE_NAME, SERVICE_TYPE);
|
||||
resolveInfo.setNetwork(ethNetwork);
|
||||
client4.resolveService(resolveInfo, Runnable::run, resolveListener);
|
||||
waitForIdle();
|
||||
mHandler.post(() -> mSocketRequestMonitor.onSocketRequestFulfilled(
|
||||
ethNetwork, ethSocket, new int[] { TRANSPORT_ETHERNET }));
|
||||
waitForIdle();
|
||||
|
||||
// client4 is foreground, but not Wi-Fi
|
||||
lockOrder.verifyNoMoreInteractions();
|
||||
|
||||
// Second client becomes foreground
|
||||
mUidImportanceListener.onUidImportance(uid2, IMPORTANCE_FOREGROUND);
|
||||
waitForIdle();
|
||||
|
||||
lockOrder.verify(mMulticastLock).acquire();
|
||||
|
||||
// Second client is lost
|
||||
mUidImportanceListener.onUidImportance(uid2, IMPORTANCE_GONE);
|
||||
waitForIdle();
|
||||
|
||||
lockOrder.verify(mMulticastLock).release();
|
||||
}
|
||||
|
||||
private void waitForIdle() {
|
||||
HandlerUtils.waitForIdle(mHandler, TIMEOUT_MS);
|
||||
}
|
||||
|
||||
@@ -32,10 +32,12 @@ import static org.junit.Assert.assertTrue;
|
||||
import static org.mockito.ArgumentMatchers.anyInt;
|
||||
import static org.mockito.ArgumentMatchers.anyString;
|
||||
import static org.mockito.ArgumentMatchers.argThat;
|
||||
import static org.mockito.ArgumentMatchers.eq;
|
||||
import static org.mockito.Mockito.any;
|
||||
import static org.mockito.Mockito.doAnswer;
|
||||
import static org.mockito.Mockito.doCallRealMethod;
|
||||
import static org.mockito.Mockito.doReturn;
|
||||
import static org.mockito.Mockito.inOrder;
|
||||
import static org.mockito.Mockito.mock;
|
||||
import static org.mockito.Mockito.never;
|
||||
import static org.mockito.Mockito.times;
|
||||
@@ -70,6 +72,7 @@ import com.android.net.module.util.netlink.RtNetlinkAddressMessage;
|
||||
import com.android.net.module.util.netlink.StructIfaddrMsg;
|
||||
import com.android.net.module.util.netlink.StructNlMsgHdr;
|
||||
import com.android.server.connectivity.mdns.MdnsSocketProvider.Dependencies;
|
||||
import com.android.server.connectivity.mdns.MdnsSocketProvider.SocketRequestMonitor;
|
||||
import com.android.server.connectivity.mdns.internal.SocketNetlinkMonitor;
|
||||
import com.android.testutils.DevSdkIgnoreRule;
|
||||
import com.android.testutils.DevSdkIgnoreRunner;
|
||||
@@ -79,6 +82,7 @@ import org.junit.Before;
|
||||
import org.junit.Test;
|
||||
import org.junit.runner.RunWith;
|
||||
import org.mockito.ArgumentCaptor;
|
||||
import org.mockito.InOrder;
|
||||
import org.mockito.Mock;
|
||||
import org.mockito.MockitoAnnotations;
|
||||
|
||||
@@ -114,6 +118,7 @@ public class MdnsSocketProviderTest {
|
||||
@Mock private NetworkInterfaceWrapper mTestNetworkIfaceWrapper;
|
||||
@Mock private NetworkInterfaceWrapper mLocalOnlyIfaceWrapper;
|
||||
@Mock private NetworkInterfaceWrapper mTetheredIfaceWrapper;
|
||||
@Mock private SocketRequestMonitor mSocketRequestMonitor;
|
||||
private Handler mHandler;
|
||||
private MdnsSocketProvider mSocketProvider;
|
||||
private NetworkCallback mNetworkCallback;
|
||||
@@ -165,7 +170,8 @@ public class MdnsSocketProviderTest {
|
||||
return mTestSocketNetLinkMonitor;
|
||||
}).when(mDeps).createSocketNetlinkMonitor(any(), any(),
|
||||
any());
|
||||
mSocketProvider = new MdnsSocketProvider(mContext, thread.getLooper(), mDeps, mLog);
|
||||
mSocketProvider = new MdnsSocketProvider(mContext, thread.getLooper(), mDeps, mLog,
|
||||
mSocketRequestMonitor);
|
||||
}
|
||||
|
||||
private void runOnHandler(Runnable r) {
|
||||
@@ -319,23 +325,30 @@ public class MdnsSocketProviderTest {
|
||||
public void testSocketRequestAndUnrequestSocket() {
|
||||
startMonitoringSockets();
|
||||
|
||||
final InOrder cbMonitorOrder = inOrder(mSocketRequestMonitor);
|
||||
final TestSocketCallback testCallback1 = new TestSocketCallback();
|
||||
runOnHandler(() -> mSocketProvider.requestSocket(TEST_NETWORK, testCallback1));
|
||||
testCallback1.expectedNoCallback();
|
||||
|
||||
postNetworkAvailable(TRANSPORT_WIFI);
|
||||
testCallback1.expectedSocketCreatedForNetwork(TEST_NETWORK, List.of(LINKADDRV4));
|
||||
cbMonitorOrder.verify(mSocketRequestMonitor).onSocketRequestFulfilled(eq(TEST_NETWORK),
|
||||
any(), eq(new int[] { TRANSPORT_WIFI }));
|
||||
|
||||
final TestSocketCallback testCallback2 = new TestSocketCallback();
|
||||
runOnHandler(() -> mSocketProvider.requestSocket(TEST_NETWORK, testCallback2));
|
||||
testCallback1.expectedNoCallback();
|
||||
testCallback2.expectedSocketCreatedForNetwork(TEST_NETWORK, List.of(LINKADDRV4));
|
||||
cbMonitorOrder.verify(mSocketRequestMonitor).onSocketRequestFulfilled(eq(TEST_NETWORK),
|
||||
any(), eq(new int[] { TRANSPORT_WIFI }));
|
||||
|
||||
final TestSocketCallback testCallback3 = new TestSocketCallback();
|
||||
runOnHandler(() -> mSocketProvider.requestSocket(null /* network */, testCallback3));
|
||||
testCallback1.expectedNoCallback();
|
||||
testCallback2.expectedNoCallback();
|
||||
testCallback3.expectedSocketCreatedForNetwork(TEST_NETWORK, List.of(LINKADDRV4));
|
||||
cbMonitorOrder.verify(mSocketRequestMonitor).onSocketRequestFulfilled(eq(TEST_NETWORK),
|
||||
any(), eq(new int[] { TRANSPORT_WIFI }));
|
||||
|
||||
runOnHandler(() -> mTetheringEventCallback.onLocalOnlyInterfacesChanged(
|
||||
List.of(LOCAL_ONLY_IFACE_NAME)));
|
||||
@@ -343,6 +356,8 @@ public class MdnsSocketProviderTest {
|
||||
testCallback1.expectedNoCallback();
|
||||
testCallback2.expectedNoCallback();
|
||||
testCallback3.expectedSocketCreatedForNetwork(null /* network */, List.of());
|
||||
cbMonitorOrder.verify(mSocketRequestMonitor).onSocketRequestFulfilled(eq(null),
|
||||
any(), eq(new int[0]));
|
||||
|
||||
runOnHandler(() -> mTetheringEventCallback.onTetheredInterfacesChanged(
|
||||
List.of(TETHERED_IFACE_NAME)));
|
||||
@@ -350,6 +365,8 @@ public class MdnsSocketProviderTest {
|
||||
testCallback1.expectedNoCallback();
|
||||
testCallback2.expectedNoCallback();
|
||||
testCallback3.expectedSocketCreatedForNetwork(null /* network */, List.of());
|
||||
cbMonitorOrder.verify(mSocketRequestMonitor).onSocketRequestFulfilled(eq(null),
|
||||
any(), eq(new int[0]));
|
||||
|
||||
runOnHandler(() -> mSocketProvider.unrequestSocket(testCallback1));
|
||||
testCallback1.expectedNoCallback();
|
||||
@@ -360,17 +377,22 @@ public class MdnsSocketProviderTest {
|
||||
testCallback1.expectedNoCallback();
|
||||
testCallback2.expectedInterfaceDestroyedForNetwork(TEST_NETWORK);
|
||||
testCallback3.expectedInterfaceDestroyedForNetwork(TEST_NETWORK);
|
||||
cbMonitorOrder.verify(mSocketRequestMonitor).onSocketDestroyed(eq(TEST_NETWORK), any());
|
||||
|
||||
runOnHandler(() -> mTetheringEventCallback.onLocalOnlyInterfacesChanged(List.of()));
|
||||
testCallback1.expectedNoCallback();
|
||||
testCallback2.expectedNoCallback();
|
||||
testCallback3.expectedInterfaceDestroyedForNetwork(null /* network */);
|
||||
cbMonitorOrder.verify(mSocketRequestMonitor).onSocketDestroyed(eq(null), any());
|
||||
|
||||
runOnHandler(() -> mSocketProvider.unrequestSocket(testCallback3));
|
||||
testCallback1.expectedNoCallback();
|
||||
testCallback2.expectedNoCallback();
|
||||
// There was still a tethered interface, but no callback should be sent once unregistered
|
||||
testCallback3.expectedNoCallback();
|
||||
|
||||
// However the socket is getting destroyed, so the callback monitor is notified
|
||||
cbMonitorOrder.verify(mSocketRequestMonitor).onSocketDestroyed(eq(null), any());
|
||||
}
|
||||
|
||||
private RtNetlinkAddressMessage createNetworkAddressUpdateNetLink(
|
||||
|
||||
Reference in New Issue
Block a user