1 /* <lambda>null2 * 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.content.pm.PackageManager.FEATURE_AUTOMOTIVE 23 import android.content.pm.PackageManager.FEATURE_LEANBACK 24 import android.content.pm.PackageManager.FEATURE_WIFI 25 import android.net.ConnectivityManager 26 import android.net.Network 27 import android.net.NetworkCapabilities 28 import android.net.NetworkRequest 29 import android.net.apf.ApfCapabilities 30 import android.net.apf.ApfConstants.ETH_ETHERTYPE_OFFSET 31 import android.net.apf.ApfConstants.ETH_HEADER_LEN 32 import android.net.apf.ApfConstants.ICMP6_CHECKSUM_OFFSET 33 import android.net.apf.ApfConstants.ICMP6_TYPE_OFFSET 34 import android.net.apf.ApfConstants.IPV6_DEST_ADDR_OFFSET 35 import android.net.apf.ApfConstants.IPV6_HEADER_LEN 36 import android.net.apf.ApfConstants.IPV6_NEXT_HEADER_OFFSET 37 import android.net.apf.ApfConstants.IPV6_SRC_ADDR_OFFSET 38 import android.net.apf.ApfCounterTracker 39 import android.net.apf.ApfCounterTracker.Counter.DROPPED_IPV6_NS_INVALID 40 import android.net.apf.ApfCounterTracker.Counter.DROPPED_IPV6_NS_REPLIED_NON_DAD 41 import android.net.apf.ApfCounterTracker.Counter.FILTER_AGE_16384THS 42 import android.net.apf.ApfCounterTracker.Counter.PASSED_IPV6_ICMP 43 import android.net.apf.ApfV4Generator 44 import android.net.apf.ApfV4GeneratorBase 45 import android.net.apf.ApfV6Generator 46 import android.net.apf.BaseApfGenerator 47 import android.net.apf.BaseApfGenerator.MemorySlot 48 import android.net.apf.BaseApfGenerator.Register.R0 49 import android.net.apf.BaseApfGenerator.Register.R1 50 import android.os.Build 51 import android.os.Handler 52 import android.os.HandlerThread 53 import android.os.PowerManager 54 import android.os.SystemProperties 55 import android.os.UserManager 56 import android.platform.test.annotations.AppModeFull 57 import android.system.Os 58 import android.system.OsConstants 59 import android.system.OsConstants.AF_INET6 60 import android.system.OsConstants.ETH_P_IPV6 61 import android.system.OsConstants.ICMP6_ECHO_REPLY 62 import android.system.OsConstants.ICMP6_ECHO_REQUEST 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.pollingCheck 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.assertEquals 104 import kotlin.test.assertFailsWith 105 import kotlin.test.assertNotNull 106 import org.junit.After 107 import org.junit.AfterClass 108 import org.junit.Assume.assumeFalse 109 import org.junit.Before 110 import org.junit.BeforeClass 111 import org.junit.Rule 112 import org.junit.Test 113 import org.junit.runner.RunWith 114 115 private const val TAG = "ApfIntegrationTest" 116 private const val TIMEOUT_MS = 2000L 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 134 fun turnScreenOff() { 135 if (!wakeLock.isHeld()) wakeLock.acquire() 136 runShellCommandOrThrow("input keyevent KEYCODE_SLEEP") 137 waitForInteractiveState(false) 138 } 139 140 fun turnScreenOn() { 141 if (wakeLock.isHeld()) wakeLock.release() 142 runShellCommandOrThrow("input keyevent KEYCODE_WAKEUP") 143 waitForInteractiveState(true) 144 } 145 146 private fun waitForInteractiveState(interactive: Boolean) { 147 // TODO(b/366037029): This test condition should be removed once 148 // PowerManager#isInteractive is fully implemented on automotive 149 // form factor with visible background user. 150 if (isAutomotiveWithVisibleBackgroundUser()) { 151 // Wait for 2 seconds to ensure the interactive state is updated. 152 // This is a workaround for b/366037029. 153 Thread.sleep(2000L) 154 } else { 155 val result = pollingCheck(timeout_ms = 2000) { 156 powerManager.isInteractive() 157 } 158 assertThat(result).isEqualTo(interactive) 159 } 160 } 161 162 private fun isAutomotiveWithVisibleBackgroundUser(): Boolean { 163 val packageManager = context.getPackageManager() 164 val userManager = context.getSystemService(UserManager::class.java)!! 165 return (packageManager.hasSystemFeature(FEATURE_AUTOMOTIVE) && 166 userManager.isVisibleBackgroundUsersSupported) 167 } 168 169 @BeforeClass 170 @JvmStatic 171 @Suppress("ktlint:standard:no-multi-spaces") 172 fun setupOnce() { 173 // TODO: assertions thrown in @BeforeClass / @AfterClass are not well supported in the 174 // test infrastructure. Consider saving exception and throwing it in setUp(). 175 176 // APF must run when the screen is off and the device is not interactive. 177 turnScreenOff() 178 179 // Wait for APF to become active. 180 Thread.sleep(1000) 181 // TODO: check that there is no active wifi network. Otherwise, ApfFilter has already been 182 // created. 183 } 184 185 @AfterClass 186 @JvmStatic 187 fun tearDownOnce() { 188 turnScreenOn() 189 } 190 } 191 192 class Icmp6PacketReader( 193 handler: Handler, 194 private val network: Network 195 ) : PacketReader(handler, RCV_BUFFER_SIZE) { 196 private data class PingContext( 197 val futureReply: CompletableFuture<List<ByteArray>>, 198 val expectReplyCount: Int, 199 val replyPayloads: MutableList<ByteArray> = mutableListOf() 200 ) 201 private var sockFd: FileDescriptor? = null 202 private var pingContext: PingContext? = null 203 204 override fun createFd(): FileDescriptor { 205 // sockFd is closed by calling super.stop() 206 val sock = Os.socket(AF_INET6, SOCK_DGRAM or SOCK_NONBLOCK, IPPROTO_ICMPV6) 207 // APF runs only on WiFi, so make sure the socket is bound to the right network. 208 network.bindSocket(sock) 209 sockFd = sock 210 return sock 211 } 212 213 override fun handlePacket(recvbuf: ByteArray, length: Int) { 214 val context = pingContext ?: return 215 216 // If zero-length or Type is not echo reply: ignore. 217 if (length == 0 || recvbuf[0] != 0x81.toByte()) { 218 return 219 } 220 // Only copy the ping data and complete the future. 221 val result = recvbuf.sliceArray(8..<length) 222 Log.i(TAG, "Received ping reply: ${result.toHexString()}") 223 context.replyPayloads.add(recvbuf.sliceArray(8..<length)) 224 if (context.replyPayloads.size == context.expectReplyCount) { 225 context.futureReply.complete(context.replyPayloads) 226 pingContext = null 227 } 228 } 229 230 fun sendPing(data: ByteArray, payloadSize: Int, expectReplyCount: Int = 1) { 231 require(data.size == payloadSize) 232 233 // rfc4443#section-4.1: Echo Request Message 234 // 0 1 2 3 235 // 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 236 // +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ 237 // | Type | Code | Checksum | 238 // +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ 239 // | Identifier | Sequence Number | 240 // +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ 241 // | Data ... 242 // +-+-+-+-+- 243 val icmp6Header = byteArrayOf(0x80.toByte(), 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00) 244 val packet = icmp6Header + data 245 Log.i(TAG, "Sent ping: ${packet.toHexString()}") 246 pingContext = PingContext( 247 futureReply = CompletableFuture<List<ByteArray>>(), 248 expectReplyCount = expectReplyCount 249 ) 250 Os.sendto(sockFd!!, packet, 0, packet.size, 0, PING_DESTINATION) 251 } 252 253 fun expectPingReply(timeoutMs: Long = TIMEOUT_MS): List<ByteArray> { 254 return pingContext!!.futureReply.get(timeoutMs, TimeUnit.MILLISECONDS) 255 } 256 257 fun expectPingDropped() { 258 assertFailsWith(TimeoutException::class) { 259 pingContext!!.futureReply.get(TIMEOUT_MS, TimeUnit.MILLISECONDS) 260 } 261 } 262 263 override fun start(): Boolean { 264 // Ignore the fact start() could return false or throw an exception. 265 handler.post({ super.start() }) 266 handler.waitForIdle(TIMEOUT_MS) 267 return true 268 } 269 270 override fun stop() { 271 handler.post({ super.stop() }) 272 handler.waitForIdle(TIMEOUT_MS) 273 } 274 } 275 276 @get:Rule val ignoreRule = DevSdkIgnoreRule() 277 @get:Rule val expect = Expect.create() 278 279 private val cm by lazy { context.getSystemService(ConnectivityManager::class.java)!! } 280 private val pm by lazy { context.packageManager } 281 private lateinit var network: Network 282 private lateinit var ifname: String 283 private lateinit var networkCallback: TestableNetworkCallback 284 private lateinit var caps: ApfCapabilities 285 private val handlerThread = HandlerThread("$TAG handler thread").apply { start() } 286 private val handler = Handler(handlerThread.looper) 287 private lateinit var packetReader: Icmp6PacketReader 288 289 fun getApfCapabilities(): ApfCapabilities { 290 val caps = runShellCommand("cmd network_stack apf $ifname capabilities").trim() 291 if (caps.isEmpty()) { 292 return ApfCapabilities(0, 0, 0) 293 } 294 val (version, maxLen, packetFormat) = caps.split(",").map { it.toInt() } 295 return ApfCapabilities(version, maxLen, packetFormat) 296 } 297 298 private fun isTvDeviceSupportFullNetworkingUnder2w(): Boolean { 299 return (pm.hasSystemFeature(FEATURE_LEANBACK) && 300 pm.hasSystemFeature("com.google.android.tv.full_networking_under_2w")) 301 } 302 303 @Before 304 fun setUp() { 305 assume().that(pm.hasSystemFeature(FEATURE_WIFI)).isTrue() 306 307 // Based on GTVS-16, Android Packet Filtering (APF) is OPTIONAL for devices that fully 308 // process all network packets on CPU at all times, even in standby, while meeting 309 // the <= 2W standby power demand requirement. 310 assumeFalse( 311 "Skipping test: TV device process full networking on CPU under 2W", 312 isTvDeviceSupportFullNetworkingUnder2w() 313 ) 314 315 networkCallback = TestableNetworkCallback() 316 cm.requestNetwork( 317 NetworkRequest.Builder() 318 .addTransportType(NetworkCapabilities.TRANSPORT_WIFI) 319 .addCapability(NetworkCapabilities.NET_CAPABILITY_INTERNET) 320 .build(), 321 networkCallback 322 ) 323 network = networkCallback.expect<Available>().network 324 networkCallback.eventuallyExpect<LinkPropertiesChanged>(TIMEOUT_MS) { 325 ifname = assertNotNull(it.lp.interfaceName) 326 true 327 } 328 // It's possible the device does not support APF, in which case this command will not be 329 // successful. Ignore the error as testApfCapabilities() already asserts APF support on the 330 // respective VSR releases and all other tests are based on the capabilities indicated. 331 runShellCommand("cmd network_stack apf $ifname pause") 332 caps = getApfCapabilities() 333 334 packetReader = Icmp6PacketReader(handler, network) 335 packetReader.start() 336 } 337 338 @After 339 fun tearDown() { 340 if (::packetReader.isInitialized) { 341 packetReader.stop() 342 } 343 handlerThread.quitSafely() 344 handlerThread.join() 345 346 if (::ifname.isInitialized) { 347 runShellCommand("cmd network_stack apf $ifname resume") 348 } 349 if (::networkCallback.isInitialized) { 350 cm.unregisterNetworkCallback(networkCallback) 351 } 352 } 353 354 @VsrTest( 355 requirements = ["VSR-5.3.12-001", "VSR-5.3.12-003", "VSR-5.3.12-004", "VSR-5.3.12-009", 356 "VSR-5.3.12-012"] 357 ) 358 @Test 359 fun testApfCapabilities() { 360 // APF became mandatory in Android 14 VSR. 361 val vsrApiLevel = getVsrApiLevel() 362 assume().that(vsrApiLevel).isAtLeast(34) 363 364 // DEVICEs launching with Android 14 with CHIPSETs that set ro.board.first_api_level to 34: 365 // - [GMS-VSR-5.3.12-003] MUST return 4 or higher as the APF version number from calls to 366 // the getApfPacketFilterCapabilities HAL method. 367 // - [GMS-VSR-5.3.12-004] MUST indicate at least 1024 bytes of usable memory from calls to 368 // the getApfPacketFilterCapabilities HAL method. 369 // TODO: check whether above text should be changed "34 or higher" 370 assertThat(caps.apfVersionSupported).isAtLeast(4) 371 assertThat(caps.maximumApfProgramSize).isAtLeast(1024) 372 373 if (caps.apfVersionSupported > 4) { 374 assertThat(caps.maximumApfProgramSize).isAtLeast(2048) 375 assertThat(caps.apfVersionSupported).isAnyOf(6000, 6100) // v6.000 or v6.100 376 } 377 378 // DEVICEs launching with Android 15 (AOSP experimental) or higher with CHIPSETs that set 379 // ro.board.first_api_level or ro.board.api_level to 202404 or higher: 380 // - [GMS-VSR-5.3.12-009] MUST indicate at least 2048 bytes of usable memory from calls to 381 // the getApfPacketFilterCapabilities HAL method. 382 if (vsrApiLevel >= 202404) { 383 assertThat(caps.maximumApfProgramSize).isAtLeast(2048) 384 } 385 386 // DEVICEs with CHIPSETs that set ro.board.first_api_level or ro.board.api_level to 202504 387 // or higher: 388 // - [VSR-5.3.12-018] MUST implement version 6 or version 6.1 of the Android Packet 389 // Filtering (APF) interpreter in the Wi-Fi firmware. 390 // - [VSR-5.3.12-019] MUST provide at least 4000 bytes of APF RAM when version 6 is 391 // implemented OR 3000 bytes when version 6.1 is implemented. 392 // - Note, the APF RAM requirement for APF version 6.1 will become 4000 bytes in Android 17 393 // with CHIPSETs that set ro.board.first_api_level or ro.board.api_level to 202604 or 394 // higher. 395 if (vsrApiLevel >= 202504) { 396 assertThat(caps.apfVersionSupported).isAnyOf(6000, 6100) 397 if (caps.apfVersionSupported == 6000) { 398 assertThat(caps.maximumApfProgramSize).isAtLeast(4000) 399 } else { 400 assertThat(caps.maximumApfProgramSize).isAtLeast(3000) 401 } 402 } 403 404 // ApfFilter does not support anything but ARPHRD_ETHER. 405 assertThat(caps.apfPacketFormat).isEqualTo(OsConstants.ARPHRD_ETHER) 406 } 407 408 // APF is backwards compatible, i.e. a v6 interpreter supports both v2 and v4 functionality. 409 fun assumeApfVersionSupportAtLeast(version: Int) { 410 assume().that(caps.apfVersionSupported).isAtLeast(version) 411 } 412 413 fun assumeNotCuttlefish() { 414 assume().that(SystemProperties.get("ro.product.board", "")).isNotEqualTo("cutf") 415 } 416 417 fun installProgram(bytes: ByteArray) { 418 val prog = bytes.toHexString() 419 val result = runShellCommandOrThrow("cmd network_stack apf $ifname install $prog").trim() 420 // runShellCommandOrThrow only throws on S+. 421 assertThat(result).isEqualTo("success") 422 } 423 424 fun readProgram(): ByteArray { 425 val progHexString = runShellCommandOrThrow("cmd network_stack apf $ifname read").trim() 426 // runShellCommandOrThrow only throws on S+. 427 assertThat(progHexString).isNotEmpty() 428 return HexDump.hexStringToByteArray(progHexString) 429 } 430 431 @VsrTest( 432 requirements = ["VSR-5.3.12-007", "VSR-5.3.12-008", "VSR-5.3.12-010", "VSR-5.3.12-011"] 433 ) 434 @SkipPresubmit(reason = "This test takes longer than 1 minute, do not run it on presubmit.") 435 // APF integration is mostly broken before V, only run the full read / write test on V+. 436 @IgnoreUpTo(Build.VERSION_CODES.UPSIDE_DOWN_CAKE) 437 // Increase timeout for test to 15 minutes to accommodate device with large APF RAM. 438 @Test(timeout = 15 * 60 * 1000) 439 fun testReadWriteProgram() { 440 assumeApfVersionSupportAtLeast(4) 441 442 val minReadWriteSize = if (getFirstApiLevel() >= Build.VERSION_CODES.VANILLA_ICE_CREAM) { 443 2 444 } else { 445 8 446 } 447 448 // The minReadWriteSize is 2 bytes. The first byte always stays PASS. 449 val program = ByteArray(caps.maximumApfProgramSize) 450 for (i in caps.maximumApfProgramSize downTo minReadWriteSize) { 451 // Randomize bytes in range [1, i). And install first [0, i) bytes of program. 452 // Note that only the very first instruction (PASS) is valid APF bytecode. 453 Random.nextBytes(program, 1 /* fromIndex */, i /* toIndex */) 454 installProgram(program.sliceArray(0..<i)) 455 456 // Compare entire memory region. 457 val readResult = readProgram() 458 val errMsg = """ 459 read/write $i byte prog failed. 460 In APFv4, the APF memory region MUST NOT be modified or cleared except by APF 461 instructions executed by the interpreter or by Android OS calls to the HAL. If this 462 requirement cannot be met, the firmware cannot declare that it supports APFv4 and 463 it should declare that it only supports APFv3(if counter is partially supported) or 464 APFv2. 465 """.trimIndent() 466 assertWithMessage(errMsg).that(readResult).isEqualTo(program) 467 } 468 } 469 470 private fun installAndVerifyProgram(program: ByteArray) { 471 installProgram(program) 472 val readResult = readProgram().take(program.size).toByteArray() 473 assertThat(readResult).isEqualTo(program) 474 } 475 476 fun ApfV4GeneratorBase<*>.addPassIfNotIcmpv6EchoReply(skipPacketLabel: Short) { 477 // If not IPv6 -> PASS 478 addLoad16intoR0(ETH_ETHERTYPE_OFFSET) 479 addJumpIfR0NotEquals(ETH_P_IPV6.toLong(), skipPacketLabel) 480 481 // If not ICMPv6 -> PASS 482 addLoad8intoR0(IPV6_NEXT_HEADER_OFFSET) 483 addJumpIfR0NotEquals(IPPROTO_ICMPV6.toLong(), skipPacketLabel) 484 485 // If not echo reply -> PASS 486 addLoad8intoR0(ICMP6_TYPE_OFFSET) 487 addJumpIfR0NotEquals(0x81, skipPacketLabel) 488 } 489 490 // APF integration is mostly broken before V 491 @VsrTest(requirements = ["VSR-5.3.12-002", "VSR-5.3.12-005"]) 492 @IgnoreUpTo(Build.VERSION_CODES.UPSIDE_DOWN_CAKE) 493 @Test 494 fun testDropPingReply() { 495 // VSR-14 mandates APF to be turned on when the screen is off and the Wi-Fi link 496 // is idle or traffic is less than 10 Mbps. Before that, we don't mandate when the APF 497 // should be turned on. 498 assume().that(getVsrApiLevel()).isAtLeast(34) 499 assumeApfVersionSupportAtLeast(4) 500 assumeNotCuttlefish() 501 502 // clear any active APF filter 503 clearApfMemory() 504 readProgram() // wait for install completion 505 506 // Assert that initial ping does not get filtered. 507 val payloadSize = if (getFirstApiLevel() >= Build.VERSION_CODES.VANILLA_ICE_CREAM) { 508 68 509 } else { 510 4 511 } 512 val data = ByteArray(payloadSize).also { Random.nextBytes(it) } 513 packetReader.sendPing(data, payloadSize) 514 assertThat(packetReader.expectPingReply()[0]).isEqualTo(data) 515 516 // Generate an APF program that drops the next ping 517 val gen = ApfV4Generator( 518 caps.apfVersionSupported, 519 caps.maximumApfProgramSize, 520 caps.maximumApfProgramSize 521 ) 522 523 val skipPacketLabel = gen.uniqueLabel 524 // If not ICMPv6 Echo Reply -> PASS 525 gen.addPassIfNotIcmpv6EchoReply(skipPacketLabel) 526 527 // if not data matches -> PASS 528 gen.addLoadImmediate(R0, ICMP6_TYPE_OFFSET + PING_HEADER_LENGTH) 529 gen.addJumpIfBytesAtR0NotEqual(data, skipPacketLabel) 530 531 // else DROP 532 // Warning: the program abuse DROPPED_IPV6_NS_INVALID/PASSED_IPV6_ICMP for debugging purpose 533 gen.addCountAndDrop(DROPPED_IPV6_NS_INVALID) 534 .defineLabel(skipPacketLabel) 535 .addCountAndPass(PASSED_IPV6_ICMP) 536 .addCountTrampoline() 537 538 val program = gen.generate() 539 installAndVerifyProgram(program) 540 541 val counterBefore = ApfCounterTracker.getCounterValue( 542 readProgram(), 543 DROPPED_IPV6_NS_INVALID 544 ) 545 packetReader.sendPing(data, payloadSize) 546 packetReader.expectPingDropped() 547 val counterAfter = ApfCounterTracker.getCounterValue( 548 readProgram(), 549 DROPPED_IPV6_NS_INVALID 550 ) 551 assertEquals(counterBefore + 1, counterAfter) 552 } 553 554 fun clearApfMemory() = installProgram(ByteArray(caps.maximumApfProgramSize)) 555 556 // APF integration is mostly broken before V 557 @VsrTest(requirements = ["VSR-5.3.12-002", "VSR-5.3.12-005"]) 558 @IgnoreUpTo(Build.VERSION_CODES.UPSIDE_DOWN_CAKE) 559 @Test 560 fun testPrefilledMemorySlotsV4() { 561 // VSR-14 mandates APF to be turned on when the screen is off and the Wi-Fi link 562 // is idle or traffic is less than 10 Mbps. Before that, we don't mandate when the APF 563 // should be turned on. 564 assume().that(getVsrApiLevel()).isAtLeast(34) 565 // Test v4 memory slots on both v4 and v6 interpreters. 566 assumeApfVersionSupportAtLeast(4) 567 assumeNotCuttlefish() 568 clearApfMemory() 569 val gen = ApfV4Generator( 570 caps.apfVersionSupported, 571 caps.maximumApfProgramSize, 572 caps.maximumApfProgramSize 573 ) 574 575 // If not ICMPv6 Echo Reply -> PASS 576 gen.addPassIfNotIcmpv6EchoReply(BaseApfGenerator.PASS_LABEL) 577 578 // Store all prefilled memory slots in counter region [500, 520) 579 val counterRegion = 500 580 gen.addLoadImmediate(R1, counterRegion) 581 gen.addLoadFromMemory(R0, MemorySlot.PROGRAM_SIZE) 582 gen.addStoreData(R0, 0) 583 gen.addLoadFromMemory(R0, MemorySlot.RAM_LEN) 584 gen.addStoreData(R0, 4) 585 gen.addLoadFromMemory(R0, MemorySlot.IPV4_HEADER_SIZE) 586 gen.addStoreData(R0, 8) 587 gen.addLoadFromMemory(R0, MemorySlot.PACKET_SIZE) 588 gen.addStoreData(R0, 12) 589 gen.addLoadFromMemory(R0, MemorySlot.FILTER_AGE_SECONDS) 590 gen.addStoreData(R0, 16) 591 592 val program = gen.generate() 593 assertThat(program.size).isLessThan(counterRegion) 594 val randomProgram = ByteArray(1) { 0 } + 595 ByteArray(counterRegion - 1).also { Random.nextBytes(it) } 596 // There are known firmware bugs where they calculate the number of non-zero bytes within 597 // the program to determine the program length. Modify the test to first install a longer 598 // program before installing a program that do the program length check. This should help us 599 // catch these types of firmware bugs in CTS. (b/395545572) 600 installAndVerifyProgram(randomProgram) 601 installAndVerifyProgram(program) 602 603 // Trigger the program by sending a ping and waiting on the reply. 604 val payloadSize = if (getFirstApiLevel() >= Build.VERSION_CODES.VANILLA_ICE_CREAM) { 605 68 606 } else { 607 4 608 } 609 val data = ByteArray(payloadSize).also { Random.nextBytes(it) } 610 packetReader.sendPing(data, payloadSize) 611 packetReader.expectPingReply() 612 613 val readResult = readProgram() 614 val buffer = ByteBuffer.wrap(readResult, counterRegion, 20 /* length */) 615 expect.withMessage("PROGRAM_SIZE").that(buffer.getInt()).isEqualTo(program.size) 616 expect.withMessage("RAM_LEN").that(buffer.getInt()).isEqualTo(caps.maximumApfProgramSize) 617 expect.withMessage("IPV4_HEADER_SIZE").that(buffer.getInt()).isEqualTo(0) 618 // Ping packet payload + ICMPv6 header (8) + IPv6 header (40) + ethernet header (14) 619 expect.withMessage("PACKET_SIZE").that(buffer.getInt()).isEqualTo(payloadSize + 8 + 40 + 14) 620 expect.withMessage("FILTER_AGE_SECONDS").that(buffer.getInt()).isLessThan(5) 621 } 622 623 // APF integration is mostly broken before V 624 @VsrTest(requirements = ["VSR-5.3.12-002", "VSR-5.3.12-005"]) 625 @IgnoreUpTo(Build.VERSION_CODES.UPSIDE_DOWN_CAKE) 626 @Test 627 fun testFilterAgeIncreasesBetweenPackets() { 628 // VSR-14 mandates APF to be turned on when the screen is off and the Wi-Fi link 629 // is idle or traffic is less than 10 Mbps. Before that, we don't mandate when the APF 630 // should be turned on. 631 assume().that(getVsrApiLevel()).isAtLeast(34) 632 assumeApfVersionSupportAtLeast(4) 633 assumeNotCuttlefish() 634 clearApfMemory() 635 val gen = ApfV4Generator( 636 caps.apfVersionSupported, 637 caps.maximumApfProgramSize, 638 caps.maximumApfProgramSize 639 ) 640 641 // If not ICMPv6 Echo Reply -> PASS 642 gen.addPassIfNotIcmpv6EchoReply(BaseApfGenerator.PASS_LABEL) 643 644 // Store all prefilled memory slots in counter region [500, 520) 645 val counterRegion = 500 646 gen.addLoadImmediate(R1, counterRegion) 647 gen.addLoadFromMemory(R0, MemorySlot.FILTER_AGE_SECONDS) 648 gen.addStoreData(R0, 0) 649 650 installAndVerifyProgram(gen.generate()) 651 652 val payloadSize = 56 653 val data = ByteArray(payloadSize).also { Random.nextBytes(it) } 654 packetReader.sendPing(data, payloadSize) 655 packetReader.expectPingReply() 656 657 var buffer = ByteBuffer.wrap(readProgram(), counterRegion, 4 /* length */) 658 val filterAgeSecondsOrig = buffer.getInt() 659 660 Thread.sleep(5100) 661 662 packetReader.sendPing(data, payloadSize) 663 packetReader.expectPingReply() 664 665 buffer = ByteBuffer.wrap(readProgram(), counterRegion, 4 /* length */) 666 val filterAgeSeconds = buffer.getInt() 667 // Assert that filter age has increased, but not too much. 668 val timeDiff = filterAgeSeconds - filterAgeSecondsOrig 669 assertThat(timeDiff).isAnyOf(5, 6) 670 } 671 672 @VsrTest(requirements = ["VSR-5.3.12-002", "VSR-5.3.12-005"]) 673 @Test 674 fun testFilterAge16384thsIncreasesBetweenPackets() { 675 assumeApfVersionSupportAtLeast(6000) 676 assumeNotCuttlefish() 677 clearApfMemory() 678 val gen = ApfV6Generator( 679 caps.apfVersionSupported, 680 caps.maximumApfProgramSize, 681 caps.maximumApfProgramSize 682 ) 683 684 // If not ICMPv6 Echo Reply -> PASS 685 gen.addPassIfNotIcmpv6EchoReply(BaseApfGenerator.PASS_LABEL) 686 687 // Store all prefilled memory slots in counter region [500, 520) 688 gen.addLoadFromMemory(R0, MemorySlot.FILTER_AGE_16384THS) 689 gen.addStoreCounter(FILTER_AGE_16384THS, R0) 690 691 installAndVerifyProgram(gen.generate()) 692 693 val payloadSize = 56 694 val data = ByteArray(payloadSize).also { Random.nextBytes(it) } 695 packetReader.sendPing(data, payloadSize) 696 packetReader.expectPingReply() 697 698 var apfRam = readProgram() 699 val filterAge16384thSecondsOrig = 700 ApfCounterTracker.getCounterValue(apfRam, FILTER_AGE_16384THS) 701 702 Thread.sleep(5000) 703 704 packetReader.sendPing(data, payloadSize) 705 packetReader.expectPingReply() 706 707 apfRam = readProgram() 708 val filterAge16384thSeconds = ApfCounterTracker.getCounterValue(apfRam, FILTER_AGE_16384THS) 709 val timeDiff = (filterAge16384thSeconds - filterAge16384thSecondsOrig) 710 // Expect the HAL plus ping latency to be less than 800ms. 711 val timeDiffLowerBound = (4.99 * 16384).toInt() 712 val timeDiffUpperBound = (5.81 * 16384).toInt() 713 // Assert that filter age has increased, but not too much. 714 assertThat(timeDiff).isGreaterThan(timeDiffLowerBound) 715 assertThat(timeDiff).isLessThan(timeDiffUpperBound) 716 } 717 718 @VsrTest( 719 requirements = ["VSR-5.3.12-002", "VSR-5.3.12-005", "VSR-5.3.12-012", "VSR-5.3.12-013", 720 "VSR-5.3.12-014", "VSR-5.3.12-015", "VSR-5.3.12-016", "VSR-5.3.12-017"] 721 ) 722 @IgnoreUpTo(Build.VERSION_CODES.UPSIDE_DOWN_CAKE) 723 @Test 724 fun testReplyPing() { 725 assumeApfVersionSupportAtLeast(6000) 726 assumeNotCuttlefish() 727 installProgram(ByteArray(caps.maximumApfProgramSize) { 0 }) // Clear previous program 728 readProgram() // Ensure installation is complete 729 730 val payloadSize = 56 731 val payload = ByteArray(payloadSize).also { Random.nextBytes(it) } 732 val firstByte = payload.take(1).toByteArray() 733 734 val pingRequestIpv6PayloadLen = PING_HEADER_LENGTH + 1 735 val pingRequestPktLen = ETH_HEADER_LEN + IPV6_HEADER_LEN + pingRequestIpv6PayloadLen 736 737 val gen = ApfV6Generator( 738 caps.apfVersionSupported, 739 caps.maximumApfProgramSize, 740 caps.maximumApfProgramSize 741 ) 742 val skipPacketLabel = gen.uniqueLabel 743 744 // Summary of the program: 745 // if the packet is not ICMPv6 echo reply 746 // pass 747 // else if the echo reply payload size is 1 748 // increase PASSED_IPV6_ICMP counter 749 // pass 750 // else 751 // transmit 3 ICMPv6 echo requests with random first byte 752 // increase DROPPED_IPV6_NS_REPLIED_NON_DAD counter 753 // drop 754 gen.addLoad16intoR0(ETH_ETHERTYPE_OFFSET) 755 .addJumpIfR0NotEquals(ETH_P_IPV6.toLong(), skipPacketLabel) 756 .addLoad8intoR0(IPV6_NEXT_HEADER_OFFSET) 757 .addJumpIfR0NotEquals(IPPROTO_ICMPV6.toLong(), skipPacketLabel) 758 .addLoad8intoR0(ICMP6_TYPE_OFFSET) 759 .addJumpIfR0NotEquals(ICMP6_ECHO_REPLY.toLong(), skipPacketLabel) 760 .addLoadFromMemory(R0, MemorySlot.PACKET_SIZE) 761 .addCountAndPassIfR0Equals( 762 (ETHER_HEADER_LEN + IPV6_HEADER_LEN + PING_HEADER_LENGTH + firstByte.size) 763 .toLong(), 764 PASSED_IPV6_ICMP 765 ) 766 767 val numOfPacketToTransmit = 3 768 val expectReplyPayloads = (0 until numOfPacketToTransmit).map { Random.nextBytes(1) } 769 expectReplyPayloads.forEach { replyPingPayload -> 770 // Ping Packet Generation 771 gen.addAllocate(pingRequestPktLen) 772 // Eth header 773 .addPacketCopy(ETHER_SRC_ADDR_OFFSET, ETHER_ADDR_LEN) // dst MAC address 774 .addPacketCopy(ETHER_DST_ADDR_OFFSET, ETHER_ADDR_LEN) // src MAC address 775 .addWriteU16(ETH_P_IPV6) // IPv6 type 776 // IPv6 Header 777 .addWrite32(0x60000000) // IPv6 Header: version, traffic class, flowlabel 778 // payload length (2 bytes) | next header: ICMPv6 (1 byte) | hop limit (1 byte) 779 .addWrite32(pingRequestIpv6PayloadLen shl 16 or (IPPROTO_ICMPV6 shl 8 or 64)) 780 .addPacketCopy(IPV6_DEST_ADDR_OFFSET, IPV6_ADDR_LEN) // src ip 781 .addPacketCopy(IPV6_SRC_ADDR_OFFSET, IPV6_ADDR_LEN) // dst ip 782 // ICMPv6 783 .addWriteU8(ICMP6_ECHO_REQUEST) 784 .addWriteU8(0) // code 785 .addWriteU16(pingRequestIpv6PayloadLen) // checksum 786 // identifier 787 .addPacketCopy(ETHER_HEADER_LEN + IPV6_HEADER_LEN + ICMPV6_HEADER_MIN_LEN, 2) 788 .addWriteU16(0) // sequence number 789 .addDataCopy(replyPingPayload) // data 790 .addTransmitL4( 791 ETHER_HEADER_LEN, // ip_ofs 792 ICMP6_CHECKSUM_OFFSET, // csum_ofs 793 IPV6_SRC_ADDR_OFFSET, // csum_start 794 IPPROTO_ICMPV6, // partial_sum 795 false // udp 796 ) 797 } 798 799 // Warning: the program abuse DROPPED_IPV6_NS_REPLIED_NON_DAD for debugging purpose 800 gen.addCountAndDrop(DROPPED_IPV6_NS_REPLIED_NON_DAD) 801 .defineLabel(skipPacketLabel) 802 .addPass() 803 804 val program = gen.generate() 805 installAndVerifyProgram(program) 806 807 val counterBefore = ApfCounterTracker.getCounterValue( 808 readProgram(), 809 DROPPED_IPV6_NS_REPLIED_NON_DAD 810 ) 811 packetReader.sendPing(payload, payloadSize, expectReplyCount = numOfPacketToTransmit) 812 val replyPayloads = try { 813 packetReader.expectPingReply(TIMEOUT_MS * 2) 814 } catch (e: TimeoutException) { 815 emptyList() 816 } 817 818 val apfCounterTracker = ApfCounterTracker() 819 val apfRam = readProgram() 820 apfCounterTracker.updateCountersFromData(apfRam) 821 Log.i(TAG, "counter map: ${apfCounterTracker.counters}") 822 823 val counterAfter = ApfCounterTracker.getCounterValue( 824 apfRam, 825 DROPPED_IPV6_NS_REPLIED_NON_DAD 826 ) 827 assertEquals(counterBefore + 1, counterAfter) 828 829 assertThat(replyPayloads.size).isEqualTo(expectReplyPayloads.size) 830 831 // Sort the payload list before comparison to ensure consistency. 832 val sortedReplyPayloads = replyPayloads.sortedBy { it[0] } 833 val sortedExpectReplyPayloads = expectReplyPayloads.sortedBy { it[0] } 834 for (i in sortedReplyPayloads.indices) { 835 assertThat(sortedReplyPayloads[i]).isEqualTo(sortedExpectReplyPayloads[i]) 836 } 837 } 838 } 839