Add MdnsInterfaceSocket and MdnsSocketProvider
MdnsInterfaceSocket: using for listening the multicast socket on specific interface. MdnsSocketProvider: using for creating and managing the multiple multicast sockets. - Listen NetworkCallback and TetheringEventCallback to know the network connected / disconnected changes. And use the callbacks to create / remove the sockets for mdns discovery Bug: 254166302 Test: atest FrameworksNetTests Change-Id: Icd8424792e89e71198fa31f9ec92e1ec8172128d
This commit is contained in:
parent
9e53b630c5
commit
2f1de80315
9 changed files with 970 additions and 15 deletions
|
|
@ -203,6 +203,7 @@ java_library {
|
|||
libs: [
|
||||
"framework-annotations-lib",
|
||||
"framework-connectivity-pre-jarjar",
|
||||
"framework-tethering",
|
||||
"framework-wifi",
|
||||
"service-connectivity-pre-jarjar",
|
||||
],
|
||||
|
|
|
|||
|
|
@ -21,6 +21,7 @@ import android.annotation.NonNull;
|
|||
import android.annotation.RequiresPermission;
|
||||
import android.text.TextUtils;
|
||||
import android.util.ArrayMap;
|
||||
import android.util.Log;
|
||||
|
||||
import com.android.internal.annotations.VisibleForTesting;
|
||||
import com.android.server.connectivity.mdns.util.MdnsLogger;
|
||||
|
|
@ -34,7 +35,8 @@ import java.util.Map;
|
|||
* notify them when a mDNS service instance is found, updated, or removed?
|
||||
*/
|
||||
public class MdnsDiscoveryManager implements MdnsSocketClient.Callback {
|
||||
|
||||
private static final String TAG = MdnsDiscoveryManager.class.getSimpleName();
|
||||
public static final boolean DBG = Log.isLoggable(TAG, Log.DEBUG);
|
||||
private static final MdnsLogger LOGGER = new MdnsLogger("MdnsDiscoveryManager");
|
||||
|
||||
private final ExecutorProvider executorProvider;
|
||||
|
|
|
|||
|
|
@ -0,0 +1,175 @@
|
|||
/*
|
||||
* 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 com.android.server.connectivity.mdns.MdnsSocket.MULTICAST_IPV4_ADDRESS;
|
||||
import static com.android.server.connectivity.mdns.MdnsSocket.MULTICAST_IPV6_ADDRESS;
|
||||
|
||||
import android.annotation.NonNull;
|
||||
import android.net.LinkAddress;
|
||||
import android.net.util.SocketUtils;
|
||||
import android.os.ParcelFileDescriptor;
|
||||
import android.system.ErrnoException;
|
||||
import android.util.Log;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.net.DatagramPacket;
|
||||
import java.net.InetSocketAddress;
|
||||
import java.net.MulticastSocket;
|
||||
import java.net.NetworkInterface;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* {@link MdnsInterfaceSocket} provides a similar interface to {@link MulticastSocket} and binds to
|
||||
* an available multicast network interfaces.
|
||||
*
|
||||
* <p>This isn't thread safe and should be always called on the same thread unless specified
|
||||
* otherwise.
|
||||
*
|
||||
* @see MulticastSocket for javadoc of each public method.
|
||||
*/
|
||||
public class MdnsInterfaceSocket {
|
||||
private static final String TAG = MdnsInterfaceSocket.class.getSimpleName();
|
||||
@NonNull private final MulticastSocket mMulticastSocket;
|
||||
@NonNull private final NetworkInterface mNetworkInterface;
|
||||
private boolean mJoinedIpv4 = false;
|
||||
private boolean mJoinedIpv6 = false;
|
||||
|
||||
public MdnsInterfaceSocket(@NonNull NetworkInterface networkInterface, int port)
|
||||
throws IOException {
|
||||
mNetworkInterface = networkInterface;
|
||||
mMulticastSocket = new MulticastSocket(port);
|
||||
// RFC Spec: https://tools.ietf.org/html/rfc6762. Time to live is set 255
|
||||
mMulticastSocket.setTimeToLive(255);
|
||||
mMulticastSocket.setNetworkInterface(networkInterface);
|
||||
|
||||
// Bind socket to the interface for receiving from that interface only.
|
||||
try (ParcelFileDescriptor pfd = ParcelFileDescriptor.fromDatagramSocket(mMulticastSocket)) {
|
||||
SocketUtils.bindSocketToInterface(pfd.getFileDescriptor(), mNetworkInterface.getName());
|
||||
} catch (ErrnoException e) {
|
||||
throw new IOException("Error setting socket options", e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Sends a datagram packet from this socket.
|
||||
*
|
||||
* <p>This method could be used on any thread.
|
||||
*/
|
||||
public void send(@NonNull DatagramPacket packet) throws IOException {
|
||||
mMulticastSocket.send(packet);
|
||||
}
|
||||
|
||||
/**
|
||||
* Receives a datagram packet from this socket.
|
||||
*
|
||||
* <p>This method could be used on any thread.
|
||||
*/
|
||||
public void receive(@NonNull DatagramPacket packet) throws IOException {
|
||||
mMulticastSocket.receive(packet);
|
||||
}
|
||||
|
||||
private boolean hasIpv4Address(List<LinkAddress> addresses) {
|
||||
for (LinkAddress address : addresses) {
|
||||
if (address.isIpv4()) return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
private boolean hasIpv6Address(List<LinkAddress> addresses) {
|
||||
for (LinkAddress address : addresses) {
|
||||
if (address.isIpv6()) return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/*** Joins both IPv4 and IPv6 multicast groups. */
|
||||
public void joinGroup(@NonNull List<LinkAddress> addresses) {
|
||||
maybeJoinIpv4(addresses);
|
||||
maybeJoinIpv6(addresses);
|
||||
}
|
||||
|
||||
private boolean joinGroup(InetSocketAddress multicastAddress) {
|
||||
try {
|
||||
mMulticastSocket.joinGroup(multicastAddress, mNetworkInterface);
|
||||
return true;
|
||||
} catch (IOException e) {
|
||||
// The address may have just been removed
|
||||
Log.e(TAG, "Error joining multicast group for " + mNetworkInterface, e);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
private void maybeJoinIpv4(List<LinkAddress> addresses) {
|
||||
final boolean hasAddr = hasIpv4Address(addresses);
|
||||
if (!mJoinedIpv4 && hasAddr) {
|
||||
mJoinedIpv4 = joinGroup(MULTICAST_IPV4_ADDRESS);
|
||||
} else if (!hasAddr) {
|
||||
// Lost IPv4 address
|
||||
mJoinedIpv4 = false;
|
||||
}
|
||||
}
|
||||
|
||||
private void maybeJoinIpv6(List<LinkAddress> addresses) {
|
||||
final boolean hasAddr = hasIpv6Address(addresses);
|
||||
if (!mJoinedIpv6 && hasAddr) {
|
||||
mJoinedIpv6 = joinGroup(MULTICAST_IPV6_ADDRESS);
|
||||
} else if (!hasAddr) {
|
||||
// Lost IPv6 address
|
||||
mJoinedIpv6 = false;
|
||||
}
|
||||
}
|
||||
|
||||
/*** Destroy this socket by leaving all joined multicast groups and closing this socket. */
|
||||
public void destroy() {
|
||||
if (mJoinedIpv4) {
|
||||
try {
|
||||
mMulticastSocket.leaveGroup(MULTICAST_IPV4_ADDRESS, mNetworkInterface);
|
||||
} catch (IOException e) {
|
||||
Log.e(TAG, "Error leaving IPv4 group for " + mNetworkInterface, e);
|
||||
}
|
||||
}
|
||||
if (mJoinedIpv6) {
|
||||
try {
|
||||
mMulticastSocket.leaveGroup(MULTICAST_IPV6_ADDRESS, mNetworkInterface);
|
||||
} catch (IOException e) {
|
||||
Log.e(TAG, "Error leaving IPv4 group for " + mNetworkInterface, e);
|
||||
}
|
||||
}
|
||||
mMulticastSocket.close();
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the index of the network interface that this socket is bound to. If the interface
|
||||
* cannot be determined, returns -1.
|
||||
*
|
||||
* <p>This method could be used on any thread.
|
||||
*/
|
||||
public int getInterfaceIndex() {
|
||||
return mNetworkInterface.getIndex();
|
||||
}
|
||||
|
||||
/*** Returns whether this socket has joined IPv4 group */
|
||||
public boolean hasJoinedIpv4() {
|
||||
return mJoinedIpv4;
|
||||
}
|
||||
|
||||
/*** Returns whether this socket has joined IPv6 group */
|
||||
public boolean hasJoinedIpv6() {
|
||||
return mJoinedIpv6;
|
||||
}
|
||||
}
|
||||
|
|
@ -17,6 +17,8 @@
|
|||
package com.android.server.connectivity.mdns;
|
||||
|
||||
import android.annotation.NonNull;
|
||||
import android.annotation.Nullable;
|
||||
import android.net.Network;
|
||||
import android.os.Parcel;
|
||||
import android.os.Parcelable;
|
||||
import android.text.TextUtils;
|
||||
|
|
@ -43,7 +45,8 @@ public class MdnsSearchOptions implements Parcelable {
|
|||
@Override
|
||||
public MdnsSearchOptions createFromParcel(Parcel source) {
|
||||
return new MdnsSearchOptions(source.createStringArrayList(),
|
||||
source.readBoolean(), source.readBoolean());
|
||||
source.readBoolean(), source.readBoolean(),
|
||||
source.readParcelable(null));
|
||||
}
|
||||
|
||||
@Override
|
||||
|
|
@ -56,15 +59,19 @@ public class MdnsSearchOptions implements Parcelable {
|
|||
|
||||
private final boolean isPassiveMode;
|
||||
private final boolean removeExpiredService;
|
||||
// The target network for searching. Null network means search on all possible interfaces.
|
||||
@Nullable private final Network mNetwork;
|
||||
|
||||
/** Parcelable constructs for a {@link MdnsServiceInfo}. */
|
||||
MdnsSearchOptions(List<String> subtypes, boolean isPassiveMode, boolean removeExpiredService) {
|
||||
/** Parcelable constructs for a {@link MdnsSearchOptions}. */
|
||||
MdnsSearchOptions(List<String> subtypes, boolean isPassiveMode, boolean removeExpiredService,
|
||||
@Nullable Network network) {
|
||||
this.subtypes = new ArrayList<>();
|
||||
if (subtypes != null) {
|
||||
this.subtypes.addAll(subtypes);
|
||||
}
|
||||
this.isPassiveMode = isPassiveMode;
|
||||
this.removeExpiredService = removeExpiredService;
|
||||
mNetwork = network;
|
||||
}
|
||||
|
||||
/** Returns a {@link Builder} for {@link MdnsSearchOptions}. */
|
||||
|
|
@ -98,6 +105,16 @@ public class MdnsSearchOptions implements Parcelable {
|
|||
return removeExpiredService;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the network which the mdns query should target on.
|
||||
*
|
||||
* @return the target network or null if search on all possible interfaces.
|
||||
*/
|
||||
@Nullable
|
||||
public Network getNetwork() {
|
||||
return mNetwork;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int describeContents() {
|
||||
return 0;
|
||||
|
|
@ -108,6 +125,7 @@ public class MdnsSearchOptions implements Parcelable {
|
|||
out.writeStringList(subtypes);
|
||||
out.writeBoolean(isPassiveMode);
|
||||
out.writeBoolean(removeExpiredService);
|
||||
out.writeParcelable(mNetwork, 0);
|
||||
}
|
||||
|
||||
/** A builder to create {@link MdnsSearchOptions}. */
|
||||
|
|
@ -115,6 +133,7 @@ public class MdnsSearchOptions implements Parcelable {
|
|||
private final Set<String> subtypes;
|
||||
private boolean isPassiveMode = true;
|
||||
private boolean removeExpiredService;
|
||||
private Network mNetwork;
|
||||
|
||||
private Builder() {
|
||||
subtypes = new ArraySet<>();
|
||||
|
|
@ -165,10 +184,20 @@ public class MdnsSearchOptions implements Parcelable {
|
|||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets if the mdns query should target on specific network.
|
||||
*
|
||||
* @param network the mdns query will target on given network.
|
||||
*/
|
||||
public Builder setNetwork(Network network) {
|
||||
mNetwork = network;
|
||||
return this;
|
||||
}
|
||||
|
||||
/** Builds a {@link MdnsSearchOptions} with the arguments supplied to this builder. */
|
||||
public MdnsSearchOptions build() {
|
||||
return new MdnsSearchOptions(
|
||||
new ArrayList<>(subtypes), isPassiveMode, removeExpiredService);
|
||||
return new MdnsSearchOptions(new ArrayList<>(subtypes), isPassiveMode,
|
||||
removeExpiredService, mNetwork);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -40,9 +40,9 @@ public class MdnsSocket {
|
|||
private static final MdnsLogger LOGGER = new MdnsLogger("MdnsSocket");
|
||||
|
||||
static final int INTERFACE_INDEX_UNSPECIFIED = -1;
|
||||
private static final InetSocketAddress MULTICAST_IPV4_ADDRESS =
|
||||
protected static final InetSocketAddress MULTICAST_IPV4_ADDRESS =
|
||||
new InetSocketAddress(MdnsConstants.getMdnsIPv4Address(), MdnsConstants.MDNS_PORT);
|
||||
private static final InetSocketAddress MULTICAST_IPV6_ADDRESS =
|
||||
protected static final InetSocketAddress MULTICAST_IPV6_ADDRESS =
|
||||
new InetSocketAddress(MdnsConstants.getMdnsIPv6Address(), MdnsConstants.MDNS_PORT);
|
||||
private final MulticastNetworkInterfaceProvider multicastNetworkInterfaceProvider;
|
||||
private final MulticastSocket multicastSocket;
|
||||
|
|
|
|||
|
|
@ -0,0 +1,460 @@
|
|||
/*
|
||||
* 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 android.annotation.NonNull;
|
||||
import android.annotation.Nullable;
|
||||
import android.content.Context;
|
||||
import android.net.ConnectivityManager;
|
||||
import android.net.ConnectivityManager.NetworkCallback;
|
||||
import android.net.INetd;
|
||||
import android.net.LinkAddress;
|
||||
import android.net.LinkProperties;
|
||||
import android.net.Network;
|
||||
import android.net.NetworkRequest;
|
||||
import android.net.TetheringManager;
|
||||
import android.net.TetheringManager.TetheringEventCallback;
|
||||
import android.os.Handler;
|
||||
import android.os.Looper;
|
||||
import android.system.OsConstants;
|
||||
import android.util.ArrayMap;
|
||||
import android.util.Log;
|
||||
|
||||
import com.android.internal.annotations.VisibleForTesting;
|
||||
import com.android.net.module.util.LinkPropertiesUtils.CompareResult;
|
||||
import com.android.net.module.util.ip.NetlinkMonitor;
|
||||
import com.android.net.module.util.netlink.NetlinkConstants;
|
||||
import com.android.net.module.util.netlink.NetlinkMessage;
|
||||
import com.android.server.connectivity.mdns.util.MdnsLogger;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.net.InterfaceAddress;
|
||||
import java.net.NetworkInterface;
|
||||
import java.net.SocketException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* The {@link MdnsSocketProvider} manages the multiple sockets for mDns.
|
||||
*
|
||||
* <p>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).
|
||||
*
|
||||
*/
|
||||
public class MdnsSocketProvider {
|
||||
private static final String TAG = MdnsSocketProvider.class.getSimpleName();
|
||||
private static final boolean DBG = MdnsDiscoveryManager.DBG;
|
||||
private static final MdnsLogger LOGGER = new MdnsLogger(TAG);
|
||||
@NonNull private final Context mContext;
|
||||
@NonNull private final Handler mHandler;
|
||||
@NonNull private final Dependencies mDependencies;
|
||||
@NonNull private final NetworkCallback mNetworkCallback;
|
||||
@NonNull private final TetheringEventCallback mTetheringEventCallback;
|
||||
@NonNull private final NetlinkMonitor mNetlinkMonitor;
|
||||
private final ArrayMap<Network, SocketInfo> mNetworkSockets = new ArrayMap<>();
|
||||
private final ArrayMap<String, SocketInfo> mTetherInterfaceSockets = new ArrayMap<>();
|
||||
private final ArrayMap<Network, LinkProperties> mActiveNetworksLinkProperties =
|
||||
new ArrayMap<>();
|
||||
private final ArrayMap<SocketCallback, Network> mCallbacksToRequestedNetworks =
|
||||
new ArrayMap<>();
|
||||
private final List<String> mLocalOnlyInterfaces = new ArrayList<>();
|
||||
private final List<String> mTetheredInterfaces = new ArrayList<>();
|
||||
private boolean mMonitoringSockets = false;
|
||||
|
||||
public MdnsSocketProvider(@NonNull Context context, @NonNull Looper looper) {
|
||||
this(context, looper, new Dependencies());
|
||||
}
|
||||
|
||||
MdnsSocketProvider(@NonNull Context context, @NonNull Looper looper,
|
||||
@NonNull Dependencies deps) {
|
||||
mContext = context;
|
||||
mHandler = new Handler(looper);
|
||||
mDependencies = deps;
|
||||
mNetworkCallback = new NetworkCallback() {
|
||||
@Override
|
||||
public void onLost(Network network) {
|
||||
mActiveNetworksLinkProperties.remove(network);
|
||||
removeSocket(network, null /* interfaceName */);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onLinkPropertiesChanged(Network network, LinkProperties lp) {
|
||||
handleLinkPropertiesChanged(network, lp);
|
||||
}
|
||||
};
|
||||
mTetheringEventCallback = new TetheringEventCallback() {
|
||||
@Override
|
||||
public void onLocalOnlyInterfacesChanged(@NonNull List<String> interfaces) {
|
||||
handleTetherInterfacesChanged(mLocalOnlyInterfaces, interfaces);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onTetheredInterfacesChanged(@NonNull List<String> interfaces) {
|
||||
handleTetherInterfacesChanged(mTetheredInterfaces, interfaces);
|
||||
}
|
||||
};
|
||||
|
||||
mNetlinkMonitor = new SocketNetlinkMonitor(mHandler);
|
||||
}
|
||||
|
||||
/**
|
||||
* Dependencies of MdnsSocketProvider, for injection in tests.
|
||||
*/
|
||||
@VisibleForTesting
|
||||
public static class Dependencies {
|
||||
/*** Get network interface by given interface name */
|
||||
public NetworkInterfaceWrapper getNetworkInterfaceByName(String interfaceName)
|
||||
throws SocketException {
|
||||
final NetworkInterface ni = NetworkInterface.getByName(interfaceName);
|
||||
return ni == null ? null : new NetworkInterfaceWrapper(ni);
|
||||
}
|
||||
|
||||
/*** Check whether given network interface can support mdns */
|
||||
public boolean canScanOnInterface(NetworkInterfaceWrapper networkInterface) {
|
||||
return MulticastNetworkInterfaceProvider.canScanOnInterface(networkInterface);
|
||||
}
|
||||
|
||||
/*** Create a MdnsInterfaceSocket */
|
||||
public MdnsInterfaceSocket createMdnsInterfaceSocket(NetworkInterface networkInterface,
|
||||
int port) throws IOException {
|
||||
return new MdnsInterfaceSocket(networkInterface, port);
|
||||
}
|
||||
}
|
||||
|
||||
/*** Data class for storing socket related info */
|
||||
private static class SocketInfo {
|
||||
final MdnsInterfaceSocket mSocket;
|
||||
final List<LinkAddress> mAddresses = new ArrayList<>();
|
||||
|
||||
SocketInfo(MdnsInterfaceSocket socket, List<LinkAddress> addresses) {
|
||||
mSocket = socket;
|
||||
mAddresses.addAll(addresses);
|
||||
}
|
||||
}
|
||||
|
||||
private static class SocketNetlinkMonitor extends NetlinkMonitor {
|
||||
SocketNetlinkMonitor(Handler handler) {
|
||||
super(handler, LOGGER.mLog, TAG, OsConstants.NETLINK_ROUTE,
|
||||
NetlinkConstants.RTMGRP_IPV4_IFADDR | NetlinkConstants.RTMGRP_IPV6_IFADDR);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void processNetlinkMessage(NetlinkMessage nlMsg, long whenMs) {
|
||||
// TODO: Handle netlink message.
|
||||
}
|
||||
}
|
||||
|
||||
private void ensureRunningOnHandlerThread() {
|
||||
if (mHandler.getLooper().getThread() != Thread.currentThread()) {
|
||||
throw new IllegalStateException(
|
||||
"Not running on Handler thread: " + Thread.currentThread().getName());
|
||||
}
|
||||
}
|
||||
|
||||
/*** Start monitoring sockets by listening callbacks for sockets creation or removal */
|
||||
public void startMonitoringSockets() {
|
||||
ensureRunningOnHandlerThread();
|
||||
if (mMonitoringSockets) {
|
||||
Log.d(TAG, "Already monitoring sockets.");
|
||||
return;
|
||||
}
|
||||
if (DBG) Log.d(TAG, "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);
|
||||
|
||||
mHandler.post(mNetlinkMonitor::start);
|
||||
mMonitoringSockets = true;
|
||||
}
|
||||
|
||||
/*** Stop monitoring sockets and unregister callbacks */
|
||||
public void stopMonitoringSockets() {
|
||||
ensureRunningOnHandlerThread();
|
||||
if (!mMonitoringSockets) {
|
||||
Log.d(TAG, "Monitoring sockets hasn't been started.");
|
||||
return;
|
||||
}
|
||||
if (DBG) Log.d(TAG, "Stop monitoring sockets.");
|
||||
mContext.getSystemService(ConnectivityManager.class)
|
||||
.unregisterNetworkCallback(mNetworkCallback);
|
||||
|
||||
final TetheringManager tetheringManager = mContext.getSystemService(TetheringManager.class);
|
||||
tetheringManager.unregisterTetheringEventCallback(mTetheringEventCallback);
|
||||
|
||||
mHandler.post(mNetlinkMonitor::stop);
|
||||
mMonitoringSockets = false;
|
||||
}
|
||||
|
||||
private static boolean isNetworkMatched(@Nullable Network targetNetwork,
|
||||
@NonNull Network currentNetwork) {
|
||||
return targetNetwork == null || targetNetwork.equals(currentNetwork);
|
||||
}
|
||||
|
||||
private boolean matchRequestedNetwork(Network network) {
|
||||
for (int i = 0; i < mCallbacksToRequestedNetworks.size(); i++) {
|
||||
final Network requestedNetwork = mCallbacksToRequestedNetworks.valueAt(i);
|
||||
if (isNetworkMatched(requestedNetwork, network)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
private boolean hasAllNetworksRequest() {
|
||||
return mCallbacksToRequestedNetworks.containsValue(null);
|
||||
}
|
||||
|
||||
private void handleLinkPropertiesChanged(Network network, LinkProperties lp) {
|
||||
mActiveNetworksLinkProperties.put(network, lp);
|
||||
if (!matchRequestedNetwork(network)) {
|
||||
if (DBG) {
|
||||
Log.d(TAG, "Ignore LinkProperties change. There is no request for the"
|
||||
+ " Network:" + network);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
final SocketInfo socketInfo = mNetworkSockets.get(network);
|
||||
if (socketInfo == null) {
|
||||
createSocket(network, lp);
|
||||
} else {
|
||||
// Update the addresses of this socket.
|
||||
final List<LinkAddress> addresses = lp.getLinkAddresses();
|
||||
socketInfo.mAddresses.clear();
|
||||
socketInfo.mAddresses.addAll(addresses);
|
||||
// Try to join the group again.
|
||||
socketInfo.mSocket.joinGroup(addresses);
|
||||
|
||||
notifyAddressesChanged(network, lp);
|
||||
}
|
||||
}
|
||||
|
||||
private static LinkProperties createLPForTetheredInterface(String interfaceName) {
|
||||
final LinkProperties linkProperties = new LinkProperties();
|
||||
linkProperties.setInterfaceName(interfaceName);
|
||||
// TODO: Use NetlinkMonitor to update addresses for tethering interfaces.
|
||||
return linkProperties;
|
||||
}
|
||||
|
||||
private void handleTetherInterfacesChanged(List<String> current, List<String> 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, this change can skip if there is no such request.
|
||||
if (DBG) {
|
||||
Log.d(TAG, "Ignore tether interfaces change. There is no request for all"
|
||||
+ " networks.");
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
final CompareResult<String> interfaceDiff = new CompareResult<>(
|
||||
current, updated);
|
||||
for (String name : interfaceDiff.added) {
|
||||
createSocket(new Network(INetd.LOCAL_NET_ID), createLPForTetheredInterface(name));
|
||||
}
|
||||
for (String name : interfaceDiff.removed) {
|
||||
removeSocket(new Network(INetd.LOCAL_NET_ID), name);
|
||||
}
|
||||
current.clear();
|
||||
current.addAll(updated);
|
||||
}
|
||||
|
||||
private static List<LinkAddress> getLinkAddressFromNetworkInterface(
|
||||
NetworkInterfaceWrapper networkInterface) {
|
||||
List<LinkAddress> addresses = new ArrayList<>();
|
||||
for (InterfaceAddress address : networkInterface.getInterfaceAddresses()) {
|
||||
addresses.add(new LinkAddress(address));
|
||||
}
|
||||
return addresses;
|
||||
}
|
||||
|
||||
private void createSocket(Network network, LinkProperties lp) {
|
||||
final String interfaceName = lp.getInterfaceName();
|
||||
if (interfaceName == null) {
|
||||
Log.e(TAG, "Can not create socket with null interface name.");
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
final NetworkInterfaceWrapper networkInterface =
|
||||
mDependencies.getNetworkInterfaceByName(interfaceName);
|
||||
if (networkInterface == null || !mDependencies.canScanOnInterface(networkInterface)) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (DBG) {
|
||||
Log.d(TAG, "Create a socket on network:" + network
|
||||
+ " with interfaceName:" + interfaceName);
|
||||
}
|
||||
final MdnsInterfaceSocket socket = mDependencies.createMdnsInterfaceSocket(
|
||||
networkInterface.getNetworkInterface(), MdnsConstants.MDNS_PORT);
|
||||
final List<LinkAddress> addresses;
|
||||
if (network.netId == INetd.LOCAL_NET_ID) {
|
||||
addresses = getLinkAddressFromNetworkInterface(networkInterface);
|
||||
mTetherInterfaceSockets.put(interfaceName, new SocketInfo(socket, addresses));
|
||||
} else {
|
||||
addresses = lp.getLinkAddresses();
|
||||
mNetworkSockets.put(network, new SocketInfo(socket, addresses));
|
||||
}
|
||||
// Try to join IPv4/IPv6 group.
|
||||
socket.joinGroup(addresses);
|
||||
|
||||
// Notify the listeners which need this socket.
|
||||
notifySocketCreated(network, socket, addresses);
|
||||
} catch (IOException e) {
|
||||
Log.e(TAG, "Create a socket failed with interface=" + interfaceName, e);
|
||||
}
|
||||
}
|
||||
|
||||
private void removeSocket(Network network, String interfaceName) {
|
||||
final SocketInfo socketInfo = network.netId == INetd.LOCAL_NET_ID
|
||||
? mTetherInterfaceSockets.remove(interfaceName)
|
||||
: mNetworkSockets.remove(network);
|
||||
if (socketInfo == null) return;
|
||||
|
||||
socketInfo.mSocket.destroy();
|
||||
notifyInterfaceDestroyed(network, socketInfo.mSocket);
|
||||
}
|
||||
|
||||
private void notifySocketCreated(Network network, MdnsInterfaceSocket socket,
|
||||
List<LinkAddress> addresses) {
|
||||
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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void notifyInterfaceDestroyed(Network network, MdnsInterfaceSocket socket) {
|
||||
for (int i = 0; i < mCallbacksToRequestedNetworks.size(); i++) {
|
||||
final Network requestedNetwork = mCallbacksToRequestedNetworks.valueAt(i);
|
||||
if (isNetworkMatched(requestedNetwork, network)) {
|
||||
mCallbacksToRequestedNetworks.keyAt(i).onInterfaceDestroyed(network, socket);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void notifyAddressesChanged(Network network, LinkProperties lp) {
|
||||
for (int i = 0; i < mCallbacksToRequestedNetworks.size(); i++) {
|
||||
final Network requestedNetwork = mCallbacksToRequestedNetworks.valueAt(i);
|
||||
if (isNetworkMatched(requestedNetwork, network)) {
|
||||
mCallbacksToRequestedNetworks.keyAt(i)
|
||||
.onAddressesChanged(network, lp.getLinkAddresses());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
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) Log.d(TAG, "There is no LinkProperties for this network:" + network);
|
||||
return;
|
||||
}
|
||||
createSocket(network, lp);
|
||||
} else {
|
||||
// Notify the socket for requested network.
|
||||
cb.onSocketCreated(network, socketInfo.mSocket, socketInfo.mAddresses);
|
||||
}
|
||||
}
|
||||
|
||||
private void retrieveAndNotifySocketFromInterface(String interfaceName, SocketCallback cb) {
|
||||
final SocketInfo socketInfo = mTetherInterfaceSockets.get(interfaceName);
|
||||
if (socketInfo == null) {
|
||||
createSocket(
|
||||
new Network(INetd.LOCAL_NET_ID), createLPForTetheredInterface(interfaceName));
|
||||
} else {
|
||||
// Notify the socket for requested network.
|
||||
cb.onSocketCreated(
|
||||
new Network(INetd.LOCAL_NET_ID), socketInfo.mSocket, socketInfo.mAddresses);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 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();
|
||||
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);
|
||||
}
|
||||
} else {
|
||||
retrieveAndNotifySocketFromNetwork(network, cb);
|
||||
}
|
||||
}
|
||||
|
||||
/*** Unrequest the socket */
|
||||
public void unrequestSocket(@NonNull SocketCallback cb) {
|
||||
ensureRunningOnHandlerThread();
|
||||
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--) {
|
||||
if (matchRequestedNetwork(mNetworkSockets.keyAt(i))) continue;
|
||||
mNetworkSockets.removeAt(i).mSocket.destroy();
|
||||
}
|
||||
|
||||
// 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--) {
|
||||
mTetherInterfaceSockets.removeAt(i).mSocket.destroy();
|
||||
}
|
||||
}
|
||||
|
||||
/*** Callbacks for listening socket changes */
|
||||
public interface SocketCallback {
|
||||
/*** Notify the socket is created */
|
||||
default void onSocketCreated(@NonNull Network network, @NonNull MdnsInterfaceSocket socket,
|
||||
@NonNull List<LinkAddress> addresses) {}
|
||||
/*** Notify the interface is destroyed */
|
||||
default void onInterfaceDestroyed(@NonNull Network network,
|
||||
@NonNull MdnsInterfaceSocket socket) {}
|
||||
/*** Notify the addresses is changed on the network */
|
||||
default void onAddressesChanged(@NonNull Network network,
|
||||
@NonNull List<LinkAddress> addresses) {}
|
||||
}
|
||||
}
|
||||
|
|
@ -147,7 +147,8 @@ public class MulticastNetworkInterfaceProvider {
|
|||
return connectivityMonitor.getAvailableNetwork();
|
||||
}
|
||||
|
||||
private boolean canScanOnInterface(@Nullable NetworkInterfaceWrapper networkInterface) {
|
||||
/*** Check whether given network interface can support mdns */
|
||||
public static boolean canScanOnInterface(@Nullable NetworkInterfaceWrapper networkInterface) {
|
||||
try {
|
||||
if ((networkInterface == null)
|
||||
|| networkInterface.isLoopback()
|
||||
|
|
@ -166,7 +167,7 @@ public class MulticastNetworkInterfaceProvider {
|
|||
return false;
|
||||
}
|
||||
|
||||
private boolean hasInet4Address(@NonNull NetworkInterfaceWrapper networkInterface) {
|
||||
private static boolean hasInet4Address(@NonNull NetworkInterfaceWrapper networkInterface) {
|
||||
for (InterfaceAddress ifAddr : networkInterface.getInterfaceAddresses()) {
|
||||
if (ifAddr.getAddress() instanceof Inet4Address) {
|
||||
return true;
|
||||
|
|
@ -175,7 +176,7 @@ public class MulticastNetworkInterfaceProvider {
|
|||
return false;
|
||||
}
|
||||
|
||||
private boolean hasInet6Address(@NonNull NetworkInterfaceWrapper networkInterface) {
|
||||
private static boolean hasInet6Address(@NonNull NetworkInterfaceWrapper networkInterface) {
|
||||
for (InterfaceAddress ifAddr : networkInterface.getInterfaceAddresses()) {
|
||||
if (ifAddr.getAddress() instanceof Inet6Address) {
|
||||
return true;
|
||||
|
|
|
|||
|
|
@ -501,8 +501,7 @@ public class MdnsSocketClientTests {
|
|||
//MdnsConfigsFlagsImpl.allowNetworkInterfaceIndexPropagation.override(true);
|
||||
|
||||
when(mockMulticastSocket.getInterfaceIndex()).thenReturn(21);
|
||||
mdnsClient =
|
||||
new MdnsSocketClient(mContext, mockMulticastLock) {
|
||||
mdnsClient = new MdnsSocketClient(mContext, mockMulticastLock) {
|
||||
@Override
|
||||
MdnsSocket createMdnsSocket(int port) {
|
||||
if (port == MdnsConstants.MDNS_PORT) {
|
||||
|
|
@ -525,8 +524,7 @@ public class MdnsSocketClientTests {
|
|||
//MdnsConfigsFlagsImpl.allowNetworkInterfaceIndexPropagation.override(false);
|
||||
|
||||
when(mockMulticastSocket.getInterfaceIndex()).thenReturn(21);
|
||||
mdnsClient =
|
||||
new MdnsSocketClient(mContext, mockMulticastLock) {
|
||||
mdnsClient = new MdnsSocketClient(mContext, mockMulticastLock) {
|
||||
@Override
|
||||
MdnsSocket createMdnsSocket(int port) {
|
||||
if (port == MdnsConstants.MDNS_PORT) {
|
||||
|
|
|
|||
|
|
@ -0,0 +1,289 @@
|
|||
/*
|
||||
* 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 com.android.testutils.ContextUtils.mockService;
|
||||
|
||||
import static org.junit.Assert.assertEquals;
|
||||
import static org.junit.Assert.assertNotNull;
|
||||
import static org.junit.Assert.assertNull;
|
||||
import static org.junit.Assert.assertTrue;
|
||||
import static org.mockito.ArgumentMatchers.anyInt;
|
||||
import static org.mockito.Mockito.any;
|
||||
import static org.mockito.Mockito.doReturn;
|
||||
import static org.mockito.Mockito.mock;
|
||||
import static org.mockito.Mockito.times;
|
||||
import static org.mockito.Mockito.verify;
|
||||
|
||||
import android.content.Context;
|
||||
import android.net.ConnectivityManager;
|
||||
import android.net.ConnectivityManager.NetworkCallback;
|
||||
import android.net.INetd;
|
||||
import android.net.LinkAddress;
|
||||
import android.net.LinkProperties;
|
||||
import android.net.Network;
|
||||
import android.net.TetheringManager;
|
||||
import android.net.TetheringManager.TetheringEventCallback;
|
||||
import android.os.Build;
|
||||
import android.os.Handler;
|
||||
import android.os.HandlerThread;
|
||||
|
||||
import com.android.net.module.util.ArrayTrackRecord;
|
||||
import com.android.server.connectivity.mdns.MdnsSocketProvider.Dependencies;
|
||||
import com.android.testutils.DevSdkIgnoreRule;
|
||||
import com.android.testutils.DevSdkIgnoreRunner;
|
||||
import com.android.testutils.HandlerUtils;
|
||||
|
||||
import org.junit.Before;
|
||||
import org.junit.Test;
|
||||
import org.junit.runner.RunWith;
|
||||
import org.mockito.ArgumentCaptor;
|
||||
import org.mockito.Mock;
|
||||
import org.mockito.MockitoAnnotations;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
|
||||
@RunWith(DevSdkIgnoreRunner.class)
|
||||
@DevSdkIgnoreRule.IgnoreUpTo(Build.VERSION_CODES.S_V2)
|
||||
public class MdnsSocketProviderTest {
|
||||
private static final String TEST_IFACE_NAME = "test";
|
||||
private static final String LOCAL_ONLY_IFACE_NAME = "local_only";
|
||||
private static final String TETHERED_IFACE_NAME = "tethered";
|
||||
private static final long DEFAULT_TIMEOUT = 2000L;
|
||||
private static final long NO_CALLBACK_TIMEOUT = 200L;
|
||||
private static final LinkAddress LINKADDRV4 = new LinkAddress("192.0.2.0/24");
|
||||
private static final LinkAddress LINKADDRV6 =
|
||||
new LinkAddress("2001:0db8:85a3:0000:0000:8a2e:0370:7334/64");
|
||||
private static final Network TEST_NETWORK = new Network(123);
|
||||
private static final Network LOCAL_NETWORK = new Network(INetd.LOCAL_NET_ID);
|
||||
|
||||
@Mock private Context mContext;
|
||||
@Mock private Dependencies mDeps;
|
||||
@Mock private ConnectivityManager mCm;
|
||||
@Mock private TetheringManager mTm;
|
||||
@Mock private NetworkInterfaceWrapper mTestNetworkIfaceWrapper;
|
||||
@Mock private NetworkInterfaceWrapper mLocalOnlyIfaceWrapper;
|
||||
@Mock private NetworkInterfaceWrapper mTetheredIfaceWrapper;
|
||||
private Handler mHandler;
|
||||
private MdnsSocketProvider mSocketProvider;
|
||||
private NetworkCallback mNetworkCallback;
|
||||
private TetheringEventCallback mTetheringEventCallback;
|
||||
|
||||
@Before
|
||||
public void setUp() throws IOException {
|
||||
MockitoAnnotations.initMocks(this);
|
||||
mockService(mContext, ConnectivityManager.class, Context.CONNECTIVITY_SERVICE, mCm);
|
||||
mockService(mContext, TetheringManager.class, Context.TETHERING_SERVICE, mTm);
|
||||
doReturn(true).when(mDeps).canScanOnInterface(any());
|
||||
doReturn(mTestNetworkIfaceWrapper).when(mDeps).getNetworkInterfaceByName(TEST_IFACE_NAME);
|
||||
doReturn(mLocalOnlyIfaceWrapper).when(mDeps)
|
||||
.getNetworkInterfaceByName(LOCAL_ONLY_IFACE_NAME);
|
||||
doReturn(mTetheredIfaceWrapper).when(mDeps).getNetworkInterfaceByName(TETHERED_IFACE_NAME);
|
||||
doReturn(mock(MdnsInterfaceSocket.class))
|
||||
.when(mDeps).createMdnsInterfaceSocket(any(), anyInt());
|
||||
final HandlerThread thread = new HandlerThread("MdnsSocketProviderTest");
|
||||
thread.start();
|
||||
mHandler = new Handler(thread.getLooper());
|
||||
|
||||
final ArgumentCaptor<NetworkCallback> nwCallbackCaptor =
|
||||
ArgumentCaptor.forClass(NetworkCallback.class);
|
||||
final ArgumentCaptor<TetheringEventCallback> teCallbackCaptor =
|
||||
ArgumentCaptor.forClass(TetheringEventCallback.class);
|
||||
mSocketProvider = new MdnsSocketProvider(mContext, thread.getLooper(), mDeps);
|
||||
mHandler.post(mSocketProvider::startMonitoringSockets);
|
||||
HandlerUtils.waitForIdle(mHandler, DEFAULT_TIMEOUT);
|
||||
verify(mCm).registerNetworkCallback(any(), nwCallbackCaptor.capture(), any());
|
||||
verify(mTm).registerTetheringEventCallback(any(), teCallbackCaptor.capture());
|
||||
|
||||
mNetworkCallback = nwCallbackCaptor.getValue();
|
||||
mTetheringEventCallback = teCallbackCaptor.getValue();
|
||||
}
|
||||
|
||||
private class TestSocketCallback implements MdnsSocketProvider.SocketCallback {
|
||||
private class SocketEvent {
|
||||
public final Network mNetwork;
|
||||
public final List<LinkAddress> mAddresses;
|
||||
|
||||
SocketEvent(Network network, List<LinkAddress> addresses) {
|
||||
mNetwork = network;
|
||||
mAddresses = Collections.unmodifiableList(addresses);
|
||||
}
|
||||
}
|
||||
|
||||
private class SocketCreatedEvent extends SocketEvent {
|
||||
SocketCreatedEvent(Network nw, List<LinkAddress> addresses) {
|
||||
super(nw, addresses);
|
||||
}
|
||||
}
|
||||
|
||||
private class InterfaceDestroyedEvent extends SocketEvent {
|
||||
InterfaceDestroyedEvent(Network nw, List<LinkAddress> addresses) {
|
||||
super(nw, addresses);
|
||||
}
|
||||
}
|
||||
|
||||
private class AddressesChangedEvent extends SocketEvent {
|
||||
AddressesChangedEvent(Network nw, List<LinkAddress> addresses) {
|
||||
super(nw, addresses);
|
||||
}
|
||||
}
|
||||
|
||||
private final ArrayTrackRecord<SocketEvent>.ReadHead mHistory =
|
||||
new ArrayTrackRecord<SocketEvent>().newReadHead();
|
||||
|
||||
@Override
|
||||
public void onSocketCreated(Network network, MdnsInterfaceSocket socket,
|
||||
List<LinkAddress> addresses) {
|
||||
mHistory.add(new SocketCreatedEvent(network, addresses));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onInterfaceDestroyed(Network network, MdnsInterfaceSocket socket) {
|
||||
mHistory.add(new InterfaceDestroyedEvent(network, List.of()));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onAddressesChanged(Network network, List<LinkAddress> addresses) {
|
||||
mHistory.add(new AddressesChangedEvent(network, addresses));
|
||||
}
|
||||
|
||||
public void expectedSocketCreatedForNetwork(Network network, List<LinkAddress> addresses) {
|
||||
final SocketEvent event = mHistory.poll(DEFAULT_TIMEOUT, c -> true);
|
||||
assertNotNull(event);
|
||||
assertTrue(event instanceof SocketCreatedEvent);
|
||||
assertEquals(network, event.mNetwork);
|
||||
assertEquals(addresses, event.mAddresses);
|
||||
}
|
||||
|
||||
public void expectedInterfaceDestroyedForNetwork(Network network) {
|
||||
final SocketEvent event = mHistory.poll(DEFAULT_TIMEOUT, c -> true);
|
||||
assertNotNull(event);
|
||||
assertTrue(event instanceof InterfaceDestroyedEvent);
|
||||
assertEquals(network, event.mNetwork);
|
||||
}
|
||||
|
||||
public void expectedAddressesChangedForNetwork(Network network,
|
||||
List<LinkAddress> addresses) {
|
||||
final SocketEvent event = mHistory.poll(DEFAULT_TIMEOUT, c -> true);
|
||||
assertNotNull(event);
|
||||
assertTrue(event instanceof AddressesChangedEvent);
|
||||
assertEquals(network, event.mNetwork);
|
||||
assertEquals(event.mAddresses, addresses);
|
||||
}
|
||||
|
||||
public void expectedNoCallback() {
|
||||
final SocketEvent event = mHistory.poll(NO_CALLBACK_TIMEOUT, c -> true);
|
||||
assertNull(event);
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testSocketRequestAndUnrequestSocket() {
|
||||
final TestSocketCallback testCallback1 = new TestSocketCallback();
|
||||
mHandler.post(() -> mSocketProvider.requestSocket(TEST_NETWORK, testCallback1));
|
||||
HandlerUtils.waitForIdle(mHandler, DEFAULT_TIMEOUT);
|
||||
testCallback1.expectedNoCallback();
|
||||
|
||||
final LinkProperties testLp = new LinkProperties();
|
||||
testLp.setInterfaceName(TEST_IFACE_NAME);
|
||||
testLp.setLinkAddresses(List.of(LINKADDRV4));
|
||||
mHandler.post(() -> mNetworkCallback.onLinkPropertiesChanged(TEST_NETWORK, testLp));
|
||||
HandlerUtils.waitForIdle(mHandler, DEFAULT_TIMEOUT);
|
||||
verify(mTestNetworkIfaceWrapper).getNetworkInterface();
|
||||
testCallback1.expectedSocketCreatedForNetwork(TEST_NETWORK, List.of(LINKADDRV4));
|
||||
|
||||
final TestSocketCallback testCallback2 = new TestSocketCallback();
|
||||
mHandler.post(() -> mSocketProvider.requestSocket(TEST_NETWORK, testCallback2));
|
||||
HandlerUtils.waitForIdle(mHandler, DEFAULT_TIMEOUT);
|
||||
testCallback1.expectedNoCallback();
|
||||
testCallback2.expectedSocketCreatedForNetwork(TEST_NETWORK, List.of(LINKADDRV4));
|
||||
|
||||
final TestSocketCallback testCallback3 = new TestSocketCallback();
|
||||
mHandler.post(() -> mSocketProvider.requestSocket(null /* network */, testCallback3));
|
||||
HandlerUtils.waitForIdle(mHandler, DEFAULT_TIMEOUT);
|
||||
testCallback1.expectedNoCallback();
|
||||
testCallback2.expectedNoCallback();
|
||||
testCallback3.expectedSocketCreatedForNetwork(TEST_NETWORK, List.of(LINKADDRV4));
|
||||
|
||||
mHandler.post(() -> mTetheringEventCallback.onLocalOnlyInterfacesChanged(
|
||||
List.of(LOCAL_ONLY_IFACE_NAME)));
|
||||
HandlerUtils.waitForIdle(mHandler, DEFAULT_TIMEOUT);
|
||||
verify(mLocalOnlyIfaceWrapper).getNetworkInterface();
|
||||
testCallback1.expectedNoCallback();
|
||||
testCallback2.expectedNoCallback();
|
||||
testCallback3.expectedSocketCreatedForNetwork(LOCAL_NETWORK, List.of());
|
||||
|
||||
mHandler.post(() -> mTetheringEventCallback.onTetheredInterfacesChanged(
|
||||
List.of(TETHERED_IFACE_NAME)));
|
||||
HandlerUtils.waitForIdle(mHandler, DEFAULT_TIMEOUT);
|
||||
verify(mTetheredIfaceWrapper).getNetworkInterface();
|
||||
testCallback1.expectedNoCallback();
|
||||
testCallback2.expectedNoCallback();
|
||||
testCallback3.expectedSocketCreatedForNetwork(LOCAL_NETWORK, List.of());
|
||||
|
||||
mHandler.post(() -> mSocketProvider.unrequestSocket(testCallback1));
|
||||
HandlerUtils.waitForIdle(mHandler, DEFAULT_TIMEOUT);
|
||||
testCallback1.expectedNoCallback();
|
||||
testCallback2.expectedNoCallback();
|
||||
testCallback3.expectedNoCallback();
|
||||
|
||||
mHandler.post(() -> mNetworkCallback.onLost(TEST_NETWORK));
|
||||
HandlerUtils.waitForIdle(mHandler, DEFAULT_TIMEOUT);
|
||||
testCallback1.expectedNoCallback();
|
||||
testCallback2.expectedInterfaceDestroyedForNetwork(TEST_NETWORK);
|
||||
testCallback3.expectedInterfaceDestroyedForNetwork(TEST_NETWORK);
|
||||
|
||||
mHandler.post(() -> mTetheringEventCallback.onLocalOnlyInterfacesChanged(List.of()));
|
||||
HandlerUtils.waitForIdle(mHandler, DEFAULT_TIMEOUT);
|
||||
testCallback1.expectedNoCallback();
|
||||
testCallback2.expectedNoCallback();
|
||||
testCallback3.expectedInterfaceDestroyedForNetwork(LOCAL_NETWORK);
|
||||
|
||||
mHandler.post(() -> mSocketProvider.unrequestSocket(testCallback3));
|
||||
HandlerUtils.waitForIdle(mHandler, DEFAULT_TIMEOUT);
|
||||
testCallback1.expectedNoCallback();
|
||||
testCallback2.expectedNoCallback();
|
||||
testCallback3.expectedNoCallback();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testAddressesChanged() throws Exception {
|
||||
final TestSocketCallback testCallback = new TestSocketCallback();
|
||||
mHandler.post(() -> mSocketProvider.requestSocket(TEST_NETWORK, testCallback));
|
||||
HandlerUtils.waitForIdle(mHandler, DEFAULT_TIMEOUT);
|
||||
testCallback.expectedNoCallback();
|
||||
|
||||
final LinkProperties testLp = new LinkProperties();
|
||||
testLp.setInterfaceName(TEST_IFACE_NAME);
|
||||
testLp.setLinkAddresses(List.of(LINKADDRV4));
|
||||
mHandler.post(() -> mNetworkCallback.onLinkPropertiesChanged(TEST_NETWORK, testLp));
|
||||
HandlerUtils.waitForIdle(mHandler, DEFAULT_TIMEOUT);
|
||||
verify(mTestNetworkIfaceWrapper, times(1)).getNetworkInterface();
|
||||
testCallback.expectedSocketCreatedForNetwork(TEST_NETWORK, List.of(LINKADDRV4));
|
||||
|
||||
final LinkProperties newTestLp = new LinkProperties();
|
||||
newTestLp.setInterfaceName(TEST_IFACE_NAME);
|
||||
newTestLp.setLinkAddresses(List.of(LINKADDRV4, LINKADDRV6));
|
||||
mHandler.post(() -> mNetworkCallback.onLinkPropertiesChanged(TEST_NETWORK, newTestLp));
|
||||
HandlerUtils.waitForIdle(mHandler, DEFAULT_TIMEOUT);
|
||||
verify(mTestNetworkIfaceWrapper, times(1)).getNetworkInterface();
|
||||
testCallback.expectedAddressesChangedForNetwork(
|
||||
TEST_NETWORK, List.of(LINKADDRV4, LINKADDRV6));
|
||||
}
|
||||
}
|
||||
Loading…
Reference in a new issue