Merge changes from topic "ethernetmanagertest"

* changes:
  Add a CTS test for EthernetManager.InterfaceStateListener.
  Add a method to create a TAP interface without bringing it up.
This commit is contained in:
Lorenzo Colitti 2022-03-18 02:26:25 +00:00 • committed by Gerrit Code Review
commit 14c851ccc3
4 changed files with 217 additions and 29 deletions

View file

@ -29,8 +29,7 @@ import android.os.ParcelFileDescriptor;
*/
interface ITestNetworkManager
{
TestNetworkInterface createTunInterface(in LinkAddress[] linkAddrs);
TestNetworkInterface createTapInterface();
TestNetworkInterface createInterface(boolean isTun, boolean bringUp, in LinkAddress[] addrs);
void setupTestNetwork(in String iface, in LinkProperties lp, in boolean isMetered,
in int[] administratorUids, in IBinder binder);

View file

@ -49,6 +49,11 @@ public class TestNetworkManager {
@NonNull private final ITestNetworkManager mService;
private static final boolean TAP = false;
private static final boolean TUN = true;
private static final boolean BRING_UP = true;
private static final LinkAddress[] NO_ADDRS = new LinkAddress[0];
/** @hide */
public TestNetworkManager(@NonNull ITestNetworkManager service) {
mService = Objects.requireNonNull(service, "missing ITestNetworkManager");
@ -155,7 +160,7 @@ public class TestNetworkManager {
public TestNetworkInterface createTunInterface(@NonNull Collection<LinkAddress> linkAddrs) {
try {
final LinkAddress[] arr = new LinkAddress[linkAddrs.size()];
return mService.createTunInterface(linkAddrs.toArray(arr));
return mService.createInterface(TUN, BRING_UP, linkAddrs.toArray(arr));
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
}
@ -173,10 +178,28 @@ public class TestNetworkManager {
@NonNull
public TestNetworkInterface createTapInterface() {
try {
return mService.createTapInterface();
return mService.createInterface(TAP, BRING_UP, NO_ADDRS);
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
}
}
/**
* Create a tap interface for testing purposes
*
* @param bringUp whether to bring up the interface before returning it.
*
* @return A ParcelFileDescriptor of the underlying TAP interface. Close this to tear down the
* TAP interface.
* @hide
*/
@RequiresPermission(Manifest.permission.MANAGE_TEST_NETWORKS)
@NonNull
public TestNetworkInterface createTapInterface(boolean bringUp) {
try {
return mService.createInterface(TAP, bringUp, NO_ADDRS);
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
}
}
}

View file

@ -99,12 +99,14 @@ class TestNetworkService extends ITestNetworkManager.Stub {
}
/**
* Create a TUN or TAP interface with the given interface name and link addresses
* Create a TUN or TAP interface with the specified parameters.
*
* <p>This method will return the FileDescriptor to the interface. Close it to tear down the
* interface.
*/
private TestNetworkInterface createInterface(boolean isTun, LinkAddress[] linkAddrs) {
@Override
public TestNetworkInterface createInterface(boolean isTun, boolean bringUp,
LinkAddress[] linkAddrs) {
enforceTestNetworkPermissions(mContext);
Objects.requireNonNull(linkAddrs, "missing linkAddrs");
@ -122,7 +124,9 @@ class TestNetworkService extends ITestNetworkManager.Stub {
addr.getPrefixLength());
}
NetdUtils.setInterfaceUp(mNetd, iface);
if (bringUp) {
NetdUtils.setInterfaceUp(mNetd, iface);
}
return new TestNetworkInterface(tunIntf, iface);
} catch (RemoteException e) {
@ -132,28 +136,6 @@ class TestNetworkService extends ITestNetworkManager.Stub {
}
}
/**
* Create a TUN interface with the given interface name and link addresses
*
* <p>This method will return the FileDescriptor to the TUN interface. Close it to tear down the
* TUN interface.
*/
@Override
public TestNetworkInterface createTunInterface(@NonNull LinkAddress[] linkAddrs) {
return createInterface(true, linkAddrs);
}
/**
* Create a TAP interface with the given interface name
*
* <p>This method will return the FileDescriptor to the TAP interface. Close it to tear down the
* TAP interface.
*/
@Override
public TestNetworkInterface createTapInterface() {
return createInterface(false, new LinkAddress[0]);
}
// Tracker for TestNetworkAgents
@GuardedBy("mTestNetworkTracker")
@NonNull

View file

@ -0,0 +1,184 @@
/*
* 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.MANAGE_TEST_NETWORKS
import android.Manifest.permission.NETWORK_SETTINGS
import android.net.IpConfiguration
import android.net.TestNetworkInterface
import android.net.TestNetworkManager
import android.platform.test.annotations.AppModeFull
import androidx.test.platform.app.InstrumentationRegistry
import androidx.test.runner.AndroidJUnit4
import com.android.net.module.util.ArrayTrackRecord
import com.android.net.module.util.TrackRecord
import com.android.testutils.DevSdkIgnoreRule
import com.android.testutils.SC_V2
import com.android.testutils.runAsShell
import org.junit.After
import org.junit.Before
import org.junit.Rule
import org.junit.Test
import org.junit.runner.RunWith
import kotlin.test.assertNull
import kotlin.test.fail
import android.net.cts.EthernetManagerTest.EthernetStateListener.CallbackEntry.InterfaceStateChanged
import android.os.Handler
import android.os.HandlerExecutor
import android.os.Looper
import com.android.networkstack.apishim.common.EthernetManagerShim.InterfaceStateListener
import com.android.networkstack.apishim.common.EthernetManagerShim.STATE_ABSENT
import com.android.networkstack.apishim.common.EthernetManagerShim.STATE_LINK_DOWN
import com.android.networkstack.apishim.common.EthernetManagerShim.STATE_LINK_UP
import com.android.networkstack.apishim.common.EthernetManagerShim.ROLE_CLIENT
import com.android.networkstack.apishim.common.EthernetManagerShim.ROLE_NONE
import com.android.networkstack.apishim.EthernetManagerShimImpl
import java.util.concurrent.Executor
import kotlin.test.assertEquals
private const val TIMEOUT_MS = 1000L
private const val NO_CALLBACK_TIMEOUT_MS = 200L
private val DEFAULT_IP_CONFIGURATION = IpConfiguration(IpConfiguration.IpAssignment.DHCP,
IpConfiguration.ProxySettings.NONE, null, null)
@AppModeFull(reason = "Instant apps can't access EthernetManager")
@RunWith(AndroidJUnit4::class)
class EthernetManagerTest {
// EthernetManager is not updatable before T, so tests do not need to be backwards compatible
@get:Rule
val ignoreRule = DevSdkIgnoreRule(ignoreClassUpTo = SC_V2)
private val context by lazy { InstrumentationRegistry.getInstrumentation().context }
private val em by lazy { EthernetManagerShimImpl.newInstance(context) }
private val createdIfaces = ArrayList<TestNetworkInterface>()
private val addedListeners = ArrayList<InterfaceStateListener>()
private open class EthernetStateListener private constructor(
private val history: ArrayTrackRecord<CallbackEntry>
) : InterfaceStateListener,
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 onInterfaceStateChanged(
iface: String,
state: Int,
role: Int,
cfg: IpConfiguration?
) {
add(InterfaceStateChanged(iface, state, role, cfg))
}
fun <T : CallbackEntry> expectCallback(expected: T): T {
val event = pollForNextCallback()
assertEquals(expected, event)
return event as T
}
fun expectCallback(iface: TestNetworkInterface, state: Int, role: Int) {
expectCallback(InterfaceStateChanged(iface.interfaceName, 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 assertNoCallback() {
val cb = events.poll(NO_CALLBACK_TIMEOUT_MS)
assertNull(cb, "Expected no callback but got $cb")
}
}
@Test
public fun testCallbacks() {
val executor = HandlerExecutor(Handler(Looper.getMainLooper()))
// If an interface exists when the callback is registered, it is reported on registration.
val iface = runAsShell(MANAGE_TEST_NETWORKS) {
createInterface()
}
val listener = EthernetStateListener()
addInterfaceStateListener(executor, listener)
listener.expectCallback(iface, STATE_LINK_UP, ROLE_CLIENT)
// If an interface appears, existing callbacks see it.
// TODO: fix the up/up/down/up callbacks and only send down/up.
val iface2 = runAsShell(MANAGE_TEST_NETWORKS) {
createInterface()
}
listener.expectCallback(iface2, STATE_LINK_UP, ROLE_CLIENT)
listener.expectCallback(iface2, STATE_LINK_UP, ROLE_CLIENT)
listener.expectCallback(iface2, STATE_LINK_DOWN, ROLE_CLIENT)
listener.expectCallback(iface2, STATE_LINK_UP, ROLE_CLIENT)
// Removing interfaces first sends link down, then STATE_ABSENT/ROLE_NONE.
removeInterface(iface)
listener.expectCallback(iface, STATE_LINK_DOWN, ROLE_CLIENT)
listener.expectCallback(iface, STATE_ABSENT, ROLE_NONE)
removeInterface(iface2)
listener.expectCallback(iface2, STATE_LINK_DOWN, ROLE_CLIENT)
listener.expectCallback(iface2, STATE_ABSENT, ROLE_NONE)
listener.assertNoCallback()
}
@Before
fun setUp() {
runAsShell(MANAGE_TEST_NETWORKS, NETWORK_SETTINGS) {
em.setIncludeTestInterfaces(true)
}
}
@After
fun tearDown() {
runAsShell(MANAGE_TEST_NETWORKS, NETWORK_SETTINGS) {
em.setIncludeTestInterfaces(false)
for (iface in createdIfaces) {
if (iface.fileDescriptor.fileDescriptor.valid()) iface.fileDescriptor.close()
}
for (listener in addedListeners) {
em.removeInterfaceStateListener(listener)
}
}
}
private fun addInterfaceStateListener(executor: Executor, listener: InterfaceStateListener) {
em.addInterfaceStateListener(executor, listener)
addedListeners.add(listener)
}
private fun createInterface(): TestNetworkInterface {
val tnm = context.getSystemService(TestNetworkManager::class.java)
return tnm.createTapInterface(false /* bringUp */).also { createdIfaces.add(it) }
}
private fun removeInterface(iface: TestNetworkInterface) {
iface.fileDescriptor.close()
createdIfaces.remove(iface)
}
}