/* * Copyright (C) 2022 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.android.server.connectivity.mdns; import static android.net.NetworkCapabilities.TRANSPORT_CELLULAR; import static android.net.NetworkCapabilities.TRANSPORT_VPN; import static android.net.NetworkCapabilities.TRANSPORT_WIFI; import static com.android.server.connectivity.mdns.util.MdnsUtils.ensureRunningOnHandlerThread; import static com.android.server.connectivity.mdns.util.MdnsUtils.isNetworkMatched; import android.annotation.NonNull; import android.annotation.Nullable; import android.annotation.RequiresApi; import android.content.BroadcastReceiver; import android.content.Context; import android.content.Intent; import android.content.IntentFilter; import android.net.ConnectivityManager; import android.net.ConnectivityManager.NetworkCallback; import android.net.LinkAddress; import android.net.LinkProperties; import android.net.Network; import android.net.NetworkCapabilities; import android.net.NetworkRequest; import android.net.TetheringManager; import android.net.TetheringManager.TetheringEventCallback; import android.net.wifi.p2p.WifiP2pGroup; import android.net.wifi.p2p.WifiP2pInfo; import android.net.wifi.p2p.WifiP2pManager; import android.os.Build; import android.os.Handler; import android.os.Looper; import android.util.ArrayMap; import android.util.SparseArray; import com.android.internal.annotations.VisibleForTesting; import com.android.net.module.util.CollectionUtils; import com.android.net.module.util.LinkPropertiesUtils.CompareResult; import com.android.net.module.util.SharedLog; import java.io.IOException; import java.net.NetworkInterface; import java.net.SocketException; import java.util.ArrayList; import java.util.List; import java.util.Objects; /** * The {@link MdnsSocketProvider} manages the multiple sockets for mDns. * *

This class is not thread safe, it is intended to be used only from the looper thread. * However, the constructor is an exception, as it is called on another thread; * therefore for thread safety all members of this class MUST either be final or initialized * to their default value (0, false or null). * */ @RequiresApi(Build.VERSION_CODES.TIRAMISU) public class MdnsSocketProvider { private static final String TAG = MdnsSocketProvider.class.getSimpleName(); private static final boolean DBG = MdnsDiscoveryManager.DBG; // This buffer size matches what MdnsSocketClient uses currently. // But 1440 should generally be enough because of standard Ethernet. // Note: mdnsresponder mDNSEmbeddedAPI.h uses 8940 for Ethernet jumbo frames. private static final int READ_BUFFER_SIZE = 2048; private static final int IFACE_IDX_NOT_EXIST = -1; @NonNull private final Context mContext; @NonNull private final Looper mLooper; @NonNull private final Handler mHandler; @NonNull private final Dependencies mDependencies; @NonNull private final NetworkCallback mNetworkCallback; @NonNull private final TetheringEventCallback mTetheringEventCallback; @NonNull private final AbstractSocketNetlinkMonitor mSocketNetlinkMonitor; @NonNull private final SharedLog mSharedLog; private final ArrayMap mNetworkSockets = new ArrayMap<>(); private final ArrayMap mTetherInterfaceSockets = new ArrayMap<>(); private final ArrayMap mActiveNetworksLinkProperties = new ArrayMap<>(); private final ArrayMap mActiveNetworksTransports = new ArrayMap<>(); private final ArrayMap mCallbacksToRequestedNetworks = new ArrayMap<>(); private final List mLocalOnlyInterfaces = new ArrayList<>(); private final List mTetheredInterfaces = new ArrayList<>(); // mIfaceIdxToLinkProperties should not be cleared in maybeStopMonitoringSockets() because // the netlink monitor is never stop and the old states must be kept. private final SparseArray 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; private final BroadcastReceiver mIntentReceiver = new BroadcastReceiver() { @Override public void onReceive(Context context, Intent intent) { final String newP2pIface = getWifiP2pInterface(intent); if (!mMonitoringSockets || !hasAllNetworksRequest()) { mWifiP2pTetherInterface = newP2pIface; return; } // If already serving from the correct interface, nothing to do. if (Objects.equals(mWifiP2pTetherInterface, newP2pIface)) return; if (mWifiP2pTetherInterface != null) { if (newP2pIface != null) { mSharedLog.wtf("Wifi p2p interface is changed from " + mWifiP2pTetherInterface + " to " + newP2pIface + " without null broadcast"); } // Remove the socket. removeTetherInterfaceSocket(mWifiP2pTetherInterface); } // Update mWifiP2pTetherInterface mWifiP2pTetherInterface = newP2pIface; // Check whether the socket for wifi p2p interface is created or not. final boolean socketAlreadyExists = mTetherInterfaceSockets.get(newP2pIface) != null; if (newP2pIface != null && !socketAlreadyExists) { // Create a socket for wifi p2p interface. final int ifaceIndex = mDependencies.getNetworkInterfaceIndexByName(newP2pIface, mSharedLog); createSocket(LOCAL_NET, createLPForTetheredInterface(newP2pIface, ifaceIndex)); } } }; @Nullable private static String getWifiP2pInterface(final Intent intent) { final WifiP2pGroup group = intent.getParcelableExtra(WifiP2pManager.EXTRA_WIFI_P2P_GROUP); final WifiP2pInfo p2pInfo = intent.getParcelableExtra(WifiP2pManager.EXTRA_WIFI_P2P_INFO); if (group == null || p2pInfo == null) { return null; } if (!p2pInfo.groupFormed) { return null; } else { return group.getInterface(); } } public MdnsSocketProvider(@NonNull Context context, @NonNull Looper looper, @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 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) { mActiveNetworksLinkProperties.remove(network); mActiveNetworksTransports.remove(network); removeNetworkSocket(network); } @Override public void onCapabilitiesChanged(@NonNull Network network, @NonNull NetworkCapabilities networkCapabilities) { mActiveNetworksTransports.put(network, networkCapabilities.getTransportTypes()); } @Override public void onLinkPropertiesChanged(Network network, LinkProperties lp) { handleLinkPropertiesChanged(network, lp); } }; mTetheringEventCallback = new TetheringEventCallback() { @Override public void onLocalOnlyInterfacesChanged(@NonNull List interfaces) { handleTetherInterfacesChanged(mLocalOnlyInterfaces, interfaces); } @Override public void onTetheredInterfacesChanged(@NonNull List interfaces) { handleTetherInterfacesChanged(mTetheredInterfaces, interfaces); } }; mSocketNetlinkMonitor = mDependencies.createSocketNetlinkMonitor(mHandler, mSharedLog.forSubComponent("NetlinkMonitor"), new NetLinkMessageProcessor()); // Register a intent receiver to listen wifi p2p interface changes. // Note: The wifi p2p interface change is only notified via // TetheringEventCallback#onLocalOnlyInterfacesChanged if the device is the wifi p2p group // owner. In this case, MdnsSocketProvider will receive duplicate interface changes and must // ignore the later notification because the socket has already been created. There is only // one notification from the wifi p2p connection change intent if the device is not the wifi // p2p group owner. final IntentFilter intentFilter = new IntentFilter(WifiP2pManager.WIFI_P2P_CONNECTION_CHANGED_ACTION); mContext.registerReceiver( mIntentReceiver, intentFilter, null /* broadcastPermission */, mHandler); } /** * Dependencies of MdnsSocketProvider, for injection in tests. */ @VisibleForTesting public static class Dependencies { /*** Get network interface by given interface name */ public NetworkInterfaceWrapper getNetworkInterfaceByName(@NonNull String interfaceName) throws SocketException { final NetworkInterface ni = NetworkInterface.getByName(interfaceName); return ni == null ? null : new NetworkInterfaceWrapper(ni); } /*** Create a MdnsInterfaceSocket */ public MdnsInterfaceSocket createMdnsInterfaceSocket( @NonNull NetworkInterface networkInterface, int port, @NonNull Looper looper, @NonNull byte[] packetReadBuffer, @NonNull SharedLog sharedLog) throws IOException { return new MdnsInterfaceSocket(networkInterface, port, looper, packetReadBuffer, sharedLog); } /*** Get network interface by given interface name */ public int getNetworkInterfaceIndexByName(@NonNull final String ifaceName, @NonNull SharedLog sharedLog) { final NetworkInterface iface; try { iface = NetworkInterface.getByName(ifaceName); } catch (SocketException e) { sharedLog.e("Error querying interface", e); return IFACE_IDX_NOT_EXIST; } if (iface == null) { sharedLog.e("Interface not found: " + ifaceName); return IFACE_IDX_NOT_EXIST; } return iface.getIndex(); } /*** Creates a SocketNetlinkMonitor */ public AbstractSocketNetlinkMonitor createSocketNetlinkMonitor( @NonNull final Handler handler, @NonNull final SharedLog log, @NonNull final NetLinkMonitorCallBack cb) { return SocketNetLinkMonitorFactory.createNetLinkMonitor(handler, log, cb); } } /** * The callback interface for the netlink monitor messages. */ public interface NetLinkMonitorCallBack { /** * Handles the interface address add or update. */ void addOrUpdateInterfaceAddress(int ifaceIdx, @NonNull LinkAddress newAddress); /** * Handles the interface address delete. */ void deleteInterfaceAddress(int ifaceIdx, @NonNull LinkAddress deleteAddress); } private class NetLinkMessageProcessor implements NetLinkMonitorCallBack { @Override public void addOrUpdateInterfaceAddress(int ifaceIdx, @NonNull final LinkAddress newAddress) { LinkProperties linkProperties; linkProperties = mIfaceIdxToLinkProperties.get(ifaceIdx); if (linkProperties == null) { linkProperties = new LinkProperties(); mIfaceIdxToLinkProperties.put(ifaceIdx, linkProperties); } boolean updated = linkProperties.addLinkAddress(newAddress); if (!updated) { return; } maybeUpdateTetheringSocketAddress(ifaceIdx, linkProperties.getLinkAddresses()); } @Override public void deleteInterfaceAddress(int ifaceIdx, @NonNull LinkAddress deleteAddress) { LinkProperties linkProperties; boolean updated = false; linkProperties = mIfaceIdxToLinkProperties.get(ifaceIdx); if (linkProperties != null) { updated = linkProperties.removeLinkAddress(deleteAddress); if (linkProperties.getLinkAddresses().isEmpty()) { mIfaceIdxToLinkProperties.remove(ifaceIdx); } } if (linkProperties == null || !updated) { return; } maybeUpdateTetheringSocketAddress(ifaceIdx, linkProperties.getLinkAddresses()); } } /*** Data class for storing socket related info */ private static class SocketInfo { final MdnsInterfaceSocket mSocket; final List mAddresses; final int[] mTransports; @NonNull final SocketKey mSocketKey; SocketInfo(MdnsInterfaceSocket socket, List addresses, int[] transports, @NonNull SocketKey socketKey) { mSocket = socket; mAddresses = new ArrayList<>(addresses); mTransports = transports; mSocketKey = socketKey; } } /*** Start monitoring sockets by listening callbacks for sockets creation or removal */ public void startMonitoringSockets() { ensureRunningOnHandlerThread(mHandler); mRequestStop = false; // Reset stop request flag. if (mMonitoringSockets) { mSharedLog.v("Already monitoring sockets."); return; } mSharedLog.i("Start monitoring sockets."); mContext.getSystemService(ConnectivityManager.class).registerNetworkCallback( new NetworkRequest.Builder().clearCapabilities().build(), mNetworkCallback, mHandler); final TetheringManager tetheringManager = mContext.getSystemService(TetheringManager.class); tetheringManager.registerTetheringEventCallback(mHandler::post, mTetheringEventCallback); if (mSocketNetlinkMonitor.isSupported()) { mHandler.post(mSocketNetlinkMonitor::startMonitoring); } mMonitoringSockets = true; } /** * Start netlink monitor. */ public void startNetLinkMonitor() { ensureRunningOnHandlerThread(mHandler); if (mSocketNetlinkMonitor.isSupported()) { mSocketNetlinkMonitor.startMonitoring(); } } private void maybeStopMonitoringSockets() { if (!mMonitoringSockets) return; // Already unregistered. if (!mRequestStop) return; // No stop request. // Only unregister the network callback if there is no socket request. if (mCallbacksToRequestedNetworks.isEmpty()) { mSharedLog.i("Stop monitoring sockets."); mContext.getSystemService(ConnectivityManager.class) .unregisterNetworkCallback(mNetworkCallback); final TetheringManager tetheringManager = mContext.getSystemService( TetheringManager.class); tetheringManager.unregisterTetheringEventCallback(mTetheringEventCallback); // Clear all saved status. mActiveNetworksLinkProperties.clear(); mNetworkSockets.clear(); mTetherInterfaceSockets.clear(); mLocalOnlyInterfaces.clear(); mTetheredInterfaces.clear(); mMonitoringSockets = false; } // The netlink monitor is not stopped here because the MdnsSocketProvider need to listen // to all the netlink updates when the system is up and running. } /*** Request to stop monitoring sockets and unregister callbacks */ public void requestStopWhenInactive() { ensureRunningOnHandlerThread(mHandler); if (!mMonitoringSockets) { mSharedLog.v("Monitoring sockets hasn't been started."); return; } mRequestStop = true; maybeStopMonitoringSockets(); } private boolean matchRequestedNetwork(Network network) { return hasAllNetworksRequest() || mCallbacksToRequestedNetworks.containsValue(network); } private boolean hasAllNetworksRequest() { return mCallbacksToRequestedNetworks.containsValue(null); } private void handleLinkPropertiesChanged(Network network, LinkProperties lp) { mActiveNetworksLinkProperties.put(network, lp); if (!matchRequestedNetwork(network)) { if (DBG) { mSharedLog.v("Ignore LinkProperties change. There is no request for the" + " Network:" + network); } return; } final NetworkAsKey networkKey = new NetworkAsKey(network); final SocketInfo socketInfo = mNetworkSockets.get(network); if (socketInfo == null) { createSocket(networkKey, lp); } else { updateSocketInfoAddress(network, socketInfo, lp.getLinkAddresses()); } } private void maybeUpdateTetheringSocketAddress(int ifaceIndex, @NonNull final List updatedAddresses) { for (int i = 0; i < mTetherInterfaceSockets.size(); ++i) { String tetheringInterfaceName = mTetherInterfaceSockets.keyAt(i); if (mDependencies.getNetworkInterfaceIndexByName(tetheringInterfaceName, mSharedLog) == ifaceIndex) { updateSocketInfoAddress(null /* network */, mTetherInterfaceSockets.valueAt(i), updatedAddresses); return; } } } private void updateSocketInfoAddress(@Nullable final Network network, @NonNull final SocketInfo socketInfo, @NonNull final List addresses) { // Update the addresses of this socket. socketInfo.mAddresses.clear(); socketInfo.mAddresses.addAll(addresses); // Try to join the group again. socketInfo.mSocket.joinGroup(addresses); notifyAddressesChanged(network, socketInfo, addresses); } private LinkProperties createLPForTetheredInterface(@NonNull final String interfaceName, int ifaceIndex) { final LinkProperties linkProperties = new LinkProperties(mIfaceIdxToLinkProperties.get(ifaceIndex)); linkProperties.setInterfaceName(interfaceName); return linkProperties; } private void handleTetherInterfacesChanged(List current, List updated) { if (!hasAllNetworksRequest()) { // Currently, the network for tethering can not be requested, so the sockets for // tethering are only created if there is a request for all networks (interfaces). // Therefore, only update the interface list and skip this change if no such request. if (DBG) { mSharedLog.v("Ignore tether interfaces change. There is no request for all" + " networks."); } current.clear(); current.addAll(updated); return; } final CompareResult interfaceDiff = new CompareResult<>( current, updated); for (String name : interfaceDiff.added) { // Check if a socket has been created for the interface final SocketInfo socketInfo = mTetherInterfaceSockets.get(name); if (socketInfo != null) { if (DBG) { mSharedLog.i("Socket is existed for interface:" + name); } continue; } int ifaceIndex = mDependencies.getNetworkInterfaceIndexByName(name, mSharedLog); createSocket(LOCAL_NET, createLPForTetheredInterface(name, ifaceIndex)); } for (String name : interfaceDiff.removed) { removeTetherInterfaceSocket(name); } current.clear(); current.addAll(updated); } private void createSocket(NetworkKey networkKey, LinkProperties lp) { final String interfaceName = lp.getInterfaceName(); if (interfaceName == null) { mSharedLog.e("Can not create socket with null interface name."); return; } try { final NetworkInterfaceWrapper networkInterface = mDependencies.getNetworkInterfaceByName(interfaceName); // There are no transports for tethered interfaces. Other interfaces should always // have transports since LinkProperties updates are always sent after // NetworkCapabilities updates. final int[] transports; if (networkKey == LOCAL_NET) { transports = new int[0]; } else { final int[] knownTransports = mActiveNetworksTransports.get(((NetworkAsKey) networkKey).mNetwork); if (knownTransports != null) { transports = knownTransports; } else { mSharedLog.wtf("transports is missing for key: " + networkKey); transports = new int[0]; } } if (networkInterface == null || !isMdnsCapableInterface(networkInterface, transports)) { return; } mSharedLog.log("Create socket on net:" + networkKey + ", ifName:" + interfaceName); final MdnsInterfaceSocket socket = mDependencies.createMdnsInterfaceSocket( networkInterface.getNetworkInterface(), MdnsConstants.MDNS_PORT, mLooper, mPacketReadBuffer, mSharedLog.forSubComponent( MdnsInterfaceSocket.class.getSimpleName() + "/" + interfaceName)); final List addresses = lp.getLinkAddresses(); final Network network = networkKey == LOCAL_NET ? null : ((NetworkAsKey) networkKey).mNetwork; final SocketKey socketKey = new SocketKey(network, networkInterface.getIndex()); // 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, socketKey); if (networkKey == LOCAL_NET) { mTetherInterfaceSockets.put(interfaceName, socketInfo); } else { mNetworkSockets.put(network, socketInfo); } // Try to join IPv4/IPv6 group. socket.joinGroup(addresses); // Notify the listeners which need this socket. notifySocketCreated(network, socketInfo); } catch (IOException e) { mSharedLog.e("Create socket failed ifName:" + interfaceName, e); } } private boolean isMdnsCapableInterface( @NonNull NetworkInterfaceWrapper iface, @NonNull int[] transports) { try { // Never try mDNS on cellular, or on interfaces with incompatible flags if (CollectionUtils.contains(transports, TRANSPORT_CELLULAR) || iface.isLoopback() || iface.isPointToPoint() || iface.isVirtual() || !iface.isUp()) { return false; } // Otherwise, always try mDNS on non-VPN Wifi. if (!CollectionUtils.contains(transports, TRANSPORT_VPN) && CollectionUtils.contains(transports, TRANSPORT_WIFI)) { return true; } // For other transports, or no transports (tethering downstreams), do mDNS based on the // interface flags. This is not always reliable (for example some Wifi interfaces may // not have the MULTICAST flag even though they can do mDNS, and some cellular // interfaces may have the BROADCAST or MULTICAST flags), so checks are done based on // transports above in priority. return iface.supportsMulticast(); } catch (SocketException e) { mSharedLog.e("Error checking interface flags", e); return false; } } private void removeNetworkSocket(Network network) { final SocketInfo socketInfo = mNetworkSockets.remove(network); if (socketInfo == null) return; socketInfo.mSocket.destroy(); notifyInterfaceDestroyed(network, socketInfo); mSocketRequestMonitor.onSocketDestroyed(network, socketInfo.mSocket); mSharedLog.log("Remove socket on net:" + network); } private void removeTetherInterfaceSocket(String interfaceName) { final SocketInfo socketInfo = mTetherInterfaceSockets.remove(interfaceName); if (socketInfo == null) return; socketInfo.mSocket.destroy(); notifyInterfaceDestroyed(null /* network */, socketInfo); mSocketRequestMonitor.onSocketDestroyed(null /* network */, socketInfo.mSocket); mSharedLog.log("Remove socket on ifName:" + interfaceName); } 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(socketInfo.mSocketKey, socketInfo.mSocket, socketInfo.mAddresses); mSocketRequestMonitor.onSocketRequestFulfilled(network, socketInfo.mSocket, socketInfo.mTransports); } } } private void notifyInterfaceDestroyed(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) .onInterfaceDestroyed(socketInfo.mSocketKey, socketInfo.mSocket); } } } private void notifyAddressesChanged(Network network, SocketInfo socketInfo, List addresses) { for (int i = 0; i < mCallbacksToRequestedNetworks.size(); i++) { final Network requestedNetwork = mCallbacksToRequestedNetworks.valueAt(i); if (isNetworkMatched(requestedNetwork, network)) { mCallbacksToRequestedNetworks.keyAt(i) .onAddressesChanged(socketInfo.mSocketKey, socketInfo.mSocket, addresses); } } } private void retrieveAndNotifySocketFromNetwork(Network network, SocketCallback cb) { final SocketInfo socketInfo = mNetworkSockets.get(network); if (socketInfo == null) { final LinkProperties lp = mActiveNetworksLinkProperties.get(network); if (lp == null) { // The requested network is not existed. Maybe wait for LinkProperties change later. if (DBG) mSharedLog.v("There is no LinkProperties for this network:" + network); return; } createSocket(new NetworkAsKey(network), lp); } else { // Notify the socket for requested network. cb.onSocketCreated(socketInfo.mSocketKey, socketInfo.mSocket, socketInfo.mAddresses); mSocketRequestMonitor.onSocketRequestFulfilled(network, socketInfo.mSocket, socketInfo.mTransports); } } private void retrieveAndNotifySocketFromInterface(String interfaceName, SocketCallback cb) { final SocketInfo socketInfo = mTetherInterfaceSockets.get(interfaceName); if (socketInfo == null) { int ifaceIndex = mDependencies.getNetworkInterfaceIndexByName(interfaceName, mSharedLog); createSocket( LOCAL_NET, createLPForTetheredInterface(interfaceName, ifaceIndex)); } else { // Notify the socket for requested network. cb.onSocketCreated(socketInfo.mSocketKey, socketInfo.mSocket, socketInfo.mAddresses); mSocketRequestMonitor.onSocketRequestFulfilled(null /* socketNetwork */, socketInfo.mSocket, socketInfo.mTransports); } } /** * Request a socket for given network. * * @param network the required network for a socket. Null means create sockets on all possible * networks (interfaces). * @param cb the callback to listen the socket creation. */ public void requestSocket(@Nullable Network network, @NonNull SocketCallback cb) { ensureRunningOnHandlerThread(mHandler); mSharedLog.log("requestSocket for net:" + network); mCallbacksToRequestedNetworks.put(cb, network); if (network == null) { // Does not specify a required network, create sockets for all possible // networks (interfaces). for (int i = 0; i < mActiveNetworksLinkProperties.size(); i++) { retrieveAndNotifySocketFromNetwork(mActiveNetworksLinkProperties.keyAt(i), cb); } for (String localInterface : mLocalOnlyInterfaces) { retrieveAndNotifySocketFromInterface(localInterface, cb); } for (String tetheredInterface : mTetheredInterfaces) { retrieveAndNotifySocketFromInterface(tetheredInterface, cb); } if (mWifiP2pTetherInterface != null && !mLocalOnlyInterfaces.contains(mWifiP2pTetherInterface)) { retrieveAndNotifySocketFromInterface(mWifiP2pTetherInterface, cb); } } else { retrieveAndNotifySocketFromNetwork(network, cb); } } /*** Unrequest the socket */ public void unrequestSocket(@NonNull SocketCallback cb) { ensureRunningOnHandlerThread(mHandler); mSharedLog.log("unrequestSocket"); mCallbacksToRequestedNetworks.remove(cb); if (hasAllNetworksRequest()) { // Still has a request for all networks (interfaces). return; } // Check if remaining requests are matched any of sockets. for (int i = mNetworkSockets.size() - 1; i >= 0; i--) { final Network network = mNetworkSockets.keyAt(i); 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"); } // Remove all sockets for tethering interface because these sockets do not have associated // networks, and they should invoke by a request for all networks (interfaces). If there is // no such request, the sockets for tethering interface should be removed. 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"); } mTetherInterfaceSockets.clear(); // Try to unregister network callback. maybeStopMonitoringSockets(); } /** * Callback used to register socket requests. */ public interface SocketCallback { /** * 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(@NonNull SocketKey socketKey, @NonNull MdnsInterfaceSocket socket, @NonNull List addresses) {} /** * 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(@NonNull SocketKey socketKey, @NonNull MdnsInterfaceSocket socket) {} /** * Notify the interface addresses have changed for the network. */ default void onAddressesChanged(@NonNull SocketKey socketKey, @NonNull MdnsInterfaceSocket socket, @NonNull List 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 { } private static final NetworkKey LOCAL_NET = new NetworkKey() { @Override public String toString() { return "NetworkKey:LOCAL_NET"; } }; private static class NetworkAsKey implements NetworkKey { private final Network mNetwork; NetworkAsKey(Network network) { this.mNetwork = network; } @Override public int hashCode() { return mNetwork.hashCode(); } @Override public boolean equals(@Nullable Object other) { if (!(other instanceof NetworkAsKey)) { return false; } return mNetwork.equals(((NetworkAsKey) other).mNetwork); } @Override public String toString() { return "NetworkAsKey{ network=" + mNetwork + " }"; } } }