DscpPolicyTest: use tap instead of tun

This switches the DscpPolicyTest to use a tap interface rather than a
tun interface. This should now test the same bpf code path that is used
in production and the raw ip version should be able to be removed.

Test: atest DscpPolicyTest
Change-Id: I1633b85810838afde6191f0a5094269e45c15ae4
diff --git a/tests/cts/net/src/android/net/cts/DscpPolicyTest.kt b/tests/cts/net/src/android/net/cts/DscpPolicyTest.kt
index 621b743..faf6943 100644
--- a/tests/cts/net/src/android/net/cts/DscpPolicyTest.kt
+++ b/tests/cts/net/src/android/net/cts/DscpPolicyTest.kt
@@ -27,6 +27,8 @@
 import android.net.IpPrefix
 import android.net.LinkAddress
 import android.net.LinkProperties
+import android.net.Network
+import android.net.MacAddress
 import android.net.NetworkAgent
 import android.net.NetworkAgent.DSCP_POLICY_STATUS_DELETED
 import android.net.NetworkAgent.DSCP_POLICY_STATUS_SUCCESS
@@ -45,10 +47,13 @@
 import android.net.TestNetworkManager
 import android.net.RouteInfo
 import android.os.HandlerThread
+import android.os.SystemClock
 import android.platform.test.annotations.AppModeFull
+import android.system.ErrnoException
 import android.system.Os
 import android.system.OsConstants.AF_INET
 import android.system.OsConstants.AF_INET6
+import android.system.OsConstants.ENETUNREACH
 import android.system.OsConstants.IPPROTO_UDP
 import android.system.OsConstants.SOCK_DGRAM
 import android.system.OsConstants.SOCK_NONBLOCK
@@ -56,9 +61,15 @@
 import android.util.Range
 import androidx.test.InstrumentationRegistry
 import androidx.test.runner.AndroidJUnit4
+import com.android.net.module.util.NetworkStackConstants.ETHER_TYPE_IPV4
+import com.android.net.module.util.NetworkStackConstants.ETHER_TYPE_IPV6
+import com.android.net.module.util.Struct
+import com.android.net.module.util.structs.EthernetHeader
+import com.android.testutils.ArpResponder
 import com.android.testutils.CompatUtil
 import com.android.testutils.DevSdkIgnoreRule
 import com.android.testutils.assertParcelingIsLossless
+import com.android.testutils.RouterAdvertisementResponder
 import com.android.testutils.runAsShell
 import com.android.testutils.SC_V2
 import com.android.testutils.TapPacketReader
@@ -74,7 +85,7 @@
 import org.junit.runner.RunWith
 import java.net.Inet4Address
 import java.net.Inet6Address
-import java.net.InetAddress
+import java.net.InetSocketAddress
 import java.nio.ByteBuffer
 import java.nio.ByteOrder
 import java.util.regex.Pattern
@@ -103,10 +114,12 @@
 
     private val LOCAL_IPV4_ADDRESS = InetAddresses.parseNumericAddress("192.0.2.1")
     private val TEST_TARGET_IPV4_ADDR =
-            InetAddresses.parseNumericAddress("8.8.8.8") as Inet4Address
-    private val LOCAL_IPV6_ADDRESS = InetAddresses.parseNumericAddress("2001:db8::1")
+            InetAddresses.parseNumericAddress("203.0.113.1") as Inet4Address
     private val TEST_TARGET_IPV6_ADDR =
-            InetAddresses.parseNumericAddress("2001:4860:4860::8888") as Inet6Address
+        InetAddresses.parseNumericAddress("2001:4860:4860::8888") as Inet6Address
+    private val TEST_ROUTER_IPV6_ADDR =
+        InetAddresses.parseNumericAddress("fe80::1234") as Inet6Address
+    private val TEST_TARGET_MAC_ADDR = MacAddress.fromString("12:34:56:78:9a:bc")
 
     private val realContext = InstrumentationRegistry.getContext()
     private val cm = realContext.getSystemService(ConnectivityManager::class.java)
