• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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