Add support for creating multiple ifaces with RA responders

The RA responder is needed in order to properly provision interfaces in
the ethernet tests. This CL wraps the tap interface creation and
destruction inside a small helper class.

Test: atest CtsNetTestCases:EthernetManagerTest
Change-Id: Id92a233873f2f8a738ca563f507a2ead4478aebc
diff --git a/tests/cts/net/src/android/net/cts/EthernetManagerTest.kt b/tests/cts/net/src/android/net/cts/EthernetManagerTest.kt
index 05d0beb..30e0015 100644
--- a/tests/cts/net/src/android/net/cts/EthernetManagerTest.kt
+++ b/tests/cts/net/src/android/net/cts/EthernetManagerTest.kt
@@ -17,7 +17,9 @@
 
 import android.Manifest.permission.MANAGE_TEST_NETWORKS
 import android.Manifest.permission.NETWORK_SETTINGS
+import android.net.InetAddresses
 import android.net.IpConfiguration
+import android.net.MacAddress
 import android.net.TestNetworkInterface
 import android.net.TestNetworkManager
 import android.platform.test.annotations.AppModeFull
@@ -32,6 +34,7 @@
 import org.junit.Before
 import org.junit.Rule
 import org.junit.Test
+import android.content.Context
 import org.junit.runner.RunWith
 import kotlin.test.assertNull
 import kotlin.test.fail
@@ -46,10 +49,15 @@
 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 com.android.testutils.RouterAdvertisementResponder
+import com.android.testutils.TapPacketReader
+import com.android.testutils.waitForIdle
+import java.net.Inet6Address
 import java.util.concurrent.Executor
 import kotlin.test.assertFalse
 import kotlin.test.assertEquals
 import kotlin.test.assertTrue
+import java.net.NetworkInterface
 
 private const val TIMEOUT_MS = 1000L
 private const val NO_CALLBACK_TIMEOUT_MS = 200L
@@ -66,9 +74,40 @@
     private val context by lazy { InstrumentationRegistry.getInstrumentation().context }
     private val em by lazy { EthernetManagerShimImpl.newInstance(context) }
 
-    private val createdIfaces = ArrayList<TestNetworkInterface>()
+    private val createdIfaces = ArrayList<EthernetTestInterface>()
     private val addedListeners = ArrayList<InterfaceStateListener>()
 
+    private class EthernetTestInterface(
+        context: Context,
+        private val handler: Handler
+    ) {
+        private val tapInterface: TestNetworkInterface
+        private val packetReader: TapPacketReader
+        private val raResponder: RouterAdvertisementResponder
+        val interfaceName get() = tapInterface.interfaceName
+
+        init {
+            tapInterface = runAsShell(MANAGE_TEST_NETWORKS) {
+                val tnm = context.getSystemService(TestNetworkManager::class.java)
+                tnm.createTapInterface(false /* bringUp */)
+            }
+            val mtu = NetworkInterface.getByName(tapInterface.interfaceName).getMTU()
+            packetReader = TapPacketReader(handler, tapInterface.fileDescriptor.fileDescriptor, mtu)
+            raResponder = RouterAdvertisementResponder(packetReader)
+            raResponder.addRouterEntry(MacAddress.fromString("01:23:45:67:89:ab"),
+                    InetAddresses.parseNumericAddress("fe80::abcd") as Inet6Address)
+
+            packetReader.startAsyncForTest()
+            raResponder.start()
+        }
+
+        fun destroy() {
+            raResponder.stop()
+            handler.post({ packetReader.stop() })
+            handler.waitForIdle(TIMEOUT_MS)
+        }
+    }
+
     private open class EthernetStateListener private constructor(
         private val history: ArrayTrackRecord<CallbackEntry>
     ) : InterfaceStateListener,
@@ -101,7 +140,7 @@
             return event as T
         }
 
-        fun expectCallback(iface: TestNetworkInterface, state: Int, role: Int) {
+        fun expectCallback(iface: EthernetTestInterface, state: Int, role: Int) {
             expectCallback(InterfaceStateChanged(iface.interfaceName, state, role,
                 if (state != STATE_ABSENT) DEFAULT_IP_CONFIGURATION else null))
         }
@@ -125,7 +164,7 @@
     fun tearDown() {
         setIncludeTestInterfaces(false)
         for (iface in createdIfaces) {
-            if (iface.fileDescriptor.fileDescriptor.valid()) iface.fileDescriptor.close()
+            iface.destroy()
         }
         for (listener in addedListeners) {
             em.removeInterfaceStateListener(listener)
@@ -137,11 +176,8 @@
         addedListeners.add(listener)
     }
 
-    private fun createInterface(): TestNetworkInterface {
-        return runAsShell(MANAGE_TEST_NETWORKS) {
-            val tnm = context.getSystemService(TestNetworkManager::class.java)
-            tnm.createTapInterface(false /* bringUp */).also { createdIfaces.add(it) }
-        }
+    private fun createInterface(): EthernetTestInterface {
+        return EthernetTestInterface(context, Handler(Looper.getMainLooper()))
     }
 
     private fun setIncludeTestInterfaces(value: Boolean) {
@@ -150,8 +186,8 @@
         }
     }
 
-    private fun removeInterface(iface: TestNetworkInterface) {
-        iface.fileDescriptor.close()
+    private fun removeInterface(iface: EthernetTestInterface) {
+        iface.destroy()
         createdIfaces.remove(iface)
     }
 
@@ -193,15 +229,15 @@
         val iface2 = createInterface()
         var ifaces = em.getInterfaceList()
         assertTrue(ifaces.size > 0)
-        assertTrue(ifaces.contains(iface1.getInterfaceName()))
-        assertTrue(ifaces.contains(iface2.getInterfaceName()))
+        assertTrue(ifaces.contains(iface1.interfaceName))
+        assertTrue(ifaces.contains(iface2.interfaceName))
 
         // 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.getInterfaceName()))
-        assertTrue(ifaces.contains(iface2.getInterfaceName()))
+        assertFalse(ifaces.contains(iface1.interfaceName))
+        assertTrue(ifaces.contains(iface2.interfaceName))
 
         removeInterface(iface2)
     }