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:
commit
81e6439298
2 changed files with 335 additions and 105 deletions
|
|
@ -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);
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
Loading…
Reference in a new issue