Adds option to control whethert to send IPv6 packet on IPv6 only network

Add options to control whether only send IPv6 packet on IPv6 only
network. In some use case, IPv6 query should not be sent on a dual
network.

Added a small improvement to send an ANY query if both SRV & PTR
records are needed.

Bug: 284480254
Test: atest CtsNetTest FrameworksNetTests
Change-Id: I8f55db6f5e8ae606ac167fb363bc9b2fb9b5fc14
This commit is contained in:
Yuyang Huang
2023-06-02 13:38:29 +09:00
parent d925df8aa6
commit 9637e84eed
10 changed files with 276 additions and 163 deletions

View File

@@ -29,7 +29,6 @@ import com.android.server.connectivity.mdns.util.MdnsUtils;
import java.io.IOException;
import java.lang.ref.WeakReference;
import java.net.DatagramPacket;
import java.net.InetAddress;
import java.net.InetSocketAddress;
import java.util.ArrayList;
import java.util.Collection;
@@ -78,6 +77,7 @@ public class EnqueueMdnsQueryCallable implements Callable<Pair<Integer, List<Str
private final List<MdnsResponse> servicesToResolve;
@NonNull
private final MdnsResponseDecoder.Clock clock;
private final boolean onlyUseIpv6OnIpv6OnlyNetworks;
EnqueueMdnsQueryCallable(
@NonNull MdnsSocketClientBase requestSender,
@@ -87,6 +87,7 @@ public class EnqueueMdnsQueryCallable implements Callable<Pair<Integer, List<Str
boolean expectUnicastResponse,
int transactionId,
@Nullable Network network,
boolean onlyUseIpv6OnIpv6OnlyNetworks,
boolean sendDiscoveryQueries,
@NonNull Collection<MdnsResponse> servicesToResolve,
@NonNull MdnsResponseDecoder.Clock clock) {
@@ -97,6 +98,7 @@ public class EnqueueMdnsQueryCallable implements Callable<Pair<Integer, List<Str
this.expectUnicastResponse = expectUnicastResponse;
this.transactionId = transactionId;
this.network = network;
this.onlyUseIpv6OnIpv6OnlyNetworks = onlyUseIpv6OnIpv6OnlyNetworks;
this.sendDiscoveryQueries = sendDiscoveryQueries;
this.servicesToResolve = new ArrayList<>(servicesToResolve);
this.clock = clock;
@@ -128,24 +130,29 @@ public class EnqueueMdnsQueryCallable implements Callable<Pair<Integer, List<Str
for (MdnsResponse response : servicesToResolve) {
final String[] serviceName = response.getServiceName();
if (serviceName == null) continue;
if (!response.hasTextRecord() || MdnsUtils.isRecordRenewalNeeded(
response.getTextRecord(), now)) {
missingKnownAnswerRecords.add(new Pair<>(serviceName, MdnsRecord.TYPE_TXT));
}
if (!response.hasServiceRecord() || MdnsUtils.isRecordRenewalNeeded(
response.getServiceRecord(), now)) {
missingKnownAnswerRecords.add(new Pair<>(serviceName, MdnsRecord.TYPE_SRV));
// The hostname is not yet known, so queries for address records will be sent
// the next time the EnqueueMdnsQueryCallable is enqueued if the reply does not
// contain them. In practice, advertisers should include the address records
// when queried for SRV, although it's not a MUST requirement (RFC6763 12.2).
// TODO: Figure out how to renew the A/AAAA record. Usually A/AAAA record will
// be included in the response to the SRV record so in high chances there is
// no need to renew them individually.
} else if (!response.hasInet4AddressRecord() && !response.hasInet6AddressRecord()) {
final String[] host = response.getServiceRecord().getServiceHost();
missingKnownAnswerRecords.add(new Pair<>(host, MdnsRecord.TYPE_A));
missingKnownAnswerRecords.add(new Pair<>(host, MdnsRecord.TYPE_AAAA));
boolean renewTxt = !response.hasTextRecord() || MdnsUtils.isRecordRenewalNeeded(
response.getTextRecord(), now);
boolean renewSrv = !response.hasServiceRecord() || MdnsUtils.isRecordRenewalNeeded(
response.getServiceRecord(), now);
if (renewSrv && renewTxt) {
missingKnownAnswerRecords.add(new Pair<>(serviceName, MdnsRecord.TYPE_ANY));
} else {
if (renewTxt) {
missingKnownAnswerRecords.add(new Pair<>(serviceName, MdnsRecord.TYPE_TXT));
}
if (renewSrv) {
missingKnownAnswerRecords.add(new Pair<>(serviceName, MdnsRecord.TYPE_SRV));
// The hostname is not yet known, so queries for address records will be
// sent the next time the EnqueueMdnsQueryCallable is enqueued if the reply
// does not contain them. In practice, advertisers should include the
// address records when queried for SRV, although it's not a MUST
// requirement (RFC6763 12.2).
} else if (!response.hasInet4AddressRecord()
&& !response.hasInet6AddressRecord()) {
final String[] host = response.getServiceRecord().getServiceHost();
missingKnownAnswerRecords.add(new Pair<>(host, MdnsRecord.TYPE_A));
missingKnownAnswerRecords.add(new Pair<>(host, MdnsRecord.TYPE_AAAA));
}
}
}
numQuestions += missingKnownAnswerRecords.size();
@@ -183,24 +190,9 @@ public class EnqueueMdnsQueryCallable implements Callable<Pair<Integer, List<Str
writeQuestion(serviceTypeLabels, MdnsRecord.TYPE_PTR);
}
if (requestSender instanceof MdnsMultinetworkSocketClient) {
sendPacketToIpv4AndIpv6(requestSender, MdnsConstants.MDNS_PORT, network);
for (Integer emulatorPort : castShellEmulatorMdnsPorts) {
sendPacketToIpv4AndIpv6(requestSender, emulatorPort, network);
}
} else if (requestSender instanceof MdnsSocketClient) {
final MdnsSocketClient client = (MdnsSocketClient) requestSender;
InetAddress mdnsAddress = MdnsConstants.getMdnsIPv4Address();
if (client.isOnIPv6OnlyNetwork()) {
mdnsAddress = MdnsConstants.getMdnsIPv6Address();
}
sendPacketTo(client, new InetSocketAddress(mdnsAddress, MdnsConstants.MDNS_PORT));
for (Integer emulatorPort : castShellEmulatorMdnsPorts) {
sendPacketTo(client, new InetSocketAddress(mdnsAddress, emulatorPort));
}
} else {
throw new IOException("Unknown socket client type: " + requestSender.getClass());
sendPacketToIpv4AndIpv6(requestSender, MdnsConstants.MDNS_PORT);
for (Integer emulatorPort : castShellEmulatorMdnsPorts) {
sendPacketToIpv4AndIpv6(requestSender, emulatorPort);
}
return Pair.create(transactionId, subtypes);
} catch (IOException e) {
@@ -218,38 +210,39 @@ public class EnqueueMdnsQueryCallable implements Callable<Pair<Integer, List<Str
| (expectUnicastResponse ? MdnsConstants.QCLASS_UNICAST : 0));
}
private void sendPacketTo(MdnsSocketClient requestSender, InetSocketAddress address)
private void sendPacket(MdnsSocketClientBase requestSender, InetSocketAddress address)
throws IOException {
DatagramPacket packet = packetWriter.getPacket(address);
if (expectUnicastResponse) {
requestSender.sendUnicastPacket(packet);
if (requestSender instanceof MdnsMultinetworkSocketClient) {
((MdnsMultinetworkSocketClient) requestSender).sendPacketRequestingUnicastResponse(
packet, network, onlyUseIpv6OnIpv6OnlyNetworks);
} else {
requestSender.sendPacketRequestingUnicastResponse(
packet, onlyUseIpv6OnIpv6OnlyNetworks);
}
} else {
requestSender.sendMulticastPacket(packet);
if (requestSender instanceof MdnsMultinetworkSocketClient) {
((MdnsMultinetworkSocketClient) requestSender)
.sendPacketRequestingMulticastResponse(
packet, network, onlyUseIpv6OnIpv6OnlyNetworks);
} else {
requestSender.sendPacketRequestingMulticastResponse(
packet, onlyUseIpv6OnIpv6OnlyNetworks);
}
}
}
private void sendPacketFromNetwork(MdnsSocketClientBase requestSender,
InetSocketAddress address, Network network)
throws IOException {
DatagramPacket packet = packetWriter.getPacket(address);
if (expectUnicastResponse) {
requestSender.sendUnicastPacket(packet, network);
} else {
requestSender.sendMulticastPacket(packet, network);
}
}
private void sendPacketToIpv4AndIpv6(MdnsSocketClientBase requestSender, int port,
Network network) {
private void sendPacketToIpv4AndIpv6(MdnsSocketClientBase requestSender, int port) {
try {
sendPacketFromNetwork(requestSender,
new InetSocketAddress(MdnsConstants.getMdnsIPv4Address(), port), network);
sendPacket(requestSender,
new InetSocketAddress(MdnsConstants.getMdnsIPv4Address(), port));
} catch (IOException e) {
Log.i(TAG, "Can't send packet to IPv4", e);
}
try {
sendPacketFromNetwork(requestSender,
new InetSocketAddress(MdnsConstants.getMdnsIPv6Address(), port), network);
sendPacket(requestSender,
new InetSocketAddress(MdnsConstants.getMdnsIPv6Address(), port));
} catch (IOException e) {
Log.i(TAG, "Can't send packet to IPv6", e);
}

View File

@@ -213,17 +213,21 @@ public class MdnsMultinetworkSocketClient implements MdnsSocketClientBase {
return true;
}
private void sendMdnsPacket(@NonNull DatagramPacket packet, @Nullable Network targetNetwork) {
private void sendMdnsPacket(@NonNull DatagramPacket packet, @Nullable Network targetNetwork,
boolean onlyUseIpv6OnIpv6OnlyNetworks) {
final boolean isIpv6 = ((InetSocketAddress) packet.getSocketAddress()).getAddress()
instanceof Inet6Address;
final boolean isIpv4 = ((InetSocketAddress) packet.getSocketAddress()).getAddress()
instanceof Inet4Address;
final ArrayMap<MdnsInterfaceSocket, SocketKey> activeSockets = getActiveSockets();
boolean shouldQueryIpv6 = !onlyUseIpv6OnIpv6OnlyNetworks || isIpv6OnlyNetworks(
activeSockets, targetNetwork);
for (int i = 0; i < activeSockets.size(); i++) {
final MdnsInterfaceSocket socket = activeSockets.keyAt(i);
final Network network = activeSockets.valueAt(i).getNetwork();
// Check ip capability and network before sending packet
if (((isIpv6 && socket.hasJoinedIpv6()) || (isIpv4 && socket.hasJoinedIpv4()))
if (((isIpv6 && socket.hasJoinedIpv6() && shouldQueryIpv6)
|| (isIpv4 && socket.hasJoinedIpv4()))
// Contrary to MdnsUtils.isNetworkMatched, only send packets targeting
// the null network to interfaces that have the null network (tethering
// downstream interfaces).
@@ -237,6 +241,19 @@ public class MdnsMultinetworkSocketClient implements MdnsSocketClientBase {
}
}
private boolean isIpv6OnlyNetworks(
@NonNull ArrayMap<MdnsInterfaceSocket, SocketKey> activeSockets,
@Nullable Network targetNetwork) {
for (int i = 0; i < activeSockets.size(); i++) {
final MdnsInterfaceSocket socket = activeSockets.keyAt(i);
final Network network = activeSockets.valueAt(i).getNetwork();
if (Objects.equals(network, targetNetwork) && socket.hasJoinedIpv4()) {
return false;
}
}
return true;
}
private void processResponsePacket(byte[] recvbuf, int length, @NonNull SocketKey socketKey) {
int packetNumber = ++mReceivedPacketNumber;
@@ -259,21 +276,38 @@ public class MdnsMultinetworkSocketClient implements MdnsSocketClientBase {
}
/**
* Sends a mDNS request packet via given network that asks for multicast response. Null network
* means sending packet via all networks.
* Send a mDNS request packet via given network that asks for multicast response.
*
* <p>The socket client may use a null network to identify some or all interfaces, in which case
* passing null sends the packet to these.
*/
public void sendPacketRequestingMulticastResponse(@NonNull DatagramPacket packet,
@Nullable Network network, boolean onlyUseIpv6OnIpv6OnlyNetworks) {
mHandler.post(() -> sendMdnsPacket(packet, network, onlyUseIpv6OnIpv6OnlyNetworks));
}
@Override
public void sendMulticastPacket(@NonNull DatagramPacket packet, @Nullable Network network) {
mHandler.post(() -> sendMdnsPacket(packet, network));
public void sendPacketRequestingMulticastResponse(
@NonNull DatagramPacket packet, boolean onlyUseIpv6OnIpv6OnlyNetworks) {
sendPacketRequestingMulticastResponse(
packet, null /* network */, onlyUseIpv6OnIpv6OnlyNetworks);
}
/**
* Sends a mDNS request packet via given network that asks for unicast response. Null network
* means sending packet via all networks.
* Send a mDNS request packet via given network that asks for unicast response.
*
* <p>The socket client may use a null network to identify some or all interfaces, in which case
* passing null sends the packet to these.
*/
public void sendPacketRequestingUnicastResponse(@NonNull DatagramPacket packet,
@Nullable Network network, boolean onlyUseIpv6OnIpv6OnlyNetworks) {
mHandler.post(() -> sendMdnsPacket(packet, network, onlyUseIpv6OnIpv6OnlyNetworks));
}
@Override
public void sendUnicastPacket(@NonNull DatagramPacket packet, @Nullable Network network) {
// TODO: Separate unicast packet.
mHandler.post(() -> sendMdnsPacket(packet, network));
public void sendPacketRequestingUnicastResponse(
@NonNull DatagramPacket packet, boolean onlyUseIpv6OnIpv6OnlyNetworks) {
sendPacketRequestingUnicastResponse(
packet, null /* network */, onlyUseIpv6OnIpv6OnlyNetworks);
}
}

View File

@@ -44,10 +44,13 @@ public class MdnsSearchOptions implements Parcelable {
new Parcelable.Creator<MdnsSearchOptions>() {
@Override
public MdnsSearchOptions createFromParcel(Parcel source) {
return new MdnsSearchOptions(source.createStringArrayList(),
source.readBoolean(), source.readBoolean(),
return new MdnsSearchOptions(
source.createStringArrayList(),
source.readBoolean(),
source.readBoolean(),
source.readParcelable(null),
source.readString());
source.readString(),
(source.dataAvail() > 0) ? source.readBoolean() : false);
}
@Override
@@ -61,18 +64,25 @@ public class MdnsSearchOptions implements Parcelable {
private final String resolveInstanceName;
private final boolean isPassiveMode;
private final boolean onlyUseIpv6OnIpv6OnlyNetworks;
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 MdnsSearchOptions}. */
MdnsSearchOptions(List<String> subtypes, boolean isPassiveMode, boolean removeExpiredService,
@Nullable Network network, @Nullable String resolveInstanceName) {
MdnsSearchOptions(
List<String> subtypes,
boolean isPassiveMode,
boolean removeExpiredService,
@Nullable Network network,
@Nullable String resolveInstanceName,
boolean onlyUseIpv6OnIpv6OnlyNetworks) {
this.subtypes = new ArrayList<>();
if (subtypes != null) {
this.subtypes.addAll(subtypes);
}
this.isPassiveMode = isPassiveMode;
this.onlyUseIpv6OnIpv6OnlyNetworks = onlyUseIpv6OnIpv6OnlyNetworks;
this.removeExpiredService = removeExpiredService;
mNetwork = network;
this.resolveInstanceName = resolveInstanceName;
@@ -104,6 +114,14 @@ public class MdnsSearchOptions implements Parcelable {
return isPassiveMode;
}
/**
* @return {@code true} if only the IPv4 mDNS host should be queried on network that supports
* both IPv6 as well as IPv4. On an IPv6-only network, this is ignored.
*/
public boolean onlyUseIpv6OnIpv6OnlyNetworks() {
return onlyUseIpv6OnIpv6OnlyNetworks;
}
/** Returns {@code true} if service will be removed after its TTL expires. */
public boolean removeExpiredService() {
return removeExpiredService;
@@ -140,12 +158,14 @@ public class MdnsSearchOptions implements Parcelable {
out.writeBoolean(removeExpiredService);
out.writeParcelable(mNetwork, 0);
out.writeString(resolveInstanceName);
out.writeBoolean(onlyUseIpv6OnIpv6OnlyNetworks);
}
/** A builder to create {@link MdnsSearchOptions}. */
public static final class Builder {
private final Set<String> subtypes;
private boolean isPassiveMode = true;
private boolean onlyUseIpv6OnIpv6OnlyNetworks = false;
private boolean removeExpiredService;
private Network mNetwork;
private String resolveInstanceName;
@@ -189,6 +209,15 @@ public class MdnsSearchOptions implements Parcelable {
return this;
}
/**
* Sets if only the IPv4 mDNS host should be queried on a network that is both IPv4 & IPv6.
* On an IPv6-only network, this is ignored.
*/
public Builder setOnlyUseIpv6OnIpv6OnlyNetworks(boolean onlyUseIpv6OnIpv6OnlyNetworks) {
this.onlyUseIpv6OnIpv6OnlyNetworks = onlyUseIpv6OnIpv6OnlyNetworks;
return this;
}
/**
* Sets if the service should be removed after TTL.
*
@@ -223,8 +252,13 @@ public class MdnsSearchOptions implements Parcelable {
/** Builds a {@link MdnsSearchOptions} with the arguments supplied to this builder. */
public MdnsSearchOptions build() {
return new MdnsSearchOptions(new ArrayList<>(subtypes), isPassiveMode,
removeExpiredService, mNetwork, resolveInstanceName);
return new MdnsSearchOptions(
new ArrayList<>(subtypes),
isPassiveMode,
removeExpiredService,
mNetwork,
resolveInstanceName,
onlyUseIpv6OnIpv6OnlyNetworks);
}
}
}

View File

@@ -198,6 +198,7 @@ public class MdnsServiceTypeClient {
final QueryTaskConfig taskConfig = new QueryTaskConfig(
searchOptions.getSubtypes(),
searchOptions.isPassiveMode(),
searchOptions.onlyUseIpv6OnIpv6OnlyNetworks(),
currentSessionId,
socketKey);
if (hadReply) {
@@ -220,7 +221,7 @@ public class MdnsServiceTypeClient {
final boolean matchesInstanceName = options.getResolveInstanceName() == null
// DNS is case-insensitive, so ignore case in the comparison
|| MdnsUtils.equalsIgnoreDnsCase(options.getResolveInstanceName(),
response.getServiceInstanceName());
response.getServiceInstanceName());
// If discovery is requiring some subtypes, the response must have one that matches a
// requested one.
@@ -427,6 +428,7 @@ public class MdnsServiceTypeClient {
private final boolean alwaysAskForUnicastResponse =
MdnsConfigs.alwaysAskForUnicastResponseInEachBurst();
private final boolean usePassiveMode;
private final boolean onlyUseIpv6OnIpv6OnlyNetworks;
private final long sessionId;
@VisibleForTesting
int transactionId;
@@ -439,9 +441,13 @@ public class MdnsServiceTypeClient {
private boolean isFirstBurst;
@NonNull private final SocketKey socketKey;
QueryTaskConfig(@NonNull Collection<String> subtypes, boolean usePassiveMode,
long sessionId, @NonNull SocketKey socketKey) {
QueryTaskConfig(@NonNull Collection<String> subtypes,
boolean usePassiveMode,
boolean onlyUseIpv6OnIpv6OnlyNetworks,
long sessionId,
@Nullable SocketKey socketKey) {
this.usePassiveMode = usePassiveMode;
this.onlyUseIpv6OnIpv6OnlyNetworks = onlyUseIpv6OnIpv6OnlyNetworks;
this.subtypes = new ArrayList<>(subtypes);
this.queriesPerBurst = QUERIES_PER_BURST;
this.burstCounter = 0;
@@ -559,6 +565,7 @@ public class MdnsServiceTypeClient {
config.expectUnicastResponse,
config.transactionId,
config.socketKey.getNetwork(),
config.onlyUseIpv6OnIpv6OnlyNetworks,
sendDiscoveryQueries,
servicesToResolve,
clock)

View File

@@ -93,6 +93,10 @@ public class MdnsSocket {
}
for (NetworkInterfaceWrapper networkInterface : networkInterfaces) {
multicastSocket.joinGroup(multicastAddress, networkInterface.getNetworkInterface());
if (!isOnIPv6OnlyNetwork) {
multicastSocket.joinGroup(
MULTICAST_IPV6_ADDRESS, networkInterface.getNetworkInterface());
}
}
}
@@ -105,6 +109,10 @@ public class MdnsSocket {
}
for (NetworkInterfaceWrapper networkInterface : networkInterfaces) {
multicastSocket.leaveGroup(multicastAddress, networkInterface.getNetworkInterface());
if (!isOnIPv6OnlyNetwork) {
multicastSocket.leaveGroup(
MULTICAST_IPV6_ADDRESS, networkInterface.getNetworkInterface());
}
}
}

View File

@@ -31,6 +31,9 @@ import com.android.server.connectivity.mdns.util.MdnsLogger;
import java.io.IOException;
import java.net.DatagramPacket;
import java.net.Inet4Address;
import java.net.Inet6Address;
import java.net.InetSocketAddress;
import java.util.ArrayDeque;
import java.util.ArrayList;
import java.util.List;
@@ -194,38 +197,22 @@ public class MdnsSocketClient implements MdnsSocketClientBase {
}
}
/** Sends a mDNS request packet that asks for multicast response. */
public void sendMulticastPacket(@NonNull DatagramPacket packet) {
sendMdnsPacket(packet, multicastPacketQueue);
@Override
public void sendPacketRequestingMulticastResponse(@NonNull DatagramPacket packet,
boolean onlyUseIpv6OnIpv6OnlyNetworks) {
sendMdnsPacket(packet, multicastPacketQueue, onlyUseIpv6OnIpv6OnlyNetworks);
}
/** Sends a mDNS request packet that asks for unicast response. */
public void sendUnicastPacket(DatagramPacket packet) {
@Override
public void sendPacketRequestingUnicastResponse(@NonNull DatagramPacket packet,
boolean onlyUseIpv6OnIpv6OnlyNetworks) {
if (useSeparateSocketForUnicast) {
sendMdnsPacket(packet, unicastPacketQueue);
sendMdnsPacket(packet, unicastPacketQueue, onlyUseIpv6OnIpv6OnlyNetworks);
} else {
sendMdnsPacket(packet, multicastPacketQueue);
sendMdnsPacket(packet, multicastPacketQueue, onlyUseIpv6OnIpv6OnlyNetworks);
}
}
@Override
public void sendMulticastPacket(@NonNull DatagramPacket packet, @Nullable Network network) {
if (network != null) {
throw new IllegalArgumentException("This socket client does not support sending to "
+ "specific networks");
}
sendMulticastPacket(packet);
}
@Override
public void sendUnicastPacket(@NonNull DatagramPacket packet, @Nullable Network network) {
if (network != null) {
throw new IllegalArgumentException("This socket client does not support sending to "
+ "specific networks");
}
sendUnicastPacket(packet);
}
@Override
public void notifyNetworkRequested(
@NonNull MdnsServiceBrowserListener listener,
@@ -243,11 +230,25 @@ public class MdnsSocketClient implements MdnsSocketClientBase {
return false;
}
private void sendMdnsPacket(DatagramPacket packet, Queue<DatagramPacket> packetQueueToUse) {
private void sendMdnsPacket(DatagramPacket packet, Queue<DatagramPacket> packetQueueToUse,
boolean onlyUseIpv6OnIpv6OnlyNetworks) {
if (shouldStopSocketLoop && !MdnsConfigs.allowAddMdnsPacketAfterDiscoveryStops()) {
LOGGER.w("sendMdnsPacket() is called after discovery already stopped");
return;
}
final boolean isIpv4 = ((InetSocketAddress) packet.getSocketAddress()).getAddress()
instanceof Inet4Address;
final boolean isIpv6 = ((InetSocketAddress) packet.getSocketAddress()).getAddress()
instanceof Inet6Address;
final boolean ipv6Only = multicastSocket != null && multicastSocket.isOnIPv6OnlyNetwork();
if (isIpv4 && ipv6Only) {
return;
}
if (isIpv6 && !ipv6Only && onlyUseIpv6OnIpv6OnlyNetworks) {
return;
}
synchronized (packetQueueToUse) {
while (packetQueueToUse.size() >= MdnsConfigs.mdnsPacketQueueMaxSize()) {
packetQueueToUse.remove();
@@ -535,8 +536,4 @@ public class MdnsSocketClient implements MdnsSocketClientBase {
}
packets.clear();
}
public boolean isOnIPv6OnlyNetwork() {
return multicastSocket != null && multicastSocket.isOnIPv6OnlyNetwork();
}
}

View File

@@ -41,19 +41,15 @@ public interface MdnsSocketClientBase {
/**
* Send a mDNS request packet via given network that asks for multicast response.
*
* <p>The socket client may use a null network to identify some or all interfaces, in which case
* passing null sends the packet to these.
*/
void sendMulticastPacket(@NonNull DatagramPacket packet, @Nullable Network network);
void sendPacketRequestingMulticastResponse(@NonNull DatagramPacket packet,
boolean onlyUseIpv6OnIpv6OnlyNetworks);
/**
* Send a mDNS request packet via given network that asks for unicast response.
*
* <p>The socket client may use a null network to identify some or all interfaces, in which case
* passing null sends the packet to these.
*/
void sendUnicastPacket(@NonNull DatagramPacket packet, @Nullable Network network);
void sendPacketRequestingUnicastResponse(@NonNull DatagramPacket packet,
boolean onlyUseIpv6OnIpv6OnlyNetworks);
/*** Notify that the given network is requested for mdns discovery / resolution */
void notifyNetworkRequested(@NonNull MdnsServiceBrowserListener listener,

View File

@@ -138,18 +138,38 @@ public class MdnsMultinetworkSocketClientTest {
verify(mSocketCreationCallback).onSocketCreated(tetherSocketKey2);
// Send packet to IPv4 with target network and verify sending has been called.
mSocketClient.sendMulticastPacket(ipv4Packet, mNetwork);
mSocketClient.sendPacketRequestingMulticastResponse(ipv4Packet, mNetwork,
false /* onlyUseIpv6OnIpv6OnlyNetworks */);
HandlerUtils.waitForIdle(mHandler, DEFAULT_TIMEOUT);
verify(mSocket).send(ipv4Packet);
verify(tetherIfaceSock1, never()).send(any());
verify(tetherIfaceSock2, never()).send(any());
// Send packet to IPv4 with onlyUseIpv6OnIpv6OnlyNetworks = true, the packet will be sent.
mSocketClient.sendPacketRequestingMulticastResponse(ipv4Packet, mNetwork,
true /* onlyUseIpv6OnIpv6OnlyNetworks */);
HandlerUtils.waitForIdle(mHandler, DEFAULT_TIMEOUT);
verify(mSocket, times(2)).send(ipv4Packet);
verify(tetherIfaceSock1, never()).send(any());
verify(tetherIfaceSock2, never()).send(any());
// Send packet to IPv6 without target network and verify sending has been called.
mSocketClient.sendMulticastPacket(ipv6Packet, null);
mSocketClient.sendPacketRequestingMulticastResponse(ipv6Packet, null,
false /* onlyUseIpv6OnIpv6OnlyNetworks */);
HandlerUtils.waitForIdle(mHandler, DEFAULT_TIMEOUT);
verify(mSocket, never()).send(ipv6Packet);
verify(tetherIfaceSock1).send(ipv6Packet);
verify(tetherIfaceSock2).send(ipv6Packet);
// Send packet to IPv6 with onlyUseIpv6OnIpv6OnlyNetworks = true, the packet will not be
// sent. Therefore, the tetherIfaceSock1.send() and tetherIfaceSock2.send() are still be
// called once.
mSocketClient.sendPacketRequestingMulticastResponse(ipv6Packet, null,
true /* onlyUseIpv6OnIpv6OnlyNetworks */);
HandlerUtils.waitForIdle(mHandler, DEFAULT_TIMEOUT);
verify(mSocket, never()).send(ipv6Packet);
verify(tetherIfaceSock1, times(1)).send(ipv6Packet);
verify(tetherIfaceSock2, times(1)).send(ipv6Packet);
}
@Test
@@ -230,7 +250,8 @@ public class MdnsMultinetworkSocketClientTest {
verify(mSocketCreationCallback).onSocketCreated(socketKey3);
// Send IPv4 packet on the non-null Network and verify sending has been called.
mSocketClient.sendMulticastPacket(ipv4Packet, mNetwork);
mSocketClient.sendPacketRequestingMulticastResponse(ipv4Packet, mNetwork,
false /* onlyUseIpv6OnIpv6OnlyNetworks */);
HandlerUtils.waitForIdle(mHandler, DEFAULT_TIMEOUT);
verify(mSocket).send(ipv4Packet);
verify(socket2, never()).send(any());
@@ -258,7 +279,8 @@ public class MdnsMultinetworkSocketClientTest {
verify(socketCreationCb2).onSocketCreated(socketKey3);
// Send IPv4 packet to null network and verify sending to the 2 tethered interface sockets.
mSocketClient.sendMulticastPacket(ipv4Packet, null);
mSocketClient.sendPacketRequestingMulticastResponse(ipv4Packet, null,
false /* onlyUseIpv6OnIpv6OnlyNetworks */);
HandlerUtils.waitForIdle(mHandler, DEFAULT_TIMEOUT);
// ipv4Packet still sent only once on mSocket: times(1) matches the packet sent earlier on
// mNetwork
@@ -271,7 +293,8 @@ public class MdnsMultinetworkSocketClientTest {
verify(mProvider, timeout(DEFAULT_TIMEOUT)).unrequestSocket(callback2);
// Send IPv4 packet again and verify it's still sent a second time
mSocketClient.sendMulticastPacket(ipv4Packet, null);
mSocketClient.sendPacketRequestingMulticastResponse(ipv4Packet, null,
false /* onlyUseIpv6OnIpv6OnlyNetworks */);
HandlerUtils.waitForIdle(mHandler, DEFAULT_TIMEOUT);
verify(socket2, times(2)).send(ipv4Packet);
verify(socket3, times(2)).send(ipv4Packet);
@@ -281,7 +304,8 @@ public class MdnsMultinetworkSocketClientTest {
verify(mProvider, timeout(DEFAULT_TIMEOUT)).unrequestSocket(callback);
// Send IPv4 packet and verify no more sending.
mSocketClient.sendMulticastPacket(ipv4Packet, null);
mSocketClient.sendPacketRequestingMulticastResponse(ipv4Packet, null,
false /* onlyUseIpv6OnIpv6OnlyNetworks */);
HandlerUtils.waitForIdle(mHandler, DEFAULT_TIMEOUT);
verify(mSocket, times(1)).send(ipv4Packet);
verify(socket2, times(2)).send(ipv4Packet);

View File

@@ -327,7 +327,8 @@ public class MdnsServiceTypeClientTests {
MdnsSearchOptions searchOptions =
MdnsSearchOptions.newBuilder().addSubtype("12345").setIsPassiveMode(false).build();
QueryTaskConfig config = new QueryTaskConfig(
searchOptions.getSubtypes(), searchOptions.isPassiveMode(), 1, socketKey);
searchOptions.getSubtypes(), searchOptions.isPassiveMode(),
false /* onlyUseIpv6OnIpv6OnlyNetworks */, 1, socketKey);
// This is the first query. We will ask for unicast response.
assertTrue(config.expectUnicastResponse);
@@ -356,7 +357,8 @@ public class MdnsServiceTypeClientTests {
MdnsSearchOptions searchOptions =
MdnsSearchOptions.newBuilder().addSubtype("12345").setIsPassiveMode(false).build();
QueryTaskConfig config = new QueryTaskConfig(
searchOptions.getSubtypes(), searchOptions.isPassiveMode(), 1, socketKey);
searchOptions.getSubtypes(), searchOptions.isPassiveMode(),
false /* onlyUseIpv6OnIpv6OnlyNetworks */, 1, socketKey);
// This is the first query. We will ask for unicast response.
assertTrue(config.expectUnicastResponse);
@@ -928,16 +930,16 @@ public class MdnsServiceTypeClientTests {
ArgumentCaptor.forClass(DatagramPacket.class);
currentThreadExecutor.getAndClearLastScheduledRunnable().run();
// Send twice for IPv4 and IPv6
inOrder.verify(mockSocketClient, times(2)).sendUnicastPacket(srvTxtQueryCaptor.capture(),
eq(mockNetwork));
inOrder.verify(mockSocketClient, times(2)).sendPacketRequestingUnicastResponse(
srvTxtQueryCaptor.capture(),
eq(mockNetwork), eq(false));
final MdnsPacket srvTxtQueryPacket = MdnsPacket.parse(
new MdnsPacketReader(srvTxtQueryCaptor.getValue()));
final String[] serviceName = getTestServiceName(instanceName);
assertFalse(hasQuestion(srvTxtQueryPacket, MdnsRecord.TYPE_PTR));
assertTrue(hasQuestion(srvTxtQueryPacket, MdnsRecord.TYPE_SRV, serviceName));
assertTrue(hasQuestion(srvTxtQueryPacket, MdnsRecord.TYPE_TXT, serviceName));
assertTrue(hasQuestion(srvTxtQueryPacket, MdnsRecord.TYPE_ANY, serviceName));
// Process a response with SRV+TXT
final MdnsPacket srvTxtResponse = new MdnsPacket(
@@ -959,8 +961,9 @@ public class MdnsServiceTypeClientTests {
final ArgumentCaptor<DatagramPacket> addressQueryCaptor =
ArgumentCaptor.forClass(DatagramPacket.class);
currentThreadExecutor.getAndClearLastScheduledRunnable().run();
inOrder.verify(mockSocketClient, times(2)).sendMulticastPacket(addressQueryCaptor.capture(),
eq(mockNetwork));
inOrder.verify(mockSocketClient, times(2)).sendPacketRequestingMulticastResponse(
addressQueryCaptor.capture(),
eq(mockNetwork), eq(false));
final MdnsPacket addressQueryPacket = MdnsPacket.parse(
new MdnsPacketReader(addressQueryCaptor.getValue()));
@@ -1018,15 +1021,15 @@ public class MdnsServiceTypeClientTests {
ArgumentCaptor.forClass(DatagramPacket.class);
currentThreadExecutor.getAndClearLastScheduledRunnable().run();
// Send twice for IPv4 and IPv6
inOrder.verify(mockSocketClient, times(2)).sendUnicastPacket(srvTxtQueryCaptor.capture(),
eq(mockNetwork));
inOrder.verify(mockSocketClient, times(2)).sendPacketRequestingUnicastResponse(
srvTxtQueryCaptor.capture(),
eq(mockNetwork), eq(false));
final MdnsPacket srvTxtQueryPacket = MdnsPacket.parse(
new MdnsPacketReader(srvTxtQueryCaptor.getValue()));
final String[] serviceName = getTestServiceName(instanceName);
assertTrue(hasQuestion(srvTxtQueryPacket, MdnsRecord.TYPE_SRV, serviceName));
assertTrue(hasQuestion(srvTxtQueryPacket, MdnsRecord.TYPE_TXT, serviceName));
assertTrue(hasQuestion(srvTxtQueryPacket, MdnsRecord.TYPE_ANY, serviceName));
// Process a response with all records
final MdnsPacket srvTxtResponse = new MdnsPacket(
@@ -1064,13 +1067,13 @@ public class MdnsServiceTypeClientTests {
final ArgumentCaptor<DatagramPacket> renewalQueryCaptor =
ArgumentCaptor.forClass(DatagramPacket.class);
// Second and later sends are sent as "expect multicast response" queries
inOrder.verify(mockSocketClient, times(2)).sendMulticastPacket(renewalQueryCaptor.capture(),
eq(mockNetwork));
inOrder.verify(mockSocketClient, times(2)).sendPacketRequestingMulticastResponse(
renewalQueryCaptor.capture(),
eq(mockNetwork), eq(false));
inOrder.verify(mockListenerOne).onDiscoveryQuerySent(any(), anyInt());
final MdnsPacket renewalPacket = MdnsPacket.parse(
new MdnsPacketReader(renewalQueryCaptor.getValue()));
assertTrue(hasQuestion(renewalPacket, MdnsRecord.TYPE_SRV, serviceName));
assertTrue(hasQuestion(renewalPacket, MdnsRecord.TYPE_TXT, serviceName));
assertTrue(hasQuestion(renewalPacket, MdnsRecord.TYPE_ANY, serviceName));
inOrder.verifyNoMoreInteractions();
long updatedReceiptTime = TEST_ELAPSED_REALTIME + TEST_TTL;
@@ -1328,18 +1331,18 @@ public class MdnsServiceTypeClientTests {
assertEquals(currentThreadExecutor.getAndClearLastScheduledDelayInMs(), timeInMs);
currentThreadExecutor.getAndClearLastScheduledRunnable().run();
if (expectsUnicastResponse) {
verify(mockSocketClient).sendUnicastPacket(
expectedIPv4Packets[index], mockNetwork);
verify(mockSocketClient).sendPacketRequestingUnicastResponse(
expectedIPv4Packets[index], mockNetwork, false);
if (multipleSocketDiscovery) {
verify(mockSocketClient).sendUnicastPacket(
expectedIPv6Packets[index], mockNetwork);
verify(mockSocketClient).sendPacketRequestingUnicastResponse(
expectedIPv6Packets[index], mockNetwork, false);
}
} else {
verify(mockSocketClient).sendMulticastPacket(
expectedIPv4Packets[index], mockNetwork);
verify(mockSocketClient).sendPacketRequestingMulticastResponse(
expectedIPv4Packets[index], mockNetwork, false);
if (multipleSocketDiscovery) {
verify(mockSocketClient).sendMulticastPacket(
expectedIPv6Packets[index], mockNetwork);
verify(mockSocketClient).sendPacketRequestingMulticastResponse(
expectedIPv6Packets[index], mockNetwork, false);
}
}
}

View File

@@ -54,6 +54,7 @@ import org.mockito.invocation.InvocationOnMock;
import java.io.IOException;
import java.net.DatagramPacket;
import java.net.InetSocketAddress;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicBoolean;
@@ -221,15 +222,17 @@ public class MdnsSocketClientTests {
assertTrue(unicastReceiverThread.isAlive());
// Sends a packet.
DatagramPacket packet = new DatagramPacket(buf, 0, 5);
mdnsClient.sendMulticastPacket(packet);
DatagramPacket packet = getTestDatagramPacket();
mdnsClient.sendPacketRequestingMulticastResponse(packet,
false /* onlyUseIpv6OnIpv6OnlyNetworks */);
// mockMulticastSocket.send() will be called on another thread. If we verify it immediately,
// it may not be called yet. So timeout is added.
verify(mockMulticastSocket, timeout(TIMEOUT).times(1)).send(packet);
verify(mockUnicastSocket, timeout(TIMEOUT).times(0)).send(packet);
// Verify the packet is sent by the unicast socket.
mdnsClient.sendUnicastPacket(packet);
mdnsClient.sendPacketRequestingUnicastResponse(packet,
false /* onlyUseIpv6OnIpv6OnlyNetworks */);
verify(mockMulticastSocket, timeout(TIMEOUT).times(1)).send(packet);
verify(mockUnicastSocket, timeout(TIMEOUT).times(1)).send(packet);
@@ -272,15 +275,17 @@ public class MdnsSocketClientTests {
assertNull(unicastReceiverThread);
// Sends a packet.
DatagramPacket packet = new DatagramPacket(buf, 0, 5);
mdnsClient.sendMulticastPacket(packet);
DatagramPacket packet = getTestDatagramPacket();
mdnsClient.sendPacketRequestingMulticastResponse(packet,
false /* onlyUseIpv6OnIpv6OnlyNetworks */);
// mockMulticastSocket.send() will be called on another thread. If we verify it immediately,
// it may not be called yet. So timeout is added.
verify(mockMulticastSocket, timeout(TIMEOUT).times(1)).send(packet);
verify(mockUnicastSocket, timeout(TIMEOUT).times(0)).send(packet);
// Verify the packet is sent by the multicast socket as well.
mdnsClient.sendUnicastPacket(packet);
mdnsClient.sendPacketRequestingUnicastResponse(packet,
false /* onlyUseIpv6OnIpv6OnlyNetworks */);
verify(mockMulticastSocket, timeout(TIMEOUT).times(2)).send(packet);
verify(mockUnicastSocket, timeout(TIMEOUT).times(0)).send(packet);
@@ -332,7 +337,8 @@ public class MdnsSocketClientTests {
public void testStopDiscovery_queueIsCleared() throws IOException {
mdnsClient.startDiscovery();
mdnsClient.stopDiscovery();
mdnsClient.sendMulticastPacket(new DatagramPacket(buf, 0, 5));
mdnsClient.sendPacketRequestingMulticastResponse(getTestDatagramPacket(),
false /* onlyUseIpv6OnIpv6OnlyNetworks */);
synchronized (mdnsClient.multicastPacketQueue) {
assertTrue(mdnsClient.multicastPacketQueue.isEmpty());
@@ -343,7 +349,8 @@ public class MdnsSocketClientTests {
public void testSendPacket_afterDiscoveryStops() throws IOException {
mdnsClient.startDiscovery();
mdnsClient.stopDiscovery();
mdnsClient.sendMulticastPacket(new DatagramPacket(buf, 0, 5));
mdnsClient.sendPacketRequestingMulticastResponse(getTestDatagramPacket(),
false /* onlyUseIpv6OnIpv6OnlyNetworks */);
synchronized (mdnsClient.multicastPacketQueue) {
assertTrue(mdnsClient.multicastPacketQueue.isEmpty());
@@ -356,7 +363,8 @@ public class MdnsSocketClientTests {
//MdnsConfigsFlagsImpl.mdnsPacketQueueMaxSize.override(2L);
mdnsClient.startDiscovery();
for (int i = 0; i < 100; i++) {
mdnsClient.sendMulticastPacket(new DatagramPacket(buf, 0, 5));
mdnsClient.sendPacketRequestingMulticastResponse(getTestDatagramPacket(),
false /* onlyUseIpv6OnIpv6OnlyNetworks */);
}
synchronized (mdnsClient.multicastPacketQueue) {
@@ -452,9 +460,11 @@ public class MdnsSocketClientTests {
enableUnicastResponse.set(true);
mdnsClient.startDiscovery();
DatagramPacket packet = new DatagramPacket(buf, 0, 5);
mdnsClient.sendUnicastPacket(packet);
mdnsClient.sendMulticastPacket(packet);
DatagramPacket packet = getTestDatagramPacket();
mdnsClient.sendPacketRequestingUnicastResponse(packet,
false /* onlyUseIpv6OnIpv6OnlyNetworks */);
mdnsClient.sendPacketRequestingMulticastResponse(packet,
false /* onlyUseIpv6OnIpv6OnlyNetworks */);
// Wait for the timer to be triggered.
Thread.sleep(MdnsConfigs.checkMulticastResponseIntervalMs() * 2);
@@ -484,8 +494,10 @@ public class MdnsSocketClientTests {
assertFalse(mdnsClient.receivedUnicastResponse);
assertFalse(mdnsClient.cannotReceiveMulticastResponse.get());
mdnsClient.sendUnicastPacket(packet);
mdnsClient.sendMulticastPacket(packet);
mdnsClient.sendPacketRequestingUnicastResponse(packet,
false /* onlyUseIpv6OnIpv6OnlyNetworks */);
mdnsClient.sendPacketRequestingMulticastResponse(packet,
false /* onlyUseIpv6OnIpv6OnlyNetworks */);
Thread.sleep(MdnsConfigs.checkMulticastResponseIntervalMs() * 2);
// Verify cannotReceiveMulticastResponse is not set the true because we didn't receive the
@@ -540,4 +552,9 @@ public class MdnsSocketClientTests {
verify(mockCallback, timeout(TIMEOUT).atLeast(1))
.onResponseReceived(any(), argThat(key -> key.getInterfaceIndex() == -1));
}
private DatagramPacket getTestDatagramPacket() {
return new DatagramPacket(buf, 0, 5,
new InetSocketAddress(MdnsConstants.getMdnsIPv4Address(), 5353 /* port */));
}
}