1 /* 2 * Copyright (C) 2024 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 // ktlint does not allow annotating function argument literals inline. Disable the specific rule 17 // since this negatively affects readability. 18 @file:Suppress("ktlint:standard:comment-wrapping") 19 20 package android.net.cts 21 22 import android.Manifest.permission.WRITE_ALLOWLISTED_DEVICE_CONFIG 23 import android.Manifest.permission.WRITE_DEVICE_CONFIG 24 import android.content.pm.PackageManager 25 import android.content.pm.PackageManager.FEATURE_AUTOMOTIVE 26 import android.content.pm.PackageManager.FEATURE_WIFI 27 import android.net.ConnectivityManager 28 import android.net.Network 29 import android.net.NetworkCapabilities 30 import android.net.NetworkRequest 31 import android.net.apf.ApfCapabilities 32 import android.net.apf.ApfConstants.ETH_ETHERTYPE_OFFSET 33 import android.net.apf.ApfConstants.ETH_HEADER_LEN 34 import android.net.apf.ApfConstants.ICMP6_CHECKSUM_OFFSET 35 import android.net.apf.ApfConstants.ICMP6_TYPE_OFFSET 36 import android.net.apf.ApfConstants.IPV6_DEST_ADDR_OFFSET 37 import android.net.apf.ApfConstants.IPV6_HEADER_LEN 38 import android.net.apf.ApfConstants.IPV6_NEXT_HEADER_OFFSET 39 import android.net.apf.ApfConstants.IPV6_SRC_ADDR_OFFSET 40 import android.net.apf.ApfCounterTracker 41 import android.net.apf.ApfCounterTracker.Counter.DROPPED_IPV6_MULTICAST_PING 42 import android.net.apf.ApfCounterTracker.Counter.FILTER_AGE_16384THS 43 import android.net.apf.ApfCounterTracker.Counter.PASSED_IPV6_ICMP 44 import android.net.apf.ApfV4Generator 45 import android.net.apf.ApfV4GeneratorBase 46 import android.net.apf.ApfV6Generator 47 import android.net.apf.BaseApfGenerator 48 import android.net.apf.BaseApfGenerator.MemorySlot 49 import android.net.apf.BaseApfGenerator.Register.R0 50 import android.net.apf.BaseApfGenerator.Register.R1 51 import android.os.Build 52 import android.os.Handler 53 import android.os.HandlerThread 54 import android.os.PowerManager 55 import android.os.UserManager 56 import android.platform.test.annotations.AppModeFull 57 import android.provider.DeviceConfig 58 import android.provider.DeviceConfig.NAMESPACE_CONNECTIVITY 59 import android.system.Os 60 import android.system.OsConstants 61 import android.system.OsConstants.AF_INET6 62 import android.system.OsConstants.ETH_P_IPV6 63 import android.system.OsConstants.IPPROTO_ICMPV6 64 import android.system.OsConstants.SOCK_DGRAM 65 import android.system.OsConstants.SOCK_NONBLOCK 66 import android.util.Log 67 import androidx.test.filters.RequiresDevice 68 import androidx.test.platform.app.InstrumentationRegistry 69 import com.android.compatibility.common.util.PropertyUtil.getFirstApiLevel 70 import com.android.compatibility.common.util.PropertyUtil.getVsrApiLevel 71 import com.android.compatibility.common.util.SystemUtil.runShellCommand 72 import com.android.compatibility.common.util.SystemUtil.runShellCommandOrThrow 73 import com.android.compatibility.common.util.VsrTest 74 import com.android.internal.util.HexDump 75 import com.android.net.module.util.NetworkStackConstants.ETHER_ADDR_LEN 76 import com.android.net.module.util.NetworkStackConstants.ETHER_DST_ADDR_OFFSET 77 import com.android.net.module.util.NetworkStackConstants.ETHER_HEADER_LEN 78 import com.android.net.module.util.NetworkStackConstants.ETHER_SRC_ADDR_OFFSET 79 import com.android.net.module.util.NetworkStackConstants.ICMPV6_HEADER_MIN_LEN 80 import com.android.net.module.util.NetworkStackConstants.IPV6_ADDR_LEN 81 import com.android.net.module.util.PacketReader 82 import com.android.testutils.DevSdkIgnoreRule 83 import com.android.testutils.DevSdkIgnoreRule.IgnoreUpTo 84 import com.android.testutils.DevSdkIgnoreRunner 85 import com.android.testutils.NetworkStackModuleTest 86 import com.android.testutils.RecorderCallback.CallbackEntry.Available 87 import com.android.testutils.RecorderCallback.CallbackEntry.LinkPropertiesChanged 88 import com.android.testutils.SkipPresubmit 89 import com.android.testutils.TestableNetworkCallback 90 import com.android.testutils.runAsShell 91 import com.android.testutils.waitForIdle 92 import com.google.common.truth.Expect 93 import com.google.common.truth.Truth.assertThat 94 import com.google.common.truth.Truth.assertWithMessage 95 import com.google.common.truth.TruthJUnit.assume 96 import java.io.FileDescriptor 97 import java.net.InetSocketAddress 98 import java.nio.ByteBuffer 99 import java.util.concurrent.CompletableFuture 100 import java.util.concurrent.TimeUnit 101 import java.util.concurrent.TimeoutException 102 import kotlin.random.Random 103 import kotlin.test.assertFailsWith 104 import kotlin.test.assertNotNull 105 import org.junit.After 106 import org.junit.AfterClass 107 import org.junit.Before 108 import org.junit.BeforeClass 109 import org.junit.Rule 110 import org.junit.Test 111 import org.junit.runner.RunWith 112 113 private const val TAG = "ApfIntegrationTest" 114 private const val TIMEOUT_MS = 2000L 115 private const val APF_NEW_RA_FILTER_VERSION = "apf_new_ra_filter_version" 116 private const val POLLING_INTERVAL_MS: Int = 100 117 private const val RCV_BUFFER_SIZE = 1480 118 private const val PING_HEADER_LENGTH = 8 119 120 @AppModeFull(reason = "CHANGE_NETWORK_STATE permission can't be granted to instant apps") 121 @RunWith(DevSdkIgnoreRunner::class) 122 @RequiresDevice 123 @NetworkStackModuleTest 124 // ByteArray.toHexString is experimental API 125 @kotlin.ExperimentalStdlibApi 126 class ApfIntegrationTest { 127 companion object { 128 private val PING_DESTINATION = InetSocketAddress("2001:4860:4860::8888", 0) 129 130 private val context = InstrumentationRegistry.getInstrumentation().context 131 private val powerManager = context.getSystemService(PowerManager::class.java)!! 132 private val wakeLock = powerManager.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, TAG) 133 pollingChecknull134 fun pollingCheck(condition: () -> Boolean, timeout_ms: Int): Boolean { 135 var polling_time = 0 136 do { 137 Thread.sleep(POLLING_INTERVAL_MS.toLong()) 138 polling_time += POLLING_INTERVAL_MS 139 if (condition()) return true 140 } while (polling_time < timeout_ms) 141 return false 142 } 143 turnScreenOffnull144 fun turnScreenOff() { 145 if (!wakeLock.isHeld()) wakeLock.acquire() 146 runShellCommandOrThrow("input keyevent KEYCODE_SLEEP") 147 waitForInteractiveState(false) 148 } 149 turnScreenOnnull150 fun turnScreenOn() { 151 if (wakeLock.isHeld()) wakeLock.release() 152 runShellCommandOrThrow("input keyevent KEYCODE_WAKEUP") 153 waitForInteractiveState(true) 154 } 155 waitForInteractiveStatenull156 private fun waitForInteractiveState(interactive: Boolean) { 157 // TODO(b/366037029): This test condition should be removed once 158 // PowerManager#isInteractive is fully implemented on automotive 159 // form factor with visible background user. 160 if (isAutomotiveWithVisibleBackgroundUser()) { 161 // Wait for 2 seconds to ensure the interactive state is updated. 162 // This is a workaround for b/366037029. 163 Thread.sleep(2000L) 164 } else { 165 val result = pollingCheck({ powerManager.isInteractive() }, timeout_ms = 2000) 166 assertThat(result).isEqualTo(interactive) 167 } 168 } 169 isAutomotiveWithVisibleBackgroundUsernull170 private fun isAutomotiveWithVisibleBackgroundUser(): Boolean { 171 val packageManager = context.getPackageManager() 172 val userManager = context.getSystemService(UserManager::class.java)!! 173 return (packageManager.hasSystemFeature(FEATURE_AUTOMOTIVE) 174 && userManager.isVisibleBackgroundUsersSupported) 175 } 176 177 @BeforeClass 178 @JvmStatic 179 @Suppress("ktlint:standard:no-multi-spaces") setupOncenull180 fun setupOnce() { 181 // TODO: assertions thrown in @BeforeClass / @AfterClass are not well supported in the 182 // test infrastructure. Consider saving exception and throwing it in setUp(). 183 184 // APF must run when the screen is off and the device is not interactive. 185 turnScreenOff() 186 187 // Wait for APF to become active. 188 Thread.sleep(1000) 189 // TODO: check that there is no active wifi network. Otherwise, ApfFilter has already been 190 // created. 191 // APF adb cmds are only implemented in ApfFilter.java. Enable experiment to prevent 192 // LegacyApfFilter.java from being used. 193 runAsShell(WRITE_DEVICE_CONFIG, WRITE_ALLOWLISTED_DEVICE_CONFIG) { 194 DeviceConfig.setProperty( 195 NAMESPACE_CONNECTIVITY, 196 APF_NEW_RA_FILTER_VERSION, 197 "1", // value => force enabled 198 false // makeDefault 199 ) 200 } 201 } 202 203 @AfterClass 204 @JvmStatic tearDownOncenull205 fun tearDownOnce() { 206 turnScreenOn() 207 } 208 } 209 210 class Icmp6PacketReader( 211 handler: Handler, 212 private val network: Network 213 ) : PacketReader(handler, RCV_BUFFER_SIZE) { 214 private var sockFd: FileDescriptor? = null 215 private var futureReply: CompletableFuture<ByteArray>? = null 216 createFdnull217 override fun createFd(): FileDescriptor { 218 // sockFd is closed by calling super.stop() 219 val sock = Os.socket(AF_INET6, SOCK_DGRAM or SOCK_NONBLOCK, IPPROTO_ICMPV6) 220 // APF runs only on WiFi, so make sure the socket is bound to the right network. 221 network.bindSocket(sock) 222 sockFd = sock 223 return sock 224 } 225 handlePacketnull226 override fun handlePacket(recvbuf: ByteArray, length: Int) { 227 // If zero-length or Type is not echo reply: ignore. 228 if (length == 0 || recvbuf[0] != 0x81.toByte()) { 229 return 230 } 231 // Only copy the ping data and complete the future. 232 val result = recvbuf.sliceArray(8..<length) 233 Log.i(TAG, "Received ping reply: ${result.toHexString()}") 234 futureReply!!.complete(recvbuf.sliceArray(8..<length)) 235 } 236 sendPingnull237 fun sendPing(data: ByteArray, payloadSize: Int) { 238 require(data.size == payloadSize) 239 240 // rfc4443#section-4.1: Echo Request Message 241 // 0 1 2 3 242 // 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 243 // +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ 244 // | Type | Code | Checksum | 245 // +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ 246 // | Identifier | Sequence Number | 247 // +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ 248 // | Data ... 249 // +-+-+-+-+- 250 val icmp6Header = byteArrayOf(0x80.toByte(), 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00) 251 val packet = icmp6Header + data 252 Log.i(TAG, "Sent ping: ${packet.toHexString()}") 253 futureReply = CompletableFuture<ByteArray>() 254 Os.sendto(sockFd!!, packet, 0, packet.size, 0, PING_DESTINATION) 255 } 256 expectPingReplynull257 fun expectPingReply(timeoutMs: Long = TIMEOUT_MS): ByteArray { 258 return futureReply!!.get(timeoutMs, TimeUnit.MILLISECONDS) 259 } 260 expectPingDroppednull261 fun expectPingDropped() { 262 assertFailsWith(TimeoutException::class) { 263 futureReply!!.get(TIMEOUT_MS, TimeUnit.MILLISECONDS) 264 } 265 } 266 startnull267 override fun start(): Boolean { 268 // Ignore the fact start() could return false or throw an exception. 269 handler.post({ super.start() }) 270 handler.waitForIdle(TIMEOUT_MS) 271 return true 272 } 273 stopnull274 override fun stop() { 275 handler.post({ super.stop() }) 276 handler.waitForIdle(TIMEOUT_MS) 277 } 278 } 279 280 @get:Rule val ignoreRule = DevSdkIgnoreRule() 281 @get:Rule val expect = Expect.create() 282 <lambda>null283 private val cm by lazy { context.getSystemService(ConnectivityManager::class.java)!! } <lambda>null284 private val pm by lazy { context.packageManager } 285 private lateinit var network: Network 286 private lateinit var ifname: String 287 private lateinit var networkCallback: TestableNetworkCallback 288 private lateinit var caps: ApfCapabilities <lambda>null289 private val handlerThread = HandlerThread("$TAG handler thread").apply { start() } 290 private val handler = Handler(handlerThread.looper) 291 private lateinit var packetReader: Icmp6PacketReader 292 getApfCapabilitiesnull293 fun getApfCapabilities(): ApfCapabilities { 294 val caps = runShellCommand("cmd network_stack apf $ifname capabilities").trim() 295 if (caps.isEmpty()) { 296 return ApfCapabilities(0, 0, 0) 297 } 298 val (version, maxLen, packetFormat) = caps.split(",").map { it.toInt() } 299 return ApfCapabilities(version, maxLen, packetFormat) 300 } 301 302 @Before setUpnull303 fun setUp() { 304 assume().that(pm.hasSystemFeature(FEATURE_WIFI)).isTrue() 305 306 networkCallback = TestableNetworkCallback() 307 cm.requestNetwork( 308 NetworkRequest.Builder() 309 .addTransportType(NetworkCapabilities.TRANSPORT_WIFI) 310 .addCapability(NetworkCapabilities.NET_CAPABILITY_INTERNET) 311 .build(), 312 networkCallback 313 ) 314 network = networkCallback.expect<Available>().network 315 networkCallback.eventuallyExpect<LinkPropertiesChanged>(TIMEOUT_MS) { 316 ifname = assertNotNull(it.lp.interfaceName) 317 true 318 } 319 // It's possible the device does not support APF, in which case this command will not be 320 // successful. Ignore the error as testApfCapabilities() already asserts APF support on the 321 // respective VSR releases and all other tests are based on the capabilities indicated. 322 runShellCommand("cmd network_stack apf $ifname pause") 323 caps = getApfCapabilities() 324 325 packetReader = Icmp6PacketReader(handler, network) 326 packetReader.start() 327 } 328 329 @After tearDownnull330 fun tearDown() { 331 if (::packetReader.isInitialized) { 332 packetReader.stop() 333 } 334 handlerThread.quitSafely() 335 handlerThread.join() 336 337 if (::ifname.isInitialized) { 338 runShellCommand("cmd network_stack apf $ifname resume") 339 } 340 if (::networkCallback.isInitialized) { 341 cm.unregisterNetworkCallback(networkCallback) 342 } 343 } 344 345 @VsrTest( 346 requirements = ["VSR-5.3.12-001", "VSR-5.3.12-003", "VSR-5.3.12-004", "VSR-5.3.12-009", 347 "VSR-5.3.12-012"] 348 ) 349 @Test testApfCapabilitiesnull350 fun testApfCapabilities() { 351 // APF became mandatory in Android 14 VSR. 352 assume().that(getVsrApiLevel()).isAtLeast(34) 353 354 // ApfFilter does not support anything but ARPHRD_ETHER. 355 assertThat(caps.apfPacketFormat).isEqualTo(OsConstants.ARPHRD_ETHER) 356 357 // DEVICEs launching with Android 14 with CHIPSETs that set ro.board.first_api_level to 34: 358 // - [GMS-VSR-5.3.12-003] MUST return 4 or higher as the APF version number from calls to 359 // the getApfPacketFilterCapabilities HAL method. 360 // - [GMS-VSR-5.3.12-004] MUST indicate at least 1024 bytes of usable memory from calls to 361 // the getApfPacketFilterCapabilities HAL method. 362 // TODO: check whether above text should be changed "34 or higher" 363 assertThat(caps.apfVersionSupported).isAtLeast(4) 364 assertThat(caps.maximumApfProgramSize).isAtLeast(1024) 365 366 if (caps.apfVersionSupported > 4) { 367 assertThat(caps.maximumApfProgramSize).isAtLeast(2048) 368 assertThat(caps.apfVersionSupported).isEqualTo(6000) // v6.0000 369 } 370 371 // DEVICEs launching with Android 15 (AOSP experimental) or higher with CHIPSETs that set 372 // ro.board.first_api_level or ro.board.api_level to 202404 or higher: 373 // - [GMS-VSR-5.3.12-009] MUST indicate at least 2048 bytes of usable memory from calls to 374 // the getApfPacketFilterCapabilities HAL method. 375 if (getVsrApiLevel() >= 202404) { 376 assertThat(caps.maximumApfProgramSize).isAtLeast(2048) 377 } 378 } 379 380 // APF is backwards compatible, i.e. a v6 interpreter supports both v2 and v4 functionality. assumeApfVersionSupportAtLeastnull381 fun assumeApfVersionSupportAtLeast(version: Int) { 382 assume().that(caps.apfVersionSupported).isAtLeast(version) 383 } 384 installProgramnull385 fun installProgram(bytes: ByteArray) { 386 val prog = bytes.toHexString() 387 val result = runShellCommandOrThrow("cmd network_stack apf $ifname install $prog").trim() 388 // runShellCommandOrThrow only throws on S+. 389 assertThat(result).isEqualTo("success") 390 } 391 readProgramnull392 fun readProgram(): ByteArray { 393 val progHexString = runShellCommandOrThrow("cmd network_stack apf $ifname read").trim() 394 // runShellCommandOrThrow only throws on S+. 395 assertThat(progHexString).isNotEmpty() 396 return HexDump.hexStringToByteArray(progHexString) 397 } 398 399 @VsrTest( 400 requirements = ["VSR-5.3.12-007", "VSR-5.3.12-008", "VSR-5.3.12-010", "VSR-5.3.12-011"] 401 ) 402 @SkipPresubmit(reason = "This test takes longer than 1 minute, do not run it on presubmit.") 403 // APF integration is mostly broken before V, only run the full read / write test on V+. 404 @IgnoreUpTo(Build.VERSION_CODES.UPSIDE_DOWN_CAKE) 405 // Increase timeout for test to 15 minutes to accommodate device with large APF RAM. 406 @Test(timeout = 15 * 60 * 1000) testReadWriteProgramnull407 fun testReadWriteProgram() { 408 assumeApfVersionSupportAtLeast(4) 409 410 val minReadWriteSize = if (getFirstApiLevel() >= Build.VERSION_CODES.VANILLA_ICE_CREAM) { 411 2 412 } else { 413 8 414 } 415 416 // The minReadWriteSize is 2 bytes. The first byte always stays PASS. 417 val program = ByteArray(caps.maximumApfProgramSize) 418 for (i in caps.maximumApfProgramSize downTo minReadWriteSize) { 419 // Randomize bytes in range [1, i). And install first [0, i) bytes of program. 420 // Note that only the very first instruction (PASS) is valid APF bytecode. 421 Random.nextBytes(program, 1 /* fromIndex */, i /* toIndex */) 422 installProgram(program.sliceArray(0..<i)) 423 424 // Compare entire memory region. 425 val readResult = readProgram() 426 val errMsg = """ 427 read/write $i byte prog failed. 428 In APFv4, the APF memory region MUST NOT be modified or cleared except by APF 429 instructions executed by the interpreter or by Android OS calls to the HAL. If this 430 requirement cannot be met, the firmware cannot declare that it supports APFv4 and 431 it should declare that it only supports APFv3(if counter is partially supported) or 432 APFv2. 433 """.trimIndent() 434 assertWithMessage(errMsg).that(readResult).isEqualTo(program) 435 } 436 } 437 installAndVerifyProgramnull438 private fun installAndVerifyProgram(program: ByteArray) { 439 installProgram(program) 440 val readResult = readProgram().take(program.size).toByteArray() 441 assertThat(readResult).isEqualTo(program) 442 } 443 addPassIfNotIcmpv6EchoReplynull444 fun ApfV4GeneratorBase<*>.addPassIfNotIcmpv6EchoReply() { 445 // If not IPv6 -> PASS 446 addLoad16(R0, ETH_ETHERTYPE_OFFSET) 447 addJumpIfR0NotEquals(ETH_P_IPV6.toLong(), BaseApfGenerator.PASS_LABEL) 448 449 // If not ICMPv6 -> PASS 450 addLoad8(R0, IPV6_NEXT_HEADER_OFFSET) 451 addJumpIfR0NotEquals(IPPROTO_ICMPV6.toLong(), BaseApfGenerator.PASS_LABEL) 452 453 // If not echo reply -> PASS 454 addLoad8(R0, ICMP6_TYPE_OFFSET) 455 addJumpIfR0NotEquals(0x81, BaseApfGenerator.PASS_LABEL) 456 } 457 458 // APF integration is mostly broken before V 459 @VsrTest(requirements = ["VSR-5.3.12-002", "VSR-5.3.12-005"]) 460 @IgnoreUpTo(Build.VERSION_CODES.UPSIDE_DOWN_CAKE) 461 @Test testDropPingReplynull462 fun testDropPingReply() { 463 // VSR-14 mandates APF to be turned on when the screen is off and the Wi-Fi link 464 // is idle or traffic is less than 10 Mbps. Before that, we don't mandate when the APF 465 // should be turned on. 466 assume().that(getVsrApiLevel()).isAtLeast(34) 467 assumeApfVersionSupportAtLeast(4) 468 469 // clear any active APF filter 470 clearApfMemory() 471 readProgram() // wait for install completion 472 473 // Assert that initial ping does not get filtered. 474 val payloadSize = if (getFirstApiLevel() >= Build.VERSION_CODES.VANILLA_ICE_CREAM) { 475 68 476 } else { 477 4 478 } 479 val data = ByteArray(payloadSize).also { Random.nextBytes(it) } 480 packetReader.sendPing(data, payloadSize) 481 assertThat(packetReader.expectPingReply()).isEqualTo(data) 482 483 // Generate an APF program that drops the next ping 484 val gen = ApfV4Generator( 485 caps.apfVersionSupported, 486 caps.maximumApfProgramSize, 487 caps.maximumApfProgramSize 488 ) 489 490 // If not ICMPv6 Echo Reply -> PASS 491 gen.addPassIfNotIcmpv6EchoReply() 492 493 // if not data matches -> PASS 494 gen.addLoadImmediate(R0, ICMP6_TYPE_OFFSET + PING_HEADER_LENGTH) 495 gen.addJumpIfBytesAtR0NotEqual(data, BaseApfGenerator.PASS_LABEL) 496 497 // else DROP 498 gen.addJump(BaseApfGenerator.DROP_LABEL) 499 500 val program = gen.generate() 501 installAndVerifyProgram(program) 502 503 packetReader.sendPing(data, payloadSize) 504 packetReader.expectPingDropped() 505 } 506 clearApfMemorynull507 fun clearApfMemory() = installProgram(ByteArray(caps.maximumApfProgramSize)) 508 509 // APF integration is mostly broken before V 510 @VsrTest(requirements = ["VSR-5.3.12-002", "VSR-5.3.12-005"]) 511 @IgnoreUpTo(Build.VERSION_CODES.UPSIDE_DOWN_CAKE) 512 @Test 513 fun testPrefilledMemorySlotsV4() { 514 // VSR-14 mandates APF to be turned on when the screen is off and the Wi-Fi link 515 // is idle or traffic is less than 10 Mbps. Before that, we don't mandate when the APF 516 // should be turned on. 517 assume().that(getVsrApiLevel()).isAtLeast(34) 518 // Test v4 memory slots on both v4 and v6 interpreters. 519 assumeApfVersionSupportAtLeast(4) 520 clearApfMemory() 521 val gen = ApfV4Generator( 522 caps.apfVersionSupported, 523 caps.maximumApfProgramSize, 524 caps.maximumApfProgramSize 525 ) 526 527 // If not ICMPv6 Echo Reply -> PASS 528 gen.addPassIfNotIcmpv6EchoReply() 529 530 // Store all prefilled memory slots in counter region [500, 520) 531 val counterRegion = 500 532 gen.addLoadImmediate(R1, counterRegion) 533 gen.addLoadFromMemory(R0, MemorySlot.PROGRAM_SIZE) 534 gen.addStoreData(R0, 0) 535 gen.addLoadFromMemory(R0, MemorySlot.RAM_LEN) 536 gen.addStoreData(R0, 4) 537 gen.addLoadFromMemory(R0, MemorySlot.IPV4_HEADER_SIZE) 538 gen.addStoreData(R0, 8) 539 gen.addLoadFromMemory(R0, MemorySlot.PACKET_SIZE) 540 gen.addStoreData(R0, 12) 541 gen.addLoadFromMemory(R0, MemorySlot.FILTER_AGE_SECONDS) 542 gen.addStoreData(R0, 16) 543 544 val program = gen.generate() 545 assertThat(program.size).isLessThan(counterRegion) 546 installAndVerifyProgram(program) 547 548 // Trigger the program by sending a ping and waiting on the reply. 549 val payloadSize = if (getFirstApiLevel() >= Build.VERSION_CODES.VANILLA_ICE_CREAM) { 550 68 551 } else { 552 4 553 } 554 val data = ByteArray(payloadSize).also { Random.nextBytes(it) } 555 packetReader.sendPing(data, payloadSize) 556 packetReader.expectPingReply() 557 558 val readResult = readProgram() 559 val buffer = ByteBuffer.wrap(readResult, counterRegion, 20 /* length */) 560 expect.withMessage("PROGRAM_SIZE").that(buffer.getInt()).isEqualTo(program.size) 561 expect.withMessage("RAM_LEN").that(buffer.getInt()).isEqualTo(caps.maximumApfProgramSize) 562 expect.withMessage("IPV4_HEADER_SIZE").that(buffer.getInt()).isEqualTo(0) 563 // Ping packet payload + ICMPv6 header (8) + IPv6 header (40) + ethernet header (14) 564 expect.withMessage("PACKET_SIZE").that(buffer.getInt()).isEqualTo(payloadSize + 8 + 40 + 14) 565 expect.withMessage("FILTER_AGE_SECONDS").that(buffer.getInt()).isLessThan(5) 566 } 567 568 // APF integration is mostly broken before V 569 @VsrTest(requirements = ["VSR-5.3.12-002", "VSR-5.3.12-005"]) 570 @IgnoreUpTo(Build.VERSION_CODES.UPSIDE_DOWN_CAKE) 571 @Test testFilterAgeIncreasesBetweenPacketsnull572 fun testFilterAgeIncreasesBetweenPackets() { 573 // VSR-14 mandates APF to be turned on when the screen is off and the Wi-Fi link 574 // is idle or traffic is less than 10 Mbps. Before that, we don't mandate when the APF 575 // should be turned on. 576 assume().that(getVsrApiLevel()).isAtLeast(34) 577 assumeApfVersionSupportAtLeast(4) 578 clearApfMemory() 579 val gen = ApfV4Generator( 580 caps.apfVersionSupported, 581 caps.maximumApfProgramSize, 582 caps.maximumApfProgramSize 583 ) 584 585 // If not ICMPv6 Echo Reply -> PASS 586 gen.addPassIfNotIcmpv6EchoReply() 587 588 // Store all prefilled memory slots in counter region [500, 520) 589 val counterRegion = 500 590 gen.addLoadImmediate(R1, counterRegion) 591 gen.addLoadFromMemory(R0, MemorySlot.FILTER_AGE_SECONDS) 592 gen.addStoreData(R0, 0) 593 594 installAndVerifyProgram(gen.generate()) 595 596 val payloadSize = 56 597 val data = ByteArray(payloadSize).also { Random.nextBytes(it) } 598 packetReader.sendPing(data, payloadSize) 599 packetReader.expectPingReply() 600 601 var buffer = ByteBuffer.wrap(readProgram(), counterRegion, 4 /* length */) 602 val filterAgeSecondsOrig = buffer.getInt() 603 604 Thread.sleep(5100) 605 606 packetReader.sendPing(data, payloadSize) 607 packetReader.expectPingReply() 608 609 buffer = ByteBuffer.wrap(readProgram(), counterRegion, 4 /* length */) 610 val filterAgeSeconds = buffer.getInt() 611 // Assert that filter age has increased, but not too much. 612 val timeDiff = filterAgeSeconds - filterAgeSecondsOrig 613 assertThat(timeDiff).isAnyOf(5, 6) 614 } 615 616 @VsrTest(requirements = ["VSR-5.3.12-002", "VSR-5.3.12-005"]) 617 @Test testFilterAge16384thsIncreasesBetweenPacketsnull618 fun testFilterAge16384thsIncreasesBetweenPackets() { 619 assumeApfVersionSupportAtLeast(6000) 620 clearApfMemory() 621 val gen = ApfV6Generator( 622 caps.apfVersionSupported, 623 caps.maximumApfProgramSize, 624 caps.maximumApfProgramSize 625 ) 626 627 // If not ICMPv6 Echo Reply -> PASS 628 gen.addPassIfNotIcmpv6EchoReply() 629 630 // Store all prefilled memory slots in counter region [500, 520) 631 gen.addLoadFromMemory(R0, MemorySlot.FILTER_AGE_16384THS) 632 gen.addStoreCounter(FILTER_AGE_16384THS, R0) 633 634 installAndVerifyProgram(gen.generate()) 635 636 val payloadSize = 56 637 val data = ByteArray(payloadSize).also { Random.nextBytes(it) } 638 packetReader.sendPing(data, payloadSize) 639 packetReader.expectPingReply() 640 641 var apfRam = readProgram() 642 val filterAge16384thSecondsOrig = 643 ApfCounterTracker.getCounterValue(apfRam, FILTER_AGE_16384THS) 644 645 Thread.sleep(5000) 646 647 packetReader.sendPing(data, payloadSize) 648 packetReader.expectPingReply() 649 650 apfRam = readProgram() 651 val filterAge16384thSeconds = ApfCounterTracker.getCounterValue(apfRam, FILTER_AGE_16384THS) 652 val timeDiff = (filterAge16384thSeconds - filterAge16384thSecondsOrig) 653 // Expect the HAL plus ping latency to be less than 800ms. 654 val timeDiffLowerBound = (4.99 * 16384).toInt() 655 val timeDiffUpperBound = (5.81 * 16384).toInt() 656 // Assert that filter age has increased, but not too much. 657 assertThat(timeDiff).isGreaterThan(timeDiffLowerBound) 658 assertThat(timeDiff).isLessThan(timeDiffUpperBound) 659 } 660 661 @VsrTest( 662 requirements = ["VSR-5.3.12-002", "VSR-5.3.12-005", "VSR-5.3.12-012", "VSR-5.3.12-013", 663 "VSR-5.3.12-014", "VSR-5.3.12-015", "VSR-5.3.12-016", "VSR-5.3.12-017"] 664 ) 665 @IgnoreUpTo(Build.VERSION_CODES.UPSIDE_DOWN_CAKE) 666 @Test testReplyPingnull667 fun testReplyPing() { 668 assumeApfVersionSupportAtLeast(6000) 669 installProgram(ByteArray(caps.maximumApfProgramSize) { 0 }) // Clear previous program 670 readProgram() // Ensure installation is complete 671 672 val payloadSize = 56 673 val payload = ByteArray(payloadSize).also { Random.nextBytes(it) } 674 val firstByte = payload.take(1).toByteArray() 675 676 val pingRequestIpv6PayloadLen = PING_HEADER_LENGTH + 1 677 val pingRequestPktLen = ETH_HEADER_LEN + IPV6_HEADER_LEN + pingRequestIpv6PayloadLen 678 679 val gen = ApfV6Generator( 680 caps.apfVersionSupported, 681 caps.maximumApfProgramSize, 682 caps.maximumApfProgramSize 683 ) 684 val skipPacketLabel = gen.uniqueLabel 685 686 // Summary of the program: 687 // if the packet is not ICMPv6 echo reply 688 // pass 689 // else if the echo reply payload size is 1 690 // increase PASSED_IPV6_ICMP counter 691 // pass 692 // else 693 // transmit a ICMPv6 echo request packet with the first byte of the payload in the reply 694 // increase DROPPED_IPV6_MULTICAST_PING counter 695 // drop 696 val program = gen 697 .addLoad16(R0, ETH_ETHERTYPE_OFFSET) 698 .addJumpIfR0NotEquals(ETH_P_IPV6.toLong(), skipPacketLabel) 699 .addLoad8(R0, IPV6_NEXT_HEADER_OFFSET) 700 .addJumpIfR0NotEquals(IPPROTO_ICMPV6.toLong(), skipPacketLabel) 701 .addLoad8(R0, ICMP6_TYPE_OFFSET) 702 .addJumpIfR0NotEquals(0x81, skipPacketLabel) // Echo reply type 703 .addLoadFromMemory(R0, MemorySlot.PACKET_SIZE) 704 .addCountAndPassIfR0Equals( 705 (ETHER_HEADER_LEN + IPV6_HEADER_LEN + PING_HEADER_LENGTH + firstByte.size) 706 .toLong(), 707 PASSED_IPV6_ICMP 708 ) 709 // Ping Packet Generation 710 .addAllocate(pingRequestPktLen) 711 // Eth header 712 .addPacketCopy(ETHER_SRC_ADDR_OFFSET, ETHER_ADDR_LEN) // dst MAC address 713 .addPacketCopy(ETHER_DST_ADDR_OFFSET, ETHER_ADDR_LEN) // src MAC address 714 .addWriteU16(ETH_P_IPV6) // IPv6 type 715 // IPv6 Header 716 .addWrite32(0x60000000) // IPv6 Header: version, traffic class, flowlabel 717 // payload length (2 bytes) | next header: ICMPv6 (1 byte) | hop limit (1 byte) 718 .addWrite32(pingRequestIpv6PayloadLen shl 16 or (IPPROTO_ICMPV6 shl 8 or 64)) 719 .addPacketCopy(IPV6_DEST_ADDR_OFFSET, IPV6_ADDR_LEN) // src ip 720 .addPacketCopy(IPV6_SRC_ADDR_OFFSET, IPV6_ADDR_LEN) // dst ip 721 // ICMPv6 722 .addWriteU8(0x80) // type: echo request 723 .addWriteU8(0) // code 724 .addWriteU16(pingRequestIpv6PayloadLen) // checksum 725 // identifier 726 .addPacketCopy(ETHER_HEADER_LEN + IPV6_HEADER_LEN + ICMPV6_HEADER_MIN_LEN, 2) 727 .addWriteU16(0) // sequence number 728 .addDataCopy(firstByte) // data 729 .addTransmitL4( 730 ETHER_HEADER_LEN, // ip_ofs 731 ICMP6_CHECKSUM_OFFSET, // csum_ofs 732 IPV6_SRC_ADDR_OFFSET, // csum_start 733 IPPROTO_ICMPV6, // partial_sum 734 false // udp 735 ) 736 // Warning: the program abuse DROPPED_IPV6_MULTICAST_PING for debugging purpose 737 .addCountAndDrop(DROPPED_IPV6_MULTICAST_PING) 738 .defineLabel(skipPacketLabel) 739 .addPass() 740 .generate() 741 742 installAndVerifyProgram(program) 743 744 packetReader.sendPing(payload, payloadSize) 745 746 val replyPayload = try { 747 packetReader.expectPingReply(TIMEOUT_MS * 2) 748 } catch (e: TimeoutException) { 749 byteArrayOf() // Empty payload if timeout occurs 750 } 751 752 val apfCounterTracker = ApfCounterTracker() 753 apfCounterTracker.updateCountersFromData(readProgram()) 754 Log.i(TAG, "counter map: ${apfCounterTracker.counters}") 755 756 assertThat(replyPayload).isEqualTo(firstByte) 757 } 758 } 759