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 const val ETHER_TYPE_OFFSET = 12 23 const val ETHER_HEADER_LENGTH = 14 24 const val IPV4_PROTOCOL_OFFSET = ETHER_HEADER_LENGTH + 9 25 const val IPV4_CHECKSUM_OFFSET = ETHER_HEADER_LENGTH + 10 26 const val IPV4_DST_OFFSET = ETHER_HEADER_LENGTH + 16 27 const val IPV4_HEADER_LENGTH = 20 28 const val IPV4_UDP_OFFSET = ETHER_HEADER_LENGTH + IPV4_HEADER_LENGTH 29 const val IPV4_UDP_SRCPORT_OFFSET = IPV4_UDP_OFFSET 30 const val IPV4_UDP_DSTPORT_OFFSET = IPV4_UDP_OFFSET + 2 31 const val UDP_HEADER_LENGTH = 8 32 const val BOOTP_OFFSET = IPV4_UDP_OFFSET + UDP_HEADER_LENGTH 33 const val BOOTP_TID_OFFSET = BOOTP_OFFSET + 4 34 const val BOOTP_CLIENT_MAC_OFFSET = BOOTP_OFFSET + 28 35 const val DHCP_OPTIONS_OFFSET = BOOTP_OFFSET + 240 36 37 const val ARP_OPCODE_OFFSET = ETHER_HEADER_LENGTH + 6 38 const val ARP_SENDER_MAC_OFFSET = ETHER_HEADER_LENGTH + 8 39 const val ARP_TARGET_IPADDR_OFFSET = ETHER_HEADER_LENGTH + 24 40 41 /** 42 * A [Predicate] that matches a [ByteArray] if it contains the specified [bytes] at the specified 43 * [offset]. 44 */ 45 class OffsetFilter(val offset: Int, vararg val bytes: Byte) : Predicate<ByteArray> { 46 override fun test(packet: ByteArray) = 47 bytes.withIndex().all { it.value == packet[offset + it.index] } 48 } 49 50 /** 51 * A [Predicate] that matches ethernet-encapped packets that contain an UDP over IPv4 datagram. 52 */ 53 class IPv4UdpFilter : Predicate<ByteArray> { 54 private val impl = OffsetFilter(ETHER_TYPE_OFFSET, 0x08, 0x00 /* IPv4 */).and( 55 OffsetFilter(IPV4_PROTOCOL_OFFSET, 17 /* UDP */)) 56 override fun test(t: ByteArray) = impl.test(t) 57 } 58 59 /** 60 * A [Predicate] that matches ethernet-encapped packets sent to the specified IPv4 destination. 61 */ 62 class IPv4DstFilter(dst: Inet4Address) : Predicate<ByteArray> { 63 private val impl = OffsetFilter(IPV4_DST_OFFSET, *dst.address) 64 override fun test(t: ByteArray) = impl.test(t) 65 } 66 67 /** 68 * A [Predicate] that matches ethernet-encapped ARP requests. 69 */ 70 class ArpRequestFilter : Predicate<ByteArray> { 71 private val impl = OffsetFilter(ETHER_TYPE_OFFSET, 0x08, 0x06 /* ARP */) 72 .and(OffsetFilter(ARP_OPCODE_OFFSET, 0x00, 0x01 /* request */)) 73 override fun test(t: ByteArray) = impl.test(t) 74 } 75 76 /** 77 * A [Predicate] that matches ethernet-encapped DHCP packets sent from a DHCP client. 78 */ 79 class DhcpClientPacketFilter : Predicate<ByteArray> { 80 private val impl = IPv4UdpFilter() 81 .and(OffsetFilter(IPV4_UDP_SRCPORT_OFFSET, 0x00, 0x44 /* 68 */)) 82 .and(OffsetFilter(IPV4_UDP_DSTPORT_OFFSET, 0x00, 0x43 /* 67 */)) 83 override fun test(t: ByteArray) = impl.test(t) 84 } 85 86 /** 87 * A [Predicate] that matches a [ByteArray] if it contains a ethernet-encapped DHCP packet that 88 * contains the specified option with the specified [bytes] as value. 89 */ 90 class DhcpOptionFilter(val option: Byte, vararg val bytes: Byte) : Predicate<ByteArray> { 91 override fun test(packet: ByteArray): Boolean { 92 val option = findDhcpOption(packet, option) ?: return false 93 return option.contentEquals(bytes) 94 } 95 } 96 97 /** 98 * Find a DHCP option in a packet and return its value, if found. 99 */ 100 fun findDhcpOption(packet: ByteArray, option: Byte): ByteArray? = 101 findOptionOffset(packet, option, DHCP_OPTIONS_OFFSET)?.let { 102 val optionLen = packet[it + 1] 103 return packet.copyOfRange(it + 2 /* type, length bytes */, it + 2 + optionLen) 104 } 105 106 private tailrec fun findOptionOffset(packet: ByteArray, option: Byte, searchOffset: Int): Int? { 107 if (packet.size <= searchOffset + 2 /* type, length bytes */) return null 108 109 return if (packet[searchOffset] == option) searchOffset else { 110 val optionLen = packet[searchOffset + 1] 111 findOptionOffset(packet, option, searchOffset + 2 + optionLen) 112 } 113 } 114