summaryrefslogtreecommitdiff
path: root/common/testutils/hostdevice/com/android/testutils/PacketFilter.kt
blob: 1bb6d683e25ac1e417158f9ba5aad042c8692103 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
/*
 * Copyright (C) 2020 The Android Open Source Project
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package com.android.testutils

import java.net.Inet4Address
import java.util.function.Predicate

// Some of the below constants are duplicated with NetworkStackConstants, but this is a hostdevice
// library usable for host-side tests, so device-side utils are not usable, and there is no
// host-side non-test library to host common constants.
private const val ETHER_TYPE_OFFSET = 12
private const val ETHER_HEADER_LENGTH = 14
private const val IPV4_PROTOCOL_OFFSET = ETHER_HEADER_LENGTH + 9
private const val IPV6_PROTOCOL_OFFSET = ETHER_HEADER_LENGTH + 6
private const val IPV4_CHECKSUM_OFFSET = ETHER_HEADER_LENGTH + 10
private const val IPV4_DST_OFFSET = ETHER_HEADER_LENGTH + 16
private const val IPV4_HEADER_LENGTH = 20
private const val IPV6_HEADER_LENGTH = 40
private const val IPV4_PAYLOAD_OFFSET = ETHER_HEADER_LENGTH + IPV4_HEADER_LENGTH
private const val IPV6_PAYLOAD_OFFSET = ETHER_HEADER_LENGTH + IPV6_HEADER_LENGTH
private const val UDP_HEADER_LENGTH = 8
private const val BOOTP_OFFSET = IPV4_PAYLOAD_OFFSET + UDP_HEADER_LENGTH
private const val BOOTP_TID_OFFSET = BOOTP_OFFSET + 4
private const val BOOTP_CLIENT_MAC_OFFSET = BOOTP_OFFSET + 28
private const val DHCP_OPTIONS_OFFSET = BOOTP_OFFSET + 240
private const val ARP_OPCODE_OFFSET = ETHER_HEADER_LENGTH + 6

/**
 * A [Predicate] that matches a [ByteArray] if it contains the specified [bytes] at the specified
 * [offset].
 */
class OffsetFilter(val offset: Int, vararg val bytes: Byte) : Predicate<ByteArray> {
    override fun test(packet: ByteArray) =
            bytes.withIndex().all { it.value == packet[offset + it.index] }
}

private class UdpPortFilter(
    private val udpOffset: Int,
    private val src: Short?,
    private val dst: Short?
) : Predicate<ByteArray> {
    override fun test(t: ByteArray): Boolean {
        if (src != null && !OffsetFilter(udpOffset,
                        src.toInt().ushr(8).toByte(), src.toByte()).test(t)) {
            return false
        }

        if (dst != null && !OffsetFilter(udpOffset + 2,
                        dst.toInt().ushr(8).toByte(), dst.toByte()).test(t)) {
            return false
        }
        return true
    }
}

/**
 * A [Predicate] that matches ethernet-encapped packets that contain an UDP over IPv4 datagram.
 */
class IPv4UdpFilter @JvmOverloads constructor(
    srcPort: Short? = null,
    dstPort: Short? = null
) : Predicate<ByteArray> {
    private val impl = OffsetFilter(ETHER_TYPE_OFFSET, 0x08, 0x00 /* IPv4 */).and(
            OffsetFilter(IPV4_PROTOCOL_OFFSET, 17 /* UDP */)).and(
            UdpPortFilter(IPV4_PAYLOAD_OFFSET, srcPort, dstPort))
    override fun test(t: ByteArray) = impl.test(t)
}

/**
 * A [Predicate] that matches ethernet-encapped packets that contain an UDP over IPv6 datagram.
 */
class IPv6UdpFilter @JvmOverloads constructor(
    srcPort: Short? = null,
    dstPort: Short? = null
) : Predicate<ByteArray> {
    private val impl = OffsetFilter(ETHER_TYPE_OFFSET, 0x86.toByte(), 0xdd.toByte() /* IPv6 */).and(
            OffsetFilter(IPV6_PROTOCOL_OFFSET, 17 /* UDP */)).and(
            UdpPortFilter(IPV6_PAYLOAD_OFFSET, srcPort, dstPort))
    override fun test(t: ByteArray) = impl.test(t)
}

/**
 * A [Predicate] that matches ethernet-encapped packets sent to the specified IPv4 destination.
 */
class IPv4DstFilter(dst: Inet4Address) : Predicate<ByteArray> {
    private val impl = OffsetFilter(IPV4_DST_OFFSET, *dst.address)
    override fun test(t: ByteArray) = impl.test(t)
}

/**
 * A [Predicate] that matches ethernet-encapped ARP requests.
 */
class ArpRequestFilter : Predicate<ByteArray> {
    private val impl = OffsetFilter(ETHER_TYPE_OFFSET, 0x08, 0x06 /* ARP */)
            .and(OffsetFilter(ARP_OPCODE_OFFSET, 0x00, 0x01 /* request */))
    override fun test(t: ByteArray) = impl.test(t)
}

/**
 * A [Predicate] that matches ethernet-encapped DHCP packets sent from a DHCP client.
 */
class DhcpClientPacketFilter : Predicate<ByteArray> {
    private val impl = IPv4UdpFilter(srcPort = 68, dstPort = 67)
    override fun test(t: ByteArray) = impl.test(t)
}

/**
 * A [Predicate] that matches a [ByteArray] if it contains a ethernet-encapped DHCP packet that
 * contains the specified option with the specified [bytes] as value.
 */
class DhcpOptionFilter(val option: Byte, vararg val bytes: Byte) : Predicate<ByteArray> {
    override fun test(packet: ByteArray): Boolean {
        val option = findDhcpOption(packet, option) ?: return false
        return option.contentEquals(bytes)
    }
}

/**
 * Find a DHCP option in a packet and return its value, if found.
 */
fun findDhcpOption(packet: ByteArray, option: Byte): ByteArray? =
        findOptionOffset(packet, option, DHCP_OPTIONS_OFFSET)?.let {
            val optionLen = packet[it + 1]
            return packet.copyOfRange(it + 2 /* type, length bytes */, it + 2 + optionLen)
        }

private tailrec fun findOptionOffset(packet: ByteArray, option: Byte, searchOffset: Int): Int? {
    if (packet.size <= searchOffset + 2 /* type, length bytes */) return null

    return if (packet[searchOffset] == option) searchOffset else {
        val optionLen = packet[searchOffset + 1]
        findOptionOffset(packet, option, searchOffset + 2 + optionLen)
    }
}