/*
* 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 + " }";
}
}
}