summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorRemi NGUYEN VAN <reminv@google.com>2020-05-20 06:16:38 +0000
committerRemi NGUYEN VAN <reminv@google.com>2020-05-29 04:21:02 +0000
commit89e8dcfdee111b3c8baa48878db00bd31b231bcf (patch)
tree1cd72f240036204691274321b96eccab5dc8ded6
parentc6ad18a80d6697ee9c72489e541ce86f42f7efd0 (diff)
Add utilities to filter packet
The utilities makes it easier to do simple matching of packets received as byte arrays. This is useful for tests operating on tap interfaces. Test: tests based on this utility Bug: 156062304 Original-Change: https://android-review.googlesource.com/1309234 Merged-In: Ie648c4aee1eae94ae01e3e29234fb432b35c108e Change-Id: Ie648c4aee1eae94ae01e3e29234fb432b35c108e
-rw-r--r--tests/lib/multivariant/com/android/testutils/PacketFilter.kt87
-rw-r--r--tests/lib/src/com/android/testutils/TapPacketReader.java29
2 files changed, 105 insertions, 11 deletions
diff --git a/tests/lib/multivariant/com/android/testutils/PacketFilter.kt b/tests/lib/multivariant/com/android/testutils/PacketFilter.kt
new file mode 100644
index 0000000..cd8d6a5
--- /dev/null
+++ b/tests/lib/multivariant/com/android/testutils/PacketFilter.kt
@@ -0,0 +1,87 @@
+/*
+ * 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.util.function.Predicate
+
+const val ETHER_TYPE_OFFSET = 12
+const val ETHER_HEADER_LENGTH = 14
+const val IPV4_PROTOCOL_OFFSET = ETHER_HEADER_LENGTH + 9
+const val IPV4_CHECKSUM_OFFSET = ETHER_HEADER_LENGTH + 10
+const val IPV4_HEADER_LENGTH = 20
+const val IPV4_UDP_OFFSET = ETHER_HEADER_LENGTH + IPV4_HEADER_LENGTH
+const val BOOTP_OFFSET = IPV4_UDP_OFFSET + 8
+const val BOOTP_TID_OFFSET = BOOTP_OFFSET + 4
+const val BOOTP_CLIENT_MAC_OFFSET = BOOTP_OFFSET + 28
+const val DHCP_OPTIONS_OFFSET = BOOTP_OFFSET + 240
+
+/**
+ * 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] }
+}
+
+/**
+ * A [Predicate] that matches ethernet-encapped packets that contain an UDP over IPv4 datagram.
+ */
+class IPv4UdpFilter : Predicate<ByteArray> {
+ private val impl = OffsetFilter(ETHER_TYPE_OFFSET, 0x08, 0x00 /* IPv4 */).and(
+ OffsetFilter(IPV4_PROTOCOL_OFFSET, 17 /* UDP */))
+ 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()
+ .and(OffsetFilter(IPV4_UDP_OFFSET /* source port */, 0x00, 0x44 /* 68 */))
+ .and(OffsetFilter(IPV4_UDP_OFFSET + 2 /* dest port */, 0x00, 0x43 /* 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)
+ }
+}
diff --git a/tests/lib/src/com/android/testutils/TapPacketReader.java b/tests/lib/src/com/android/testutils/TapPacketReader.java
index c68e5a5..e55ed44 100644
--- a/tests/lib/src/com/android/testutils/TapPacketReader.java
+++ b/tests/lib/src/com/android/testutils/TapPacketReader.java
@@ -19,6 +19,7 @@ package com.android.testutils;
import android.net.util.PacketReader;
import android.os.Handler;
+import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import java.io.FileDescriptor;
@@ -26,12 +27,16 @@ import java.io.FileOutputStream;
import java.io.IOException;
import java.nio.ByteBuffer;
import java.util.Arrays;
-import java.util.concurrent.LinkedBlockingQueue;
-import java.util.concurrent.TimeUnit;
+import java.util.function.Predicate;
+
+import kotlin.Lazy;
+import kotlin.LazyKt;
public class TapPacketReader extends PacketReader {
private final FileDescriptor mTapFd;
- private final LinkedBlockingQueue<byte[]> mReceivedPackets = new LinkedBlockingQueue<byte[]>();
+ private final ArrayTrackRecord<byte[]> mReceivedPackets = new ArrayTrackRecord<>();
+ private final Lazy<ArrayTrackRecord<byte[]>.ReadHead> mReadHead =
+ LazyKt.lazy(mReceivedPackets::newReadHead);
public TapPacketReader(Handler h, FileDescriptor tapFd, int maxPacketSize) {
super(h, maxPacketSize);
@@ -46,23 +51,25 @@ public class TapPacketReader extends PacketReader {
@Override
protected void handlePacket(byte[] recvbuf, int length) {
final byte[] newPacket = Arrays.copyOf(recvbuf, length);
- if (!mReceivedPackets.offer(newPacket)) {
+ if (!mReceivedPackets.add(newPacket)) {
throw new AssertionError("More than " + Integer.MAX_VALUE + " packets outstanding!");
}
}
/**
* Get the next packet that was received on the interface.
- *
*/
@Nullable
public byte[] popPacket(long timeoutMs) {
- try {
- return mReceivedPackets.poll(timeoutMs, TimeUnit.MILLISECONDS);
- } catch (InterruptedException e) {
- // Fall through
- }
- return null;
+ return mReadHead.getValue().poll(timeoutMs, packet -> true);
+ }
+
+ /**
+ * Get the next packet that was received on the interface and matches the specified filter.
+ */
+ @Nullable
+ public byte[] popPacket(long timeoutMs, @NonNull Predicate<byte[]> filter) {
+ return mReadHead.getValue().poll(timeoutMs, filter::test);
}
public void sendResponse(final ByteBuffer packet) throws IOException {