Fix replying to queries via unicast
Instead of (wrongly) looking at the DNS message flags for the Query Unicast bit, look at the flag in the question record. The previous bug means that all queries would be replied multicast instead of sometimes unicast. The fix is flagged off as this is a behavior change that may affect performance and latency. Bug: 289482497 Test: atest Change-Id: I08e30c4ffa747c9073d631e8addca1278ea40648
This commit is contained in:
parent
7078ac59b9
commit
0ca094b577
10 changed files with 453 additions and 75 deletions
|
|
@ -176,7 +176,7 @@ public class NsdService extends INsdManager.Stub {
|
|||
"mdns_advertiser_allowlist_";
|
||||
private static final String MDNS_ALLOWLIST_FLAG_SUFFIX = "_version";
|
||||
|
||||
|
||||
private static final String FORCE_ENABLE_FLAG_FOR_TEST_PREFIX = "test_";
|
||||
|
||||
@VisibleForTesting
|
||||
static final String MDNS_CONFIG_RUNNING_APP_ACTIVE_IMPORTANCE_CUTOFF =
|
||||
|
|
@ -1739,6 +1739,10 @@ public class NsdService extends INsdManager.Stub {
|
|||
mContext, MdnsFeatureFlags.NSD_LIMIT_LABEL_COUNT))
|
||||
.setIsKnownAnswerSuppressionEnabled(mDeps.isFeatureEnabled(
|
||||
mContext, MdnsFeatureFlags.NSD_KNOWN_ANSWER_SUPPRESSION))
|
||||
.setIsUnicastReplyEnabled(mDeps.isFeatureEnabled(
|
||||
mContext, MdnsFeatureFlags.NSD_UNICAST_REPLY_ENABLED))
|
||||
.setOverrideProvider(flag -> mDeps.isFeatureEnabled(
|
||||
mContext, FORCE_ENABLE_FLAG_FOR_TEST_PREFIX + flag))
|
||||
.build();
|
||||
mMdnsSocketClient =
|
||||
new MdnsMultinetworkSocketClient(handler.getLooper(), mMdnsSocketProvider,
|
||||
|
|
|
|||
|
|
@ -15,6 +15,9 @@
|
|||
*/
|
||||
package com.android.server.connectivity.mdns;
|
||||
|
||||
import android.annotation.NonNull;
|
||||
import android.annotation.Nullable;
|
||||
|
||||
/**
|
||||
* The class that contains mDNS feature flags;
|
||||
*/
|
||||
|
|
@ -46,6 +49,14 @@ public class MdnsFeatureFlags {
|
|||
*/
|
||||
public static final String NSD_KNOWN_ANSWER_SUPPRESSION = "nsd_known_answer_suppression";
|
||||
|
||||
/**
|
||||
* A feature flag to control whether unicast replies should be enabled.
|
||||
*
|
||||
* <p>Enabling this feature causes replies to queries with the Query Unicast (QU) flag set to be
|
||||
* sent unicast instead of multicast, as per RFC6762 5.4.
|
||||
*/
|
||||
public static final String NSD_UNICAST_REPLY_ENABLED = "nsd_unicast_reply_enabled";
|
||||
|
||||
// Flag for offload feature
|
||||
public final boolean mIsMdnsOffloadFeatureEnabled;
|
||||
|
||||
|
|
@ -61,6 +72,36 @@ public class MdnsFeatureFlags {
|
|||
// Flag for known-answer suppression
|
||||
public final boolean mIsKnownAnswerSuppressionEnabled;
|
||||
|
||||
// Flag to enable replying unicast to queries requesting unicast replies
|
||||
public final boolean mIsUnicastReplyEnabled;
|
||||
|
||||
@Nullable
|
||||
private final FlagOverrideProvider mOverrideProvider;
|
||||
|
||||
/**
|
||||
* A provider that can indicate whether a flag should be force-enabled for testing purposes.
|
||||
*/
|
||||
public interface FlagOverrideProvider {
|
||||
/**
|
||||
* Indicates whether the flag should be force-enabled for testing purposes.
|
||||
*/
|
||||
boolean isForceEnabledForTest(@NonNull String flag);
|
||||
}
|
||||
|
||||
/**
|
||||
* Indicates whether the flag should be force-enabled for testing purposes.
|
||||
*/
|
||||
private boolean isForceEnabledForTest(@NonNull String flag) {
|
||||
return mOverrideProvider != null && mOverrideProvider.isForceEnabledForTest(flag);
|
||||
}
|
||||
|
||||
/**
|
||||
* Indicates whether {@link #NSD_UNICAST_REPLY_ENABLED} is enabled, including for testing.
|
||||
*/
|
||||
public boolean isUnicastReplyEnabled() {
|
||||
return mIsUnicastReplyEnabled || isForceEnabledForTest(NSD_UNICAST_REPLY_ENABLED);
|
||||
}
|
||||
|
||||
/**
|
||||
* The constructor for {@link MdnsFeatureFlags}.
|
||||
*/
|
||||
|
|
@ -68,12 +109,16 @@ public class MdnsFeatureFlags {
|
|||
boolean includeInetAddressRecordsInProbing,
|
||||
boolean isExpiredServicesRemovalEnabled,
|
||||
boolean isLabelCountLimitEnabled,
|
||||
boolean isKnownAnswerSuppressionEnabled) {
|
||||
boolean isKnownAnswerSuppressionEnabled,
|
||||
boolean isUnicastReplyEnabled,
|
||||
@Nullable FlagOverrideProvider overrideProvider) {
|
||||
mIsMdnsOffloadFeatureEnabled = isOffloadFeatureEnabled;
|
||||
mIncludeInetAddressRecordsInProbing = includeInetAddressRecordsInProbing;
|
||||
mIsExpiredServicesRemovalEnabled = isExpiredServicesRemovalEnabled;
|
||||
mIsLabelCountLimitEnabled = isLabelCountLimitEnabled;
|
||||
mIsKnownAnswerSuppressionEnabled = isKnownAnswerSuppressionEnabled;
|
||||
mIsUnicastReplyEnabled = isUnicastReplyEnabled;
|
||||
mOverrideProvider = overrideProvider;
|
||||
}
|
||||
|
||||
|
||||
|
|
@ -90,6 +135,8 @@ public class MdnsFeatureFlags {
|
|||
private boolean mIsExpiredServicesRemovalEnabled;
|
||||
private boolean mIsLabelCountLimitEnabled;
|
||||
private boolean mIsKnownAnswerSuppressionEnabled;
|
||||
private boolean mIsUnicastReplyEnabled;
|
||||
private FlagOverrideProvider mOverrideProvider;
|
||||
|
||||
/**
|
||||
* The constructor for {@link Builder}.
|
||||
|
|
@ -100,6 +147,8 @@ public class MdnsFeatureFlags {
|
|||
mIsExpiredServicesRemovalEnabled = false;
|
||||
mIsLabelCountLimitEnabled = true; // Default enabled.
|
||||
mIsKnownAnswerSuppressionEnabled = false;
|
||||
mIsUnicastReplyEnabled = true;
|
||||
mOverrideProvider = null;
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -153,6 +202,27 @@ public class MdnsFeatureFlags {
|
|||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set whether the unicast reply feature is enabled.
|
||||
*
|
||||
* @see #NSD_UNICAST_REPLY_ENABLED
|
||||
*/
|
||||
public Builder setIsUnicastReplyEnabled(boolean isUnicastReplyEnabled) {
|
||||
mIsUnicastReplyEnabled = isUnicastReplyEnabled;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set a {@link FlagOverrideProvider} to be used by {@link #isForceEnabledForTest(String)}.
|
||||
*
|
||||
* If non-null, features that use {@link #isForceEnabledForTest(String)} will use that
|
||||
* provider to query whether the flag should be force-enabled.
|
||||
*/
|
||||
public Builder setOverrideProvider(@Nullable FlagOverrideProvider overrideProvider) {
|
||||
mOverrideProvider = overrideProvider;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Builds a {@link MdnsFeatureFlags} with the arguments supplied to this builder.
|
||||
*/
|
||||
|
|
@ -161,7 +231,9 @@ public class MdnsFeatureFlags {
|
|||
mIncludeInetAddressRecordsInProbing,
|
||||
mIsExpiredServicesRemovalEnabled,
|
||||
mIsLabelCountLimitEnabled,
|
||||
mIsKnownAnswerSuppressionEnabled);
|
||||
mIsKnownAnswerSuppressionEnabled,
|
||||
mIsUnicastReplyEnabled,
|
||||
mOverrideProvider);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -175,6 +175,16 @@ public abstract class MdnsRecord {
|
|||
return (cls & FLAG_CACHE_FLUSH) != 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* For questions, returns whether a unicast reply was requested.
|
||||
*
|
||||
* In practice this is identical to {@link #getCacheFlush()}, as the "cache flush" flag in
|
||||
* replies is the same as "unicast reply requested" in questions.
|
||||
*/
|
||||
public final boolean isUnicastReplyRequested() {
|
||||
return (cls & MdnsConstants.QCLASS_UNICAST) != 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the record's remaining TTL.
|
||||
*
|
||||
|
|
|
|||
|
|
@ -499,26 +499,30 @@ public class MdnsRecordRepository {
|
|||
@Nullable
|
||||
public MdnsReplyInfo getReply(MdnsPacket packet, InetSocketAddress src) {
|
||||
final long now = SystemClock.elapsedRealtime();
|
||||
final boolean replyUnicast = (packet.flags & MdnsConstants.QCLASS_UNICAST) != 0;
|
||||
|
||||
// Use LinkedHashSet for preserving the insert order of the RRs, so that RRs of the same
|
||||
// service or host are grouped together (which is more developer-friendly).
|
||||
final Set<RecordInfo<?>> answerInfo = new LinkedHashSet<>();
|
||||
final Set<RecordInfo<?>> additionalAnswerInfo = new LinkedHashSet<>();
|
||||
|
||||
// Reply unicast if the feature is enabled AND all replied questions request unicast
|
||||
final boolean replyUnicastEnabled = mMdnsFeatureFlags.isUnicastReplyEnabled();
|
||||
boolean replyUnicast = replyUnicastEnabled;
|
||||
for (MdnsRecord question : packet.questions) {
|
||||
// Add answers from general records
|
||||
addReplyFromService(question, mGeneralRecords, null /* servicePtrRecord */,
|
||||
null /* serviceSrvRecord */, null /* serviceTxtRecord */, replyUnicast, now,
|
||||
answerInfo, additionalAnswerInfo, Collections.emptyList());
|
||||
if (addReplyFromService(question, mGeneralRecords, null /* servicePtrRecord */,
|
||||
null /* serviceSrvRecord */, null /* serviceTxtRecord */, replyUnicastEnabled,
|
||||
now, answerInfo, additionalAnswerInfo, Collections.emptyList())) {
|
||||
replyUnicast &= question.isUnicastReplyRequested();
|
||||
}
|
||||
|
||||
// Add answers from each service
|
||||
for (int i = 0; i < mServices.size(); i++) {
|
||||
final ServiceRegistration registration = mServices.valueAt(i);
|
||||
if (registration.exiting || registration.isProbing) continue;
|
||||
if (addReplyFromService(question, registration.allRecords, registration.ptrRecords,
|
||||
registration.srvRecord, registration.txtRecord, replyUnicast, now,
|
||||
registration.srvRecord, registration.txtRecord, replyUnicastEnabled, now,
|
||||
answerInfo, additionalAnswerInfo, packet.answers)) {
|
||||
replyUnicast &= question.isUnicastReplyRequested();
|
||||
registration.repliedServiceCount++;
|
||||
registration.sentPacketCount++;
|
||||
}
|
||||
|
|
@ -570,6 +574,12 @@ public class MdnsRecordRepository {
|
|||
// Determine the send destination
|
||||
final InetSocketAddress dest;
|
||||
if (replyUnicast) {
|
||||
// As per RFC6762 5.4, "if the responder has not multicast that record recently (within
|
||||
// one quarter of its TTL), then the responder SHOULD instead multicast the response so
|
||||
// as to keep all the peer caches up to date": this SHOULD is not implemented to
|
||||
// minimize latency for queriers who have just started, so they did not receive previous
|
||||
// multicast responses. Unicast replies are faster as they do not need to wait for the
|
||||
// beacon interval on Wi-Fi.
|
||||
dest = src;
|
||||
} else if (src.getAddress() instanceof Inet4Address) {
|
||||
dest = IPV4_SOCKET_ADDR;
|
||||
|
|
@ -608,7 +618,7 @@ public class MdnsRecordRepository {
|
|||
@Nullable List<RecordInfo<MdnsPointerRecord>> servicePtrRecords,
|
||||
@Nullable RecordInfo<MdnsServiceRecord> serviceSrvRecord,
|
||||
@Nullable RecordInfo<MdnsTextRecord> serviceTxtRecord,
|
||||
boolean replyUnicast, long now, @NonNull Set<RecordInfo<?>> answerInfo,
|
||||
boolean replyUnicastEnabled, long now, @NonNull Set<RecordInfo<?>> answerInfo,
|
||||
@NonNull Set<RecordInfo<?>> additionalAnswerInfo,
|
||||
@NonNull List<MdnsRecord> knownAnswerRecords) {
|
||||
boolean hasDnsSdPtrRecordAnswer = false;
|
||||
|
|
@ -659,7 +669,8 @@ public class MdnsRecordRepository {
|
|||
|
||||
// TODO: responses to probe queries should bypass this check and only ensure the
|
||||
// reply is sent 250ms after the last sent time (RFC 6762 p.15)
|
||||
if (!replyUnicast && info.lastAdvertisedTimeMs > 0L
|
||||
if (!(replyUnicastEnabled && question.isUnicastReplyRequested())
|
||||
&& info.lastAdvertisedTimeMs > 0L
|
||||
&& now - info.lastAdvertisedTimeMs < MIN_MULTICAST_REPLY_INTERVAL_MS) {
|
||||
continue;
|
||||
}
|
||||
|
|
|
|||
86
staticlibs/testutils/devicetests/NSResponder.kt
Normal file
86
staticlibs/testutils/devicetests/NSResponder.kt
Normal file
|
|
@ -0,0 +1,86 @@
|
|||
/*
|
||||
* Copyright (C) 2023 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.testutils
|
||||
|
||||
import android.net.MacAddress
|
||||
import android.util.Log
|
||||
import com.android.net.module.util.Ipv6Utils
|
||||
import com.android.net.module.util.NetworkStackConstants.ETHER_HEADER_LEN
|
||||
import com.android.net.module.util.NetworkStackConstants.ICMPV6_ND_OPTION_TLLA
|
||||
import com.android.net.module.util.NetworkStackConstants.NEIGHBOR_ADVERTISEMENT_FLAG_SOLICITED
|
||||
import com.android.net.module.util.Struct
|
||||
import com.android.net.module.util.structs.Icmpv6Header
|
||||
import com.android.net.module.util.structs.Ipv6Header
|
||||
import com.android.net.module.util.structs.LlaOption
|
||||
import com.android.net.module.util.structs.NsHeader
|
||||
import com.android.testutils.PacketReflector.IPV6_HEADER_LENGTH
|
||||
import java.lang.IllegalArgumentException
|
||||
import java.net.Inet6Address
|
||||
import java.nio.ByteBuffer
|
||||
|
||||
private const val NS_TYPE = 135.toShort()
|
||||
|
||||
/**
|
||||
* A class that can be used to reply to Neighbor Solicitation packets on a [TapPacketReader].
|
||||
*/
|
||||
class NSResponder(
|
||||
reader: TapPacketReader,
|
||||
table: Map<Inet6Address, MacAddress>,
|
||||
name: String = NSResponder::class.java.simpleName
|
||||
) : PacketResponder(reader, Icmpv6Filter(), name) {
|
||||
companion object {
|
||||
private val TAG = NSResponder::class.simpleName
|
||||
}
|
||||
|
||||
// Copy the map if not already immutable (toMap) to make sure it is not modified
|
||||
private val table = table.toMap()
|
||||
|
||||
override fun replyToPacket(packet: ByteArray, reader: TapPacketReader) {
|
||||
if (packet.size < IPV6_HEADER_LENGTH) {
|
||||
return
|
||||
}
|
||||
val buf = ByteBuffer.wrap(packet, ETHER_HEADER_LEN, packet.size - ETHER_HEADER_LEN)
|
||||
val ipv6Header = parseOrLog(Ipv6Header::class.java, buf) ?: return
|
||||
val icmpHeader = parseOrLog(Icmpv6Header::class.java, buf) ?: return
|
||||
if (icmpHeader.type != NS_TYPE) {
|
||||
return
|
||||
}
|
||||
val ns = parseOrLog(NsHeader::class.java, buf) ?: return
|
||||
val replyMacAddr = table[ns.target] ?: return
|
||||
val slla = parseOrLog(LlaOption::class.java, buf) ?: return
|
||||
val requesterMac = slla.linkLayerAddress
|
||||
|
||||
val tlla = LlaOption.build(ICMPV6_ND_OPTION_TLLA.toByte(), replyMacAddr)
|
||||
reader.sendResponse(Ipv6Utils.buildNaPacket(
|
||||
replyMacAddr /* srcMac */,
|
||||
requesterMac /* dstMac */,
|
||||
ns.target /* srcIp */,
|
||||
ipv6Header.srcIp /* dstIp */,
|
||||
NEIGHBOR_ADVERTISEMENT_FLAG_SOLICITED,
|
||||
ns.target,
|
||||
tlla))
|
||||
}
|
||||
|
||||
private fun <T> parseOrLog(clazz: Class<T>, buf: ByteBuffer): T? where T : Struct {
|
||||
return try {
|
||||
Struct.parse(clazz, buf)
|
||||
} catch (e: IllegalArgumentException) {
|
||||
Log.e(TAG, "Invalid ${clazz.simpleName} in ICMPv6 packet", e)
|
||||
null
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -22,12 +22,12 @@ import android.provider.DeviceConfig
|
|||
import android.util.Log
|
||||
import com.android.modules.utils.build.SdkLevel
|
||||
import com.android.testutils.FunctionalUtils.ThrowingRunnable
|
||||
import org.junit.rules.TestRule
|
||||
import org.junit.runner.Description
|
||||
import org.junit.runners.model.Statement
|
||||
import java.util.concurrent.CompletableFuture
|
||||
import java.util.concurrent.Executor
|
||||
import java.util.concurrent.TimeUnit
|
||||
import org.junit.rules.TestRule
|
||||
import org.junit.runner.Description
|
||||
import org.junit.runners.model.Statement
|
||||
|
||||
private val TAG = DeviceConfigRule::class.simpleName
|
||||
|
||||
|
|
@ -147,11 +147,11 @@ class DeviceConfigRule @JvmOverloads constructor(
|
|||
return tryTest {
|
||||
runAsShell(*readWritePermissions) {
|
||||
DeviceConfig.addOnPropertiesChangedListener(
|
||||
DeviceConfig.NAMESPACE_CONNECTIVITY,
|
||||
namespace,
|
||||
inlineExecutor,
|
||||
listener)
|
||||
DeviceConfig.setProperty(
|
||||
DeviceConfig.NAMESPACE_CONNECTIVITY,
|
||||
namespace,
|
||||
key,
|
||||
value,
|
||||
false /* makeDefault */)
|
||||
|
|
|
|||
|
|
@ -110,6 +110,12 @@ class ArpRequestFilter : Predicate<ByteArray> {
|
|||
override fun test(t: ByteArray) = impl.test(t)
|
||||
}
|
||||
|
||||
class Icmpv6Filter : Predicate<ByteArray> {
|
||||
private val impl = OffsetFilter(ETHER_TYPE_OFFSET, 0x86.toByte(), 0xdd.toByte() /* IPv6 */).and(
|
||||
OffsetFilter(IPV6_PROTOCOL_OFFSET, 58 /* ICMPv6 */))
|
||||
override fun test(t: ByteArray) = impl.test(t)
|
||||
}
|
||||
|
||||
/**
|
||||
* A [Predicate] that matches ethernet-encapped DHCP packets sent from a DHCP client.
|
||||
*/
|
||||
|
|
|
|||
|
|
@ -23,11 +23,15 @@ import android.os.Process
|
|||
import com.android.net.module.util.ArrayTrackRecord
|
||||
import com.android.net.module.util.DnsPacket
|
||||
import com.android.net.module.util.NetworkStackConstants.ETHER_HEADER_LEN
|
||||
import com.android.net.module.util.NetworkStackConstants.IPV6_ADDR_LEN
|
||||
import com.android.net.module.util.NetworkStackConstants.IPV6_DST_ADDR_OFFSET
|
||||
import com.android.net.module.util.NetworkStackConstants.IPV6_HEADER_LEN
|
||||
import com.android.net.module.util.NetworkStackConstants.UDP_HEADER_LEN
|
||||
import com.android.net.module.util.TrackRecord
|
||||
import com.android.testutils.IPv6UdpFilter
|
||||
import com.android.testutils.TapPacketReader
|
||||
import java.net.Inet6Address
|
||||
import java.net.InetAddress
|
||||
import kotlin.test.assertEquals
|
||||
import kotlin.test.assertNotNull
|
||||
import kotlin.test.assertNull
|
||||
|
|
@ -236,19 +240,28 @@ class NsdServiceInfoCallbackRecord : NsdManager.ServiceInfoCallback,
|
|||
private fun getMdnsPayload(packet: ByteArray) = packet.copyOfRange(
|
||||
ETHER_HEADER_LEN + IPV6_HEADER_LEN + UDP_HEADER_LEN, packet.size)
|
||||
|
||||
private fun getDstAddr(packet: ByteArray): Inet6Address {
|
||||
val v6AddrPos = ETHER_HEADER_LEN + IPV6_DST_ADDR_OFFSET
|
||||
return Inet6Address.getByAddress(packet.copyOfRange(v6AddrPos, v6AddrPos + IPV6_ADDR_LEN))
|
||||
as Inet6Address
|
||||
}
|
||||
|
||||
fun TapPacketReader.pollForMdnsPacket(
|
||||
timeoutMs: Long = MDNS_REGISTRATION_TIMEOUT_MS,
|
||||
predicate: (TestDnsPacket) -> Boolean
|
||||
): TestDnsPacket? {
|
||||
val mdnsProbeFilter = IPv6UdpFilter(srcPort = MDNS_PORT, dstPort = MDNS_PORT).and {
|
||||
val dst = getDstAddr(it)
|
||||
val mdnsPayload = getMdnsPayload(it)
|
||||
try {
|
||||
predicate(TestDnsPacket(mdnsPayload))
|
||||
predicate(TestDnsPacket(mdnsPayload, dst))
|
||||
} catch (e: DnsPacket.ParseException) {
|
||||
false
|
||||
}
|
||||
}
|
||||
return poll(timeoutMs, mdnsProbeFilter)?.let { TestDnsPacket(getMdnsPayload(it)) }
|
||||
return poll(timeoutMs, mdnsProbeFilter)?.let {
|
||||
TestDnsPacket(getMdnsPayload(it), getDstAddr(it))
|
||||
}
|
||||
}
|
||||
|
||||
fun TapPacketReader.pollForProbe(
|
||||
|
|
@ -281,7 +294,7 @@ fun TapPacketReader.pollForReply(
|
|||
it.isReplyFor("$serviceName.$serviceType.local")
|
||||
}
|
||||
|
||||
class TestDnsPacket(data: ByteArray) : DnsPacket(data) {
|
||||
class TestDnsPacket(data: ByteArray, val dstAddr: InetAddress) : DnsPacket(data) {
|
||||
val header: DnsHeader
|
||||
get() = mHeader
|
||||
val records: Array<List<DnsRecord>>
|
||||
|
|
@ -290,9 +303,10 @@ class TestDnsPacket(data: ByteArray) : DnsPacket(data) {
|
|||
it.dName == name && it.nsType == DnsResolver.TYPE_ANY
|
||||
}
|
||||
|
||||
fun isReplyFor(name: String): Boolean = mRecords[ANSECTION].any {
|
||||
it.dName == name && it.nsType == DnsResolver.TYPE_SRV
|
||||
}
|
||||
fun isReplyFor(name: String, type: Int = DnsResolver.TYPE_SRV): Boolean =
|
||||
mRecords[ANSECTION].any {
|
||||
it.dName == name && it.nsType == type
|
||||
}
|
||||
|
||||
fun isQueryFor(name: String, vararg requiredTypes: Int): Boolean = requiredTypes.all { type ->
|
||||
mRecords[QDSECTION].any {
|
||||
|
|
|
|||
|
|
@ -61,6 +61,7 @@ import android.os.Build
|
|||
import android.os.Handler
|
||||
import android.os.HandlerThread
|
||||
import android.platform.test.annotations.AppModeFull
|
||||
import android.provider.DeviceConfig.NAMESPACE_TETHERING
|
||||
import android.system.ErrnoException
|
||||
import android.system.Os
|
||||
import android.system.OsConstants.AF_INET6
|
||||
|
|
@ -69,6 +70,7 @@ import android.system.OsConstants.ENETUNREACH
|
|||
import android.system.OsConstants.ETH_P_IPV6
|
||||
import android.system.OsConstants.IPPROTO_IPV6
|
||||
import android.system.OsConstants.IPPROTO_UDP
|
||||
import android.system.OsConstants.RT_SCOPE_LINK
|
||||
import android.system.OsConstants.SOCK_DGRAM
|
||||
import android.util.Log
|
||||
import androidx.test.filters.SmallTest
|
||||
|
|
@ -78,12 +80,14 @@ import com.android.compatibility.common.util.PropertyUtil
|
|||
import com.android.modules.utils.build.SdkLevel.isAtLeastU
|
||||
import com.android.net.module.util.DnsPacket
|
||||
import com.android.net.module.util.HexDump
|
||||
import com.android.net.module.util.NetworkStackConstants.IPV6_ADDR_LEN
|
||||
import com.android.net.module.util.PacketBuilder
|
||||
import com.android.testutils.ConnectivityModuleTest
|
||||
import com.android.testutils.DevSdkIgnoreRule
|
||||
import com.android.testutils.DevSdkIgnoreRule.IgnoreAfter
|
||||
import com.android.testutils.DevSdkIgnoreRule.IgnoreUpTo
|
||||
import com.android.testutils.DevSdkIgnoreRunner
|
||||
import com.android.testutils.DeviceConfigRule
|
||||
import com.android.testutils.NSResponder
|
||||
import com.android.testutils.RecorderCallback.CallbackEntry.CapabilitiesChanged
|
||||
import com.android.testutils.RecorderCallback.CallbackEntry.LinkPropertiesChanged
|
||||
import com.android.testutils.TapPacketReader
|
||||
|
|
@ -133,6 +137,7 @@ private const val DBG = false
|
|||
private const val TEST_PORT = 12345
|
||||
private const val MDNS_PORT = 5353.toShort()
|
||||
private val multicastIpv6Addr = parseNumericAddress("ff02::fb") as Inet6Address
|
||||
private val testSrcAddr = parseNumericAddress("2001:db8::123") as Inet6Address
|
||||
|
||||
@AppModeFull(reason = "Socket cannot bind in instant app mode")
|
||||
@RunWith(DevSdkIgnoreRunner::class)
|
||||
|
|
@ -144,6 +149,9 @@ class NsdManagerTest {
|
|||
@get:Rule
|
||||
val ignoreRule = DevSdkIgnoreRule()
|
||||
|
||||
@get:Rule
|
||||
val deviceConfigRule = DeviceConfigRule()
|
||||
|
||||
private val context by lazy { InstrumentationRegistry.getInstrumentation().context }
|
||||
private val nsdManager by lazy {
|
||||
context.getSystemService(NsdManager::class.java) ?: fail("Could not get NsdManager service")
|
||||
|
|
@ -682,7 +690,7 @@ class NsdManagerTest {
|
|||
assertEquals(OffloadEngine.OFFLOAD_TYPE_REPLY.toLong(), serviceInfo.offloadType)
|
||||
val offloadPayload = serviceInfo.offloadPayload
|
||||
assertNotNull(offloadPayload)
|
||||
val dnsPacket = TestDnsPacket(offloadPayload)
|
||||
val dnsPacket = TestDnsPacket(offloadPayload, dstAddr = multicastIpv6Addr)
|
||||
assertEquals(0x8400, dnsPacket.header.flags)
|
||||
assertEquals(0, dnsPacket.records[DnsPacket.QDSECTION].size)
|
||||
assertTrue(dnsPacket.records[DnsPacket.ANSECTION].size >= 5)
|
||||
|
|
@ -1286,7 +1294,8 @@ class NsdManagerTest {
|
|||
// Resolve service on testNetwork1
|
||||
val resolveRecord = NsdResolveRecord()
|
||||
val packetReader = TapPacketReader(Handler(handlerThread.looper),
|
||||
testNetwork1.iface.fileDescriptor.fileDescriptor, 1500 /* maxPacketSize */)
|
||||
testNetwork1.iface.fileDescriptor.fileDescriptor, 1500 /* maxPacketSize */
|
||||
)
|
||||
packetReader.startAsyncForTest()
|
||||
handlerThread.waitForIdle(TIMEOUT_MS)
|
||||
|
||||
|
|
@ -1349,6 +1358,68 @@ class NsdManagerTest {
|
|||
serviceResolved.serviceInfo.hostAddresses.toSet())
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testUnicastReplyUsedWhenQueryUnicastFlagSet() {
|
||||
// The flag may be removed in the future but unicast replies should be enabled by default
|
||||
// in that case. The rule will reset flags automatically on teardown.
|
||||
deviceConfigRule.setConfig(NAMESPACE_TETHERING, "test_nsd_unicast_reply_enabled", "1")
|
||||
|
||||
val si = makeTestServiceInfo(testNetwork1.network)
|
||||
|
||||
// Register service on testNetwork1
|
||||
val registrationRecord = NsdRegistrationRecord()
|
||||
var nsResponder: NSResponder? = null
|
||||
tryTest {
|
||||
registerService(registrationRecord, si)
|
||||
val packetReader = TapPacketReader(Handler(handlerThread.looper),
|
||||
testNetwork1.iface.fileDescriptor.fileDescriptor, 1500 /* maxPacketSize */)
|
||||
packetReader.startAsyncForTest()
|
||||
|
||||
handlerThread.waitForIdle(TIMEOUT_MS)
|
||||
/*
|
||||
Send a "query unicast" query.
|
||||
Generated with:
|
||||
scapy.raw(scapy.DNS(rd=0, qr=0, aa=0, qd =
|
||||
scapy.DNSQR(qname='_nmt123456789._tcp.local', qtype='PTR', qclass=0x8001)
|
||||
)).hex()
|
||||
*/
|
||||
val mdnsPayload = HexDump.hexStringToByteArray("0000000000010000000000000d5f6e6d74313" +
|
||||
"233343536373839045f746370056c6f63616c00000c8001")
|
||||
replaceServiceNameAndTypeWithTestSuffix(mdnsPayload)
|
||||
|
||||
val testSrcAddr = makeLinkLocalAddressOfOtherDeviceOnPrefix(testNetwork1.network)
|
||||
nsResponder = NSResponder(packetReader, mapOf(
|
||||
testSrcAddr to MacAddress.fromString("01:02:03:04:05:06")
|
||||
)).apply { start() }
|
||||
|
||||
packetReader.sendResponse(buildMdnsPacket(mdnsPayload, testSrcAddr))
|
||||
// The reply is sent unicast to the source address. There may be announcements sent
|
||||
// multicast around this time, so filter by destination address.
|
||||
val reply = packetReader.pollForMdnsPacket { pkt ->
|
||||
pkt.isReplyFor("$serviceType.local", DnsResolver.TYPE_PTR) &&
|
||||
pkt.dstAddr == testSrcAddr
|
||||
}
|
||||
assertNotNull(reply)
|
||||
} cleanup {
|
||||
nsResponder?.stop()
|
||||
nsdManager.unregisterService(registrationRecord)
|
||||
registrationRecord.expectCallback<ServiceUnregistered>()
|
||||
}
|
||||
}
|
||||
|
||||
private fun makeLinkLocalAddressOfOtherDeviceOnPrefix(network: Network): Inet6Address {
|
||||
val lp = cm.getLinkProperties(network) ?: fail("No LinkProperties for net $network")
|
||||
// Expect to have a /64 link-local address
|
||||
val linkAddr = lp.linkAddresses.firstOrNull {
|
||||
it.isIPv6 && it.scope == RT_SCOPE_LINK && it.prefixLength == 64
|
||||
} ?: fail("No /64 link-local address found in ${lp.linkAddresses} for net $network")
|
||||
|
||||
// Add one to the device address to simulate the address of another device on the prefix
|
||||
val addrBytes = linkAddr.address.address
|
||||
addrBytes[IPV6_ADDR_LEN - 1]++
|
||||
return Inet6Address.getByAddress(addrBytes) as Inet6Address
|
||||
}
|
||||
|
||||
private fun buildConflictingAnnouncement(): ByteBuffer {
|
||||
/*
|
||||
Generated with:
|
||||
|
|
@ -1393,7 +1464,10 @@ class NsdManagerTest {
|
|||
replaceAll(buffer, source, replacement)
|
||||
}
|
||||
|
||||
private fun buildMdnsPacket(mdnsPayload: ByteArray): ByteBuffer {
|
||||
private fun buildMdnsPacket(
|
||||
mdnsPayload: ByteArray,
|
||||
srcAddr: Inet6Address = testSrcAddr
|
||||
): ByteBuffer {
|
||||
val packetBuffer = PacketBuilder.allocate(true /* hasEther */, IPPROTO_IPV6,
|
||||
IPPROTO_UDP, mdnsPayload.size)
|
||||
val packetBuilder = PacketBuilder(packetBuffer)
|
||||
|
|
@ -1408,7 +1482,7 @@ class NsdManagerTest {
|
|||
0x60000000, // version=6, traffic class=0x0, flowlabel=0x0
|
||||
IPPROTO_UDP.toByte(),
|
||||
64 /* hop limit */,
|
||||
parseNumericAddress("2001:db8::123") as Inet6Address /* srcIp */,
|
||||
srcAddr,
|
||||
multicastIpv6Addr /* dstIp */)
|
||||
packetBuilder.writeUdpHeader(MDNS_PORT /* srcPort */, MDNS_PORT /* dstPort */)
|
||||
packetBuffer.put(mdnsPayload)
|
||||
|
|
|
|||
|
|
@ -24,7 +24,6 @@ import android.os.HandlerThread
|
|||
import com.android.server.connectivity.mdns.MdnsAnnouncer.AnnouncementInfo
|
||||
import com.android.server.connectivity.mdns.MdnsRecord.TYPE_A
|
||||
import com.android.server.connectivity.mdns.MdnsRecord.TYPE_AAAA
|
||||
import com.android.server.connectivity.mdns.MdnsRecord.TYPE_ANY
|
||||
import com.android.server.connectivity.mdns.MdnsRecord.TYPE_PTR
|
||||
import com.android.server.connectivity.mdns.MdnsRecord.TYPE_SRV
|
||||
import com.android.server.connectivity.mdns.MdnsRecord.TYPE_TXT
|
||||
|
|
@ -95,7 +94,6 @@ class MdnsRecordRepositoryTest {
|
|||
override fun getInterfaceInetAddresses(iface: NetworkInterface) =
|
||||
Collections.enumeration(TEST_ADDRESSES.map { it.address })
|
||||
}
|
||||
private val flags = MdnsFeatureFlags.newBuilder().build()
|
||||
|
||||
@Before
|
||||
fun setUp() {
|
||||
|
|
@ -108,9 +106,19 @@ class MdnsRecordRepositoryTest {
|
|||
thread.join()
|
||||
}
|
||||
|
||||
private fun makeFlags(
|
||||
includeInetAddressesInProbing: Boolean = false,
|
||||
isKnownAnswerSuppressionEnabled: Boolean = false,
|
||||
unicastReplyEnabled: Boolean = true
|
||||
) = MdnsFeatureFlags.Builder()
|
||||
.setIncludeInetAddressRecordsInProbing(includeInetAddressesInProbing)
|
||||
.setIsKnownAnswerSuppressionEnabled(isKnownAnswerSuppressionEnabled)
|
||||
.setIsUnicastReplyEnabled(unicastReplyEnabled)
|
||||
.build()
|
||||
|
||||
@Test
|
||||
fun testAddServiceAndProbe() {
|
||||
val repository = MdnsRecordRepository(thread.looper, deps, TEST_HOSTNAME, flags)
|
||||
val repository = MdnsRecordRepository(thread.looper, deps, TEST_HOSTNAME, makeFlags())
|
||||
assertEquals(0, repository.servicesCount)
|
||||
assertEquals(-1, repository.addService(TEST_SERVICE_ID_1, TEST_SERVICE_1))
|
||||
assertEquals(1, repository.servicesCount)
|
||||
|
|
@ -144,7 +152,7 @@ class MdnsRecordRepositoryTest {
|
|||
|
||||
@Test
|
||||
fun testAddAndConflicts() {
|
||||
val repository = MdnsRecordRepository(thread.looper, deps, TEST_HOSTNAME, flags)
|
||||
val repository = MdnsRecordRepository(thread.looper, deps, TEST_HOSTNAME, makeFlags())
|
||||
repository.initWithService(TEST_SERVICE_ID_1, TEST_SERVICE_1)
|
||||
assertFailsWith(NameConflictException::class) {
|
||||
repository.addService(TEST_SERVICE_ID_2, TEST_SERVICE_1)
|
||||
|
|
@ -156,7 +164,7 @@ class MdnsRecordRepositoryTest {
|
|||
|
||||
@Test
|
||||
fun testAddAndUpdates() {
|
||||
val repository = MdnsRecordRepository(thread.looper, deps, TEST_HOSTNAME, flags)
|
||||
val repository = MdnsRecordRepository(thread.looper, deps, TEST_HOSTNAME, makeFlags())
|
||||
repository.initWithService(TEST_SERVICE_ID_1, TEST_SERVICE_1)
|
||||
|
||||
assertFailsWith(IllegalArgumentException::class) {
|
||||
|
|
@ -190,7 +198,7 @@ class MdnsRecordRepositoryTest {
|
|||
|
||||
@Test
|
||||
fun testInvalidReuseOfServiceId() {
|
||||
val repository = MdnsRecordRepository(thread.looper, deps, TEST_HOSTNAME, flags)
|
||||
val repository = MdnsRecordRepository(thread.looper, deps, TEST_HOSTNAME, makeFlags())
|
||||
repository.addService(TEST_SERVICE_ID_1, TEST_SERVICE_1)
|
||||
assertFailsWith(IllegalArgumentException::class) {
|
||||
repository.addService(TEST_SERVICE_ID_1, TEST_SERVICE_2)
|
||||
|
|
@ -199,7 +207,7 @@ class MdnsRecordRepositoryTest {
|
|||
|
||||
@Test
|
||||
fun testHasActiveService() {
|
||||
val repository = MdnsRecordRepository(thread.looper, deps, TEST_HOSTNAME, flags)
|
||||
val repository = MdnsRecordRepository(thread.looper, deps, TEST_HOSTNAME, makeFlags())
|
||||
assertFalse(repository.hasActiveService(TEST_SERVICE_ID_1))
|
||||
|
||||
repository.addService(TEST_SERVICE_ID_1, TEST_SERVICE_1)
|
||||
|
|
@ -216,7 +224,7 @@ class MdnsRecordRepositoryTest {
|
|||
|
||||
@Test
|
||||
fun testExitAnnouncements() {
|
||||
val repository = MdnsRecordRepository(thread.looper, deps, TEST_HOSTNAME, flags)
|
||||
val repository = MdnsRecordRepository(thread.looper, deps, TEST_HOSTNAME, makeFlags())
|
||||
repository.initWithService(TEST_SERVICE_ID_1, TEST_SERVICE_1)
|
||||
repository.onAdvertisementSent(TEST_SERVICE_ID_1, 2 /* sentPacketCount */)
|
||||
|
||||
|
|
@ -246,7 +254,7 @@ class MdnsRecordRepositoryTest {
|
|||
|
||||
@Test
|
||||
fun testExitAnnouncements_WithSubtypes() {
|
||||
val repository = MdnsRecordRepository(thread.looper, deps, TEST_HOSTNAME, flags)
|
||||
val repository = MdnsRecordRepository(thread.looper, deps, TEST_HOSTNAME, makeFlags())
|
||||
repository.initWithService(TEST_SERVICE_ID_1, TEST_SERVICE_1,
|
||||
setOf(TEST_SUBTYPE, TEST_SUBTYPE2))
|
||||
repository.onAdvertisementSent(TEST_SERVICE_ID_1, 2 /* sentPacketCount */)
|
||||
|
|
@ -288,7 +296,7 @@ class MdnsRecordRepositoryTest {
|
|||
|
||||
@Test
|
||||
fun testExitingServiceReAdded() {
|
||||
val repository = MdnsRecordRepository(thread.looper, deps, TEST_HOSTNAME, flags)
|
||||
val repository = MdnsRecordRepository(thread.looper, deps, TEST_HOSTNAME, makeFlags())
|
||||
repository.initWithService(TEST_SERVICE_ID_1, TEST_SERVICE_1)
|
||||
repository.onAdvertisementSent(TEST_SERVICE_ID_1, 2 /* sentPacketCount */)
|
||||
repository.exitService(TEST_SERVICE_ID_1)
|
||||
|
|
@ -303,7 +311,7 @@ class MdnsRecordRepositoryTest {
|
|||
|
||||
@Test
|
||||
fun testOnProbingSucceeded() {
|
||||
val repository = MdnsRecordRepository(thread.looper, deps, TEST_HOSTNAME, flags)
|
||||
val repository = MdnsRecordRepository(thread.looper, deps, TEST_HOSTNAME, makeFlags())
|
||||
val announcementInfo = repository.initWithService(TEST_SERVICE_ID_1, TEST_SERVICE_1,
|
||||
setOf(TEST_SUBTYPE, TEST_SUBTYPE2))
|
||||
repository.onAdvertisementSent(TEST_SERVICE_ID_1, 2 /* sentPacketCount */)
|
||||
|
|
@ -435,7 +443,7 @@ class MdnsRecordRepositoryTest {
|
|||
|
||||
@Test
|
||||
fun testGetOffloadPacket() {
|
||||
val repository = MdnsRecordRepository(thread.looper, deps, TEST_HOSTNAME, flags)
|
||||
val repository = MdnsRecordRepository(thread.looper, deps, TEST_HOSTNAME, makeFlags())
|
||||
repository.initWithService(TEST_SERVICE_ID_1, TEST_SERVICE_1)
|
||||
val serviceName = arrayOf("MyTestService", "_testservice", "_tcp", "local")
|
||||
val serviceType = arrayOf("_testservice", "_tcp", "local")
|
||||
|
|
@ -497,7 +505,7 @@ class MdnsRecordRepositoryTest {
|
|||
|
||||
@Test
|
||||
fun testGetReplyCaseInsensitive() {
|
||||
val repository = MdnsRecordRepository(thread.looper, deps, TEST_HOSTNAME, flags)
|
||||
val repository = MdnsRecordRepository(thread.looper, deps, TEST_HOSTNAME, makeFlags())
|
||||
repository.initWithService(TEST_SERVICE_ID_1, TEST_SERVICE_1)
|
||||
val questionsCaseInSensitive = listOf(
|
||||
MdnsPointerRecord(arrayOf("_TESTSERVICE", "_TCP", "local"), false /* isUnicast */))
|
||||
|
|
@ -517,7 +525,7 @@ class MdnsRecordRepositoryTest {
|
|||
private fun makeQuery(vararg queries: Pair<Int, Array<String>>): MdnsPacket {
|
||||
val questions = queries.map { (type, name) -> makeQuestionRecord(name, type) }
|
||||
return MdnsPacket(0 /* flags */, questions, listOf() /* answers */,
|
||||
listOf() /* authorityRecords */, listOf() /* additionalRecords */)
|
||||
listOf() /* authorityRecords */, listOf() /* additionalRecords */)
|
||||
}
|
||||
|
||||
private fun makeQuestionRecord(name: Array<String>, type: Int): MdnsRecord {
|
||||
|
|
@ -532,7 +540,7 @@ class MdnsRecordRepositoryTest {
|
|||
|
||||
@Test
|
||||
fun testGetReply_singlePtrQuestion_returnsSrvTxtAddressNsecRecords() {
|
||||
val repository = MdnsRecordRepository(thread.looper, deps, TEST_HOSTNAME, flags)
|
||||
val repository = MdnsRecordRepository(thread.looper, deps, TEST_HOSTNAME, makeFlags())
|
||||
repository.initWithService(TEST_SERVICE_ID_1, TEST_SERVICE_1, setOf(TEST_SUBTYPE))
|
||||
val src = InetSocketAddress(parseNumericAddress("192.0.2.123"), 5353)
|
||||
val serviceName = arrayOf("MyTestService", "_testservice", "_tcp", "local")
|
||||
|
|
@ -563,7 +571,7 @@ class MdnsRecordRepositoryTest {
|
|||
|
||||
@Test
|
||||
fun testGetReply_singleSubtypePtrQuestion_returnsSrvTxtAddressNsecRecords() {
|
||||
val repository = MdnsRecordRepository(thread.looper, deps, TEST_HOSTNAME, flags)
|
||||
val repository = MdnsRecordRepository(thread.looper, deps, TEST_HOSTNAME, makeFlags())
|
||||
repository.initWithService(TEST_SERVICE_ID_1, TEST_SERVICE_1, setOf(TEST_SUBTYPE))
|
||||
val src = InetSocketAddress(parseNumericAddress("192.0.2.123"), 5353)
|
||||
val serviceName = arrayOf("MyTestService", "_testservice", "_tcp", "local")
|
||||
|
|
@ -596,7 +604,7 @@ class MdnsRecordRepositoryTest {
|
|||
|
||||
@Test
|
||||
fun testGetReply_duplicatePtrQuestions_doesNotReturnDuplicateRecords() {
|
||||
val repository = MdnsRecordRepository(thread.looper, deps, TEST_HOSTNAME, flags)
|
||||
val repository = MdnsRecordRepository(thread.looper, deps, TEST_HOSTNAME, makeFlags())
|
||||
repository.initWithService(TEST_SERVICE_ID_1, TEST_SERVICE_1, setOf(TEST_SUBTYPE))
|
||||
val src = InetSocketAddress(parseNumericAddress("192.0.2.123"), 5353)
|
||||
val serviceName = arrayOf("MyTestService", "_testservice", "_tcp", "local")
|
||||
|
|
@ -629,7 +637,7 @@ class MdnsRecordRepositoryTest {
|
|||
|
||||
@Test
|
||||
fun testGetReply_multiplePtrQuestionsWithSubtype_doesNotReturnDuplicateRecords() {
|
||||
val repository = MdnsRecordRepository(thread.looper, deps, TEST_HOSTNAME, flags)
|
||||
val repository = MdnsRecordRepository(thread.looper, deps, TEST_HOSTNAME, makeFlags())
|
||||
repository.initWithService(TEST_SERVICE_ID_1, TEST_SERVICE_1, setOf(TEST_SUBTYPE))
|
||||
val src = InetSocketAddress(parseNumericAddress("192.0.2.123"), 5353)
|
||||
val serviceName = arrayOf("MyTestService", "_testservice", "_tcp", "local")
|
||||
|
|
@ -665,7 +673,7 @@ class MdnsRecordRepositoryTest {
|
|||
|
||||
@Test
|
||||
fun testGetReply_txtQuestion_returnsNoNsecRecord() {
|
||||
val repository = MdnsRecordRepository(thread.looper, deps, TEST_HOSTNAME, flags)
|
||||
val repository = MdnsRecordRepository(thread.looper, deps, TEST_HOSTNAME, makeFlags())
|
||||
repository.initWithService(TEST_SERVICE_ID_1, TEST_SERVICE_1, setOf(TEST_SUBTYPE))
|
||||
val src = InetSocketAddress(parseNumericAddress("192.0.2.123"), 5353)
|
||||
val serviceName = arrayOf("MyTestService", "_testservice", "_tcp", "local")
|
||||
|
|
@ -682,7 +690,7 @@ class MdnsRecordRepositoryTest {
|
|||
|
||||
@Test
|
||||
fun testGetReply_AAAAQuestionButNoIpv6Address_returnsNsecRecord() {
|
||||
val repository = MdnsRecordRepository(thread.looper, deps, TEST_HOSTNAME, flags)
|
||||
val repository = MdnsRecordRepository(thread.looper, deps, TEST_HOSTNAME, makeFlags())
|
||||
repository.initWithService(
|
||||
TEST_SERVICE_ID_1, TEST_SERVICE_1, setOf(TEST_SUBTYPE),
|
||||
listOf(LinkAddress(parseNumericAddress("192.0.2.111"), 24)))
|
||||
|
|
@ -701,7 +709,7 @@ class MdnsRecordRepositoryTest {
|
|||
|
||||
@Test
|
||||
fun testGetReply_ptrAndSrvQuestions_doesNotReturnSrvRecordInAdditionalAnswerSection() {
|
||||
val repository = MdnsRecordRepository(thread.looper, deps, TEST_HOSTNAME, flags)
|
||||
val repository = MdnsRecordRepository(thread.looper, deps, TEST_HOSTNAME, makeFlags())
|
||||
repository.initWithService(TEST_SERVICE_ID_1, TEST_SERVICE_1, setOf(TEST_SUBTYPE))
|
||||
val src = InetSocketAddress(parseNumericAddress("192.0.2.123"), 5353)
|
||||
val serviceName = arrayOf("MyTestService", "_testservice", "_tcp", "local")
|
||||
|
|
@ -723,7 +731,7 @@ class MdnsRecordRepositoryTest {
|
|||
|
||||
@Test
|
||||
fun testGetReply_srvTxtAddressQuestions_returnsAllRecordsInAnswerSectionExceptNsec() {
|
||||
val repository = MdnsRecordRepository(thread.looper, deps, TEST_HOSTNAME, flags)
|
||||
val repository = MdnsRecordRepository(thread.looper, deps, TEST_HOSTNAME, makeFlags())
|
||||
repository.initWithService(TEST_SERVICE_ID_1, TEST_SERVICE_1, setOf(TEST_SUBTYPE))
|
||||
val src = InetSocketAddress(parseNumericAddress("192.0.2.123"), 5353)
|
||||
val serviceName = arrayOf("MyTestService", "_testservice", "_tcp", "local")
|
||||
|
|
@ -757,7 +765,7 @@ class MdnsRecordRepositoryTest {
|
|||
|
||||
@Test
|
||||
fun testGetReply_queryWithIpv4Address_replyWithIpv4Address() {
|
||||
val repository = MdnsRecordRepository(thread.looper, deps, TEST_HOSTNAME, flags)
|
||||
val repository = MdnsRecordRepository(thread.looper, deps, TEST_HOSTNAME, makeFlags())
|
||||
repository.initWithService(TEST_SERVICE_ID_1, TEST_SERVICE_1, setOf(TEST_SUBTYPE))
|
||||
val query = makeQuery(TYPE_PTR to arrayOf("_testservice", "_tcp", "local"))
|
||||
|
||||
|
|
@ -771,7 +779,7 @@ class MdnsRecordRepositoryTest {
|
|||
|
||||
@Test
|
||||
fun testGetReply_queryWithIpv6Address_replyWithIpv6Address() {
|
||||
val repository = MdnsRecordRepository(thread.looper, deps, TEST_HOSTNAME, flags)
|
||||
val repository = MdnsRecordRepository(thread.looper, deps, TEST_HOSTNAME, makeFlags())
|
||||
repository.initWithService(TEST_SERVICE_ID_1, TEST_SERVICE_1, setOf(TEST_SUBTYPE))
|
||||
val query = makeQuery(TYPE_PTR to arrayOf("_testservice", "_tcp", "local"))
|
||||
|
||||
|
|
@ -785,7 +793,7 @@ class MdnsRecordRepositoryTest {
|
|||
|
||||
@Test
|
||||
fun testGetConflictingServices() {
|
||||
val repository = MdnsRecordRepository(thread.looper, deps, TEST_HOSTNAME, flags)
|
||||
val repository = MdnsRecordRepository(thread.looper, deps, TEST_HOSTNAME, makeFlags())
|
||||
repository.addService(TEST_SERVICE_ID_1, TEST_SERVICE_1)
|
||||
repository.addService(TEST_SERVICE_ID_2, TEST_SERVICE_2)
|
||||
|
||||
|
|
@ -813,7 +821,7 @@ class MdnsRecordRepositoryTest {
|
|||
|
||||
@Test
|
||||
fun testGetConflictingServicesCaseInsensitive() {
|
||||
val repository = MdnsRecordRepository(thread.looper, deps, TEST_HOSTNAME, flags)
|
||||
val repository = MdnsRecordRepository(thread.looper, deps, TEST_HOSTNAME, makeFlags())
|
||||
repository.addService(TEST_SERVICE_ID_1, TEST_SERVICE_1)
|
||||
repository.addService(TEST_SERVICE_ID_2, TEST_SERVICE_2)
|
||||
|
||||
|
|
@ -841,7 +849,7 @@ class MdnsRecordRepositoryTest {
|
|||
|
||||
@Test
|
||||
fun testGetConflictingServices_IdenticalService() {
|
||||
val repository = MdnsRecordRepository(thread.looper, deps, TEST_HOSTNAME, flags)
|
||||
val repository = MdnsRecordRepository(thread.looper, deps, TEST_HOSTNAME, makeFlags())
|
||||
repository.addService(TEST_SERVICE_ID_1, TEST_SERVICE_1)
|
||||
repository.addService(TEST_SERVICE_ID_2, TEST_SERVICE_2)
|
||||
|
||||
|
|
@ -870,7 +878,7 @@ class MdnsRecordRepositoryTest {
|
|||
|
||||
@Test
|
||||
fun testGetConflictingServicesCaseInsensitive_IdenticalService() {
|
||||
val repository = MdnsRecordRepository(thread.looper, deps, TEST_HOSTNAME, flags)
|
||||
val repository = MdnsRecordRepository(thread.looper, deps, TEST_HOSTNAME, makeFlags())
|
||||
repository.addService(TEST_SERVICE_ID_1, TEST_SERVICE_1)
|
||||
repository.addService(TEST_SERVICE_ID_2, TEST_SERVICE_2)
|
||||
|
||||
|
|
@ -899,7 +907,7 @@ class MdnsRecordRepositoryTest {
|
|||
|
||||
@Test
|
||||
fun testGetServiceRepliedRequestsCount() {
|
||||
val repository = MdnsRecordRepository(thread.looper, deps, TEST_HOSTNAME, flags)
|
||||
val repository = MdnsRecordRepository(thread.looper, deps, TEST_HOSTNAME, makeFlags())
|
||||
repository.initWithService(TEST_SERVICE_ID_1, TEST_SERVICE_1)
|
||||
// Verify that there is no packet replied.
|
||||
assertEquals(MdnsConstants.NO_PACKET,
|
||||
|
|
@ -924,7 +932,7 @@ class MdnsRecordRepositoryTest {
|
|||
@Test
|
||||
fun testIncludeInetAddressRecordsInProbing() {
|
||||
val repository = MdnsRecordRepository(thread.looper, deps, TEST_HOSTNAME,
|
||||
MdnsFeatureFlags.newBuilder().setIncludeInetAddressRecordsInProbing(true).build())
|
||||
makeFlags(includeInetAddressesInProbing = true))
|
||||
repository.updateAddresses(TEST_ADDRESSES)
|
||||
assertEquals(0, repository.servicesCount)
|
||||
assertEquals(-1, repository.addService(TEST_SERVICE_ID_1, TEST_SERVICE_1))
|
||||
|
|
@ -990,7 +998,7 @@ class MdnsRecordRepositoryTest {
|
|||
expectReply: Boolean
|
||||
) {
|
||||
val repository = MdnsRecordRepository(thread.looper, deps, TEST_HOSTNAME,
|
||||
MdnsFeatureFlags.newBuilder().setIsKnownAnswerSuppressionEnabled(true).build())
|
||||
makeFlags(isKnownAnswerSuppressionEnabled = true))
|
||||
repository.initWithService(TEST_SERVICE_ID_1, TEST_SERVICE_1)
|
||||
val query = MdnsPacket(0 /* flags */, questions, knownAnswers,
|
||||
listOf() /* authorityRecords */, listOf() /* additionalRecords */)
|
||||
|
|
@ -1222,23 +1230,109 @@ class MdnsRecordRepositoryTest {
|
|||
MdnsPointerRecord(queriedName, false /* isUnicast */),
|
||||
MdnsServiceRecord(serviceName, false /* isUnicast */))
|
||||
val knownAnswers = listOf(
|
||||
MdnsPointerRecord(
|
||||
queriedName,
|
||||
0L /* receiptTimeMillis */,
|
||||
false /* cacheFlush */,
|
||||
LONG_TTL - 1000L,
|
||||
serviceName),
|
||||
MdnsServiceRecord(
|
||||
serviceName,
|
||||
0L /* receiptTimeMillis */,
|
||||
false /* cacheFlush */,
|
||||
SHORT_TTL - 15_000L,
|
||||
0 /* servicePriority */,
|
||||
0 /* serviceWeight */,
|
||||
TEST_PORT,
|
||||
TEST_HOSTNAME))
|
||||
doGetReplyWithAnswersTest(questions, knownAnswers, listOf() /* replyAnswers */,
|
||||
listOf() /* additionalAnswers */, false /* expectReply */)
|
||||
MdnsPointerRecord(
|
||||
queriedName,
|
||||
0L /* receiptTimeMillis */,
|
||||
false /* cacheFlush */,
|
||||
LONG_TTL - 1000L,
|
||||
serviceName
|
||||
),
|
||||
MdnsServiceRecord(
|
||||
serviceName,
|
||||
0L /* receiptTimeMillis */,
|
||||
false /* cacheFlush */,
|
||||
SHORT_TTL - 15_000L,
|
||||
0 /* servicePriority */,
|
||||
0 /* serviceWeight */,
|
||||
TEST_PORT,
|
||||
TEST_HOSTNAME
|
||||
)
|
||||
)
|
||||
doGetReplyWithAnswersTest(
|
||||
questions, knownAnswers, listOf() /* replyAnswers */,
|
||||
listOf() /* additionalAnswers */, false /* expectReply */
|
||||
)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testReplyUnicastToQueryUnicastQuestions() {
|
||||
val repository = MdnsRecordRepository(thread.looper, deps, TEST_HOSTNAME, makeFlags())
|
||||
repository.initWithService(TEST_SERVICE_ID_1, TEST_SERVICE_1)
|
||||
|
||||
// Ask for 2 services, only the first one is known and requests unicast reply
|
||||
val questions = listOf(
|
||||
MdnsPointerRecord(arrayOf("_testservice", "_tcp", "local"), true /* isUnicast */),
|
||||
MdnsPointerRecord(arrayOf("_otherservice", "_tcp", "local"), true /* isUnicast */))
|
||||
val query = MdnsPacket(0 /* flags */, questions, listOf() /* answers */,
|
||||
listOf() /* authorityRecords */, listOf() /* additionalRecords */)
|
||||
val src = InetSocketAddress(parseNumericAddress("2001:db8::123"), 5353)
|
||||
|
||||
// Reply to the question and verify it is sent to the source.
|
||||
val reply = repository.getReply(query, src)
|
||||
assertNotNull(reply)
|
||||
assertEquals(src, reply.destination)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testReplyMulticastToQueryUnicastAndMulticastMixedQuestions() {
|
||||
val repository = MdnsRecordRepository(thread.looper, deps, TEST_HOSTNAME, makeFlags())
|
||||
repository.initWithService(TEST_SERVICE_ID_1, TEST_SERVICE_1)
|
||||
repository.addServiceAndFinishProbing(TEST_SERVICE_ID_2, NsdServiceInfo().apply {
|
||||
serviceType = "_otherservice._tcp"
|
||||
serviceName = "OtherTestService"
|
||||
port = TEST_PORT
|
||||
})
|
||||
|
||||
// Ask for 2 services, both are known and only the first one requests unicast reply
|
||||
val questions = listOf(
|
||||
MdnsPointerRecord(arrayOf("_testservice", "_tcp", "local"), true /* isUnicast */),
|
||||
MdnsPointerRecord(arrayOf("_otherservice", "_tcp", "local"), false /* isUnicast */))
|
||||
val query = MdnsPacket(0 /* flags */, questions, listOf() /* answers */,
|
||||
listOf() /* authorityRecords */, listOf() /* additionalRecords */)
|
||||
val src = InetSocketAddress(parseNumericAddress("2001:db8::123"), 5353)
|
||||
|
||||
// Reply to the question and verify it is sent multicast.
|
||||
val reply = repository.getReply(query, src)
|
||||
assertNotNull(reply)
|
||||
assertEquals(MdnsConstants.getMdnsIPv6Address(), reply.destination.address)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testReplyMulticastWhenNoUnicastQueryMatches() {
|
||||
val repository = MdnsRecordRepository(thread.looper, deps, TEST_HOSTNAME, makeFlags())
|
||||
repository.initWithService(TEST_SERVICE_ID_1, TEST_SERVICE_1)
|
||||
|
||||
// Ask for 2 services, the first one requests a unicast reply but is unknown
|
||||
val questions = listOf(
|
||||
MdnsPointerRecord(arrayOf("_otherservice", "_tcp", "local"), true /* isUnicast */),
|
||||
MdnsPointerRecord(arrayOf("_testservice", "_tcp", "local"), false /* isUnicast */))
|
||||
val query = MdnsPacket(0 /* flags */, questions, listOf() /* answers */,
|
||||
listOf() /* authorityRecords */, listOf() /* additionalRecords */)
|
||||
val src = InetSocketAddress(parseNumericAddress("2001:db8::123"), 5353)
|
||||
|
||||
// Reply to the question and verify it is sent multicast.
|
||||
val reply = repository.getReply(query, src)
|
||||
assertNotNull(reply)
|
||||
assertEquals(MdnsConstants.getMdnsIPv6Address(), reply.destination.address)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testReplyMulticastWhenUnicastFeatureDisabled() {
|
||||
val repository = MdnsRecordRepository(thread.looper, deps, TEST_HOSTNAME,
|
||||
makeFlags(unicastReplyEnabled = false))
|
||||
repository.initWithService(TEST_SERVICE_ID_1, TEST_SERVICE_1)
|
||||
|
||||
// The service is known and requests unicast reply, but the feature is disabled
|
||||
val questions = listOf(
|
||||
MdnsPointerRecord(arrayOf("_testservice", "_tcp", "local"), true /* isUnicast */))
|
||||
val query = MdnsPacket(0 /* flags */, questions, listOf() /* answers */,
|
||||
listOf() /* authorityRecords */, listOf() /* additionalRecords */)
|
||||
val src = InetSocketAddress(parseNumericAddress("2001:db8::123"), 5353)
|
||||
|
||||
// Reply to the question and verify it is sent multicast.
|
||||
val reply = repository.getReply(query, src)
|
||||
assertNotNull(reply)
|
||||
assertEquals(MdnsConstants.getMdnsIPv6Address(), reply.destination.address)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -1250,6 +1344,13 @@ private fun MdnsRecordRepository.initWithService(
|
|||
): AnnouncementInfo {
|
||||
updateAddresses(addresses)
|
||||
serviceInfo.setSubtypes(subtypes)
|
||||
return addServiceAndFinishProbing(serviceId, serviceInfo)
|
||||
}
|
||||
|
||||
private fun MdnsRecordRepository.addServiceAndFinishProbing(
|
||||
serviceId: Int,
|
||||
serviceInfo: NsdServiceInfo
|
||||
): AnnouncementInfo {
|
||||
addService(serviceId, serviceInfo)
|
||||
val probingInfo = setServiceProbing(serviceId)
|
||||
assertNotNull(probingInfo)
|
||||
|
|
|
|||
Loading…
Reference in a new issue