[mdns] fix wrong NSEC answer for unique RRs
Existing MdnsRecordRepositiry#getReply will send a NSEC RR in the reply
when 1) a query for a unique RR is not found or 2) any unique name which is
included in the answer sections. The second one is not aligned with the
spec:
```
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.
```
This commit fixes the issue in 2) by checking that all unique records of
the name are included in the answers before adding a NSEC record.
Bug: 265095929
Test: atest FrameworksNetTests
Change-Id: I0531fbccf98e69a50bb8a45e529caaed3ad80b59
diff --git a/service-t/src/com/android/server/connectivity/mdns/MdnsRecordRepository.java b/service-t/src/com/android/server/connectivity/mdns/MdnsRecordRepository.java
index 3a46e5f..c8fe651 100644
--- a/service-t/src/com/android/server/connectivity/mdns/MdnsRecordRepository.java
+++ b/service-t/src/com/android/server/connectivity/mdns/MdnsRecordRepository.java
@@ -720,7 +720,7 @@
* 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
@@ -736,6 +736,12 @@
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) {
@@ -753,6 +759,27 @@
}
}
+ /** 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.
@@ -767,10 +794,10 @@
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);
diff --git a/tests/unit/java/com/android/server/connectivity/mdns/MdnsRecordRepositoryTest.kt b/tests/unit/java/com/android/server/connectivity/mdns/MdnsRecordRepositoryTest.kt
index dc5507c..69f039c 100644
--- a/tests/unit/java/com/android/server/connectivity/mdns/MdnsRecordRepositoryTest.kt
+++ b/tests/unit/java/com/android/server/connectivity/mdns/MdnsRecordRepositoryTest.kt
@@ -664,6 +664,42 @@
}
@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))
@@ -1140,13 +1176,6 @@
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 */,
true /* cacheFlush */,