1 /*
2  * Copyright (C) 2020 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 
17 package com.android.testutils
18 
19 import java.net.Inet4Address
20 import java.util.function.Predicate
21 
22 // Some of the below constants are duplicated with NetworkStackConstants, but this is a hostdevice
23 // library usable for host-side tests, so device-side utils are not usable, and there is no
24 // host-side non-test library to host common constants.
25 private const val ETHER_TYPE_OFFSET = 12
26 private const val ETHER_HEADER_LENGTH = 14
27 private const val IPV4_PROTOCOL_OFFSET = ETHER_HEADER_LENGTH + 9
28 private const val IPV6_PROTOCOL_OFFSET = ETHER_HEADER_LENGTH + 6
29 private const val IPV4_CHECKSUM_OFFSET = ETHER_HEADER_LENGTH + 10
30 private const val IPV4_DST_OFFSET = ETHER_HEADER_LENGTH + 16
31 private const val IPV4_HEADER_LENGTH = 20
32 private const val IPV6_HEADER_LENGTH = 40
33 private const val IPV4_PAYLOAD_OFFSET = ETHER_HEADER_LENGTH + IPV4_HEADER_LENGTH
34 private const val IPV6_PAYLOAD_OFFSET = ETHER_HEADER_LENGTH + IPV6_HEADER_LENGTH
35 private const val UDP_HEADER_LENGTH = 8
36 private const val BOOTP_OFFSET = IPV4_PAYLOAD_OFFSET + UDP_HEADER_LENGTH
37 private const val BOOTP_TID_OFFSET = BOOTP_OFFSET + 4
38 private const val BOOTP_CLIENT_MAC_OFFSET = BOOTP_OFFSET + 28
39 private const val DHCP_OPTIONS_OFFSET = BOOTP_OFFSET + 240
40 private const val ARP_OPCODE_OFFSET = ETHER_HEADER_LENGTH + 6
41 
42 /**
43  * A [Predicate] that matches a [ByteArray] if it contains the specified [bytes] at the specified
44  * [offset].
45  */
46 class OffsetFilter(val offset: Int, vararg val bytes: Byte) : Predicate<ByteArray> {
testnull47     override fun test(packet: ByteArray) =
48             bytes.withIndex().all { it.value == packet[offset + it.index] }
49 }
50 
51 private class UdpPortFilter(
52     private val udpOffset: Int,
53     private val src: Short?,
54     private val dst: Short?
55 ) : Predicate<ByteArray> {
testnull56     override fun test(t: ByteArray): Boolean {
57         if (src != null && !OffsetFilter(udpOffset,
58                         src.toInt().ushr(8).toByte(), src.toByte()).test(t)) {
59             return false
60         }
61 
62         if (dst != null && !OffsetFilter(udpOffset + 2,
63                         dst.toInt().ushr(8).toByte(), dst.toByte()).test(t)) {
64             return false
65         }
66         return true
67     }
68 }
69 
70 /**
71  * A [Predicate] that matches ethernet-encapped packets that contain an UDP over IPv4 datagram.
72  */
73 class IPv4UdpFilter @JvmOverloads constructor(
74     srcPort: Short? = null,
75     dstPort: Short? = null
76 ) : Predicate<ByteArray> {
77     private val impl = OffsetFilter(ETHER_TYPE_OFFSET, 0x08, 0x00 /* IPv4 */).and(
78             OffsetFilter(IPV4_PROTOCOL_OFFSET, 17 /* UDP */)).and(
79             UdpPortFilter(IPV4_PAYLOAD_OFFSET, srcPort, dstPort))
testnull80     override fun test(t: ByteArray) = impl.test(t)
81 }
82 
83 /**
84  * A [Predicate] that matches ethernet-encapped packets that contain an UDP over IPv6 datagram.
85  */
86 class IPv6UdpFilter @JvmOverloads constructor(
87     srcPort: Short? = null,
88     dstPort: Short? = null
89 ) : Predicate<ByteArray> {
90     private val impl = OffsetFilter(ETHER_TYPE_OFFSET, 0x86.toByte(), 0xdd.toByte() /* IPv6 */).and(
91             OffsetFilter(IPV6_PROTOCOL_OFFSET, 17 /* UDP */)).and(
92             UdpPortFilter(IPV6_PAYLOAD_OFFSET, srcPort, dstPort))
93     override fun test(t: ByteArray) = impl.test(t)
94 }
95 
96 /**
97  * A [Predicate] that matches ethernet-encapped packets sent to the specified IPv4 destination.
98  */
99 class IPv4DstFilter(dst: Inet4Address) : Predicate<ByteArray> {
100     private val impl = OffsetFilter(IPV4_DST_OFFSET, *dst.address)
testnull101     override fun test(t: ByteArray) = impl.test(t)
102 }
103 
104 /**
105  * A [Predicate] that matches ethernet-encapped ARP requests.
106  */
107 class ArpRequestFilter : Predicate<ByteArray> {
108     private val impl = OffsetFilter(ETHER_TYPE_OFFSET, 0x08, 0x06 /* ARP */)
109             .and(OffsetFilter(ARP_OPCODE_OFFSET, 0x00, 0x01 /* request */))
110     override fun test(t: ByteArray) = impl.test(t)
111 }
112 
113 class Icmpv6Filter : Predicate<ByteArray> {
114     private val impl = OffsetFilter(ETHER_TYPE_OFFSET, 0x86.toByte(), 0xdd.toByte() /* IPv6 */).and(
115         OffsetFilter(IPV6_PROTOCOL_OFFSET, 58 /* ICMPv6 */))
testnull116     override fun test(t: ByteArray) = impl.test(t)
117 }
118 
119 /**
120  * A [Predicate] that matches ethernet-encapped DHCP packets sent from a DHCP client.
121  */
122 class DhcpClientPacketFilter : Predicate<ByteArray> {
123     private val impl = IPv4UdpFilter(srcPort = 68, dstPort = 67)
124     override fun test(t: ByteArray) = impl.test(t)
125 }
126 
127 /**
128  * A [Predicate] that matches a [ByteArray] if it contains a ethernet-encapped DHCP packet that
129  * contains the specified option with the specified [bytes] as value.
130  */
131 class DhcpOptionFilter(val option: Byte, vararg val bytes: Byte) : Predicate<ByteArray> {
testnull132     override fun test(packet: ByteArray): Boolean {
133         val option = findDhcpOption(packet, option) ?: return false
134         return option.contentEquals(bytes)
135     }
136 }
137 
138 /**
139  * Find a DHCP option in a packet and return its value, if found.
140  */
findDhcpOptionnull141 fun findDhcpOption(packet: ByteArray, option: Byte): ByteArray? =
142         findOptionOffset(packet, option, DHCP_OPTIONS_OFFSET)?.let {
143             val optionLen = packet[it + 1]
144             return packet.copyOfRange(it + 2 /* type, length bytes */, it + 2 + optionLen)
145         }
146 
findOptionOffsetnull147 private tailrec fun findOptionOffset(packet: ByteArray, option: Byte, searchOffset: Int): Int? {
148     if (packet.size <= searchOffset + 2 /* type, length bytes */) return null
149 
150     return if (packet[searchOffset] == option) searchOffset else {
151         val optionLen = packet[searchOffset + 1]
152         findOptionOffset(packet, option, searchOffset + 2 + optionLen)
153     }
154 }
155