Implement announcements on probing success

Once probing succeeds, the advertiser sends announcements for its
records as per RFC6762 8.3.

Implement MdnsRecordRepository.onProbingSucceeded to return the
AnnouncementInfo which will be sent.

Bug: 241738458
Test: atest
Change-Id: Id4c2e610911fdf471a6d6ae08c2127fbf1530dc7
This commit is contained in:
Remi NGUYEN VAN
2023-01-12 18:42:15 +09:00
parent 2bb41be9d3
commit bdc2d50c49
5 changed files with 347 additions and 23 deletions

View File

@@ -22,11 +22,10 @@ import android.os.HandlerThread
import android.os.SystemClock
import com.android.internal.util.HexDump
import com.android.server.connectivity.mdns.MdnsAnnouncer.AnnouncementInfo
import com.android.server.connectivity.mdns.MdnsRecordRepository.getReverseDnsAddress
import com.android.testutils.DevSdkIgnoreRule.IgnoreUpTo
import com.android.testutils.DevSdkIgnoreRunner
import java.net.DatagramPacket
import java.net.Inet6Address
import java.net.InetAddress
import kotlin.test.assertEquals
import kotlin.test.assertTrue
import org.junit.After
@@ -150,8 +149,8 @@ class MdnsAnnouncerTest {
val v6Addr1 = parseNumericAddress("2001:DB8::123")
val v6Addr2 = parseNumericAddress("2001:DB8::456")
val v4AddrRev = arrayOf("123", "0", "2", "192", "in-addr", "arpa")
val v6Addr1Rev = getReverseV6AddressName(v6Addr1)
val v6Addr2Rev = getReverseV6AddressName(v6Addr2)
val v6Addr1Rev = getReverseDnsAddress(v6Addr1)
val v6Addr2Rev = getReverseDnsAddress(v6Addr2)
val announcedRecords = listOf(
// Reverse address records
@@ -267,13 +266,3 @@ class MdnsAnnouncerTest {
}
}
}
/**
* Compute 2001:db8::1 --> 1.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.8.B.D.1.0.0.2.ip6.arpa
*/
private fun getReverseV6AddressName(addr: InetAddress): Array<String> {
assertTrue(addr is Inet6Address)
return addr.address.flatMapTo(mutableListOf("arpa", "ip6")) {
HexDump.toHexString(it).toCharArray().map(Char::toString)
}.reversed().toTypedArray()
}

View File

