diff --git a/tests/cts/net/src/android/net/cts/EthernetManagerTest.kt b/tests/cts/net/src/android/net/cts/EthernetManagerTest.kt index db24b4493b..d0d44dcc47 100644 --- a/tests/cts/net/src/android/net/cts/EthernetManagerTest.kt +++ b/tests/cts/net/src/android/net/cts/EthernetManagerTest.kt @@ -30,11 +30,14 @@ import android.net.EthernetManager.STATE_LINK_DOWN import android.net.EthernetManager.STATE_LINK_UP import android.net.EthernetManager.TetheredInterfaceCallback import android.net.EthernetManager.TetheredInterfaceRequest +import android.net.EthernetNetworkManagementException import android.net.EthernetNetworkSpecifier +import android.net.EthernetNetworkUpdateRequest import android.net.InetAddresses import android.net.IpConfiguration import android.net.MacAddress import android.net.Network +import android.net.NetworkCapabilities import android.net.NetworkCapabilities.NET_CAPABILITY_TRUSTED import android.net.NetworkCapabilities.TRANSPORT_ETHERNET import android.net.NetworkCapabilities.TRANSPORT_TEST @@ -44,8 +47,8 @@ import android.net.TestNetworkManager import android.net.cts.EthernetManagerTest.EthernetStateListener.CallbackEntry.InterfaceStateChanged import android.os.Build import android.os.Handler -import android.os.HandlerExecutor import android.os.Looper +import android.os.OutcomeReceiver import android.os.SystemProperties import android.platform.test.annotations.AppModeFull import android.util.ArraySet @@ -100,29 +103,34 @@ class EthernetManagerTest { private val context by lazy { InstrumentationRegistry.getInstrumentation().context } private val em by lazy { context.getSystemService(EthernetManager::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 createdIfaces = ArrayList() private val addedListeners = ArrayList() - private val networkRequests = ArrayList() + private val registeredCallbacks = ArrayList() private var tetheredInterfaceRequest: TetheredInterfaceRequest? = null private class EthernetTestInterface( context: Context, - private val handler: Handler + private val handler: Handler, + hasCarrier: Boolean ) { private val tapInterface: TestNetworkInterface private val packetReader: TapPacketReader private val raResponder: RouterAdvertisementResponder - val interfaceName get() = tapInterface.interfaceName + private val tnm: TestNetworkManager + val name get() = tapInterface.interfaceName init { - tapInterface = runAsShell(MANAGE_TEST_NETWORKS) { - val tnm = context.getSystemService(TestNetworkManager::class.java) - tnm.createTapInterface(false /* bringUp */) + tnm = runAsShell(MANAGE_TEST_NETWORKS) { + context.getSystemService(TestNetworkManager::class.java) } - val mtu = 1500 + tapInterface = runAsShell(MANAGE_TEST_NETWORKS) { + tnm.createTapInterface(hasCarrier, false /* bringUp */) + } + val mtu = tapInterface.mtu packetReader = TapPacketReader(handler, tapInterface.fileDescriptor.fileDescriptor, mtu) raResponder = RouterAdvertisementResponder(packetReader) raResponder.addRouterEntry(MacAddress.fromString("01:23:45:67:89:ab"), @@ -132,6 +140,12 @@ class EthernetManagerTest { raResponder.start() } + fun setCarrierEnabled(enabled: Boolean) { + runAsShell(MANAGE_TEST_NETWORKS) { + tnm.setCarrierEnabled(tapInterface, enabled) + } + } + fun destroy() { raResponder.stop() handler.post({ packetReader.stop() }) @@ -172,7 +186,7 @@ class EthernetManagerTest { } 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) = @@ -190,7 +204,7 @@ class EthernetManagerTest { } fun eventuallyExpect(iface: EthernetTestInterface, state: Int, role: Int) { - eventuallyExpect(iface.interfaceName, state, role) + eventuallyExpect(iface.name, state, role) } fun assertNoCallback() { @@ -227,6 +241,35 @@ class EthernetManagerTest { } } + private class EthernetOutcomeReceiver : + OutcomeReceiver { + private val result = CompletableFuture() + + 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 fun setUp() { setIncludeTestInterfaces(true) @@ -243,26 +286,28 @@ class EthernetManagerTest { for (listener in addedListeners) { em.removeInterfaceStateListener(listener) } - networkRequests.forEach { cm.unregisterNetworkCallback(it) } + registeredCallbacks.forEach { cm.unregisterNetworkCallback(it) } releaseTetheredInterface() } private fun addInterfaceStateListener(listener: EthernetStateListener) { runAsShell(CONNECTIVITY_USE_RESTRICTED_NETWORKS) { - em.addInterfaceStateListener(HandlerExecutor(Handler(Looper.getMainLooper())), listener) + em.addInterfaceStateListener(handler::post, listener) } addedListeners.add(listener) } - private fun createInterface(): EthernetTestInterface { + private fun createInterface(hasCarrier: Boolean = true): EthernetTestInterface { val iface = EthernetTestInterface( context, - Handler(Looper.getMainLooper()) + handler, + hasCarrier ).also { createdIfaces.add(it) } - with(ifaceListener) { - // when an interface comes up, we should always see a down cb before an up cb. - eventuallyExpect(iface, STATE_LINK_DOWN, ROLE_CLIENT) - expectCallback(iface, STATE_LINK_UP, ROLE_CLIENT) + + // when an interface comes up, we should always see a down cb before an up cb. + ifaceListener.eventuallyExpect(iface, STATE_LINK_DOWN, ROLE_CLIENT) + if (hasCarrier) { + ifaceListener.expectCallback(iface, STATE_LINK_UP, ROLE_CLIENT) } return iface } @@ -282,18 +327,20 @@ class EthernetManagerTest { private fun requestNetwork(request: NetworkRequest): TestableNetworkCallback { return TestableNetworkCallback().also { cm.requestNetwork(request, it) - networkRequests.add(it) + registeredCallbacks.add(it) } } - private fun releaseNetwork(cb: TestableNetworkCallback) { - cm.unregisterNetworkCallback(cb) - networkRequests.remove(cb) + private fun registerNetworkListener(request: NetworkRequest): TestableNetworkCallback { + return TestableNetworkCallback().also { + cm.registerNetworkCallback(request, it) + registeredCallbacks.add(it) + } } private fun requestTetheredInterface() = TetheredInterfaceListener().also { 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) = NetworkRequest.Builder(NetworkRequest(ETH_REQUEST)) .setNetworkSpecifier(EthernetNetworkSpecifier(ifaceName)).build() // It can take multiple seconds for the network to become available. private fun TestableNetworkCallback.expectAvailable() = - expectCallback(anyNetwork(), 5000/*ms timeout*/).network + expectCallback(anyNetwork(), 5000 /* ms timeout */).network // b/233534110: eventuallyExpect() does not advance ReadHead, use // eventuallyExpect(Lost::class) instead. private fun TestableNetworkCallback.eventuallyExpectLost(n: Network? = null) = 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) } + 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 fun testCallbacks() { // 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(ifaces.contains(iface), "Untracked interface $iface returned") // If the event's iface was created in the test, additional criteria can be validated. - createdIfaces.find { it.interfaceName.equals(iface) }?.let { - assertEquals(event, - listener.createChangeEvent(it.interfaceName, - STATE_LINK_UP, - ROLE_CLIENT)) + createdIfaces.find { it.name.equals(iface) }?.let { + assertEquals(event, listener.createChangeEvent(it.name, STATE_LINK_UP, ROLE_CLIENT)) } } // Assert all callbacks are accounted for. @@ -411,87 +498,75 @@ class EthernetManagerTest { @Test fun testGetInterfaceList() { - setIncludeTestInterfaces(true) - // Create two test interfaces and check the return list contains the interface names. val iface1 = createInterface() val iface2 = createInterface() var ifaces = em.getInterfaceList() assertTrue(ifaces.size > 0) - assertTrue(ifaces.contains(iface1.interfaceName)) - assertTrue(ifaces.contains(iface2.interfaceName)) + assertTrue(ifaces.contains(iface1.name)) + assertTrue(ifaces.contains(iface2.name)) // Remove one existing test interface and check the return list doesn't contain the // removed interface name. removeInterface(iface1) ifaces = em.getInterfaceList() - assertFalse(ifaces.contains(iface1.interfaceName)) - assertTrue(ifaces.contains(iface2.interfaceName)) + assertFalse(ifaces.contains(iface1.name)) + assertTrue(ifaces.contains(iface2.name)) removeInterface(iface2) } @Test fun testNetworkRequest_withSingleExistingInterface() { - setIncludeTestInterfaces(true) createInterface() // install a listener which will later be used to verify the Lost callback - val listenerCb = TestableNetworkCallback() - cm.registerNetworkCallback(ETH_REQUEST, listenerCb) - networkRequests.add(listenerCb) + val listenerCb = registerNetworkListener(ETH_REQUEST) val cb = requestNetwork(ETH_REQUEST) val network = cb.expectAvailable() - cb.assertNotLost() - releaseNetwork(cb) + cb.assertNeverLost() + releaseRequest(cb) listenerCb.eventuallyExpectLost(network) } @Test fun testNetworkRequest_beforeSingleInterfaceIsUp() { - setIncludeTestInterfaces(true) - 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 network = cb.expectAvailable() // remove interface before network request has been removed - cb.assertNotLost() + cb.assertNeverLost() removeInterface(iface) cb.eventuallyExpectLost() - - releaseNetwork(cb) } @Test fun testNetworkRequest_withMultipleInterfaces() { - setIncludeTestInterfaces(true) - val iface1 = createInterface() val iface2 = createInterface() - val cb = requestNetwork(ETH_REQUEST.createCopyWithEthernetSpecifier(iface2.interfaceName)) + val cb = requestNetwork(ETH_REQUEST.createCopyWithEthernetSpecifier(iface2.name)) val network = cb.expectAvailable() - cb.expectCapabilitiesThat(network) { - it.networkSpecifier == EthernetNetworkSpecifier(iface2.interfaceName) - } + cb.expectCapabilitiesWithInterfaceName(iface2.name) removeInterface(iface1) - cb.assertNotLost() + cb.assertNeverLost() removeInterface(iface2) cb.eventuallyExpectLost() - - releaseNetwork(cb) } @Test fun testNetworkRequest_withInterfaceBeingReplaced() { - setIncludeTestInterfaces(true) val iface1 = createInterface() val cb = requestNetwork(ETH_REQUEST) @@ -499,36 +574,67 @@ class EthernetManagerTest { // create another network and verify the request sticks to the current network val iface2 = createInterface() - cb.assertNotLost() + cb.assertNeverLost() // remove iface1 and verify the request brings up iface2 removeInterface(iface1) cb.eventuallyExpectLost(network) val network2 = cb.expectAvailable() - - releaseNetwork(cb) } @Test fun testNetworkRequest_withMultipleInterfacesAndRequests() { - setIncludeTestInterfaces(true) val iface1 = createInterface() val iface2 = createInterface() - val cb1 = requestNetwork(ETH_REQUEST.createCopyWithEthernetSpecifier(iface1.interfaceName)) - val cb2 = requestNetwork(ETH_REQUEST.createCopyWithEthernetSpecifier(iface2.interfaceName)) + val cb1 = requestNetwork(ETH_REQUEST.createCopyWithEthernetSpecifier(iface1.name)) + val cb2 = requestNetwork(ETH_REQUEST.createCopyWithEthernetSpecifier(iface2.name)) val cb3 = requestNetwork(ETH_REQUEST) cb1.expectAvailable() + cb1.expectCapabilitiesWithInterfaceName(iface1.name) cb2.expectAvailable() + cb2.expectCapabilitiesWithInterfaceName(iface2.name) + // this request can be matched by either network. cb3.expectAvailable() - cb1.assertNotLost() - cb2.assertNotLost() - cb3.assertNotLost() + cb1.assertNeverLost() + cb2.assertNeverLost() + cb3.assertNeverLost() + } - releaseNetwork(cb1) - releaseNetwork(cb2) - releaseNetwork(cb3) + @Test + fun testNetworkRequest_ensureProperRefcounting() { + // 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() } }