Merge changes I70384830,Ie97c2611,I7064edda,I290136b4,Ide697f65, ...

* changes:
  ethernet: add test that covers link up / down behavior
  ethernet: add updateConfiguration support to EthernetManagerTest
  ethernet: add support for enable/disableInterface in tests
  ethernet: clean up HandlerExecutor usage in test
  ethernet: address test review comments
  ethernet: get MTU size directly from TestNetworkInterface
This commit is contained in:
Patrick Rohr 2022-06-17 01:54:47 +00:00 • committed by Gerrit Code Review
commit bbae90476d

View file

@ -30,11 +30,14 @@ import android.net.EthernetManager.STATE_LINK_DOWN
import android.net.EthernetManager.STATE_LINK_UP import android.net.EthernetManager.STATE_LINK_UP
import android.net.EthernetManager.TetheredInterfaceCallback import android.net.EthernetManager.TetheredInterfaceCallback
import android.net.EthernetManager.TetheredInterfaceRequest import android.net.EthernetManager.TetheredInterfaceRequest
import android.net.EthernetNetworkManagementException
import android.net.EthernetNetworkSpecifier import android.net.EthernetNetworkSpecifier
import android.net.EthernetNetworkUpdateRequest
import android.net.InetAddresses import android.net.InetAddresses
import android.net.IpConfiguration import android.net.IpConfiguration
import android.net.MacAddress import android.net.MacAddress
import android.net.Network import android.net.Network
import android.net.NetworkCapabilities
import android.net.NetworkCapabilities.NET_CAPABILITY_TRUSTED import android.net.NetworkCapabilities.NET_CAPABILITY_TRUSTED
import android.net.NetworkCapabilities.TRANSPORT_ETHERNET import android.net.NetworkCapabilities.TRANSPORT_ETHERNET
import android.net.NetworkCapabilities.TRANSPORT_TEST import android.net.NetworkCapabilities.TRANSPORT_TEST
@ -44,8 +47,8 @@ import android.net.TestNetworkManager
import android.net.cts.EthernetManagerTest.EthernetStateListener.CallbackEntry.InterfaceStateChanged import android.net.cts.EthernetManagerTest.EthernetStateListener.CallbackEntry.InterfaceStateChanged
import android.os.Build import android.os.Build
import android.os.Handler import android.os.Handler
import android.os.HandlerExecutor
import android.os.Looper import android.os.Looper
import android.os.OutcomeReceiver
import android.os.SystemProperties import android.os.SystemProperties
import android.platform.test.annotations.AppModeFull import android.platform.test.annotations.AppModeFull
import android.util.ArraySet import android.util.ArraySet
@ -100,29 +103,34 @@ class EthernetManagerTest {
private val context by lazy { InstrumentationRegistry.getInstrumentation().context } private val context by lazy { InstrumentationRegistry.getInstrumentation().context }
private val em by lazy { context.getSystemService(EthernetManager::class.java) } private val em by lazy { context.getSystemService(EthernetManager::class.java) }
private val cm by lazy { context.getSystemService(ConnectivityManager::class.java) } private val cm by lazy { context.getSystemService(ConnectivityManager::class.java) }
private val handler by lazy { Handler(Looper.getMainLooper()) }
private val ifaceListener = EthernetStateListener() private val ifaceListener = EthernetStateListener()
private val createdIfaces = ArrayList<EthernetTestInterface>() private val createdIfaces = ArrayList<EthernetTestInterface>()
private val addedListeners = ArrayList<EthernetStateListener>() private val addedListeners = ArrayList<EthernetStateListener>()
private val networkRequests = ArrayList<TestableNetworkCallback>() private val registeredCallbacks = ArrayList<TestableNetworkCallback>()
private var tetheredInterfaceRequest: TetheredInterfaceRequest? = null private var tetheredInterfaceRequest: TetheredInterfaceRequest? = null
private class EthernetTestInterface( private class EthernetTestInterface(
context: Context, context: Context,
private val handler: Handler private val handler: Handler,
hasCarrier: Boolean
) { ) {
private val tapInterface: TestNetworkInterface private val tapInterface: TestNetworkInterface
private val packetReader: TapPacketReader private val packetReader: TapPacketReader
private val raResponder: RouterAdvertisementResponder private val raResponder: RouterAdvertisementResponder
val interfaceName get() = tapInterface.interfaceName private val tnm: TestNetworkManager
val name get() = tapInterface.interfaceName
init { init {
tapInterface = runAsShell(MANAGE_TEST_NETWORKS) { tnm = runAsShell(MANAGE_TEST_NETWORKS) {
val tnm = context.getSystemService(TestNetworkManager::class.java) context.getSystemService(TestNetworkManager::class.java)
tnm.createTapInterface(false /* bringUp */)
} }
val mtu = 1500 tapInterface = runAsShell(MANAGE_TEST_NETWORKS) {
tnm.createTapInterface(hasCarrier, false /* bringUp */)
}
val mtu = tapInterface.mtu
packetReader = TapPacketReader(handler, tapInterface.fileDescriptor.fileDescriptor, mtu) packetReader = TapPacketReader(handler, tapInterface.fileDescriptor.fileDescriptor, mtu)
raResponder = RouterAdvertisementResponder(packetReader) raResponder = RouterAdvertisementResponder(packetReader)
raResponder.addRouterEntry(MacAddress.fromString("01:23:45:67:89:ab"), raResponder.addRouterEntry(MacAddress.fromString("01:23:45:67:89:ab"),
@ -132,6 +140,12 @@ class EthernetManagerTest {
raResponder.start() raResponder.start()
} }
fun setCarrierEnabled(enabled: Boolean) {
runAsShell(MANAGE_TEST_NETWORKS) {
tnm.setCarrierEnabled(tapInterface, enabled)
}
}
fun destroy() { fun destroy() {
raResponder.stop() raResponder.stop()
handler.post({ packetReader.stop() }) handler.post({ packetReader.stop() })
@ -172,7 +186,7 @@ class EthernetManagerTest {
} }
fun expectCallback(iface: EthernetTestInterface, state: Int, role: Int) { fun expectCallback(iface: EthernetTestInterface, state: Int, role: Int) {
expectCallback(createChangeEvent(iface.interfaceName, state, role)) expectCallback(createChangeEvent(iface.name, state, role))
} }
fun createChangeEvent(iface: String, state: Int, role: Int) = fun createChangeEvent(iface: String, state: Int, role: Int) =
@ -190,7 +204,7 @@ class EthernetManagerTest {
} }
fun eventuallyExpect(iface: EthernetTestInterface, state: Int, role: Int) { fun eventuallyExpect(iface: EthernetTestInterface, state: Int, role: Int) {
eventuallyExpect(iface.interfaceName, state, role) eventuallyExpect(iface.name, state, role)
} }
fun assertNoCallback() { fun assertNoCallback() {
@ -227,6 +241,35 @@ class EthernetManagerTest {
} }
} }
private class EthernetOutcomeReceiver :
OutcomeReceiver<String, EthernetNetworkManagementException> {
private val result = CompletableFuture<String>()
override fun onResult(iface: String) {
result.complete(iface)
}
override fun onError(e: EthernetNetworkManagementException) {
result.completeExceptionally(e)
}
fun expectResult(expected: String) {
assertEquals(expected, result.get(TIMEOUT_MS, TimeUnit.MILLISECONDS))
}
fun expectError() {
// Assert that the future fails with EthernetNetworkManagementException from the
// completeExceptionally() call inside onUnavailable.
assertFailsWith(EthernetNetworkManagementException::class) {
try {
result.get()
} catch (e: ExecutionException) {
throw e.cause!!
}
}
}
}
@Before @Before
fun setUp() { fun setUp() {
setIncludeTestInterfaces(true) setIncludeTestInterfaces(true)
@ -243,26 +286,28 @@ class EthernetManagerTest {
for (listener in addedListeners) { for (listener in addedListeners) {
em.removeInterfaceStateListener(listener) em.removeInterfaceStateListener(listener)
} }
networkRequests.forEach { cm.unregisterNetworkCallback(it) } registeredCallbacks.forEach { cm.unregisterNetworkCallback(it) }
releaseTetheredInterface() releaseTetheredInterface()
} }
private fun addInterfaceStateListener(listener: EthernetStateListener) { private fun addInterfaceStateListener(listener: EthernetStateListener) {
runAsShell(CONNECTIVITY_USE_RESTRICTED_NETWORKS) { runAsShell(CONNECTIVITY_USE_RESTRICTED_NETWORKS) {
em.addInterfaceStateListener(HandlerExecutor(Handler(Looper.getMainLooper())), listener) em.addInterfaceStateListener(handler::post, listener)
} }
addedListeners.add(listener) addedListeners.add(listener)
} }
private fun createInterface(): EthernetTestInterface { private fun createInterface(hasCarrier: Boolean = true): EthernetTestInterface {
val iface = EthernetTestInterface( val iface = EthernetTestInterface(
context, context,
Handler(Looper.getMainLooper()) handler,
hasCarrier
).also { createdIfaces.add(it) } ).also { createdIfaces.add(it) }
with(ifaceListener) {
// when an interface comes up, we should always see a down cb before an up cb. // when an interface comes up, we should always see a down cb before an up cb.
eventuallyExpect(iface, STATE_LINK_DOWN, ROLE_CLIENT) ifaceListener.eventuallyExpect(iface, STATE_LINK_DOWN, ROLE_CLIENT)
expectCallback(iface, STATE_LINK_UP, ROLE_CLIENT) if (hasCarrier) {
ifaceListener.expectCallback(iface, STATE_LINK_UP, ROLE_CLIENT)
} }
return iface return iface
} }
@ -282,18 +327,20 @@ class EthernetManagerTest {
private fun requestNetwork(request: NetworkRequest): TestableNetworkCallback { private fun requestNetwork(request: NetworkRequest): TestableNetworkCallback {
return TestableNetworkCallback().also { return TestableNetworkCallback().also {
cm.requestNetwork(request, it) cm.requestNetwork(request, it)
networkRequests.add(it) registeredCallbacks.add(it)
} }
} }
private fun releaseNetwork(cb: TestableNetworkCallback) { private fun registerNetworkListener(request: NetworkRequest): TestableNetworkCallback {
cm.unregisterNetworkCallback(cb) return TestableNetworkCallback().also {
networkRequests.remove(cb) cm.registerNetworkCallback(request, it)
registeredCallbacks.add(it)
}
} }
private fun requestTetheredInterface() = TetheredInterfaceListener().also { private fun requestTetheredInterface() = TetheredInterfaceListener().also {
tetheredInterfaceRequest = runAsShell(NETWORK_SETTINGS) { tetheredInterfaceRequest = runAsShell(NETWORK_SETTINGS) {
em.requestTetheredInterface(HandlerExecutor(Handler(Looper.getMainLooper())), it) em.requestTetheredInterface(handler::post, it)
} }
} }
@ -304,22 +351,65 @@ class EthernetManagerTest {
} }
} }
private fun releaseRequest(cb: TestableNetworkCallback) {
cm.unregisterNetworkCallback(cb)
registeredCallbacks.remove(cb)
}
private fun disableInterface(iface: EthernetTestInterface) = EthernetOutcomeReceiver().also {
runAsShell(MANAGE_TEST_NETWORKS) {
em.disableInterface(iface.name, handler::post, it)
}
}
private fun enableInterface(iface: EthernetTestInterface) = EthernetOutcomeReceiver().also {
runAsShell(MANAGE_TEST_NETWORKS) {
em.enableInterface(iface.name, handler::post, it)
}
}
private fun updateConfiguration(
iface: EthernetTestInterface,
ipConfig: IpConfiguration? = null,
capabilities: NetworkCapabilities? = null
) = EthernetOutcomeReceiver().also {
runAsShell(MANAGE_TEST_NETWORKS) {
em.updateConfiguration(
iface.name,
EthernetNetworkUpdateRequest.Builder()
.setIpConfiguration(ipConfig)
.setNetworkCapabilities(capabilities).build(),
handler::post,
it)
}
}
// NetworkRequest.Builder does not create a copy of the passed NetworkRequest, so in order to
// keep ETH_REQUEST as it is, a defensive copy is created here.
private fun NetworkRequest.createCopyWithEthernetSpecifier(ifaceName: String) = private fun NetworkRequest.createCopyWithEthernetSpecifier(ifaceName: String) =
NetworkRequest.Builder(NetworkRequest(ETH_REQUEST)) NetworkRequest.Builder(NetworkRequest(ETH_REQUEST))
.setNetworkSpecifier(EthernetNetworkSpecifier(ifaceName)).build() .setNetworkSpecifier(EthernetNetworkSpecifier(ifaceName)).build()
// It can take multiple seconds for the network to become available. // It can take multiple seconds for the network to become available.
private fun TestableNetworkCallback.expectAvailable() = private fun TestableNetworkCallback.expectAvailable() =
expectCallback<Available>(anyNetwork(), 5000/*ms timeout*/).network expectCallback<Available>(anyNetwork(), 5000 /* ms timeout */).network
// b/233534110: eventuallyExpect<Lost>() does not advance ReadHead, use // b/233534110: eventuallyExpect<Lost>() does not advance ReadHead, use
// eventuallyExpect(Lost::class) instead. // eventuallyExpect(Lost::class) instead.
private fun TestableNetworkCallback.eventuallyExpectLost(n: Network? = null) = private fun TestableNetworkCallback.eventuallyExpectLost(n: Network? = null) =
eventuallyExpect(Lost::class, TIMEOUT_MS) { n?.equals(it.network) ?: true } eventuallyExpect(Lost::class, TIMEOUT_MS) { n?.equals(it.network) ?: true }
private fun TestableNetworkCallback.assertNotLost(n: Network? = null) = private fun TestableNetworkCallback.assertNeverLost(n: Network? = null) =
assertNoCallbackThat() { it is Lost && (n?.equals(it.network) ?: true) } assertNoCallbackThat() { it is Lost && (n?.equals(it.network) ?: true) }
private fun TestableNetworkCallback.assertNeverAvailable(n: Network? = null) =
assertNoCallbackThat() { it is Available && (n?.equals(it.network) ?: true) }
private fun TestableNetworkCallback.expectCapabilitiesWithInterfaceName(name: String) =
expectCapabilitiesThat(anyNetwork()) {
it.networkSpecifier == EthernetNetworkSpecifier(name)
}
@Test @Test
fun testCallbacks() { fun testCallbacks() {
// If an interface exists when the callback is registered, it is reported on registration. // If an interface exists when the callback is registered, it is reported on registration.
@ -398,11 +488,8 @@ class EthernetManagerTest {
assertTrue(polledIfaces.add(iface), "Duplicate interface $iface returned") assertTrue(polledIfaces.add(iface), "Duplicate interface $iface returned")
assertTrue(ifaces.contains(iface), "Untracked interface $iface returned") assertTrue(ifaces.contains(iface), "Untracked interface $iface returned")
// If the event's iface was created in the test, additional criteria can be validated. // If the event's iface was created in the test, additional criteria can be validated.
createdIfaces.find { it.interfaceName.equals(iface) }?.let { createdIfaces.find { it.name.equals(iface) }?.let {
assertEquals(event, assertEquals(event, listener.createChangeEvent(it.name, STATE_LINK_UP, ROLE_CLIENT))
listener.createChangeEvent(it.interfaceName,
STATE_LINK_UP,
ROLE_CLIENT))
} }
} }
// Assert all callbacks are accounted for. // Assert all callbacks are accounted for.
@ -411,87 +498,75 @@ class EthernetManagerTest {
@Test @Test
fun testGetInterfaceList() { fun testGetInterfaceList() {
setIncludeTestInterfaces(true)
// Create two test interfaces and check the return list contains the interface names. // Create two test interfaces and check the return list contains the interface names.
val iface1 = createInterface() val iface1 = createInterface()
val iface2 = createInterface() val iface2 = createInterface()
var ifaces = em.getInterfaceList() var ifaces = em.getInterfaceList()
assertTrue(ifaces.size > 0) assertTrue(ifaces.size > 0)
assertTrue(ifaces.contains(iface1.interfaceName)) assertTrue(ifaces.contains(iface1.name))
assertTrue(ifaces.contains(iface2.interfaceName)) assertTrue(ifaces.contains(iface2.name))
// Remove one existing test interface and check the return list doesn't contain the // Remove one existing test interface and check the return list doesn't contain the
// removed interface name. // removed interface name.
removeInterface(iface1) removeInterface(iface1)
ifaces = em.getInterfaceList() ifaces = em.getInterfaceList()
assertFalse(ifaces.contains(iface1.interfaceName)) assertFalse(ifaces.contains(iface1.name))
assertTrue(ifaces.contains(iface2.interfaceName)) assertTrue(ifaces.contains(iface2.name))
removeInterface(iface2) removeInterface(iface2)
} }
@Test @Test
fun testNetworkRequest_withSingleExistingInterface() { fun testNetworkRequest_withSingleExistingInterface() {
setIncludeTestInterfaces(true)
createInterface() createInterface()
// install a listener which will later be used to verify the Lost callback // install a listener which will later be used to verify the Lost callback
val listenerCb = TestableNetworkCallback() val listenerCb = registerNetworkListener(ETH_REQUEST)
cm.registerNetworkCallback(ETH_REQUEST, listenerCb)
networkRequests.add(listenerCb)
val cb = requestNetwork(ETH_REQUEST) val cb = requestNetwork(ETH_REQUEST)
val network = cb.expectAvailable() val network = cb.expectAvailable()
cb.assertNotLost() cb.assertNeverLost()
releaseNetwork(cb) releaseRequest(cb)
listenerCb.eventuallyExpectLost(network) listenerCb.eventuallyExpectLost(network)
} }
@Test @Test
fun testNetworkRequest_beforeSingleInterfaceIsUp() { fun testNetworkRequest_beforeSingleInterfaceIsUp() {
setIncludeTestInterfaces(true)
val cb = requestNetwork(ETH_REQUEST) val cb = requestNetwork(ETH_REQUEST)
// bring up interface after network has been requested // bring up interface after network has been requested.
// Note: there is no guarantee that the NetworkRequest has been processed before the
// interface is actually created. That being said, it takes a few seconds between calling
// createInterface and the interface actually being properly registered with the ethernet
// module, so it is extremely unlikely that the CS handler thread has not run until then.
val iface = createInterface() val iface = createInterface()
val network = cb.expectAvailable() val network = cb.expectAvailable()
// remove interface before network request has been removed // remove interface before network request has been removed
cb.assertNotLost() cb.assertNeverLost()
removeInterface(iface) removeInterface(iface)
cb.eventuallyExpectLost() cb.eventuallyExpectLost()
releaseNetwork(cb)
} }
@Test @Test
fun testNetworkRequest_withMultipleInterfaces() { fun testNetworkRequest_withMultipleInterfaces() {
setIncludeTestInterfaces(true)
val iface1 = createInterface() val iface1 = createInterface()
val iface2 = createInterface() val iface2 = createInterface()
val cb = requestNetwork(ETH_REQUEST.createCopyWithEthernetSpecifier(iface2.interfaceName)) val cb = requestNetwork(ETH_REQUEST.createCopyWithEthernetSpecifier(iface2.name))
val network = cb.expectAvailable() val network = cb.expectAvailable()
cb.expectCapabilitiesThat(network) { cb.expectCapabilitiesWithInterfaceName(iface2.name)
it.networkSpecifier == EthernetNetworkSpecifier(iface2.interfaceName)
}
removeInterface(iface1) removeInterface(iface1)
cb.assertNotLost() cb.assertNeverLost()
removeInterface(iface2) removeInterface(iface2)
cb.eventuallyExpectLost() cb.eventuallyExpectLost()
releaseNetwork(cb)
} }
@Test @Test
fun testNetworkRequest_withInterfaceBeingReplaced() { fun testNetworkRequest_withInterfaceBeingReplaced() {
setIncludeTestInterfaces(true)
val iface1 = createInterface() val iface1 = createInterface()
val cb = requestNetwork(ETH_REQUEST) val cb = requestNetwork(ETH_REQUEST)
@ -499,36 +574,67 @@ class EthernetManagerTest {
// create another network and verify the request sticks to the current network // create another network and verify the request sticks to the current network
val iface2 = createInterface() val iface2 = createInterface()
cb.assertNotLost() cb.assertNeverLost()
// remove iface1 and verify the request brings up iface2 // remove iface1 and verify the request brings up iface2
removeInterface(iface1) removeInterface(iface1)
cb.eventuallyExpectLost(network) cb.eventuallyExpectLost(network)
val network2 = cb.expectAvailable() val network2 = cb.expectAvailable()
releaseNetwork(cb)
} }
@Test @Test
fun testNetworkRequest_withMultipleInterfacesAndRequests() { fun testNetworkRequest_withMultipleInterfacesAndRequests() {
setIncludeTestInterfaces(true)
val iface1 = createInterface() val iface1 = createInterface()
val iface2 = createInterface() val iface2 = createInterface()
val cb1 = requestNetwork(ETH_REQUEST.createCopyWithEthernetSpecifier(iface1.interfaceName)) val cb1 = requestNetwork(ETH_REQUEST.createCopyWithEthernetSpecifier(iface1.name))
val cb2 = requestNetwork(ETH_REQUEST.createCopyWithEthernetSpecifier(iface2.interfaceName)) val cb2 = requestNetwork(ETH_REQUEST.createCopyWithEthernetSpecifier(iface2.name))
val cb3 = requestNetwork(ETH_REQUEST) val cb3 = requestNetwork(ETH_REQUEST)
cb1.expectAvailable() cb1.expectAvailable()
cb1.expectCapabilitiesWithInterfaceName(iface1.name)
cb2.expectAvailable() cb2.expectAvailable()
cb2.expectCapabilitiesWithInterfaceName(iface2.name)
// this request can be matched by either network.
cb3.expectAvailable() cb3.expectAvailable()
cb1.assertNotLost() cb1.assertNeverLost()
cb2.assertNotLost() cb2.assertNeverLost()
cb3.assertNotLost() cb3.assertNeverLost()
}
releaseNetwork(cb1) @Test
releaseNetwork(cb2) fun testNetworkRequest_ensureProperRefcounting() {
releaseNetwork(cb3) // create first request before interface is up / exists; create another request after it has
// been created; release one of them and check that the network stays up.
val listener = registerNetworkListener(ETH_REQUEST)
val cb1 = requestNetwork(ETH_REQUEST)
val iface = createInterface()
val network = cb1.expectAvailable()
val cb2 = requestNetwork(ETH_REQUEST)
cb2.expectAvailable()
// release the first request; this used to trigger b/197548738
releaseRequest(cb1)
cb2.assertNeverLost()
releaseRequest(cb2)
listener.eventuallyExpectLost(network)
}
@Test
fun testNetworkRequest_forInterfaceWhileTogglingCarrier() {
val iface = createInterface(false /* hasCarrier */)
val cb = requestNetwork(ETH_REQUEST)
cb.assertNeverAvailable()
iface.setCarrierEnabled(true)
cb.expectAvailable()
iface.setCarrierEnabled(false)
cb.eventuallyExpectLost()
} }
} }