@@ -116,9 +129,12 @@
 
     private val handlerThread = HandlerThread(DscpPolicyTest::class.java.simpleName)
 
+    private lateinit var srcAddressV6: Inet6Address
     private lateinit var iface: TestNetworkInterface
     private lateinit var tunNetworkCallback: TestNetworkCallback
     private lateinit var reader: TapPacketReader
+    private lateinit var arpResponder: ArpResponder
+    private lateinit var raResponder: RouterAdvertisementResponder
 
     private fun getKernelVersion(): IntArray {
         // Example:
@@ -129,6 +145,7 @@
         return intArrayOf(Integer.parseInt(m.group(1)), Integer.parseInt(m.group(2)))
     }
 
+    // TODO: replace with DeviceInfoUtils#isKernelVersionAtLeast
     private fun kernelIsAtLeast(major: Int, minor: Int): Boolean {
         val version = getKernelVersion()
         return (version.get(0) > major || (version.get(0) == major && version.get(1) >= minor))
@@ -142,9 +159,10 @@
         runAsShell(MANAGE_TEST_NETWORKS) {
             val tnm = realContext.getSystemService(TestNetworkManager::class.java)
 
-            iface = tnm.createTunInterface(arrayOf(
-                    LinkAddress(LOCAL_IPV4_ADDRESS, IP4_PREFIX_LEN),
-                    LinkAddress(LOCAL_IPV6_ADDRESS, IP6_PREFIX_LEN)))
+            // Only statically configure the IPv4 address; for IPv6, use the SLAAC generated
+            // address.
+            iface = tnm.createTapInterface(true /* disableIpv6ProvisioningDelay */,
+                    arrayOf(LinkAddress(LOCAL_IPV4_ADDRESS, IP4_PREFIX_LEN)))
             assertNotNull(iface)
         }
 
@@ -154,6 +172,12 @@
                 iface.fileDescriptor.fileDescriptor,
                 MAX_PACKET_LENGTH)
         reader.startAsyncForTest()
+
+        arpResponder = ArpResponder(reader, mapOf(TEST_TARGET_IPV4_ADDR to TEST_TARGET_MAC_ADDR))
+        arpResponder.start()
+        raResponder = RouterAdvertisementResponder(reader)
+        raResponder.addRouterEntry(TEST_TARGET_MAC_ADDR, TEST_ROUTER_IPV6_ADDR)
+        raResponder.start()
     }
 
     @After
@@ -161,14 +185,17 @@
         if (!kernelIsAtLeast(5, 15)) {
             return;
         }
+        raResponder.stop()
+        arpResponder.stop()
+
         agentsToCleanUp.forEach { it.unregister() }
         callbacksToCleanUp.forEach { cm.unregisterNetworkCallback(it) }
 
         // reader.stop() cleans up tun fd
         reader.handler.post { reader.stop() }
-        if (iface.fileDescriptor.fileDescriptor != null)
-            Os.close(iface.fileDescriptor.fileDescriptor)
+        // quitSafely processes all events in the queue, except delayed messages.
         handlerThread.quitSafely()
+        handlerThread.join()
     }
 
     private fun requestNetwork(request: NetworkRequest, callback: TestableNetworkCallback) {
@@ -189,6 +216,39 @@
                 .build()
     }
 