@@ -121,6 +121,8 @@ class MdnsInterfaceAdvertiserTest {
doReturn(testExitInfo).`when`(repository).exitService(TEST_SERVICE_ID_1)
advertiser.removeService(TEST_SERVICE_ID_1)
verify(prober).stop(TEST_SERVICE_ID_1)
verify(announcer).stop(TEST_SERVICE_ID_1)
verify(announcer).startSending(TEST_SERVICE_ID_1, testExitInfo, EXIT_ANNOUNCEMENT_DELAY_MS)
// TODO: after exit announcements are implemented, verify that announceCb.onFinished causes

View File

@@ -17,10 +17,12 @@
package com.android.server.connectivity.mdns
import android.net.InetAddresses.parseNumericAddress
import android.net.LinkAddress
import android.net.nsd.NsdServiceInfo
import android.os.Build
import android.os.HandlerThread
import com.android.server.connectivity.mdns.MdnsRecordRepository.Dependencies
import com.android.server.connectivity.mdns.MdnsRecordRepository.getReverseDnsAddress
import com.android.testutils.DevSdkIgnoreRule
import com.android.testutils.DevSdkIgnoreRunner
import java.net.NetworkInterface
@@ -39,10 +41,10 @@ private const val TEST_SERVICE_ID_1 = 42
private const val TEST_SERVICE_ID_2 = 43
private const val TEST_PORT = 12345
private val TEST_HOSTNAME = arrayOf("Android_000102030405060708090A0B0C0D0E0F", "local")
private val TEST_ADDRESSES = arrayOf(
parseNumericAddress("192.0.2.111"),
parseNumericAddress("2001:db8::111"),
parseNumericAddress("2001:db8::222"))
private val TEST_ADDRESSES = listOf(
LinkAddress(parseNumericAddress("192.0.2.111"), 24),
LinkAddress(parseNumericAddress("2001:db8::111"), 64),
LinkAddress(parseNumericAddress("2001:db8::222"), 64))
private val TEST_SERVICE_1 = NsdServiceInfo().apply {
serviceType = "_testservice._tcp"
@@ -50,6 +52,12 @@ private val TEST_SERVICE_1 = NsdServiceInfo().apply {
port = TEST_PORT
}
private val TEST_SERVICE_2 = NsdServiceInfo().apply {
serviceType = "_testservice._tcp"
serviceName = "MyOtherTestService"
port = TEST_PORT
}
@RunWith(DevSdkIgnoreRunner::class)
@DevSdkIgnoreRule.IgnoreUpTo(Build.VERSION_CODES.S_V2)
class MdnsRecordRepositoryTest {
@@ -57,7 +65,7 @@ class MdnsRecordRepositoryTest {
private val deps = object : Dependencies() {
override fun getHostname() = TEST_HOSTNAME
override fun getInterfaceInetAddresses(iface: NetworkInterface) =
Collections.enumeration(TEST_ADDRESSES.toList())
Collections.enumeration(TEST_ADDRESSES.map { it.address })
}
@Before
@@ -112,6 +120,15 @@ class MdnsRecordRepositoryTest {
}
}
@Test
fun testInvalidReuseOfServiceId() {
val repository = MdnsRecordRepository(thread.looper, deps)
repository.addService(TEST_SERVICE_ID_1, TEST_SERVICE_1)
assertFailsWith(IllegalArgumentException::class) {
repository.addService(TEST_SERVICE_ID_1, TEST_SERVICE_2)
}
}
@Test
fun testExitingServiceReAdded() {
val repository = MdnsRecordRepository(thread.looper, deps)
@@ -124,4 +141,131 @@ class MdnsRecordRepositoryTest {
repository.removeService(TEST_SERVICE_ID_2)
assertEquals(0, repository.servicesCount)
}
@Test
fun testOnProbingSucceeded() {
val repository = MdnsRecordRepository(thread.looper, deps)
repository.updateAddresses(TEST_ADDRESSES)
repository.addService(TEST_SERVICE_ID_1, TEST_SERVICE_1)
val probingInfo = repository.setServiceProbing(TEST_SERVICE_ID_1)
val announcementInfo = repository.onProbingSucceeded(probingInfo)
val packet = announcementInfo.getPacket(0)
assertEquals(0x8400 /* response, authoritative */, packet.flags)
assertEquals(0, packet.questions.size)
assertEquals(0, packet.authorityRecords.size)
val serviceType = arrayOf("_testservice", "_tcp", "local")
val serviceName = arrayOf("MyTestService", "_testservice", "_tcp", "local")
val v4AddrRev = getReverseDnsAddress(TEST_ADDRESSES[0].address)
val v6Addr1Rev = getReverseDnsAddress(TEST_ADDRESSES[1].address)
val v6Addr2Rev = getReverseDnsAddress(TEST_ADDRESSES[2].address)
assertContentEquals(listOf(
// Reverse address and address records for the hostname
MdnsPointerRecord(v4AddrRev,
0L /* receiptTimeMillis */,
true /* cacheFlush */,
120000L /* ttlMillis */,
TEST_HOSTNAME),
MdnsInetAddressRecord(TEST_HOSTNAME,
0L /* receiptTimeMillis */,
true /* cacheFlush */,
120000L /* ttlMillis */,
TEST_ADDRESSES[0].address),
MdnsPointerRecord(v6Addr1Rev,
0L /* receiptTimeMillis */,
true /* cacheFlush */,
120000L /* ttlMillis */,
TEST_HOSTNAME),
MdnsInetAddressRecord(TEST_HOSTNAME,
0L /* receiptTimeMillis */,
true /* cacheFlush */,
120000L /* ttlMillis */,
TEST_ADDRESSES[1].address),
MdnsPointerRecord(v6Addr2Rev,
0L /* receiptTimeMillis */,
true /* cacheFlush */,
120000L /* ttlMillis */,
TEST_HOSTNAME),
MdnsInetAddressRecord(TEST_HOSTNAME,
0L /* receiptTimeMillis */,
true /* cacheFlush */,
120000L /* ttlMillis */,
TEST_ADDRESSES[2].address),
// Service registration records (RFC6763)
MdnsPointerRecord(
serviceType,
0L /* receiptTimeMillis */,
// Not a unique name owned by the announcer, so cacheFlush=false
false /* cacheFlush */,
4500000L /* ttlMillis */,
serviceName),
MdnsServiceRecord(
serviceName,
0L /* receiptTimeMillis */,
true /* cacheFlush */,
120000L /* ttlMillis */,
0 /* servicePriority */,
0 /* serviceWeight */,
TEST_PORT /* servicePort */,
TEST_HOSTNAME),
MdnsTextRecord(
serviceName,
0L /* receiptTimeMillis */,
true /* cacheFlush */,
4500000L /* ttlMillis */,
emptyList() /* entries */),
// Service type enumeration record (RFC6763 9.)
MdnsPointerRecord(
arrayOf("_services", "_dns-sd", "_udp", "local"),
0L /* receiptTimeMillis */,
false /* cacheFlush */,
4500000L /* ttlMillis */,
serviceType)
), packet.answers)
assertContentEquals(listOf(
MdnsNsecRecord(v4AddrRev,
0L /* receiptTimeMillis */,
true /* cacheFlush */,
120000L /* ttlMillis */,
v4AddrRev,
intArrayOf(MdnsRecord.TYPE_PTR)),
MdnsNsecRecord(TEST_HOSTNAME,
0L /* receiptTimeMillis */,
true /* cacheFlush */,
120000L /* ttlMillis */,
TEST_HOSTNAME,
intArrayOf(MdnsRecord.TYPE_A, MdnsRecord.TYPE_AAAA)),
MdnsNsecRecord(v6Addr1Rev,
0L /* receiptTimeMillis */,
true /* cacheFlush */,
120000L /* ttlMillis */,
v6Addr1Rev,
intArrayOf(MdnsRecord.TYPE_PTR)),
MdnsNsecRecord(v6Addr2Rev,
0L /* receiptTimeMillis */,
true /* cacheFlush */,
120000L /* ttlMillis */,
v6Addr2Rev,
intArrayOf(MdnsRecord.TYPE_PTR)),
MdnsNsecRecord(serviceName,
0L /* receiptTimeMillis */,
true /* cacheFlush */,
4500000L /* ttlMillis */,
serviceName,
intArrayOf(MdnsRecord.TYPE_TXT, MdnsRecord.TYPE_SRV))
), packet.additionalRecords)
}
@Test
fun testGetReverseDnsAddress() {
val expectedV6 = "1.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.8.B.D.1.0.0.2.ip6.arpa"
.split(".").toTypedArray()
assertContentEquals(expectedV6, getReverseDnsAddress(parseNumericAddress("2001:db8::1")))
val expectedV4 = "123.2.0.192.in-addr.arpa".split(".").toTypedArray()
assertContentEquals(expectedV4, getReverseDnsAddress(parseNumericAddress("192.0.2.123")))
}
}