Merge "Support mDns Multipacket Known-Answer Suppression" into main am: eeb79c6937

Original change: https://android-review.googlesource.com/c/platform/packages/modules/Connectivity/+/2854586

Change-Id: I123f70215f47358612cbafbfbb92ae21ddd03fef
Signed-off-by: Automerger Merge Worker <android-build-automerger-merge-worker@system.gserviceaccount.com>
This commit is contained in:
Paul Hu 2024-01-25 03:44:29 +00:00 • committed by Automerger Merge Worker
commit 78ceed67b0
5 changed files with 516 additions and 83 deletions

View file

@ -55,6 +55,7 @@ class MdnsAnnouncerTest {
private val socket = mock(MdnsInterfaceSocket::class.java)
private val sharedLog = mock(SharedLog::class.java)
private val buffer = ByteArray(1500)
private val flags = MdnsFeatureFlags.newBuilder().build()
@Before
fun setUp() {
@ -83,7 +84,7 @@ class MdnsAnnouncerTest {
@Test
fun testAnnounce() {
val replySender = MdnsReplySender(
thread.looper, socket, buffer, sharedLog, true /* enableDebugLog */)
thread.looper, socket, buffer, sharedLog, true /* enableDebugLog */, flags)
@Suppress("UNCHECKED_CAST")
val cb = mock(MdnsPacketRepeater.PacketRepeaterCallback::class.java)
as MdnsPacketRepeater.PacketRepeaterCallback<BaseAnnouncementInfo>

View file

@ -45,6 +45,7 @@ import org.mockito.ArgumentCaptor
import org.mockito.Mockito.any
import org.mockito.Mockito.anyInt
import org.mockito.Mockito.anyString
import org.mockito.Mockito.argThat
import org.mockito.Mockito.doAnswer
import org.mockito.Mockito.doReturn
import org.mockito.Mockito.eq
@ -87,7 +88,8 @@ class MdnsInterfaceAdvertiserTest {
private val announcer = mock(MdnsAnnouncer::class.java)
private val prober = mock(MdnsProber::class.java)
private val sharedlog = SharedLog("MdnsInterfaceAdvertiserTest")
private val flags = MdnsFeatureFlags.newBuilder().build()
private val flags = MdnsFeatureFlags.newBuilder()
.setIsKnownAnswerSuppressionEnabled(true).build()
@Suppress("UNCHECKED_CAST")
private val probeCbCaptor = ArgumentCaptor.forClass(PacketRepeaterCallback::class.java)
as ArgumentCaptor<PacketRepeaterCallback<ProbingInfo>>
@ -118,7 +120,8 @@ class MdnsInterfaceAdvertiserTest {
@Before
fun setUp() {
doReturn(repository).`when`(deps).makeRecordRepository(any(), eq(TEST_HOSTNAME), any())
doReturn(replySender).`when`(deps).makeReplySender(anyString(), any(), any(), any(), any())
doReturn(replySender).`when`(deps).makeReplySender(
anyString(), any(), any(), any(), any(), any())
doReturn(announcer).`when`(deps).makeMdnsAnnouncer(anyString(), any(), any(), any(), any())
doReturn(prober).`when`(deps).makeMdnsProber(anyString(), any(), any(), any(), any())
@ -200,7 +203,8 @@ class MdnsInterfaceAdvertiserTest {
fun testReplyToQuery() {
addServiceAndFinishProbing(TEST_SERVICE_ID_1, TEST_SERVICE_1)
val testReply = MdnsReplyInfo(emptyList(), emptyList(), 0, InetSocketAddress(0))
val testReply = MdnsReplyInfo(emptyList(), emptyList(), 0, InetSocketAddress(0),
InetSocketAddress(0), emptyList())
doReturn(testReply).`when`(repository).getReply(any(), any())
// Query obtained with:
@ -234,6 +238,112 @@ class MdnsInterfaceAdvertiserTest {
verify(replySender).queueReply(testReply)
}
@Test
fun testReplyToQuery_TruncatedBitSet() {
addServiceAndFinishProbing(TEST_SERVICE_ID_1, TEST_SERVICE_1)
val src = InetSocketAddress(parseNumericAddress("2001:db8::456"), MdnsConstants.MDNS_PORT)
val testReply = MdnsReplyInfo(emptyList(), emptyList(), 400L, InetSocketAddress(0), src,
emptyList())
val knownAnswersReply = MdnsReplyInfo(emptyList(), emptyList(), 400L, InetSocketAddress(0),
src, emptyList())
val knownAnswersReply2 = MdnsReplyInfo(emptyList(), emptyList(), 0L, InetSocketAddress(0),
src, emptyList())
doReturn(testReply).`when`(repository).getReply(
argThat { pkg -> pkg.questions.size != 0 && pkg.answers.size == 0 &&
(pkg.flags and MdnsConstants.FLAG_TRUNCATED) != 0},
eq(src))
doReturn(knownAnswersReply).`when`(repository).getReply(
argThat { pkg -> pkg.questions.size == 0 && pkg.answers.size != 0 &&
(pkg.flags and MdnsConstants.FLAG_TRUNCATED) != 0},
eq(src))
doReturn(knownAnswersReply2).`when`(repository).getReply(
argThat { pkg -> pkg.questions.size == 0 && pkg.answers.size != 0 &&
(pkg.flags and MdnsConstants.FLAG_TRUNCATED) == 0},
eq(src))
// Query obtained with:
// scapy.raw(scapy.DNS(
// tc = 1, qd = scapy.DNSQR(qtype='PTR', qname='_testservice._tcp.local'))
// ).hex().upper()
val query = HexDump.hexStringToByteArray(
"0000030000010000000000000C5F7465737473657276696365045F746370056C6F63616C00000C0001"
)
packetHandler.handlePacket(query, query.size, src)
val packetCaptor = ArgumentCaptor.forClass(MdnsPacket::class.java)
verify(repository).getReply(packetCaptor.capture(), eq(src))
packetCaptor.value.let {
assertTrue((it.flags and MdnsConstants.FLAG_TRUNCATED) != 0)
assertEquals(1, it.questions.size)
assertEquals(0, it.answers.size)
assertEquals(0, it.authorityRecords.size)
assertEquals(0, it.additionalRecords.size)
assertTrue(it.questions[0] is MdnsPointerRecord)
assertContentEquals(arrayOf("_testservice", "_tcp", "local"), it.questions[0].name)
}
verify(replySender).queueReply(testReply)
// Known-Answer packet with truncated bit set obtained with:
// scapy.raw(scapy.DNS(
// tc = 1, qd = None, an = scapy.DNSRR(type='PTR', rrname='_testtype._tcp.local',
// rdata='othertestservice._testtype._tcp.local', rclass='IN', ttl=4500))
// ).hex().upper()
val knownAnswers = HexDump.hexStringToByteArray(
"000003000000000100000000095F7465737474797065045F746370056C6F63616C00000C0001000" +
"011940027106F746865727465737473657276696365095F7465737474797065045F7463" +
"70056C6F63616C00"
)
packetHandler.handlePacket(knownAnswers, knownAnswers.size, src)
verify(repository, times(2)).getReply(packetCaptor.capture(), eq(src))
packetCaptor.value.let {
assertTrue((it.flags and MdnsConstants.FLAG_TRUNCATED) != 0)
assertEquals(0, it.questions.size)
assertEquals(1, it.answers.size)
assertEquals(0, it.authorityRecords.size)
assertEquals(0, it.additionalRecords.size)
assertTrue(it.answers[0] is MdnsPointerRecord)
assertContentEquals(arrayOf("_testtype", "_tcp", "local"), it.answers[0].name)
}
verify(replySender).queueReply(knownAnswersReply)
// Known-Answer packet obtained with:
// scapy.raw(scapy.DNS(
// qd = None, an = scapy.DNSRR(type='PTR', rrname='_testtype._tcp.local',
// rdata='testservice._testtype._tcp.local', rclass='IN', ttl=4500))
// ).hex().upper()
val knownAnswers2 = HexDump.hexStringToByteArray(
"000001000000000100000000095F7465737474797065045F746370056C6F63616C00000C0001000" +
"0119400220B7465737473657276696365095F7465737474797065045F746370056C6F63" +
"616C00"
)
packetHandler.handlePacket(knownAnswers2, knownAnswers2.size, src)
verify(repository, times(3)).getReply(packetCaptor.capture(), eq(src))
packetCaptor.value.let {
assertTrue((it.flags and MdnsConstants.FLAG_TRUNCATED) == 0)
assertEquals(0, it.questions.size)
assertEquals(1, it.answers.size)
assertEquals(0, it.authorityRecords.size)
assertEquals(0, it.additionalRecords.size)
assertTrue(it.answers[0] is MdnsPointerRecord)
assertContentEquals(arrayOf("_testtype", "_tcp", "local"), it.answers[0].name)
}
verify(replySender).queueReply(knownAnswersReply2)
}
@Test
fun testConflict() {
addServiceAndFinishProbing(TEST_SERVICE_ID_1, TEST_SERVICE_1)

View file

@ -61,6 +61,7 @@ class MdnsProberTest {
private val cb = mock(MdnsPacketRepeater.PacketRepeaterCallback::class.java)
as MdnsPacketRepeater.PacketRepeaterCallback<ProbingInfo>
private val buffer = ByteArray(1500)
private val flags = MdnsFeatureFlags.newBuilder().build()
@Before
fun setUp() {
@ -120,7 +121,7 @@ class MdnsProberTest {
@Test
fun testProbe() {
val replySender = MdnsReplySender(
thread.looper, socket, buffer, sharedLog, true /* enableDebugLog */)
thread.looper, socket, buffer, sharedLog, true /* enableDebugLog */, flags)
val prober = TestProber(thread.looper, replySender, cb, sharedLog)
val probeInfo = TestProbeInfo(
listOf(makeServiceRecord(TEST_SERVICE_NAME_1, 37890)))
@ -145,7 +146,7 @@ class MdnsProberTest {
@Test
fun testProbeMultipleRecords() {
val replySender = MdnsReplySender(
thread.looper, socket, buffer, sharedLog, true /* enableDebugLog */)
thread.looper, socket, buffer, sharedLog, true /* enableDebugLog */, flags)
val prober = TestProber(thread.looper, replySender, cb, sharedLog)
val probeInfo = TestProbeInfo(listOf(
makeServiceRecord(TEST_SERVICE_NAME_1, 37890),
@ -184,7 +185,7 @@ class MdnsProberTest {
@Test
fun testStopProbing() {
val replySender = MdnsReplySender(
thread.looper, socket, buffer, sharedLog, true /* enableDebugLog */)
thread.looper, socket, buffer, sharedLog, true /* enableDebugLog */, flags)
val prober = TestProber(thread.looper, replySender, cb, sharedLog)
val probeInfo = TestProbeInfo(
listOf(makeServiceRecord(TEST_SERVICE_NAME_1, 37890)),

View file

@ -175,8 +175,8 @@ class MdnsRecordRepositoryTest {
val queriedName = arrayOf(TEST_SUBTYPE, "_sub", "_testservice", "_tcp", "local")
val questions = listOf(MdnsPointerRecord(queriedName, false /* isUnicast */))
val query = MdnsPacket(0 /* flags */, questions, listOf() /* answers */,
listOf() /* authorityRecords */, listOf() /* additionalRecords */)
val query = MdnsPacket(0 /* flags */, questions, emptyList() /* answers */,
emptyList() /* authorityRecords */, emptyList() /* additionalRecords */)
val src = InetSocketAddress(parseNumericAddress("192.0.2.123"), 5353)
val reply = repository.getReply(query, src)
@ -510,8 +510,8 @@ class MdnsRecordRepositoryTest {
val questionsCaseInSensitive = listOf(
MdnsPointerRecord(arrayOf("_TESTSERVICE", "_TCP", "local"), false /* isUnicast */))
val queryCaseInsensitive = MdnsPacket(0 /* flags */, questionsCaseInSensitive,
listOf() /* answers */, listOf() /* authorityRecords */,
listOf() /* additionalRecords */)
emptyList() /* answers */, emptyList() /* authorityRecords */,
emptyList() /* additionalRecords */)
val src = InetSocketAddress(parseNumericAddress("192.0.2.123"), 5353)
val replyCaseInsensitive = repository.getReply(queryCaseInsensitive, src)
assertNotNull(replyCaseInsensitive)
@ -524,8 +524,8 @@ 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 */)
return MdnsPacket(0 /* flags */, questions, emptyList() /* answers */,
emptyList() /* authorityRecords */, emptyList() /* additionalRecords */)
}
private fun makeQuestionRecord(name: Array<String>, type: Int): MdnsRecord {
@ -554,7 +554,7 @@ class MdnsRecordRepositoryTest {
arrayOf("_testservice", "_tcp", "local"), 0L, false, LONG_TTL, serviceName)),
reply.answers)
assertEquals(listOf(
MdnsTextRecord(serviceName, 0L, true, LONG_TTL, listOf()),
MdnsTextRecord(serviceName, 0L, true, LONG_TTL, emptyList()),
MdnsServiceRecord(serviceName, 0L, true, SHORT_TTL, 0, 0, TEST_PORT, TEST_HOSTNAME),
MdnsInetAddressRecord(
TEST_HOSTNAME, 0L, true, SHORT_TTL, TEST_ADDRESSES[0].address),
@ -587,7 +587,7 @@ class MdnsRecordRepositoryTest {
LONG_TTL, serviceName)),
reply.answers)
assertEquals(listOf(
MdnsTextRecord(serviceName, 0L, true, LONG_TTL, listOf()),
MdnsTextRecord(serviceName, 0L, true, LONG_TTL, emptyList()),
MdnsServiceRecord(serviceName, 0L, true, SHORT_TTL, 0, 0, TEST_PORT, TEST_HOSTNAME),
MdnsInetAddressRecord(
TEST_HOSTNAME, 0L, true, SHORT_TTL, TEST_ADDRESSES[0].address),
@ -620,7 +620,7 @@ class MdnsRecordRepositoryTest {
arrayOf("_testservice", "_tcp", "local"), 0L, false, LONG_TTL, serviceName)),
reply.answers)
assertEquals(listOf(
MdnsTextRecord(serviceName, 0L, true, LONG_TTL, listOf()),
MdnsTextRecord(serviceName, 0L, true, LONG_TTL, emptyList()),
MdnsServiceRecord(serviceName, 0L, true, SHORT_TTL, 0, 0, TEST_PORT, TEST_HOSTNAME),
MdnsInetAddressRecord(
TEST_HOSTNAME, 0L, true, SHORT_TTL, TEST_ADDRESSES[0].address),
@ -656,7 +656,7 @@ class MdnsRecordRepositoryTest {
0L, false, LONG_TTL, serviceName)),
reply.answers)
assertEquals(listOf(
MdnsTextRecord(serviceName, 0L, true, LONG_TTL, listOf()),
MdnsTextRecord(serviceName, 0L, true, LONG_TTL, emptyList()),
MdnsServiceRecord(serviceName, 0L, true, SHORT_TTL, 0, 0, TEST_PORT, TEST_HOSTNAME),
MdnsInetAddressRecord(
TEST_HOSTNAME, 0L, true, SHORT_TTL, TEST_ADDRESSES[0].address),
@ -682,7 +682,7 @@ class MdnsRecordRepositoryTest {
val reply = repository.getReply(query, src)
assertNotNull(reply)
assertEquals(listOf(MdnsTextRecord(serviceName, 0L, true, LONG_TTL, listOf())),
assertEquals(listOf(MdnsTextRecord(serviceName, 0L, true, LONG_TTL, emptyList())),
reply.answers)
// No NSEC records because the reply doesn't include the SRV record
assertTrue(reply.additionalAnswers.isEmpty())
@ -747,7 +747,7 @@ class MdnsRecordRepositoryTest {
assertNotNull(reply)
assertEquals(listOf(
MdnsServiceRecord(serviceName, 0L, true, SHORT_TTL, 0, 0, TEST_PORT, TEST_HOSTNAME),
MdnsTextRecord(serviceName, 0L, true, LONG_TTL, listOf()),
MdnsTextRecord(serviceName, 0L, true, LONG_TTL, emptyList()),
MdnsInetAddressRecord(
TEST_HOSTNAME, 0L, true, SHORT_TTL, TEST_ADDRESSES[0].address),
MdnsInetAddressRecord(
@ -915,8 +915,8 @@ class MdnsRecordRepositoryTest {
val questions = listOf(
MdnsPointerRecord(arrayOf("_testservice", "_tcp", "local"), false /* isUnicast */))
val query = MdnsPacket(0 /* flags */, questions, listOf() /* answers */,
listOf() /* authorityRecords */, listOf() /* additionalRecords */)
val query = MdnsPacket(0 /* flags */, questions, emptyList() /* answers */,
emptyList() /* authorityRecords */, emptyList() /* additionalRecords */)
val src = InetSocketAddress(parseNumericAddress("192.0.2.123"), 5353)
// Reply to the question and verify there is one packet replied.
@ -994,18 +994,17 @@ class MdnsRecordRepositoryTest {
questions: List<MdnsRecord>,
knownAnswers: List<MdnsRecord>,
replyAnswers: List<MdnsRecord>,
additionalAnswers: List<MdnsRecord>,
expectReply: Boolean
additionalAnswers: List<MdnsRecord>
) {
val repository = MdnsRecordRepository(thread.looper, deps, TEST_HOSTNAME,
makeFlags(isKnownAnswerSuppressionEnabled = true))
repository.initWithService(TEST_SERVICE_ID_1, TEST_SERVICE_1)
val query = MdnsPacket(0 /* flags */, questions, knownAnswers,
listOf() /* authorityRecords */, listOf() /* additionalRecords */)
emptyList() /* authorityRecords */, emptyList() /* additionalRecords */)
val src = InetSocketAddress(parseNumericAddress("192.0.2.123"), 5353)
val reply = repository.getReply(query, src)
if (!expectReply) {
if (replyAnswers.isEmpty() || additionalAnswers.isEmpty()) {
assertNull(reply)
return
}
@ -1016,6 +1015,7 @@ class MdnsRecordRepositoryTest {
assertEquals(MdnsConstants.MDNS_PORT, reply.destination.port)
assertEquals(replyAnswers, reply.answers)
assertEquals(additionalAnswers, reply.additionalAnswers)
assertEquals(knownAnswers, reply.knownAnswers)
}
@Test
@ -1028,8 +1028,8 @@ class MdnsRecordRepositoryTest {
false /* cacheFlush */,
LONG_TTL,
arrayOf("MyTestService", "_testservice", "_tcp", "local")))
doGetReplyWithAnswersTest(questions, knownAnswers, listOf() /* replyAnswers */,
listOf() /* additionalAnswers */, false /* expectReply */)
doGetReplyWithAnswersTest(questions, knownAnswers, emptyList() /* replyAnswers */,
emptyList() /* additionalAnswers */)
}
@Test
@ -1055,7 +1055,7 @@ class MdnsRecordRepositoryTest {
0L /* receiptTimeMillis */,
true /* cacheFlush */,
LONG_TTL,
listOf() /* entries */),
emptyList() /* entries */),
MdnsServiceRecord(
serviceName,
0L /* receiptTimeMillis */,
@ -1097,8 +1097,7 @@ class MdnsRecordRepositoryTest {
SHORT_TTL,
TEST_HOSTNAME /* nextDomain */,
intArrayOf(MdnsRecord.TYPE_A, MdnsRecord.TYPE_AAAA)))
doGetReplyWithAnswersTest(questions, knownAnswers, replyAnswers, additionalAnswers,
true /* expectReply */)
doGetReplyWithAnswersTest(questions, knownAnswers, replyAnswers, additionalAnswers)
}
@Test
@ -1124,7 +1123,7 @@ class MdnsRecordRepositoryTest {
0L /* receiptTimeMillis */,
true /* cacheFlush */,
LONG_TTL,
listOf() /* entries */),
emptyList() /* entries */),
MdnsServiceRecord(
serviceName,
0L /* receiptTimeMillis */,
@ -1166,8 +1165,7 @@ class MdnsRecordRepositoryTest {
SHORT_TTL,
TEST_HOSTNAME /* nextDomain */,
intArrayOf(MdnsRecord.TYPE_A, MdnsRecord.TYPE_AAAA)))
doGetReplyWithAnswersTest(questions, knownAnswers, replyAnswers, additionalAnswers,
true /* expectReply */)
doGetReplyWithAnswersTest(questions, knownAnswers, replyAnswers, additionalAnswers)
}
@Test
@ -1218,8 +1216,7 @@ class MdnsRecordRepositoryTest {
SHORT_TTL,
TEST_HOSTNAME /* nextDomain */,
intArrayOf(MdnsRecord.TYPE_A, MdnsRecord.TYPE_AAAA)))
doGetReplyWithAnswersTest(questions, knownAnswers, replyAnswers, additionalAnswers,
true /* expectReply */)
doGetReplyWithAnswersTest(questions, knownAnswers, replyAnswers, additionalAnswers)
}
@Test
@ -1248,10 +1245,8 @@ class MdnsRecordRepositoryTest {
TEST_HOSTNAME
)
)
doGetReplyWithAnswersTest(
questions, knownAnswers, listOf() /* replyAnswers */,
listOf() /* additionalAnswers */, false /* expectReply */
)
doGetReplyWithAnswersTest(questions, knownAnswers, emptyList() /* replyAnswers */,
emptyList() /* additionalAnswers */)
}
@Test
@ -1263,8 +1258,8 @@ class MdnsRecordRepositoryTest {
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 query = MdnsPacket(0 /* flags */, questions, emptyList() /* answers */,
emptyList() /* authorityRecords */, emptyList() /* additionalRecords */)
val src = InetSocketAddress(parseNumericAddress("2001:db8::123"), 5353)
// Reply to the question and verify it is sent to the source.
@ -1287,8 +1282,8 @@ class MdnsRecordRepositoryTest {
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 query = MdnsPacket(0 /* flags */, questions, emptyList() /* answers */,
emptyList() /* authorityRecords */, emptyList() /* additionalRecords */)
val src = InetSocketAddress(parseNumericAddress("2001:db8::123"), 5353)
// Reply to the question and verify it is sent multicast.
@ -1306,8 +1301,8 @@ class MdnsRecordRepositoryTest {
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 query = MdnsPacket(0 /* flags */, questions, emptyList() /* answers */,
emptyList() /* authorityRecords */, emptyList() /* additionalRecords */)
val src = InetSocketAddress(parseNumericAddress("2001:db8::123"), 5353)
// Reply to the question and verify it is sent multicast.
@ -1325,8 +1320,8 @@ class MdnsRecordRepositoryTest {
// 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 query = MdnsPacket(0 /* flags */, questions, emptyList() /* answers */,
emptyList() /* authorityRecords */, emptyList() /* additionalRecords */)
val src = InetSocketAddress(parseNumericAddress("2001:db8::123"), 5353)
// Reply to the question and verify it is sent multicast.
@ -1334,6 +1329,28 @@ class MdnsRecordRepositoryTest {
assertNotNull(reply)
assertEquals(MdnsConstants.getMdnsIPv6Address(), reply.destination.address)
}
@Test
fun testGetReply_OnlyKnownAnswers() {
val repository = MdnsRecordRepository(thread.looper, deps, TEST_HOSTNAME,
makeFlags(isKnownAnswerSuppressionEnabled = true))
repository.initWithService(TEST_SERVICE_ID_1, TEST_SERVICE_1)
val knownAnswers = listOf(MdnsPointerRecord(
arrayOf("_testservice", "_tcp", "local"),
0L /* receiptTimeMillis */,
false /* cacheFlush */,
LONG_TTL - 1000L,
arrayOf("MyTestService", "_testservice", "_tcp", "local")))
val query = MdnsPacket(MdnsConstants.FLAG_TRUNCATED /* flags */, emptyList(),
knownAnswers, emptyList() /* authorityRecords */,
emptyList() /* additionalRecords */)
val src = InetSocketAddress(parseNumericAddress("192.0.2.123"), 5353)
val reply = repository.getReply(query, src)
assertNotNull(reply)
assertEquals(0, reply.answers.size)
assertEquals(0, reply.additionalAnswers.size)
assertEquals(knownAnswers, reply.knownAnswers)
}
}
private fun MdnsRecordRepository.initWithService(

View file

@ -24,21 +24,28 @@ import android.os.HandlerThread
import android.os.Message
import com.android.net.module.util.SharedLog
import com.android.server.connectivity.mdns.MdnsConstants.IPV4_SOCKET_ADDR
import com.android.server.connectivity.mdns.MdnsConstants.IPV6_SOCKET_ADDR
import com.android.server.connectivity.mdns.MdnsReplySender.getReplyDestination
import com.android.testutils.DevSdkIgnoreRule.IgnoreUpTo
import com.android.testutils.DevSdkIgnoreRunner
import java.net.DatagramPacket
import java.net.InetSocketAddress
import java.util.concurrent.CompletableFuture
import java.util.concurrent.TimeUnit
import kotlin.test.assertEquals
import org.junit.After
import org.junit.Before
import org.junit.Test
import org.junit.runner.RunWith
import org.mockito.ArgumentCaptor
import org.mockito.Mockito.any
import org.mockito.Mockito.anyLong
import org.mockito.Mockito.argThat
import org.mockito.Mockito.doReturn
import org.mockito.Mockito.eq
import org.mockito.Mockito.mock
import org.mockito.Mockito.timeout
import org.mockito.Mockito.times
import org.mockito.Mockito.verify
private const val TEST_PORT = 12345
@ -50,8 +57,12 @@ private const val SHORT_TTL = 120_000L
@IgnoreUpTo(Build.VERSION_CODES.S_V2)
class MdnsReplySenderTest {
private val serviceName = arrayOf("MyTestService", "_testservice", "_tcp", "local")
private val otherServiceName = arrayOf("OtherTestService", "_testservice", "_tcp", "local")
private val serviceType = arrayOf("_testservice", "_tcp", "local")
private val source = InetSocketAddress(
InetAddresses.parseNumericAddress("192.0.2.1"), TEST_PORT)
private val hostname = arrayOf("Android_000102030405060708090A0B0C0D0E0F", "local")
private val otherHostname = arrayOf("Android_0F0E0D0C0B0A09080706050403020100", "local")
private val hostAddresses = listOf(
LinkAddress(InetAddresses.parseNumericAddress("192.0.2.111"), 24),
LinkAddress(InetAddresses.parseNumericAddress("2001:db8::111"), 64),
@ -59,9 +70,12 @@ class MdnsReplySenderTest {
private val answers = listOf(
MdnsPointerRecord(serviceType, 0L /* receiptTimeMillis */, false /* cacheFlush */,
LONG_TTL, serviceName))
private val otherAnswers = listOf(
MdnsPointerRecord(serviceType, 0L /* receiptTimeMillis */, false /* cacheFlush */,
LONG_TTL, otherServiceName))
private val additionalAnswers = listOf(
MdnsTextRecord(serviceName, 0L /* receiptTimeMillis */, true /* cacheFlush */, LONG_TTL,
listOf() /* entries */),
emptyList() /* entries */),
MdnsServiceRecord(serviceName, 0L /* receiptTimeMillis */, true /* cacheFlush */,
SHORT_TTL, 0 /* servicePriority */, 0 /* serviceWeight */, TEST_PORT, hostname),
MdnsInetAddressRecord(hostname, 0L /* receiptTimeMillis */, true /* cacheFlush */,
@ -75,15 +89,30 @@ class MdnsReplySenderTest {
intArrayOf(MdnsRecord.TYPE_TXT, MdnsRecord.TYPE_SRV)),
MdnsNsecRecord(hostname, 0L /* receiptTimeMillis */, true /* cacheFlush */, SHORT_TTL,
hostname /* nextDomain */, intArrayOf(MdnsRecord.TYPE_A, MdnsRecord.TYPE_AAAA)))
private val otherAdditionalAnswers = listOf(
MdnsTextRecord(otherServiceName, 0L /* receiptTimeMillis */, true /* cacheFlush */,
LONG_TTL, emptyList() /* entries */),
MdnsServiceRecord(otherServiceName, 0L /* receiptTimeMillis */, true /* cacheFlush */,
SHORT_TTL, 0 /* servicePriority */, 0 /* serviceWeight */, TEST_PORT,
otherHostname),
MdnsInetAddressRecord(otherHostname, 0L /* receiptTimeMillis */, true /* cacheFlush */,
SHORT_TTL, hostAddresses[0].address),
MdnsInetAddressRecord(otherHostname, 0L /* receiptTimeMillis */, true /* cacheFlush */,
SHORT_TTL, hostAddresses[1].address),
MdnsInetAddressRecord(otherHostname, 0L /* receiptTimeMillis */, true /* cacheFlush */,
SHORT_TTL, hostAddresses[2].address),
MdnsNsecRecord(otherServiceName, 0L /* receiptTimeMillis */, true /* cacheFlush */,
LONG_TTL, otherServiceName /* nextDomain */,
intArrayOf(MdnsRecord.TYPE_TXT, MdnsRecord.TYPE_SRV)),
MdnsNsecRecord(otherHostname, 0L /* receiptTimeMillis */, true /* cacheFlush */,
SHORT_TTL, otherHostname /* nextDomain */,
intArrayOf(MdnsRecord.TYPE_A, MdnsRecord.TYPE_AAAA)))
private val thread = HandlerThread(MdnsReplySenderTest::class.simpleName)
private val socket = mock(MdnsInterfaceSocket::class.java)
private val buffer = ByteArray(1500)
private val sharedLog = SharedLog(MdnsReplySenderTest::class.simpleName)
private val deps = mock(MdnsReplySender.Dependencies::class.java)
private val handler by lazy { Handler(thread.looper) }
private val replySender by lazy {
MdnsReplySender(thread.looper, socket, buffer, sharedLog, false /* enableDebugLog */, deps)
}
@Before
fun setUp() {
@ -106,37 +135,180 @@ class MdnsReplySenderTest {
return future.get(DEFAULT_TIMEOUT_MS, TimeUnit.MILLISECONDS)
}
private fun sendNow(packet: MdnsPacket, destination: InetSocketAddress):
Unit = runningOnHandlerAndReturn { replySender.sendNow(packet, destination) }
private fun sendNow(sender: MdnsReplySender, packet: MdnsPacket, dest: InetSocketAddress):
Unit = runningOnHandlerAndReturn { sender.sendNow(packet, dest) }
private fun queueReply(reply: MdnsReplyInfo):
Unit = runningOnHandlerAndReturn { replySender.queueReply(reply) }
private fun queueReply(sender: MdnsReplySender, reply: MdnsReplyInfo):
Unit = runningOnHandlerAndReturn { sender.queueReply(reply) }
private fun buildFlags(enableKAS: Boolean): MdnsFeatureFlags {
return MdnsFeatureFlags.newBuilder()
.setIsKnownAnswerSuppressionEnabled(enableKAS).build()
}
private fun createSender(enableKAS: Boolean): MdnsReplySender =
MdnsReplySender(thread.looper, socket, buffer, sharedLog, false /* enableDebugLog */,
deps, buildFlags(enableKAS))
@Test
fun testSendNow() {
val replySender = createSender(enableKAS = false)
val packet = MdnsPacket(0x8400,
listOf() /* questions */,
emptyList() /* questions */,
answers,
listOf() /* authorityRecords */,
emptyList() /* authorityRecords */,
additionalAnswers)
sendNow(packet, IPV4_SOCKET_ADDR)
sendNow(replySender, packet, IPV4_SOCKET_ADDR)
verify(socket).send(argThat{ it.socketAddress.equals(IPV4_SOCKET_ADDR) })
}
private fun verifyMessageQueued(
sender: MdnsReplySender,
replies: List<MdnsReplyInfo>
): Pair<Handler, Message> {
val handlerCaptor = ArgumentCaptor.forClass(Handler::class.java)
val messageCaptor = ArgumentCaptor.forClass(Message::class.java)
for (reply in replies) {
queueReply(sender, reply)
verify(deps).sendMessageDelayed(
handlerCaptor.capture(), messageCaptor.capture(), eq(reply.sendDelayMs))
}
return Pair(handlerCaptor.value, messageCaptor.value)
}
private fun verifyReplySent(
realHandler: Handler,
delayMessage: Message,
remainingAnswers: List<MdnsRecord>
) {
val datagramPacketCaptor = ArgumentCaptor.forClass(DatagramPacket::class.java)
realHandler.sendMessage(delayMessage)
verify(socket, timeout(DEFAULT_TIMEOUT_MS)).send(datagramPacketCaptor.capture())
val dPacket = datagramPacketCaptor.value
val mdnsPacket = MdnsPacket.parse(MdnsPacketReader(
dPacket.data, dPacket.length, buildFlags(enableKAS = false)))
assertEquals(mdnsPacket.answers.toSet(), remainingAnswers.toSet())
}
@Test
fun testQueueReply() {
val replySender = createSender(enableKAS = false)
val reply = MdnsReplyInfo(answers, additionalAnswers, 20L /* sendDelayMs */,
IPV4_SOCKET_ADDR)
val handlerCaptor = ArgumentCaptor.forClass(Handler::class.java)
val messageCaptor = ArgumentCaptor.forClass(Message::class.java)
queueReply(reply)
verify(deps).sendMessageDelayed(handlerCaptor.capture(), messageCaptor.capture(), eq(20L))
IPV4_SOCKET_ADDR, source, emptyList())
val (handler, message) = verifyMessageQueued(replySender, listOf(reply))
verifyReplySent(handler, message, answers)
}
val realHandler = handlerCaptor.value
val delayMessage = messageCaptor.value
realHandler.sendMessage(delayMessage)
verify(socket, timeout(DEFAULT_TIMEOUT_MS)).send(argThat{
it.socketAddress.equals(IPV4_SOCKET_ADDR)
})
@Test
fun testQueueReply_KnownAnswerSuppressionEnabled() {
val replySender = createSender(enableKAS = true)
val reply = MdnsReplyInfo(answers, additionalAnswers, 20L /* sendDelayMs */,
IPV4_SOCKET_ADDR, source, emptyList())
val (handler, message) = verifyMessageQueued(replySender, listOf(reply))
verifyReplySent(handler, message, answers)
}
@Test
fun testQueueReply_MultiplePacket() {
val replySender = createSender(enableKAS = true)
val reply = MdnsReplyInfo(answers, additionalAnswers, 400L /* sendDelayMs */,
IPV4_SOCKET_ADDR, source, emptyList())
verifyMessageQueued(replySender, listOf(reply))
// Receive a known-answer packet and verify no message queued.
val knownAnswersReply = MdnsReplyInfo(emptyList(), emptyList(), 0L /* sendDelayMs */,
IPV4_SOCKET_ADDR, source, answers)
queueReply(replySender, knownAnswersReply)
verify(deps, times(1)).sendMessageDelayed(any(), any(), anyLong())
}
@Test
fun testQueueReply_MultiplePacket_LostSubsequentPacket() {
val replySender = createSender(enableKAS = true)
val reply = MdnsReplyInfo(answers, additionalAnswers, 400L /* sendDelayMs */,
IPV4_SOCKET_ADDR, source, emptyList())
val (handler, message) = verifyMessageQueued(replySender, listOf(reply))
// No subsequent packets
verifyReplySent(handler, message, answers)
}
@Test
fun testQueueReply_MultiplePacket_OtherKnownAnswer() {
val replySender = createSender(enableKAS = true)
val reply = MdnsReplyInfo(answers, additionalAnswers, 400L /* sendDelayMs */,
IPV4_SOCKET_ADDR, source, emptyList())
// Other known-answer service
val otherKnownAnswersReply = MdnsReplyInfo(emptyList(), emptyList(), 0L /* sendDelayMs */,
IPV4_SOCKET_ADDR, source, otherAnswers)
val (handler, message) = verifyMessageQueued(
replySender, listOf(reply, otherKnownAnswersReply))
verifyReplySent(handler, message, answers)
}
@Test
fun testQueueReply_MultiplePacket_TwoKnownAnswerPackets() {
val replySender = createSender(enableKAS = true)
val reply = MdnsReplyInfo(answers, additionalAnswers, 400L /* sendDelayMs */,
IPV4_SOCKET_ADDR, source, emptyList())
val firstKnownAnswerReply = MdnsReplyInfo(emptyList(), emptyList(), 401L /* sendDelayMs */,
IPV4_SOCKET_ADDR, source, otherAnswers)
verifyMessageQueued(replySender, listOf(reply, firstKnownAnswerReply))
// Second known-answer service
val secondKnownAnswerReply = MdnsReplyInfo(emptyList(), emptyList(), 0L /* sendDelayMs */,
IPV4_SOCKET_ADDR, source, answers)
queueReply(replySender, secondKnownAnswerReply)
// Verify that no reply is queued, as all answers are known.
verify(deps, times(2)).sendMessageDelayed(any(), any(), anyLong())
}
@Test
fun testQueueReply_MultiplePacket_LostSecondaryPacket() {
val replySender = createSender(enableKAS = true)
val reply = MdnsReplyInfo(answers, additionalAnswers, 400L /* sendDelayMs */,
IPV4_SOCKET_ADDR, source, emptyList())
val firstKnownAnswerReply = MdnsReplyInfo(emptyList(), emptyList(), 401L /* sendDelayMs */,
IPV4_SOCKET_ADDR, source, otherAnswers)
val (handler, message) = verifyMessageQueued(
replySender, listOf(reply, firstKnownAnswerReply))
// Second known-answer service lost
verifyReplySent(handler, message, answers)
}
@Test
fun testQueueReply_MultiplePacket_WithMultipleQuestions() {
val replySender = createSender(enableKAS = true)
val twoAnswers = listOf(
MdnsPointerRecord(serviceType, 0L /* receiptTimeMillis */, false /* cacheFlush */,
LONG_TTL, serviceName),
MdnsServiceRecord(otherServiceName, 0L /* receiptTimeMillis */,
true /* cacheFlush */, SHORT_TTL, 0 /* servicePriority */,
0 /* serviceWeight */, TEST_PORT, otherHostname))
val reply = MdnsReplyInfo(twoAnswers, additionalAnswers, 400L /* sendDelayMs */,
IPV4_SOCKET_ADDR, source, emptyList())
val knownAnswersReply = MdnsReplyInfo(otherAnswers, otherAdditionalAnswers,
20L /* sendDelayMs */, IPV4_SOCKET_ADDR, source, answers)
val (handler, message) = verifyMessageQueued(replySender, listOf(reply, knownAnswersReply))
val remainingAnswers = listOf(
MdnsPointerRecord(serviceType, 0L /* receiptTimeMillis */, false /* cacheFlush */,
LONG_TTL, otherServiceName),
MdnsServiceRecord(otherServiceName, 0L /* receiptTimeMillis */,
true /* cacheFlush */, SHORT_TTL, 0 /* servicePriority */,
0 /* serviceWeight */, TEST_PORT, otherHostname))
verifyReplySent(handler, message, remainingAnswers)
}
@Test
fun testGetReplyDestination() {
assertEquals(IPV4_SOCKET_ADDR, getReplyDestination(IPV4_SOCKET_ADDR, IPV4_SOCKET_ADDR))
assertEquals(IPV6_SOCKET_ADDR, getReplyDestination(IPV6_SOCKET_ADDR, IPV6_SOCKET_ADDR))
assertEquals(IPV4_SOCKET_ADDR, getReplyDestination(source, IPV4_SOCKET_ADDR))
assertEquals(IPV6_SOCKET_ADDR, getReplyDestination(source, IPV6_SOCKET_ADDR))
assertEquals(source, getReplyDestination(source, source))
}
}