Merge changes from topic "fix-mdns" into main

* changes:
  [mdns] add tests for verifying address of mDNS reply
  [mdns] fix wrong NSEC answer for unique RRs
  [mdns] fix duplicated records for multiple questions
This commit is contained in:
Kangping Dong 2023-12-27 13:15:30 +00:00 • committed by Gerrit Code Review
commit 81e6439298
2 changed files with 335 additions and 105 deletions

View file

@ -46,6 +46,7 @@ import java.util.Arrays;
import java.util.Collections;
import java.util.Enumeration;
import java.util.Iterator;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Map;
import java.util.Objects;
@ -140,6 +141,9 @@ public class MdnsRecordRepository {
* Last time (as per SystemClock.elapsedRealtime) when sent via unicast or multicast,
* 0 if never
*/
// FIXME: the `lastSentTimeMs` and `lastAdvertisedTimeMs` should be maintained separately
// for IPv4 and IPv6, because neither IPv4 nor and IPv6 clients can receive replies in
// different address space.
public long lastSentTimeMs;
RecordInfo(NsdServiceInfo serviceInfo, T record, boolean sharedName) {
@ -497,13 +501,17 @@ public class MdnsRecordRepository {
public MdnsReplyInfo getReply(MdnsPacket packet, InetSocketAddress src) {
final long now = SystemClock.elapsedRealtime();
final boolean replyUnicast = (packet.flags & MdnsConstants.QCLASS_UNICAST) != 0;
final ArrayList<MdnsRecord> additionalAnswerRecords = new ArrayList<>();
final ArrayList<RecordInfo<?>> answerInfo = new ArrayList<>();
// 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<>();
for (MdnsRecord question : packet.questions) {
// Add answers from general records
addReplyFromService(question, mGeneralRecords, null /* servicePtrRecord */,
null /* serviceSrvRecord */, null /* serviceTxtRecord */, replyUnicast, now,
answerInfo, additionalAnswerRecords, Collections.emptyList());
answerInfo, additionalAnswerInfo, Collections.emptyList());
// Add answers from each service
for (int i = 0; i < mServices.size(); i++) {
@ -511,13 +519,33 @@ public class MdnsRecordRepository {
if (registration.exiting || registration.isProbing) continue;
if (addReplyFromService(question, registration.allRecords, registration.ptrRecords,
registration.srvRecord, registration.txtRecord, replyUnicast, now,
answerInfo, additionalAnswerRecords, packet.answers)) {
answerInfo, additionalAnswerInfo, packet.answers)) {
registration.repliedServiceCount++;
registration.sentPacketCount++;
}
}
}
// If any record was already in the answer section, remove it from the additional answer
// section. This can typically happen when there are both queries for
// SRV / TXT / A / AAAA and PTR (which can cause SRV / TXT / A / AAAA records being added
// to the additional answer section).
additionalAnswerInfo.removeAll(answerInfo);
final List<MdnsRecord> additionalAnswerRecords =
new ArrayList<>(additionalAnswerInfo.size());
for (RecordInfo<?> info : additionalAnswerInfo) {
additionalAnswerRecords.add(info.record);
}
// RFC6762 6.1: negative responses
// "On receipt of a question for a particular name, rrtype, and rrclass, for which a
// responder does have one or more unique answers, the responder MAY also include an NSEC
// record in the Additional Record Section indicating the nonexistence of other rrtypes
// for that name and rrclass."
addNsecRecordsForUniqueNames(additionalAnswerRecords,
answerInfo.iterator(), additionalAnswerInfo.iterator());
if (answerInfo.size() == 0 && additionalAnswerRecords.size() == 0) {
return null;
}
@ -581,15 +609,15 @@ public class MdnsRecordRepository {
@Nullable List<RecordInfo<MdnsPointerRecord>> servicePtrRecords,
@Nullable RecordInfo<MdnsServiceRecord> serviceSrvRecord,
@Nullable RecordInfo<MdnsTextRecord> serviceTxtRecord,
boolean replyUnicast, long now, @NonNull List<RecordInfo<?>> answerInfo,
@NonNull List<MdnsRecord> additionalAnswerRecords,
boolean replyUnicast, long now, @NonNull Set<RecordInfo<?>> answerInfo,
@NonNull Set<RecordInfo<?>> additionalAnswerInfo,
@NonNull List<MdnsRecord> knownAnswerRecords) {
boolean hasDnsSdPtrRecordAnswer = false;
boolean hasDnsSdSrvRecordAnswer = false;
boolean hasFullyOwnedNameMatch = false;
boolean hasKnownAnswer = false;
final int answersStartIndex = answerInfo.size();
final int answersStartSize = answerInfo.size();
for (RecordInfo<?> info : serviceRecords) {
/* RFC6762 6.: the record name must match the question name, the record rrtype
@ -645,7 +673,7 @@ public class MdnsRecordRepository {
// ownership, for a type for which that name has no records, the responder MUST [...]
// respond asserting the nonexistence of that record"
if (hasFullyOwnedNameMatch && !hasKnownAnswer) {
additionalAnswerRecords.add(new MdnsNsecRecord(
MdnsNsecRecord nsecRecord = new MdnsNsecRecord(
question.getName(),
0L /* receiptTimeMillis */,
true /* cacheFlush */,
@ -653,13 +681,14 @@ public class MdnsRecordRepository {
// be the same as the TTL that the record would have had, had it existed."
NAME_RECORDS_TTL_MILLIS,
question.getName(),
new int[] { question.getType() }));
new int[] { question.getType() });
additionalAnswerInfo.add(
new RecordInfo<>(null /* serviceInfo */, nsecRecord, false /* isSharedName */));
}
// No more records to add if no answer
if (answerInfo.size() == answersStartIndex) return false;
if (answerInfo.size() == answersStartSize) return false;
final List<RecordInfo<?>> additionalAnswerInfo = new ArrayList<>();
// RFC6763 12.1: if including PTR record, include the SRV and TXT records it names
if (hasDnsSdPtrRecordAnswer) {
if (serviceTxtRecord != null) {
@ -678,15 +707,6 @@ public class MdnsRecordRepository {
}
}
}
for (RecordInfo<?> info : additionalAnswerInfo) {
additionalAnswerRecords.add(info.record);
}
// RFC6762 6.1: negative responses
addNsecRecordsForUniqueNames(additionalAnswerRecords,
answerInfo.listIterator(answersStartIndex),
additionalAnswerInfo.listIterator());
return true;
}
@ -703,7 +723,7 @@ public class MdnsRecordRepository {
* answer and additionalAnswer sections)
*/
@SafeVarargs
private static void addNsecRecordsForUniqueNames(
private void addNsecRecordsForUniqueNames(
List<MdnsRecord> destinationList,
Iterator<RecordInfo<?>>... answerRecords) {
// Group unique records by name. Use a TreeMap with comparator as arrays don't implement
@ -719,6 +739,12 @@ public class MdnsRecordRepository {
for (String[] nsecName : namesInAddedOrder) {
final List<MdnsRecord> entryRecords = nsecByName.get(nsecName);
// Add NSEC records only when the answers include all unique records of this name
if (entryRecords.size() != countUniqueRecords(nsecName)) {
continue;
}
long minTtl = Long.MAX_VALUE;
final Set<Integer> types = new ArraySet<>(entryRecords.size());
for (MdnsRecord record : entryRecords) {
@ -736,6 +762,27 @@ public class MdnsRecordRepository {
}
}
/** Returns the number of unique records on this device for a given {@code name}. */
private int countUniqueRecords(String[] name) {
int cnt = countUniqueRecords(mGeneralRecords, name);
for (int i = 0; i < mServices.size(); i++) {
final ServiceRegistration registration = mServices.valueAt(i);
cnt += countUniqueRecords(registration.allRecords, name);
}
return cnt;
}
private static int countUniqueRecords(List<RecordInfo<?>> records, String[] name) {
int cnt = 0;
for (RecordInfo<?> record : records) {
if (!record.isSharedName && Arrays.equals(name, record.record.getName())) {
cnt++;
}
}
return cnt;
}
/**
* Add non-shared records to a map listing them by record name, and to a list of names that
* remembers the adding order.
@ -750,10 +797,10 @@ public class MdnsRecordRepository {
private static void addNonSharedRecordsToMap(
Iterator<RecordInfo<?>> records,
Map<String[], List<MdnsRecord>> dest,
List<String[]> namesInAddedOrder) {
@Nullable List<String[]> namesInAddedOrder) {
while (records.hasNext()) {
final RecordInfo<?> record = records.next();
if (record.isSharedName) continue;
if (record.isSharedName || record.record instanceof MdnsNsecRecord) continue;
final List<MdnsRecord> recordsForName = dest.computeIfAbsent(record.record.name,
key -> {
namesInAddedOrder.add(key);

View file

@ -22,6 +22,12 @@ import android.net.nsd.NsdServiceInfo
import android.os.Build
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
import com.android.server.connectivity.mdns.MdnsRecordRepository.Dependencies
import com.android.server.connectivity.mdns.MdnsRecordRepository.getReverseDnsAddress
import com.android.server.connectivity.mdns.MdnsServiceInfo.TextEntry
@ -38,6 +44,7 @@ import kotlin.test.assertFalse
import kotlin.test.assertNotNull
import kotlin.test.assertNull
import kotlin.test.assertTrue
import kotlin.test.fail
import org.junit.After
import org.junit.Before
import org.junit.Test
@ -398,31 +405,31 @@ class MdnsRecordRepositoryTest {
true /* cacheFlush */,
120000L /* ttlMillis */,
v4AddrRev,
intArrayOf(MdnsRecord.TYPE_PTR)),
intArrayOf(TYPE_PTR)),
MdnsNsecRecord(TEST_HOSTNAME,
0L /* receiptTimeMillis */,
true /* cacheFlush */,
120000L /* ttlMillis */,
TEST_HOSTNAME,
intArrayOf(MdnsRecord.TYPE_A, MdnsRecord.TYPE_AAAA)),
intArrayOf(TYPE_A, TYPE_AAAA)),
MdnsNsecRecord(v6Addr1Rev,
0L /* receiptTimeMillis */,
true /* cacheFlush */,
120000L /* ttlMillis */,
v6Addr1Rev,
intArrayOf(MdnsRecord.TYPE_PTR)),
intArrayOf(TYPE_PTR)),
MdnsNsecRecord(v6Addr2Rev,
0L /* receiptTimeMillis */,
true /* cacheFlush */,
120000L /* ttlMillis */,
v6Addr2Rev,
intArrayOf(MdnsRecord.TYPE_PTR)),
intArrayOf(TYPE_PTR)),
MdnsNsecRecord(serviceName,
0L /* receiptTimeMillis */,
true /* cacheFlush */,
4500000L /* ttlMillis */,
serviceName,
intArrayOf(MdnsRecord.TYPE_TXT, MdnsRecord.TYPE_SRV))
intArrayOf(TYPE_TXT, TYPE_SRV))
), packet.additionalRecords)
}
@ -504,94 +511,276 @@ class MdnsRecordRepositoryTest {
assertEquals(7, replyCaseInsensitive.additionalAnswers.size)
}
@Test
fun testGetReply() {
doGetReplyTest(queryWithSubtype = false)
}
@Test
fun testGetReply_WithSubtype() {
doGetReplyTest(queryWithSubtype = true)
}
private fun doGetReplyTest(queryWithSubtype: Boolean) {
val repository = MdnsRecordRepository(thread.looper, deps, TEST_HOSTNAME, flags)
repository.initWithService(TEST_SERVICE_ID_1, TEST_SERVICE_1,
setOf(TEST_SUBTYPE, TEST_SUBTYPE2))
val queriedName = if (!queryWithSubtype) arrayOf("_testservice", "_tcp", "local")
else arrayOf(TEST_SUBTYPE, "_sub", "_testservice", "_tcp", "local")
val questions = listOf(MdnsPointerRecord(queriedName, false /* isUnicast */))
val query = MdnsPacket(0 /* flags */, questions, listOf() /* answers */,
/**
* Creates mDNS query packet with given query names and types.
*/
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 */)
}
private fun makeQuestionRecord(name: Array<String>, type: Int): MdnsRecord {
when (type) {
TYPE_PTR -> return MdnsPointerRecord(name, false /* isUnicast */)
TYPE_SRV -> return MdnsServiceRecord(name, false /* isUnicast */)
TYPE_TXT -> return MdnsTextRecord(name, false /* isUnicast */)
TYPE_A, TYPE_AAAA -> return MdnsInetAddressRecord(name, type, false /* isUnicast */)
else -> fail("Unexpected question type: $type")
}
}
@Test
fun testGetReply_singlePtrQuestion_returnsSrvTxtAddressNsecRecords() {
val repository = MdnsRecordRepository(thread.looper, deps, TEST_HOSTNAME, flags)
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")
val query = makeQuery(TYPE_PTR to arrayOf("_testservice", "_tcp", "local"))
val reply = repository.getReply(query, src)
assertNotNull(reply)
// Source address is IPv4
assertEquals(MdnsConstants.getMdnsIPv4Address(), reply.destination.address)
assertEquals(MdnsConstants.MDNS_PORT, reply.destination.port)
val serviceName = arrayOf("MyTestService", "_testservice", "_tcp", "local")
assertEquals(listOf(
MdnsPointerRecord(
queriedName,
0L /* receiptTimeMillis */,
false /* cacheFlush */,
LONG_TTL,
serviceName),
), reply.answers)
arrayOf("_testservice", "_tcp", "local"), 0L, false, LONG_TTL, serviceName)),
reply.answers)
assertEquals(listOf(
MdnsTextRecord(
serviceName,
0L /* receiptTimeMillis */,
true /* cacheFlush */,
LONG_TTL,
listOf() /* entries */),
MdnsServiceRecord(
serviceName,
0L /* receiptTimeMillis */,
true /* cacheFlush */,
SHORT_TTL,
0 /* servicePriority */,
0 /* serviceWeight */,
TEST_PORT,
TEST_HOSTNAME),
MdnsInetAddressRecord(
TEST_HOSTNAME,
0L /* receiptTimeMillis */,
true /* cacheFlush */,
SHORT_TTL,
TEST_ADDRESSES[0].address),
MdnsInetAddressRecord(
TEST_HOSTNAME,
0L /* receiptTimeMillis */,
true /* cacheFlush */,
SHORT_TTL,
TEST_ADDRESSES[1].address),
MdnsInetAddressRecord(
TEST_HOSTNAME,
0L /* receiptTimeMillis */,
true /* cacheFlush */,
SHORT_TTL,
TEST_ADDRESSES[2].address),
MdnsNsecRecord(
serviceName,
0L /* receiptTimeMillis */,
true /* cacheFlush */,
LONG_TTL,
serviceName /* nextDomain */,
intArrayOf(MdnsRecord.TYPE_TXT, MdnsRecord.TYPE_SRV)),
MdnsNsecRecord(
TEST_HOSTNAME,
0L /* receiptTimeMillis */,
true /* cacheFlush */,
SHORT_TTL,
TEST_HOSTNAME /* nextDomain */,
intArrayOf(MdnsRecord.TYPE_A, MdnsRecord.TYPE_AAAA)),
), reply.additionalAnswers)
MdnsTextRecord(serviceName, 0L, true, LONG_TTL, listOf()),
MdnsServiceRecord(serviceName, 0L, true, SHORT_TTL, 0, 0, TEST_PORT, TEST_HOSTNAME),
MdnsInetAddressRecord(
TEST_HOSTNAME, 0L, true, SHORT_TTL, TEST_ADDRESSES[0].address),
MdnsInetAddressRecord(
TEST_HOSTNAME, 0L, true, SHORT_TTL, TEST_ADDRESSES[1].address),
MdnsInetAddressRecord(
TEST_HOSTNAME, 0L, true, SHORT_TTL, TEST_ADDRESSES[2].address),
MdnsNsecRecord(serviceName, 0L, true, LONG_TTL, serviceName /* nextDomain */,
intArrayOf(TYPE_TXT, TYPE_SRV)),
MdnsNsecRecord(TEST_HOSTNAME, 0L, true, SHORT_TTL, TEST_HOSTNAME /* nextDomain */,
intArrayOf(TYPE_A, TYPE_AAAA)),
), reply.additionalAnswers)
}
@Test
fun testGetReply_singleSubtypePtrQuestion_returnsSrvTxtAddressNsecRecords() {
val repository = MdnsRecordRepository(thread.looper, deps, TEST_HOSTNAME, flags)
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")
val query = makeQuery(
TYPE_PTR to arrayOf(TEST_SUBTYPE, "_sub", "_testservice", "_tcp", "local"))
val reply = repository.getReply(query, src)
assertNotNull(reply)
assertEquals(listOf(
MdnsPointerRecord(
arrayOf(TEST_SUBTYPE, "_sub", "_testservice", "_tcp", "local"), 0L, false,
LONG_TTL, serviceName)),
reply.answers)
assertEquals(listOf(
MdnsTextRecord(serviceName, 0L, true, LONG_TTL, listOf()),
MdnsServiceRecord(serviceName, 0L, true, SHORT_TTL, 0, 0, TEST_PORT, TEST_HOSTNAME),
MdnsInetAddressRecord(
TEST_HOSTNAME, 0L, true, SHORT_TTL, TEST_ADDRESSES[0].address),
MdnsInetAddressRecord(
TEST_HOSTNAME, 0L, true, SHORT_TTL, TEST_ADDRESSES[1].address),
MdnsInetAddressRecord(
TEST_HOSTNAME, 0L, true, SHORT_TTL, TEST_ADDRESSES[2].address),
MdnsNsecRecord(serviceName, 0L, true, LONG_TTL, serviceName /* nextDomain */,
intArrayOf(TYPE_TXT, TYPE_SRV)),
MdnsNsecRecord(TEST_HOSTNAME, 0L, true, SHORT_TTL, TEST_HOSTNAME /* nextDomain */,
intArrayOf(TYPE_A, TYPE_AAAA)),
), reply.additionalAnswers)
}
@Test
fun testGetReply_duplicatePtrQuestions_doesNotReturnDuplicateRecords() {
val repository = MdnsRecordRepository(thread.looper, deps, TEST_HOSTNAME, flags)
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")
val query = makeQuery(
TYPE_PTR to arrayOf("_testservice", "_tcp", "local"),
TYPE_PTR to arrayOf("_testservice", "_tcp", "local"))
val reply = repository.getReply(query, src)
assertNotNull(reply)
assertEquals(listOf(
MdnsPointerRecord(
arrayOf("_testservice", "_tcp", "local"), 0L, false, LONG_TTL, serviceName)),
reply.answers)
assertEquals(listOf(
MdnsTextRecord(serviceName, 0L, true, LONG_TTL, listOf()),
MdnsServiceRecord(serviceName, 0L, true, SHORT_TTL, 0, 0, TEST_PORT, TEST_HOSTNAME),
MdnsInetAddressRecord(
TEST_HOSTNAME, 0L, true, SHORT_TTL, TEST_ADDRESSES[0].address),
MdnsInetAddressRecord(
TEST_HOSTNAME, 0L, true, SHORT_TTL, TEST_ADDRESSES[1].address),
MdnsInetAddressRecord(
TEST_HOSTNAME, 0L, true, SHORT_TTL, TEST_ADDRESSES[2].address),
MdnsNsecRecord(serviceName, 0L, true, LONG_TTL, serviceName /* nextDomain */,
intArrayOf(TYPE_TXT, TYPE_SRV)),
MdnsNsecRecord(TEST_HOSTNAME, 0L, true, SHORT_TTL, TEST_HOSTNAME /* nextDomain */,
intArrayOf(TYPE_A, TYPE_AAAA)),
), reply.additionalAnswers)
}
@Test
fun testGetReply_multiplePtrQuestionsWithSubtype_doesNotReturnDuplicateRecords() {
val repository = MdnsRecordRepository(thread.looper, deps, TEST_HOSTNAME, flags)
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")
val query = makeQuery(
TYPE_PTR to arrayOf("_testservice", "_tcp", "local"),
TYPE_PTR to arrayOf(TEST_SUBTYPE, "_sub", "_testservice", "_tcp", "local"))
val reply = repository.getReply(query, src)
assertNotNull(reply)
assertEquals(listOf(
MdnsPointerRecord(
arrayOf("_testservice", "_tcp", "local"), 0L, false, LONG_TTL, serviceName),
MdnsPointerRecord(
arrayOf(TEST_SUBTYPE, "_sub", "_testservice", "_tcp", "local"),
0L, false, LONG_TTL, serviceName)),
reply.answers)
assertEquals(listOf(
MdnsTextRecord(serviceName, 0L, true, LONG_TTL, listOf()),
MdnsServiceRecord(serviceName, 0L, true, SHORT_TTL, 0, 0, TEST_PORT, TEST_HOSTNAME),
MdnsInetAddressRecord(
TEST_HOSTNAME, 0L, true, SHORT_TTL, TEST_ADDRESSES[0].address),
MdnsInetAddressRecord(
TEST_HOSTNAME, 0L, true, SHORT_TTL, TEST_ADDRESSES[1].address),
MdnsInetAddressRecord(
TEST_HOSTNAME, 0L, true, SHORT_TTL, TEST_ADDRESSES[2].address),
MdnsNsecRecord(serviceName, 0L, true, LONG_TTL, serviceName /* nextDomain */,
intArrayOf(TYPE_TXT, TYPE_SRV)),
MdnsNsecRecord(TEST_HOSTNAME, 0L, true, SHORT_TTL, TEST_HOSTNAME /* nextDomain */,
intArrayOf(TYPE_A, TYPE_AAAA)),
), reply.additionalAnswers)
}
@Test
fun testGetReply_txtQuestion_returnsNoNsecRecord() {
val repository = MdnsRecordRepository(thread.looper, deps, TEST_HOSTNAME, flags)
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")
val query = makeQuery(TYPE_TXT to serviceName)
val reply = repository.getReply(query, src)
assertNotNull(reply)
assertEquals(listOf(MdnsTextRecord(serviceName, 0L, true, LONG_TTL, listOf())),
reply.answers)
// No NSEC records because the reply doesn't include the SRV record
assertTrue(reply.additionalAnswers.isEmpty())
}
@Test
fun testGetReply_AAAAQuestionButNoIpv6Address_returnsNsecRecord() {
val repository = MdnsRecordRepository(thread.looper, deps, TEST_HOSTNAME, flags)
repository.initWithService(
TEST_SERVICE_ID_1, TEST_SERVICE_1, setOf(TEST_SUBTYPE),
listOf(LinkAddress(parseNumericAddress("192.0.2.111"), 24)))
val src = InetSocketAddress(parseNumericAddress("192.0.2.123"), 5353)
val query = makeQuery(TYPE_AAAA to TEST_HOSTNAME)
val reply = repository.getReply(query, src)
assertNotNull(reply)
assertTrue(reply.answers.isEmpty())
assertEquals(listOf(
MdnsNsecRecord(TEST_HOSTNAME, 0L, true, LONG_TTL, TEST_HOSTNAME /* nextDomain */,
intArrayOf(TYPE_AAAA))),
reply.additionalAnswers)
}
@Test
fun testGetReply_ptrAndSrvQuestions_doesNotReturnSrvRecordInAdditionalAnswerSection() {
val repository = MdnsRecordRepository(thread.looper, deps, TEST_HOSTNAME, flags)
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")
val query = makeQuery(
TYPE_PTR to arrayOf("_testservice", "_tcp", "local"),
TYPE_SRV to serviceName)
val reply = repository.getReply(query, src)
assertNotNull(reply)
assertEquals(listOf(
MdnsPointerRecord(
arrayOf("_testservice", "_tcp", "local"), 0L, false, LONG_TTL, serviceName),
MdnsServiceRecord(
serviceName, 0L, true, SHORT_TTL, 0, 0, TEST_PORT, TEST_HOSTNAME)),
reply.answers)
assertFalse(reply.additionalAnswers.any { it -> it is MdnsServiceRecord })
}
@Test
fun testGetReply_srvTxtAddressQuestions_returnsAllRecordsInAnswerSectionExceptNsec() {
val repository = MdnsRecordRepository(thread.looper, deps, TEST_HOSTNAME, flags)
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")
val query = makeQuery(
TYPE_SRV to serviceName,
TYPE_TXT to serviceName,
TYPE_SRV to serviceName,
TYPE_A to TEST_HOSTNAME,
TYPE_AAAA to TEST_HOSTNAME)
val reply = repository.getReply(query, src)
assertNotNull(reply)
assertEquals(listOf(
MdnsServiceRecord(serviceName, 0L, true, SHORT_TTL, 0, 0, TEST_PORT, TEST_HOSTNAME),
MdnsTextRecord(serviceName, 0L, true, LONG_TTL, listOf()),
MdnsInetAddressRecord(
TEST_HOSTNAME, 0L, true, SHORT_TTL, TEST_ADDRESSES[0].address),
MdnsInetAddressRecord(
TEST_HOSTNAME, 0L, true, SHORT_TTL, TEST_ADDRESSES[1].address),
MdnsInetAddressRecord(
TEST_HOSTNAME, 0L, true, SHORT_TTL, TEST_ADDRESSES[2].address)),
reply.answers)
assertEquals(listOf(
MdnsNsecRecord(serviceName, 0L, true, LONG_TTL, serviceName /* nextDomain */,
intArrayOf(TYPE_TXT, TYPE_SRV)),
MdnsNsecRecord(TEST_HOSTNAME, 0L, true, SHORT_TTL, TEST_HOSTNAME /* nextDomain */,
intArrayOf(TYPE_A, TYPE_AAAA))),
reply.additionalAnswers)
}
@Test
fun testGetReply_queryWithIpv4Address_replyWithIpv4Address() {
val repository = MdnsRecordRepository(thread.looper, deps, TEST_HOSTNAME, flags)
repository.initWithService(TEST_SERVICE_ID_1, TEST_SERVICE_1, setOf(TEST_SUBTYPE))
val query = makeQuery(TYPE_PTR to arrayOf("_testservice", "_tcp", "local"))
val srcIpv4 = InetSocketAddress(parseNumericAddress("192.0.2.123"), 5353)
val replyIpv4 = repository.getReply(query, srcIpv4)
assertNotNull(replyIpv4)
assertEquals(MdnsConstants.getMdnsIPv4Address(), replyIpv4.destination.address)
assertEquals(MdnsConstants.MDNS_PORT, replyIpv4.destination.port)
}
@Test
fun testGetReply_queryWithIpv6Address_replyWithIpv6Address() {
val repository = MdnsRecordRepository(thread.looper, deps, TEST_HOSTNAME, flags)
repository.initWithService(TEST_SERVICE_ID_1, TEST_SERVICE_1, setOf(TEST_SUBTYPE))
val query = makeQuery(TYPE_PTR to arrayOf("_testservice", "_tcp", "local"))
val srcIpv6 = InetSocketAddress(parseNumericAddress("2001:db8::123"), 5353)
val replyIpv6 = repository.getReply(query, srcIpv6)
assertNotNull(replyIpv6)
assertEquals(MdnsConstants.getMdnsIPv6Address(), replyIpv6.destination.address)
assertEquals(MdnsConstants.MDNS_PORT, replyIpv6.destination.port)
}
@Test
@ -1014,13 +1203,6 @@ class MdnsRecordRepositoryTest {
true /* cacheFlush */,
SHORT_TTL,
TEST_ADDRESSES[2].address),
MdnsNsecRecord(
serviceName,
0L /* receiptTimeMillis */,
true /* cacheFlush */,
LONG_TTL,
serviceName /* nextDomain */,
intArrayOf(MdnsRecord.TYPE_SRV)),
MdnsNsecRecord(
TEST_HOSTNAME,
0L /* receiptTimeMillis */,
@ -1064,8 +1246,9 @@ private fun MdnsRecordRepository.initWithService(
serviceId: Int,
serviceInfo: NsdServiceInfo,
subtypes: Set<String> = setOf(),
addresses: List<LinkAddress> = TEST_ADDRESSES
): AnnouncementInfo {
updateAddresses(TEST_ADDRESSES)
updateAddresses(addresses)
serviceInfo.setSubtypes(subtypes)
addService(serviceId, serviceInfo)
val probingInfo = setServiceProbing(serviceId)