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 package android.net.apf 17 18 import android.net.apf.ApfConstants.DHCP_SERVER_PORT 19 import android.net.apf.ApfConstants.ETH_HEADER_LEN 20 import android.net.apf.ApfConstants.ICMP6_TYPE_OFFSET 21 import android.net.apf.ApfConstants.IPV4_BROADCAST_ADDRESS 22 import android.net.apf.ApfConstants.IPV4_DEST_ADDR_OFFSET 23 import android.net.apf.ApfConstants.IPV4_PROTOCOL_OFFSET 24 import android.net.apf.ApfConstants.IPV4_SRC_ADDR_OFFSET 25 import android.net.apf.ApfConstants.IPV6_NEXT_HEADER_OFFSET 26 import android.net.apf.ApfConstants.TCP_UDP_DESTINATION_PORT_OFFSET 27 import android.net.apf.BaseApfGenerator.APF_VERSION_4 28 import android.net.apf.BaseApfGenerator.MemorySlot 29 import android.net.apf.BaseApfGenerator.Register.R0 30 import android.net.apf.BaseApfGenerator.Register.R1 31 import android.system.OsConstants 32 import android.system.OsConstants.ETH_P_IP 33 import android.system.OsConstants.IPPROTO_ICMPV6 34 import android.util.Log 35 import androidx.test.filters.SmallTest 36 import com.android.net.module.util.HexDump 37 import com.android.net.module.util.NetworkStackConstants.ETHER_TYPE_OFFSET 38 import com.android.net.module.util.NetworkStackConstants.ICMPV6_ROUTER_SOLICITATION 39 import com.android.testutils.DevSdkIgnoreRunner 40 import kotlin.test.assertEquals 41 import org.junit.Test 42 import org.junit.runner.RunWith 43 44 /** 45 * This class generate ApfStandaloneTest programs for side-loading into firmware without needing the 46 * ApfFilter.java dependency. Its bytecode facilitates Wi-Fi chipset vendor regression tests, 47 * preventing issues caused by APF interpreter integration. 48 * 49 * Note: Code size optimization is not a priority for these test programs, so some redundancy may 50 * exist. 51 */ 52 @RunWith(DevSdkIgnoreRunner::class) 53 @SmallTest 54 class ApfStandaloneTest { 55 56 private val etherTypeDenyList = listOf(0x88A2, 0x88A4, 0x88B8, 0x88CD, 0x88E1, 0x88E3) 57 runApfTestnull58 fun runApfTest(isSuspendMode: Boolean) { 59 val program = generateApfV4Program(isSuspendMode) 60 Log.w( 61 TAG, 62 "Program should be run in SETSUSPENDMODE $isSuspendMode: " + 63 HexDump.toHexString(program) 64 ) 65 // packet that in ethertype denylist: 66 // ###[ Ethernet ]### 67 // dst = ff:ff:ff:ff:ff:ff 68 // src = 04:7b:cb:46:3f:b5 69 // type = 0x88a2 70 // ###[ Raw ]### 71 // load = '01' 72 // 73 // raw bytes: 74 // ffffffffffff047bcb463fb588a21 75 76 val packetBadEtherType = 77 HexDump.hexStringToByteArray("ffffffffffff047bcb463fb588a201") 78 val dataRegion = ByteArray(Counter.totalSize()) { 0 } 79 ApfTestUtils.assertVerdict( 80 APF_VERSION_4, 81 ApfTestUtils.DROP, 82 program, 83 packetBadEtherType, 84 dataRegion 85 ) 86 assertEquals(mapOf<Counter, Long>( 87 Counter.TOTAL_PACKETS to 1, 88 Counter.DROPPED_ETHERTYPE_DENYLISTED to 1 89 ), decodeCountersIntoMap(dataRegion)) 90 91 // dhcp request packet. 92 // ###[ Ethernet ]### 93 // dst = ff:ff:ff:ff:ff:ff 94 // src = 04:7b:cb:46:3f:b5 95 // type = IPv4 96 // ###[ IP ]### 97 // version = 4 98 // ihl = None 99 // tos = 0x0 100 // len = None 101 // id = 1 102 // flags = 103 // frag = 0 104 // ttl = 64 105 // proto = udp 106 // chksum = None 107 // src = 0.0.0.0 108 // dst = 255.255.255.255 109 // \options \ 110 // ###[ UDP ]### 111 // sport = bootpc 112 // dport = bootps 113 // len = None 114 // chksum = None 115 // ###[ BOOTP ]### 116 // op = BOOTREQUEST 117 // htype = Ethernet (10Mb) 118 // hlen = 6 119 // hops = 0 120 // xid = 0x1020304 121 // secs = 0 122 // flags = 123 // ciaddr = 0.0.0.0 124 // yiaddr = 0.0.0.0 125 // siaddr = 0.0.0.0 126 // giaddr = 0.0.0.0 127 // chaddr = 30:34:3a:37:62:3a (pad: b'cb:46:3f:b5') 128 // sname = '' 129 // file = '' 130 // options = b'c\x82Sc' (DHCP magic) 131 // ###[ DHCP options ]### 132 // options = [message-type='request' server_id=192.168.1.1 requested_addr=192.168.1.100 end] 133 // 134 // raw bytes: 135 // ffffffffffff047bcb463fb508004500011c00010000401179d100000000ffffffff004400430108393b010106000000000b000000000000000000000000000000000000000030343a37623a63623a34363a33663a62000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000638253633501033604c0a801013204c0a80164ff 136 137 val dhcpRequestPkt = HexDump.hexStringToByteArray( 138 "ffffffffffff047bcb463fb508004500011c00010000401179d100000000ffffffff004400430108393b010106000000000b000000000000000000000000000000000000000030343a37623a63623a34363a33663a62000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000638253633501033604c0a801013204c0a80164ff" 139 ) 140 ApfTestUtils.assertVerdict( 141 APF_VERSION_4, 142 ApfTestUtils.DROP, 143 program, 144 dhcpRequestPkt, 145 dataRegion 146 ) 147 assertEquals(mapOf<Counter, Long>( 148 Counter.TOTAL_PACKETS to 2, 149 Counter.DROPPED_ETHERTYPE_DENYLISTED to 1, 150 Counter.DROPPED_DHCP_REQUEST_DISCOVERY to 1 151 ), decodeCountersIntoMap(dataRegion)) 152 153 // RS packet: 154 // ###[ Ethernet ]### 155 // dst = ff:ff:ff:ff:ff:ff 156 // src = 04:7b:cb:46:3f:b5 157 // type = IPv6 158 // ###[ IPv6 ]### 159 // version = 6 160 // tc = 0 161 // fl = 0 162 // plen = None 163 // nh = ICMPv6 164 // hlim = 255 165 // src = fe80::30b4:5e42:ef3d:36e5 166 // dst = ff02::2 167 // ###[ ICMPv6 Neighbor Discovery - Router Solicitation ]### 168 // type = Router Solicitation 169 // code = 0 170 // cksum = None 171 // res = 0 172 // 173 // raw bytes: 174 // ffffffffffff047bcb463fb586dd6000000000083afffe8000000000000030b45e42ef3d36e5ff0200000000000000000000000000028500c81d00000000 175 val rsPkt = HexDump.hexStringToByteArray( 176 "ffffffffffff047bcb463fb586dd6000000000083afffe8000000000000030b45e42ef3d36e5ff0200000000000000000000000000028500c81d00000000" 177 ) 178 ApfTestUtils.assertVerdict(APF_VERSION_4, ApfTestUtils.DROP, program, rsPkt, dataRegion) 179 assertEquals(mapOf<Counter, Long>( 180 Counter.TOTAL_PACKETS to 3, 181 Counter.DROPPED_RS to 1, 182 Counter.DROPPED_ETHERTYPE_DENYLISTED to 1, 183 Counter.DROPPED_DHCP_REQUEST_DISCOVERY to 1 184 ), decodeCountersIntoMap(dataRegion)) 185 if (isSuspendMode) { 186 // Ping request packet 187 // ###[ Ethernet ]### 188 // dst = ff:ff:ff:ff:ff:ff 189 // src = 04:7b:cb:46:3f:b5 190 // type = IPv4 191 // ###[ IP ]### 192 // version = 4 193 // ihl = None 194 // tos = 0x0 195 // len = None 196 // id = 1 197 // flags = 198 // frag = 0 199 // ttl = 64 200 // proto = icmp 201 // chksum = None 202 // src = 100.79.97.84 203 // dst = 8.8.8.8 204 // \options \ 205 // ###[ ICMP ]### 206 // type = echo-request 207 // code = 0 208 // chksum = None 209 // id = 0x0 210 // seq = 0x0 211 // unused = '' 212 // 213 // raw bytes: 84 214 // ffffffffffff047bcb463fb508004500001c000100004001a52d644f6154080808080800f7ff00000000 215 val pingRequestPkt = HexDump.hexStringToByteArray( 216 "ffffffffffff047bcb463fb508004500001c000100004001a52d644f6154080808080800f7ff00000000" 217 ) 218 ApfTestUtils.assertVerdict( 219 APF_VERSION_4, 220 ApfTestUtils.DROP, 221 program, 222 pingRequestPkt, 223 dataRegion 224 ) 225 assertEquals(mapOf<Counter, Long>( 226 Counter.TOTAL_PACKETS to 4, 227 Counter.DROPPED_RS to 1, 228 Counter.DROPPED_ICMP4_ECHO_REQUEST to 1, 229 Counter.DROPPED_ETHERTYPE_DENYLISTED to 1, 230 Counter.DROPPED_DHCP_REQUEST_DISCOVERY to 1 231 ), decodeCountersIntoMap(dataRegion)) 232 } 233 } 234 235 @Test testApfProgramInNormalModenull236 fun testApfProgramInNormalMode() { 237 runApfTest(isSuspendMode = false) 238 } 239 240 @Test testApfProgramInSuspendModenull241 fun testApfProgramInSuspendMode() { 242 runApfTest(isSuspendMode = true) 243 } 244 generateApfV4Programnull245 private fun generateApfV4Program(isDeviceIdle: Boolean): ByteArray { 246 val countAndPassLabel = "countAndPass" 247 val countAndDropLabel = "countAndDrop" 248 val endOfDhcpFilter = "endOfDhcpFilter" 249 val endOfRsFilter = "endOfRsFiler" 250 val endOfPingFilter = "endOfPingFilter" 251 val gen = ApfV4Generator(APF_VERSION_4) 252 253 maybeSetupCounter(gen, Counter.TOTAL_PACKETS) 254 gen.addLoadData(R0, 0) 255 gen.addAdd(1) 256 gen.addStoreData(R0, 0) 257 258 maybeSetupCounter(gen, Counter.FILTER_AGE_SECONDS) 259 gen.addLoadFromMemory(R0, MemorySlot.FILTER_AGE_SECONDS) 260 gen.addStoreData(R0, 0) 261 262 maybeSetupCounter(gen, Counter.FILTER_AGE_16384THS) 263 gen.addLoadFromMemory(R0, MemorySlot.FILTER_AGE_16384THS) 264 gen.addStoreData(R0, 0) 265 266 // ethtype filter 267 gen.addLoad16(R0, ETHER_TYPE_OFFSET) 268 maybeSetupCounter(gen, Counter.DROPPED_ETHERTYPE_DENYLISTED) 269 for (p in etherTypeDenyList) { 270 gen.addJumpIfR0Equals(p.toLong(), countAndDropLabel) 271 } 272 273 // dhcp request filters 274 275 // Check IPv4 276 gen.addLoad16(R0, ETHER_TYPE_OFFSET) 277 gen.addJumpIfR0NotEquals(ETH_P_IP.toLong(), endOfDhcpFilter) 278 279 // Pass DHCP addressed to us. 280 // Check src is IP is 0.0.0.0 281 gen.addLoad32(R0, IPV4_SRC_ADDR_OFFSET) 282 gen.addJumpIfR0NotEquals(0, endOfDhcpFilter) 283 // Check dst ip is 255.255.255.255 284 gen.addLoad32(R0, IPV4_DEST_ADDR_OFFSET) 285 gen.addJumpIfR0NotEquals(IPV4_BROADCAST_ADDRESS.toLong(), endOfDhcpFilter) 286 // Check it's UDP. 287 gen.addLoad8(R0, IPV4_PROTOCOL_OFFSET) 288 gen.addJumpIfR0NotEquals(OsConstants.IPPROTO_UDP.toLong(), endOfDhcpFilter) 289 // Check it's addressed to DHCP client port. 290 gen.addLoadFromMemory(R1, MemorySlot.IPV4_HEADER_SIZE) 291 gen.addLoad16Indexed(R0, TCP_UDP_DESTINATION_PORT_OFFSET) 292 gen.addJumpIfR0NotEquals(DHCP_SERVER_PORT.toLong(), endOfDhcpFilter) 293 // drop dhcp the discovery and request 294 maybeSetupCounter(gen, Counter.DROPPED_DHCP_REQUEST_DISCOVERY) 295 gen.addJump(countAndDropLabel) 296 297 gen.defineLabel(endOfDhcpFilter) 298 299 // rs filters 300 301 // check IPv6 302 gen.addLoad16(R0, ETHER_TYPE_OFFSET) 303 gen.addJumpIfR0NotEquals(OsConstants.ETH_P_IPV6.toLong(), endOfRsFilter) 304 // check ICMP6 packet 305 gen.addLoad8(R0, IPV6_NEXT_HEADER_OFFSET) 306 gen.addJumpIfR0NotEquals(IPPROTO_ICMPV6.toLong(), endOfRsFilter) 307 // check type it is RS 308 gen.addLoad8(R0, ICMP6_TYPE_OFFSET) 309 gen.addJumpIfR0NotEquals(ICMPV6_ROUTER_SOLICITATION.toLong(), endOfRsFilter) 310 // drop rs packet 311 maybeSetupCounter(gen, Counter.DROPPED_RS) 312 gen.addJump(countAndDropLabel) 313 314 gen.defineLabel(endOfRsFilter) 315 316 if (isDeviceIdle) { 317 // ping filter 318 319 // Check IPv4 320 gen.addLoad16(R0, ETHER_TYPE_OFFSET) 321 gen.addJumpIfR0NotEquals(ETH_P_IP.toLong(), endOfPingFilter) 322 // Check it's ICMP. 323 gen.addLoad8(R0, IPV4_PROTOCOL_OFFSET) 324 gen.addJumpIfR0NotEquals(OsConstants.IPPROTO_ICMP.toLong(), endOfPingFilter) 325 // Check if it is echo request 326 gen.addLoadFromMemory(R1, MemorySlot.IPV4_HEADER_SIZE) 327 gen.addLoad8Indexed(R0, ETH_HEADER_LEN) 328 gen.addJumpIfR0NotEquals(8, endOfPingFilter) 329 // drop ping request 330 maybeSetupCounter(gen, Counter.DROPPED_ICMP4_ECHO_REQUEST) 331 gen.addJump(countAndDropLabel) 332 333 gen.defineLabel(endOfPingFilter) 334 } 335 336 // end of filters. 337 maybeSetupCounter(gen, Counter.PASSED_PACKET) 338 339 gen.defineLabel(countAndPassLabel) 340 gen.addLoadData(BaseApfGenerator.Register.R0, 0) // R0 = *(R1 + 0) 341 gen.addAdd(1) // R0++ 342 gen.addStoreData(BaseApfGenerator.Register.R0, 0) // *(R1 + 0) = R0 343 gen.addJump(BaseApfGenerator.PASS_LABEL) 344 345 gen.defineLabel(countAndDropLabel) 346 gen.addLoadData(BaseApfGenerator.Register.R0, 0) // R0 = *(R1 + 0) 347 gen.addAdd(1) // R0++ 348 gen.addStoreData(BaseApfGenerator.Register.R0, 0) // *(R1 + 0) = R0 349 gen.addJump(BaseApfGenerator.DROP_LABEL) 350 351 return gen.generate() 352 } 353 354 enum class Counter { 355 RESERVED, 356 ENDIANNESS, 357 FILTER_AGE_SECONDS, 358 FILTER_AGE_16384THS, 359 TOTAL_PACKETS, 360 DROPPED_ETHERTYPE_DENYLISTED, 361 DROPPED_DHCP_REQUEST_DISCOVERY, 362 DROPPED_ICMP4_ECHO_REQUEST, 363 DROPPED_RS, 364 PASSED_PACKET; 365 offsetnull366 fun offset(): Int { 367 return -4 * this.ordinal 368 } 369 370 companion object { totalSizenull371 fun totalSize(): Int { 372 return (Counter::class.java.enumConstants.size - 1) * 4 373 } 374 } 375 } 376 maybeSetupCounternull377 private fun maybeSetupCounter(gen: ApfV4Generator, c: Counter) { 378 gen.addLoadImmediate(R1, c.offset()) 379 } 380 decodeCountersIntoMapnull381 private fun decodeCountersIntoMap(counterBytes: ByteArray): Map<Counter, Long> { 382 val counters = Counter::class.java.enumConstants 383 val ret = HashMap<Counter, Long>() 384 // starting from index 2 to skip the endianness mark 385 for (c in listOf(*counters).subList(2, counters.size)) { 386 val value = getCounterValue(counterBytes, c) 387 if (value != 0L) { 388 ret[c] = value 389 } 390 } 391 return ret 392 } 393 getCounterValuenull394 private fun getCounterValue(data: ByteArray, counter: Counter): Long { 395 var offset = data.size + Counter.ENDIANNESS.offset() 396 var endianness = 0 397 for (i in 0..3) { 398 endianness = endianness shl 8 or (data[offset + i].toInt() and 0xff) 399 } 400 // Follow the same wrap-around addressing scheme of the interpreter. 401 offset = data.size + counter.offset() 402 var isBe = true 403 when (endianness) { 404 0, 0x12345678 -> isBe = true 405 0x78563412 -> isBe = false 406 } 407 408 var value: Long = 0 409 for (i in 0..3) { 410 value = value shl 8 or 411 (data[offset + (if (isBe) i else 3 - i)].toInt() and 0xff).toLong() 412 } 413 return value 414 } 415 416 companion object { 417 const val TAG = "ApfStandaloneTest" 418 } 419 } 420