+    private fun waitForGlobalIpv6Address(network: Network): Inet6Address {
+        // Wait for global IPv6 address to be available
+        val sock = Os.socket(AF_INET6, SOCK_DGRAM, IPPROTO_UDP)
+        network.bindSocket(sock)
+
+        var inet6Addr: Inet6Address? = null
+        val timeout = SystemClock.elapsedRealtime() + PACKET_TIMEOUT_MS
+        while (timeout > SystemClock.elapsedRealtime()) {
+            try {
+                // Pick any arbitrary port
+                Os.connect(sock, TEST_TARGET_IPV6_ADDR, 12345)
+                val sockAddr = Os.getsockname(sock) as InetSocketAddress
+
+                // TODO: make RouterAdvertisementResponder.SLAAC_PREFIX public and use it here,
+                // or make it configurable and configure it here.
+                if (IpPrefix("2001:db8::/64").contains(sockAddr.address)) {
+                    inet6Addr = sockAddr.address as Inet6Address
+                    break
+                }
+            } catch (e: ErrnoException) {
+                // ignore ENETUNREACH -- there may not be an address available yet.
+                if (e.errno != ENETUNREACH) {
+                    Os.close(sock)
+                    throw e
+                }
+            }
+            SystemClock.sleep(10 /* ms */)
+        }
+        Os.close(sock)
+        assertNotNull(inet6Addr)
+        return inet6Addr!!
+    }
+
     private fun createConnectedNetworkAgent(
         context: Context = realContext,
         specifier: String? = iface.getInterfaceName()
@@ -211,9 +271,8 @@
         }
         val lp = LinkProperties().apply {
             addLinkAddress(LinkAddress(LOCAL_IPV4_ADDRESS, IP4_PREFIX_LEN))
-            addLinkAddress(LinkAddress(LOCAL_IPV6_ADDRESS, IP6_PREFIX_LEN))
             addRoute(RouteInfo(IpPrefix("0.0.0.0/0"), null, null))
-            addRoute(RouteInfo(InetAddress.getByName("fe80::1234")))
+            addRoute(RouteInfo(IpPrefix("::/0"), TEST_ROUTER_IPV6_ADDR))
             setInterfaceName(specifier)
         }
         val config = NetworkAgentConfig.Builder().build()
@@ -226,7 +285,9 @@
         agent.expectCallback<OnNetworkCreated>()
         agent.expectSignalStrengths(intArrayOf())
         agent.expectValidationBypassedStatus()
+
         val network = agent.network ?: fail("Expected a non-null network")
+        srcAddressV6 = waitForGlobalIpv6Address(network)
         return agent to callback
     }
 
@@ -292,7 +353,7 @@
 
         Log.e(TAG, "IP Src:" + srcIp + " dst: " + dstIp)
 
-        if ((sendV6 && srcIp == LOCAL_IPV6_ADDRESS && dstIp == TEST_TARGET_IPV6_ADDR) ||
+        if ((sendV6 && srcIp == srcAddressV6 && dstIp == TEST_TARGET_IPV6_ADDR) ||
                 (!sendV6 && srcIp == LOCAL_IPV4_ADDRESS && dstIp == TEST_TARGET_IPV4_ADDR)) {
             Log.e(TAG, "IP return true");
             return true
@@ -333,8 +394,15 @@
         // TODO: grab source port from socket in sendPacket
 
         Log.e(TAG, "find DSCP value:" + dscpValue)
-        generateSequence { reader.poll(PACKET_TIMEOUT_MS) }.forEach { packet ->
+        val packets = generateSequence { reader.poll(PACKET_TIMEOUT_MS) }
+        for (packet in packets) {
             val buffer = ByteBuffer.wrap(packet, 0, packet.size).order(ByteOrder.BIG_ENDIAN)
+            // TODO: consider using Struct.parse for all packet parsing.
+            val etherHdr = Struct.parse(EthernetHeader::class.java, buffer)
+            val expectedType = if (sendV6) ETHER_TYPE_IPV6 else ETHER_TYPE_IPV4
+            if (etherHdr.etherType != expectedType) {
+                continue
+            }
             val dscp = if (sendV6) parseV6PacketDscp(buffer) else parseV4PacketDscp(buffer)
             Log.e(TAG, "DSCP value:" + dscp)
 
@@ -420,7 +488,7 @@
         val policy2 = DscpPolicy.Builder(1, 4)
                 .setDestinationPortRange(Range(5555, 5555))
                 .setDestinationAddress(TEST_TARGET_IPV6_ADDR)
-                .setSourceAddress(LOCAL_IPV6_ADDRESS)
+                .setSourceAddress(srcAddressV6)
                 .setProtocol(IPPROTO_UDP).build()
         agent.sendAddDscpPolicy(policy2)
         agent.expectCallback<OnDscpPolicyStatusUpdated>().let {