Merge "Fix replying to queries via unicast" into main
This commit is contained in:
commit
0c905a152e
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