blob: e46ba24867ff5f697fe3e78974f755062c54c701 [file] [log] [blame]
/*
* Copyright (C) 2022 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package android.net.cts
import android.Manifest.permission.CONNECTIVITY_USE_RESTRICTED_NETWORKS
import android.Manifest.permission.MANAGE_TEST_NETWORKS
import android.Manifest.permission.NETWORK_SETTINGS
import android.content.Context
import android.net.ConnectivityManager
import android.net.EthernetManager
import android.net.EthernetManager.ETHERNET_STATE_DISABLED
import android.net.EthernetManager.ETHERNET_STATE_ENABLED
import android.net.EthernetManager.InterfaceStateListener
import android.net.EthernetManager.ROLE_CLIENT
import android.net.EthernetManager.ROLE_NONE
import android.net.EthernetManager.ROLE_SERVER
import android.net.EthernetManager.STATE_ABSENT
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.LinkAddress
import android.net.MacAddress
import android.net.Network
import android.net.NetworkCapabilities
import android.net.NetworkCapabilities.NET_CAPABILITY_TEMPORARILY_NOT_METERED
import android.net.NetworkCapabilities.NET_CAPABILITY_TRUSTED
import android.net.NetworkCapabilities.TRANSPORT_ETHERNET
import android.net.NetworkCapabilities.TRANSPORT_TEST
import android.net.NetworkRequest
import android.net.StaticIpConfiguration
import android.net.TestNetworkInterface
import android.net.TestNetworkManager
import android.net.cts.EthernetManagerTest.EthernetStateListener.CallbackEntry.EthernetStateChanged
import android.net.cts.EthernetManagerTest.EthernetStateListener.CallbackEntry.InterfaceStateChanged
import android.os.Build
import android.os.Handler
import android.os.Looper
import android.os.OutcomeReceiver
import android.os.SystemProperties
import android.platform.test.annotations.AppModeFull
import android.util.ArraySet
import androidx.test.platform.app.InstrumentationRegistry
import com.android.net.module.util.ArrayTrackRecord
import com.android.net.module.util.TrackRecord
import com.android.testutils.ConnectivityModuleTest
import com.android.testutils.DevSdkIgnoreRule
import com.android.testutils.DevSdkIgnoreRunner
import com.android.testutils.DeviceInfoUtils.isKernelVersionAtLeast
import com.android.testutils.RecorderCallback.CallbackEntry.Available
import com.android.testutils.RecorderCallback.CallbackEntry.CapabilitiesChanged
import com.android.testutils.RecorderCallback.CallbackEntry.Lost
import com.android.testutils.RouterAdvertisementResponder
import com.android.testutils.SkipPresubmit
import com.android.testutils.TapPacketReader
import com.android.testutils.TestableNetworkCallback
import com.android.testutils.anyNetwork
import com.android.testutils.runAsShell
import com.android.testutils.waitForIdle
import org.junit.After
import org.junit.Assume.assumeFalse
import org.junit.Assume.assumeTrue
import org.junit.Before
import org.junit.Ignore
import org.junit.Test
import org.junit.runner.RunWith
import java.net.Inet6Address
import java.util.Random
import java.util.concurrent.CompletableFuture
import java.util.concurrent.ExecutionException
import java.util.concurrent.TimeUnit
import java.util.concurrent.TimeoutException
import java.util.function.IntConsumer
import kotlin.test.assertEquals
import kotlin.test.assertFailsWith
import kotlin.test.assertFalse
import kotlin.test.assertNotNull
import kotlin.test.assertNull
import kotlin.test.assertTrue
import kotlin.test.fail
private const val TAG = "EthernetManagerTest"
private const val TIMEOUT_MS = 1000L
// Timeout used to confirm no callbacks matching given criteria are received. Must be long enough to
// process all callbacks including ip provisioning when using the updateConfiguration API.
// Note that increasing this timeout increases the test duration.
private const val NO_CALLBACK_TIMEOUT_MS = 200L
private val DEFAULT_IP_CONFIGURATION = IpConfiguration(IpConfiguration.IpAssignment.DHCP,
IpConfiguration.ProxySettings.NONE, null, null)
private val ETH_REQUEST: NetworkRequest = NetworkRequest.Builder()
.addTransportType(TRANSPORT_TEST)
.addTransportType(TRANSPORT_ETHERNET)
.removeCapability(NET_CAPABILITY_TRUSTED)
.build()
private val STATIC_IP_CONFIGURATION = IpConfiguration.Builder()
.setStaticIpConfiguration(StaticIpConfiguration.Builder()
.setIpAddress(LinkAddress("192.0.2.1/30")).build())
.build()
@AppModeFull(reason = "Instant apps can't access EthernetManager")
// EthernetManager is not updatable before T, so tests do not need to be backwards compatible.
@RunWith(DevSdkIgnoreRunner::class)
// This test depends on behavior introduced post-T as part of connectivity module updates
@ConnectivityModuleTest
@SkipPresubmit(reason = "Flaky: b/240323229; remove annotation after fixing")
@DevSdkIgnoreRule.IgnoreUpTo(Build.VERSION_CODES.S_V2)
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<EthernetTestInterface>()
private val addedListeners = ArrayList<EthernetStateListener>()
private val registeredCallbacks = ArrayList<TestableNetworkCallback>()
private var tetheredInterfaceRequest: TetheredInterfaceRequest? = null
private class EthernetTestInterface(
context: Context,
private val handler: Handler,
hasCarrier: Boolean
) {
private val tapInterface: TestNetworkInterface
private val packetReader: TapPacketReader
private val raResponder: RouterAdvertisementResponder
private val tnm: TestNetworkManager
val name get() = tapInterface.interfaceName
init {
tnm = runAsShell(MANAGE_TEST_NETWORKS) {
context.getSystemService(TestNetworkManager::class.java)
}
tapInterface = runAsShell(MANAGE_TEST_NETWORKS) {
// setting RS delay to 0 and disabling DAD speeds up tests.
tnm.createTapInterface(hasCarrier, false /* bringUp */,
true /* disableIpv6ProvisioningDelay */)
}
val mtu = tapInterface.mtu
packetReader = TapPacketReader(handler, tapInterface.fileDescriptor.fileDescriptor, mtu)
raResponder = RouterAdvertisementResponder(packetReader)
val iidString = "fe80::${Integer.toHexString(Random().nextInt(65536))}"
val linklocal = InetAddresses.parseNumericAddress(iidString) as Inet6Address
raResponder.addRouterEntry(MacAddress.fromString("01:23:45:67:89:ab"), linklocal)
packetReader.startAsyncForTest()
raResponder.start()
}
// WARNING: this function requires kernel support. Call assumeChangingCarrierSupported() at
// the top of your test.
fun setCarrierEnabled(enabled: Boolean) {
runAsShell(MANAGE_TEST_NETWORKS) {
tnm.setCarrierEnabled(tapInterface, enabled)
}
}
fun destroy() {
raResponder.stop()
handler.post({ packetReader.stop() })
handler.waitForIdle(TIMEOUT_MS)
}
}
private open class EthernetStateListener private constructor(
private val history: ArrayTrackRecord<CallbackEntry>
) : InterfaceStateListener, IntConsumer,
TrackRecord<EthernetStateListener.CallbackEntry> by history {
constructor() : this(ArrayTrackRecord())
val events = history.newReadHead()
sealed class CallbackEntry {
data class InterfaceStateChanged(
val iface: String,
val state: Int,
val role: Int,
val configuration: IpConfiguration?
) : CallbackEntry() {
override fun toString(): String {
val stateString = when (state) {
STATE_ABSENT -> "STATE_ABSENT"
STATE_LINK_UP -> "STATE_LINK_UP"
STATE_LINK_DOWN -> "STATE_LINK_DOWN"
else -> state.toString()
}
val roleString = when (role) {
ROLE_NONE -> "ROLE_NONE"
ROLE_CLIENT -> "ROLE_CLIENT"
ROLE_SERVER -> "ROLE_SERVER"
else -> role.toString()
}
return ("InterfaceStateChanged(iface=$iface, state=$stateString, " +
"role=$roleString, ipConfig=$configuration)")
}
}
data class EthernetStateChanged(val state: Int) : CallbackEntry() {
override fun toString(): String {
val stateString = when (state) {
ETHERNET_STATE_ENABLED -> "ETHERNET_STATE_ENABLED"
ETHERNET_STATE_DISABLED -> "ETHERNET_STATE_DISABLED"
else -> state.toString()
}
return "EthernetStateChanged(state=$stateString)"
}
}
}
override fun onInterfaceStateChanged(
iface: String,
state: Int,
role: Int,
cfg: IpConfiguration?
) {
add(InterfaceStateChanged(iface, state, role, cfg))
}
override fun accept(state: Int) {
add(EthernetStateChanged(state))
}
fun <T : CallbackEntry> expectCallback(expected: T): T {
val event = pollForNextCallback()
assertEquals(expected, event)
return event as T
}
fun expectCallback(iface: EthernetTestInterface, state: Int, role: Int) {
expectCallback(createChangeEvent(iface.name, state, role))
}
fun expectCallback(state: Int) {
expectCallback(EthernetStateChanged(state))
}
fun createChangeEvent(iface: String, state: Int, role: Int) =
InterfaceStateChanged(iface, state, role,
if (state != STATE_ABSENT) DEFAULT_IP_CONFIGURATION else null)
fun pollForNextCallback(): CallbackEntry {
return events.poll(TIMEOUT_MS) ?: fail("Did not receive callback after ${TIMEOUT_MS}ms")
}
fun eventuallyExpect(expected: CallbackEntry) = events.poll(TIMEOUT_MS) { it == expected }
fun eventuallyExpect(iface: EthernetTestInterface, state: Int, role: Int) {
val event = createChangeEvent(iface.name, state, role)
assertNotNull(eventuallyExpect(event), "Never received expected $event")
}
fun eventuallyExpect(state: Int) {
val event = EthernetStateChanged(state)
assertNotNull(eventuallyExpect(event), "Never received expected $event")
}
fun assertNoCallback() {
val cb = events.poll(NO_CALLBACK_TIMEOUT_MS)
assertNull(cb, "Expected no callback but got $cb")
}
}
private class TetheredInterfaceListener : TetheredInterfaceCallback {
private val available = CompletableFuture<String>()
override fun onAvailable(iface: String) {
available.complete(iface)
}
override fun onUnavailable() {
available.completeExceptionally(IllegalStateException("onUnavailable was called"))
}
fun expectOnAvailable(): String {
return available.get(TIMEOUT_MS, TimeUnit.MILLISECONDS)
}
fun expectOnUnavailable() {
// Assert that the future fails with the IllegalStateException from the
// completeExceptionally() call inside onUnavailable.
assertFailsWith(IllegalStateException::class) {
try {
available.get(TIMEOUT_MS, TimeUnit.MILLISECONDS)
} catch (e: ExecutionException) {
throw e.cause!!
}
}
}
}
private class EthernetOutcomeReceiver :
OutcomeReceiver<String, EthernetNetworkManagementException> {
private val result = CompletableFuture<String>()
override fun onResult(iface: String) {
assertFalse(result.isDone())
result.complete(iface)
}
override fun onError(e: EthernetNetworkManagementException) {
assertFalse(result.isDone())
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!!
}
}
}
}
private fun isEthernetSupported() = em != null
@Before
fun setUp() {
assumeTrue(isEthernetSupported())
setIncludeTestInterfaces(true)
addInterfaceStateListener(ifaceListener)
// Handler.post() events may get processed after native fd events, so it is possible that
// RTM_NEWLINK (from a subsequent createInterface() call) arrives before the interface state
// listener is registered. This affects the callbacks and breaks the tests.
// setEthernetEnabled() will always wait on a callback, so it is used as a barrier to ensure
// proper listener registration before proceeding.
setEthernetEnabled(true)
}
@After
fun tearDown() {
if (!isEthernetSupported()) return
// Reenable ethernet, so ABSENT callbacks are received.
setEthernetEnabled(true)
for (iface in createdIfaces) {
iface.destroy()
ifaceListener.eventuallyExpect(iface, STATE_ABSENT, ROLE_NONE)
}
// After test interfaces are removed, disable tracking.
setIncludeTestInterfaces(false)
for (listener in addedListeners) {
em.removeInterfaceStateListener(listener)
}
registeredCallbacks.forEach { cm.unregisterNetworkCallback(it) }
releaseTetheredInterface()
}
// Setting the carrier up / down relies on TUNSETCARRIER which was added in kernel version 5.0.
private fun assumeChangingCarrierSupported() = assumeTrue(isKernelVersionAtLeast("5.0.0"))
private fun isAdbOverEthernet(): Boolean {
// If no ethernet interface is available, adb is not connected over ethernet.
if (em.getInterfaceList().isEmpty()) return false
// cuttlefish is special and does not connect adb over ethernet.
if (SystemProperties.get("ro.product.board", "") == "cutf") return false
// Check if adb is connected over the network.
return (SystemProperties.getInt("persist.adb.tcp.port", -1) > -1 ||
SystemProperties.getInt("service.adb.tcp.port", -1) > -1)
}
private fun addInterfaceStateListener(listener: EthernetStateListener) {
runAsShell(CONNECTIVITY_USE_RESTRICTED_NETWORKS) {
em.addInterfaceStateListener(handler::post, listener)
}
addedListeners.add(listener)
}
// WARNING: setting hasCarrier to false requires kernel support. Call
// assumeChangingCarrierSupported() at the top of your test.
private fun createInterface(hasCarrier: Boolean = true): EthernetTestInterface {
val iface = EthernetTestInterface(
context,
handler,
hasCarrier
).also { createdIfaces.add(it) }
// 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
}
private fun setIncludeTestInterfaces(value: Boolean) {
runAsShell(NETWORK_SETTINGS) {
em.setIncludeTestInterfaces(value)
}
}
private fun removeInterface(iface: EthernetTestInterface) {
iface.destroy()
createdIfaces.remove(iface)
ifaceListener.eventuallyExpect(iface, STATE_ABSENT, ROLE_NONE)
}
private fun requestNetwork(request: NetworkRequest): TestableNetworkCallback {
return TestableNetworkCallback().also {
cm.requestNetwork(request, it)
registeredCallbacks.add(it)
}
}
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(handler::post, it)
}
}
private fun releaseTetheredInterface() {
runAsShell(NETWORK_SETTINGS) {
tetheredInterfaceRequest?.release()
tetheredInterfaceRequest = null
}
}
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)
}
}
// WARNING: check that isAdbOverEthernet() is false before calling setEthernetEnabled(false).
private fun setEthernetEnabled(enabled: Boolean) {
runAsShell(NETWORK_SETTINGS) { em.setEthernetEnabled(enabled) }
val listener = EthernetStateListener()
em.addEthernetStateListener(handler::post, listener)
try {
listener.eventuallyExpect(
if (enabled) ETHERNET_STATE_ENABLED else ETHERNET_STATE_DISABLED)
} finally {
em.removeEthernetStateListener(listener)
}
}
// 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<Available>(anyNetwork(), 5000 /* ms timeout */).network
private fun TestableNetworkCallback.expectLost(n: Network = anyNetwork()) =
expectCallback<Lost>(n, 5000 /* ms timeout */)
// b/233534110: eventuallyExpect<Lost>() 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.assertNeverLost(n: Network? = null) =
assertNoCallbackThat(NO_CALLBACK_TIMEOUT_MS) {
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)
}
private fun TestableNetworkCallback.expectCapabilitiesWithCapability(cap: Int) =
expectCapabilitiesThat(anyNetwork(), TIMEOUT_MS) {
it.hasCapability(cap)
}
private fun TestableNetworkCallback.expectLinkPropertiesWithLinkAddress(addr: LinkAddress) =
expectLinkPropertiesThat(anyNetwork(), TIMEOUT_MS) {
// LinkAddress.equals isn't possible as the system changes the LinkAddress.flags value.
// any() must be used since the interface may also have a link-local address.
it.linkAddresses.any { x -> x.isSameAddressAs(addr) }
}
@Test
fun testCallbacks() {
// If an interface exists when the callback is registered, it is reported on registration.
val iface = createInterface()
val listener1 = EthernetStateListener()
addInterfaceStateListener(listener1)
validateListenerOnRegistration(listener1)
// If an interface appears, existing callbacks see it.
val iface2 = createInterface()
listener1.expectCallback(iface2, STATE_LINK_DOWN, ROLE_CLIENT)
listener1.expectCallback(iface2, STATE_LINK_UP, ROLE_CLIENT)
// Register a new listener, it should see state of all existing interfaces immediately.
val listener2 = EthernetStateListener()
addInterfaceStateListener(listener2)
validateListenerOnRegistration(listener2)
// Removing interfaces first sends link down, then STATE_ABSENT/ROLE_NONE.
removeInterface(iface)
for (listener in listOf(listener1, listener2)) {
listener.expectCallback(iface, STATE_LINK_DOWN, ROLE_CLIENT)
listener.expectCallback(iface, STATE_ABSENT, ROLE_NONE)
}
removeInterface(iface2)
for (listener in listOf(listener1, listener2)) {
listener.expectCallback(iface2, STATE_LINK_DOWN, ROLE_CLIENT)
listener.expectCallback(iface2, STATE_ABSENT, ROLE_NONE)
listener.assertNoCallback()
}
}
@Test
fun testCallbacks_withRunningInterface() {
// This test disables ethernet, so check that adb is not connected over ethernet.
assumeFalse(isAdbOverEthernet())
assumeTrue(em.getInterfaceList().isEmpty())
val iface = createInterface()
val listener = EthernetStateListener()
addInterfaceStateListener(listener)
listener.eventuallyExpect(iface, STATE_LINK_UP, ROLE_CLIENT)
// Remove running interface. The interface stays running but is no longer tracked.
setEthernetEnabled(false)
listener.expectCallback(iface, STATE_ABSENT, ROLE_NONE)
setEthernetEnabled(true)
listener.expectCallback(iface, STATE_LINK_UP, ROLE_CLIENT)
listener.expectCallback(iface, STATE_LINK_UP, ROLE_CLIENT)
listener.assertNoCallback()
}
private fun assumeNoInterfaceForTetheringAvailable() {
// Interfaces that have configured NetworkCapabilities will never be used for tethering,
// see aosp/2123900.
try {
// assumeException does not exist.
requestTetheredInterface().expectOnAvailable()
// interface used for tethering is available, throw an assumption error.
assumeTrue(false)
} catch (e: TimeoutException) {
// do nothing -- the TimeoutException indicates that no interface is available for
// tethering.
releaseTetheredInterface()
}
}
@Test
fun testCallbacks_forServerModeInterfaces() {
// do not run this test if an interface that can be used for tethering already exists.
assumeNoInterfaceForTetheringAvailable()
val iface = createInterface()
requestTetheredInterface().expectOnAvailable()
val listener = EthernetStateListener()
addInterfaceStateListener(listener)
// TODO(b/236895792): THIS IS A BUG! Existing server mode interfaces are not reported when
// an InterfaceStateListener is registered.
// Note: using eventuallyExpect as there may be other interfaces present.
// listener.eventuallyExpect(iface, STATE_LINK_UP, ROLE_SERVER)
releaseTetheredInterface()
listener.eventuallyExpect(iface, STATE_LINK_UP, ROLE_CLIENT)
requestTetheredInterface().expectOnAvailable()
// This should be changed to expectCallback, once b/236895792 is fixed.
listener.eventuallyExpect(iface, STATE_LINK_UP, ROLE_SERVER)
releaseTetheredInterface()
listener.expectCallback(iface, STATE_LINK_UP, ROLE_CLIENT)
}
/**
* Validate all interfaces are returned for an EthernetStateListener upon registration.
*/
private fun validateListenerOnRegistration(listener: EthernetStateListener) {
// Get all tracked interfaces to validate on listener registration. Ordering and interface
// state (up/down) can't be validated for interfaces not created as part of testing.
val ifaces = em.getInterfaceList()
val polledIfaces = ArraySet<String>()
for (i in ifaces) {
val event = (listener.pollForNextCallback() as InterfaceStateChanged)
val iface = event.iface
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.name.equals(iface) }?.let {
assertEquals(event, listener.createChangeEvent(it.name, STATE_LINK_UP, ROLE_CLIENT))
}
}
// Assert all callbacks are accounted for.
listener.assertNoCallback()
}
@Test
fun testGetInterfaceList() {
// 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.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.name))
assertTrue(ifaces.contains(iface2.name))
removeInterface(iface2)
}
@Test
fun testNetworkRequest_withSingleExistingInterface() {
createInterface()
// install a listener which will later be used to verify the Lost callback
val listenerCb = registerNetworkListener(ETH_REQUEST)
val cb = requestNetwork(ETH_REQUEST)
val network = cb.expectAvailable()
cb.assertNeverLost()
releaseRequest(cb)
listenerCb.eventuallyExpectLost(network)
}
@Test
fun testNetworkRequest_beforeSingleInterfaceIsUp() {
val cb = requestNetwork(ETH_REQUEST)
// 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.assertNeverLost()
removeInterface(iface)
cb.eventuallyExpectLost()
}
@Test
fun testNetworkRequest_withMultipleInterfaces() {
val iface1 = createInterface()
val iface2 = createInterface()
val cb = requestNetwork(ETH_REQUEST.createCopyWithEthernetSpecifier(iface2.name))
val network = cb.expectAvailable()
cb.expectCapabilitiesWithInterfaceName(iface2.name)
removeInterface(iface1)
cb.assertNeverLost()
removeInterface(iface2)
cb.eventuallyExpectLost()
}
@Test
fun testNetworkRequest_withInterfaceBeingReplaced() {
val iface1 = createInterface()
val cb = requestNetwork(ETH_REQUEST)
val network = cb.expectAvailable()
// create another network and verify the request sticks to the current network
val iface2 = createInterface()
cb.assertNeverLost()
// remove iface1 and verify the request brings up iface2
removeInterface(iface1)
cb.eventuallyExpectLost(network)
val network2 = cb.expectAvailable()
}
@Test
fun testNetworkRequest_withMultipleInterfacesAndRequests() {
val iface1 = createInterface()
val iface2 = createInterface()
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.assertNeverLost()
cb2.assertNeverLost()
cb3.assertNeverLost()
}
@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() {
assumeChangingCarrierSupported()
val iface = createInterface(false /* hasCarrier */)
val cb = requestNetwork(ETH_REQUEST)
cb.assertNeverAvailable()
iface.setCarrierEnabled(true)
cb.expectAvailable()
iface.setCarrierEnabled(false)
cb.eventuallyExpectLost()
}
@Test
fun testRemoveInterface_whileInServerMode() {
assumeNoInterfaceForTetheringAvailable()
val listener = EthernetStateListener()
addInterfaceStateListener(listener)
val iface = createInterface()
val ifaceName = requestTetheredInterface().expectOnAvailable()
assertEquals(iface.name, ifaceName)
listener.eventuallyExpect(iface, STATE_LINK_UP, ROLE_SERVER)
removeInterface(iface)
// Note: removeInterface already verifies that a STATE_ABSENT, ROLE_NONE callback is
// received, but it can't hurt to explicitly check for it.
listener.expectCallback(iface, STATE_ABSENT, ROLE_NONE)
releaseTetheredInterface()
listener.assertNoCallback()
}
@Test
fun testEnableDisableInterface_withActiveRequest() {
val iface = createInterface()
val cb = requestNetwork(ETH_REQUEST)
cb.expectAvailable()
cb.assertNeverLost()
disableInterface(iface).expectResult(iface.name)
cb.eventuallyExpectLost()
enableInterface(iface).expectResult(iface.name)
cb.expectAvailable()
}
@Test
fun testEnableDisableInterface_withoutStateChange() {
val iface = createInterface()
// Interface is already enabled, so enableInterface() should return success
enableInterface(iface).expectResult(iface.name)
disableInterface(iface).expectResult(iface.name)
// Interface is already disabled, so disableInterface() should return success.
disableInterface(iface).expectResult(iface.name)
}
@Test
fun testEnableDisableInterface_withMissingInterface() {
val iface = createInterface()
removeInterface(iface)
// Interface does not exist, enable/disableInterface() should both return an error.
enableInterface(iface).expectError()
disableInterface(iface).expectError()
}
@Test
fun testUpdateConfiguration_forBothIpConfigAndCapabilities() {
val iface = createInterface()
val cb = requestNetwork(ETH_REQUEST.createCopyWithEthernetSpecifier(iface.name))
val network = cb.expectAvailable()
cb.assertNeverLost()
val testCapability = NET_CAPABILITY_TEMPORARILY_NOT_METERED
val nc = NetworkCapabilities
.Builder(ETH_REQUEST.networkCapabilities)
.addCapability(testCapability)
.build()
updateConfiguration(iface, STATIC_IP_CONFIGURATION, nc).expectResult(iface.name)
// UpdateConfiguration() currently does a restarts on the ethernet interface therefore lost
// will be expected first before available, as part of the restart.
cb.expectLost(network)
cb.expectAvailable()
cb.expectCapabilitiesWithCapability(testCapability)
cb.expectLinkPropertiesWithLinkAddress(
STATIC_IP_CONFIGURATION.staticIpConfiguration.ipAddress!!)
}
@Test
fun testUpdateConfiguration_forOnlyIpConfig() {
val iface: EthernetTestInterface = createInterface()
val cb = requestNetwork(ETH_REQUEST.createCopyWithEthernetSpecifier(iface.name))
val network = cb.expectAvailable()
cb.assertNeverLost()
updateConfiguration(iface, STATIC_IP_CONFIGURATION).expectResult(iface.name)
// UpdateConfiguration() currently does a restarts on the ethernet interface therefore lost
// will be expected first before available, as part of the restart.
cb.expectLost(network)
cb.expectAvailable()
cb.expectCallback<CapabilitiesChanged>()
cb.expectLinkPropertiesWithLinkAddress(
STATIC_IP_CONFIGURATION.staticIpConfiguration.ipAddress!!)
}
// TODO(b/240323229): This test is currently flaky due to a race between IpClient restarting and
// NetworkAgent tearing down the routes. This problem is exacerbated by disabling RS delay.
@Ignore
@Test
fun testUpdateConfiguration_forOnlyCapabilities() {
val iface: EthernetTestInterface = createInterface()
val cb = requestNetwork(ETH_REQUEST.createCopyWithEthernetSpecifier(iface.name))
val network = cb.expectAvailable()
cb.assertNeverLost()
val testCapability = NET_CAPABILITY_TEMPORARILY_NOT_METERED
val nc = NetworkCapabilities
.Builder(ETH_REQUEST.networkCapabilities)
.addCapability(testCapability)
.build()
updateConfiguration(iface, capabilities = nc).expectResult(iface.name)
// UpdateConfiguration() currently does a restarts on the ethernet interface therefore lost
// will be expected first before available, as part of the restart.
cb.expectLost(network)
cb.expectAvailable()
cb.expectCapabilitiesWithCapability(testCapability)
}
}