Merge "Remove apex_available for bluetooth.updatable"
diff --git a/staticlibs/Android.bp b/staticlibs/Android.bp
index 0585c09..4c58228 100644
--- a/staticlibs/Android.bp
+++ b/staticlibs/Android.bp
@@ -139,6 +139,7 @@
srcs: [
"device/com/android/net/module/util/HexDump.java",
"device/com/android/net/module/util/Ipv6Utils.java",
+ "device/com/android/net/module/util/PacketBuilder.java",
"device/com/android/net/module/util/Struct.java",
"device/com/android/net/module/util/structs/*.java",
],
diff --git a/staticlibs/device/com/android/net/module/util/PacketBuilder.java b/staticlibs/device/com/android/net/module/util/PacketBuilder.java
new file mode 100644
index 0000000..c908528
--- /dev/null
+++ b/staticlibs/device/com/android/net/module/util/PacketBuilder.java
@@ -0,0 +1,257 @@
+/*
+ * Copyright (C) 2021 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.net.module.util;
+
+import static android.system.OsConstants.IPPROTO_IP;
+import static android.system.OsConstants.IPPROTO_TCP;
+import static android.system.OsConstants.IPPROTO_UDP;
+
+import static com.android.net.module.util.IpUtils.ipChecksum;
+import static com.android.net.module.util.IpUtils.tcpChecksum;
+import static com.android.net.module.util.IpUtils.udpChecksum;
+import static com.android.net.module.util.NetworkStackConstants.IPV4_CHECKSUM_OFFSET;
+import static com.android.net.module.util.NetworkStackConstants.IPV4_LENGTH_OFFSET;
+import static com.android.net.module.util.NetworkStackConstants.TCP_CHECKSUM_OFFSET;
+import static com.android.net.module.util.NetworkStackConstants.UDP_CHECKSUM_OFFSET;
+import static com.android.net.module.util.NetworkStackConstants.UDP_LENGTH_OFFSET;
+
+import android.net.MacAddress;
+
+import androidx.annotation.NonNull;
+
+import com.android.net.module.util.structs.EthernetHeader;
+import com.android.net.module.util.structs.Ipv4Header;
+import com.android.net.module.util.structs.TcpHeader;
+import com.android.net.module.util.structs.UdpHeader;
+
+import java.io.IOException;
+import java.net.Inet4Address;
+import java.nio.BufferOverflowException;
+import java.nio.ByteBuffer;
+
+/**
+ * The class is used to build a packet.
+ *
+ * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+ * | Layer 2 header (EthernetHeader) | (optional)
+ * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+ * | Layer 3 header (Ipv4Header) |
+ * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+ * | Layer 4 header (TcpHeader, UdpHeader) |
+ * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+ * | Payload | (optional)
+ * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+ *
+ * Below is a sample code to build a packet.
+ *
+ * // Initialize builder
+ * final ByteBuffer buf = ByteBuffer.allocate(...);
+ * final PacketBuilder pb = new PacketBuilder(buf);
+ * // Write headers
+ * pb.writeL2Header(...);
+ * pb.writeIpHeader(...);
+ * pb.writeTcpHeader(...);
+ * // Write payload
+ * buf.putInt(...);
+ * buf.putShort(...);
+ * buf.putByte(...);
+ * // Finalize and use the packet
+ * pb.finalizePacket();
+ * sendPacket(buf);
+ */
+public class PacketBuilder {
+ private final ByteBuffer mBuffer;
+
+ private int mIpv4HeaderOffset = -1;
+ private int mTcpHeaderOffset = -1;
+ private int mUdpHeaderOffset = -1;
+
+ public PacketBuilder(@NonNull ByteBuffer buffer) {
+ mBuffer = buffer;
+ }
+
+ /**
+ * Write an ethernet header.
+ *
+ * @param srcMac source MAC address
+ * @param dstMac destination MAC address
+ * @param etherType ether type
+ */
+ public void writeL2Header(MacAddress srcMac, MacAddress dstMac, short etherType) throws
+ IOException {
+ final EthernetHeader ethv4Header = new EthernetHeader(dstMac, srcMac, etherType);
+ try {
+ ethv4Header.writeToByteBuffer(mBuffer);
+ } catch (IllegalArgumentException | BufferOverflowException e) {
+ throw new IOException("Error writing to buffer: ", e);
+ }
+ }
+
+ /**
+ * Write an IPv4 header.
+ * The IP header length and checksum are calculated and written back in #finalizePacket.
+ *
+ * @param tos type of service
+ * @param id the identification
+ * @param flagsAndFragmentOffset flags and fragment offset
+ * @param ttl time to live
+ * @param protocol protocol
+ * @param srcIp source IP address
+ * @param dstIp destination IP address
+ */
+ public void writeIpv4Header(byte tos, short id, short flagsAndFragmentOffset, byte ttl,
+ byte protocol, @NonNull final Inet4Address srcIp, @NonNull final Inet4Address dstIp)
+ throws IOException {
+ mIpv4HeaderOffset = mBuffer.position();
+ final Ipv4Header ipv4Header = new Ipv4Header(tos,
+ (short) 0 /* totalLength, calculate in #finalizePacket */, id,
+ flagsAndFragmentOffset, ttl, protocol,
+ (short) 0 /* checksum, calculate in #finalizePacket */, srcIp, dstIp);
+
+ try {
+ ipv4Header.writeToByteBuffer(mBuffer);
+ } catch (IllegalArgumentException | BufferOverflowException e) {
+ throw new IOException("Error writing to buffer: ", e);
+ }
+ }
+
+ /**
+ * Write a TCP header.
+ * The TCP header checksum is calculated and written back in #finalizePacket.
+ *
+ * @param srcPort source port
+ * @param dstPort destination port
+ * @param seq sequence number
+ * @param ack acknowledgement number
+ * @param tcpFlags tcp flags
+ * @param window window size
+ * @param urgentPointer urgent pointer
+ */
+ public void writeTcpHeader(short srcPort, short dstPort, short seq, short ack,
+ byte tcpFlags, short window, short urgentPointer) throws IOException {
+ mTcpHeaderOffset = mBuffer.position();
+ final TcpHeader tcpHeader = new TcpHeader(srcPort, dstPort, seq, ack,
+ (short) ((short) 0x5000 | ((byte) 0x3f & tcpFlags)) /* dataOffsetAndControlBits,
+ dataOffset is always 5(*4bytes) because options not supported */, window,
+ (short) 0 /* checksum, calculate in #finalizePacket */,
+ urgentPointer);
+
+ try {
+ tcpHeader.writeToByteBuffer(mBuffer);
+ } catch (IllegalArgumentException | BufferOverflowException e) {
+ throw new IOException("Error writing to buffer: ", e);
+ }
+ }
+
+ /**
+ * Write a UDP header.
+ * The UDP header length and checksum are calculated and written back in #finalizePacket.
+ *
+ * @param srcPort source port
+ * @param dstPort destination port
+ */
+ public void writeUdpHeader(short srcPort, short dstPort) throws IOException {
+ mUdpHeaderOffset = mBuffer.position();
+ final UdpHeader udpHeader = new UdpHeader(srcPort, dstPort,
+ (short) 0 /* length, calculate in #finalizePacket */,
+ (short) 0 /* checksum, calculate in #finalizePacket */);
+
+ try {
+ udpHeader.writeToByteBuffer(mBuffer);
+ } catch (IllegalArgumentException | BufferOverflowException e) {
+ throw new IOException("Error writing to buffer: ", e);
+ }
+ }
+
+ /**
+ * Finalize the packet.
+ *
+ * Call after writing L4 header (no payload) or payload to the buffer used by the builder.
+ * L3 header length, L3 header checksum and L4 header checksum are calculated and written back
+ * after finalization.
+ */
+ @NonNull
+ public ByteBuffer finalizePacket() throws IOException {
+ if (mIpv4HeaderOffset < 0) {
+ // TODO: add support for IPv6
+ throw new IOException("Packet is missing IPv4 header");
+ }
+
+ // Populate the IPv4 totalLength field.
+ mBuffer.putShort(mIpv4HeaderOffset + IPV4_LENGTH_OFFSET,
+ (short) (mBuffer.position() - mIpv4HeaderOffset));
+
+ // Populate the IPv4 header checksum field.
+ mBuffer.putShort(mIpv4HeaderOffset + IPV4_CHECKSUM_OFFSET,
+ ipChecksum(mBuffer, mIpv4HeaderOffset /* headerOffset */));
+
+ if (mTcpHeaderOffset > 0) {
+ // Populate the TCP header checksum field.
+ mBuffer.putShort(mTcpHeaderOffset + TCP_CHECKSUM_OFFSET, tcpChecksum(mBuffer,
+ mIpv4HeaderOffset /* ipOffset */, mTcpHeaderOffset /* transportOffset */,
+ mBuffer.position() - mTcpHeaderOffset /* transportLen */));
+ } else if (mUdpHeaderOffset > 0) {
+ // Populate the UDP header length field.
+ mBuffer.putShort(mUdpHeaderOffset + UDP_LENGTH_OFFSET,
+ (short) (mBuffer.position() - mUdpHeaderOffset));
+
+ // Populate the UDP header checksum field.
+ mBuffer.putShort(mUdpHeaderOffset + UDP_CHECKSUM_OFFSET, udpChecksum(mBuffer,
+ mIpv4HeaderOffset /* ipOffset */, mUdpHeaderOffset /* transportOffset */));
+ } else {
+ throw new IOException("Packet is missing neither TCP nor UDP header");
+ }
+
+ mBuffer.flip();
+ return mBuffer;
+ }
+
+ /**
+ * Allocate bytebuffer for building the packet.
+ *
+ * @param hasEther has ethernet header. Set this flag to indicate that the packet has an
+ * ethernet header.
+ * @param l3proto the layer 3 protocol. Only {@code IPPROTO_IP} currently supported.
+ * @param l4proto the layer 4 protocol. Only {@code IPPROTO_TCP} and {@code IPPROTO_UDP}
+ * currently supported.
+ * @param payloadLen length of the payload.
+ */
+ @NonNull
+ public static ByteBuffer allocate(boolean hasEther, int l3proto, int l4proto, int payloadLen) {
+ if (l3proto != IPPROTO_IP) {
+ // TODO: add support for IPv6
+ throw new IllegalArgumentException("Unsupported layer 3 protocol " + l3proto);
+ }
+
+ if (l4proto != IPPROTO_TCP && l4proto != IPPROTO_UDP) {
+ throw new IllegalArgumentException("Unsupported layer 4 protocol " + l4proto);
+ }
+
+ if (payloadLen < 0) {
+ throw new IllegalArgumentException("Invalid payload length " + payloadLen);
+ }
+
+ int packetLen = 0;
+ if (hasEther) packetLen += Struct.getSize(EthernetHeader.class);
+ packetLen += Struct.getSize(Ipv4Header.class);
+ packetLen += (l4proto == IPPROTO_TCP) ? Struct.getSize(TcpHeader.class)
+ : Struct.getSize(UdpHeader.class);
+ packetLen += payloadLen;
+
+ return ByteBuffer.allocate(packetLen);
+ }
+}
diff --git a/staticlibs/device/com/android/net/module/util/netlink/RtNetlinkAddressMessage.java b/staticlibs/device/com/android/net/module/util/netlink/RtNetlinkAddressMessage.java
index a518c76..f7b0d02 100644
--- a/staticlibs/device/com/android/net/module/util/netlink/RtNetlinkAddressMessage.java
+++ b/staticlibs/device/com/android/net/module/util/netlink/RtNetlinkAddressMessage.java
@@ -114,9 +114,10 @@
// and will overwrite the flags set above.
byteBuffer.position(baseOffset);
nlAttr = StructNlAttr.findNextAttrOfType(IFA_FLAGS, byteBuffer);
- if (nlAttr != null) {
- addrMsg.mFlags = nlAttr.getValueAsInt(0 /* default value */);
- }
+ if (nlAttr == null) return null;
+ final Integer value = nlAttr.getValueAsInteger();
+ if (value == null) return null;
+ addrMsg.mFlags = value;
return addrMsg;
}
diff --git a/staticlibs/device/com/android/net/module/util/netlink/StructNlAttr.java b/staticlibs/device/com/android/net/module/util/netlink/StructNlAttr.java
index 485e67c..a9b6495 100644
--- a/staticlibs/device/com/android/net/module/util/netlink/StructNlAttr.java
+++ b/staticlibs/device/com/android/net/module/util/netlink/StructNlAttr.java
@@ -287,14 +287,22 @@
}
/**
- * Get attribute value as Integer.
+ * Get attribute value as Integer, or null if malformed (e.g., length is not 4 bytes).
*/
- public int getValueAsInt(int defaultValue) {
+ public Integer getValueAsInteger() {
final ByteBuffer byteBuffer = getValueAsByteBuffer();
if (byteBuffer == null || byteBuffer.remaining() != Integer.BYTES) {
- return defaultValue;
+ return null;
}
- return getValueAsByteBuffer().getInt();
+ return byteBuffer.getInt();
+ }
+
+ /**
+ * Get attribute value as Int, default value if malformed.
+ */
+ public int getValueAsInt(int defaultValue) {
+ final Integer value = getValueAsInteger();
+ return (value != null) ? value : defaultValue;
}
/**
@@ -341,6 +349,7 @@
public String getValueAsString() {
if (nla_value == null) return null;
// Check the attribute value length after removing string termination flag '\0'.
+ // This assumes that all netlink strings are null-terminated.
if (nla_value.length < (nla_len - NLA_HEADERLEN - 1)) return null;
try {
diff --git a/staticlibs/device/com/android/net/module/util/structs/Ipv4Header.java b/staticlibs/device/com/android/net/module/util/structs/Ipv4Header.java
new file mode 100644
index 0000000..5249454
--- /dev/null
+++ b/staticlibs/device/com/android/net/module/util/structs/Ipv4Header.java
@@ -0,0 +1,94 @@
+/*
+ * Copyright (C) 2021 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.net.module.util.structs;
+
+import androidx.annotation.VisibleForTesting;
+
+import com.android.net.module.util.Struct;
+import com.android.net.module.util.Struct.Field;
+import com.android.net.module.util.Struct.Type;
+
+import java.net.Inet4Address;
+
+/**
+ * L3 IPv4 header as per https://tools.ietf.org/html/rfc791.
+ * This class doesn't contain options field.
+ *
+ * 0 1 2 3
+ * 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
+ * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+ * |Version| IHL |Type of Service| Total Length |
+ * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+ * | Identification |Flags| Fragment Offset |
+ * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+ * | Time to Live | Protocol | Header Checksum |
+ * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+ * | Source Address |
+ * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+ * | Destination Address |
+ * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+ */
+public class Ipv4Header extends Struct {
+ // IP Version=IPv4, IHL is always 5(*4bytes) because options are not supported.
+ @VisibleForTesting
+ public static final byte IPHDR_VERSION_IHL = 0x45;
+
+ @Field(order = 0, type = Type.S8)
+ // version (4 bits), IHL (4 bits)
+ public final byte vi;
+ @Field(order = 1, type = Type.S8)
+ public final byte tos;
+ @Field(order = 2, type = Type.U16)
+ public final int totalLength;
+ @Field(order = 3, type = Type.S16)
+ public final short id;
+ @Field(order = 4, type = Type.S16)
+ // flags (3 bits), fragment offset (13 bits)
+ public final short flagsAndFragmentOffset;
+ @Field(order = 5, type = Type.U8)
+ public final short ttl;
+ @Field(order = 6, type = Type.S8)
+ public final byte protocol;
+ @Field(order = 7, type = Type.S16)
+ public final short checksum;
+ @Field(order = 8, type = Type.Ipv4Address)
+ public final Inet4Address srcIp;
+ @Field(order = 9, type = Type.Ipv4Address)
+ public final Inet4Address dstIp;
+
+ public Ipv4Header(final byte tos, final int totalLength, final short id,
+ final short flagsAndFragmentOffset, final short ttl, final byte protocol,
+ final short checksum, final Inet4Address srcIp, final Inet4Address dstIp) {
+ this(IPHDR_VERSION_IHL, tos, totalLength, id, flagsAndFragmentOffset, ttl,
+ protocol, checksum, srcIp, dstIp);
+ }
+
+ private Ipv4Header(final byte vi, final byte tos, final int totalLength, final short id,
+ final short flagsAndFragmentOffset, final short ttl, final byte protocol,
+ final short checksum, final Inet4Address srcIp, final Inet4Address dstIp) {
+ this.vi = vi;
+ this.tos = tos;
+ this.totalLength = totalLength;
+ this.id = id;
+ this.flagsAndFragmentOffset = flagsAndFragmentOffset;
+ this.ttl = ttl;
+ this.protocol = protocol;
+ this.checksum = checksum;
+ this.srcIp = srcIp;
+ this.dstIp = dstIp;
+ }
+}
diff --git a/staticlibs/device/com/android/net/module/util/structs/TcpHeader.java b/staticlibs/device/com/android/net/module/util/structs/TcpHeader.java
new file mode 100644
index 0000000..0c97401
--- /dev/null
+++ b/staticlibs/device/com/android/net/module/util/structs/TcpHeader.java
@@ -0,0 +1,79 @@
+/*
+ * Copyright (C) 2021 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.net.module.util.structs;
+
+import com.android.net.module.util.Struct;
+import com.android.net.module.util.Struct.Field;
+import com.android.net.module.util.Struct.Type;
+
+/**
+ * L4 TCP header as per https://tools.ietf.org/html/rfc793.
+ * This class does not contain option and data fields.
+ *
+ * 0 1 2 3
+ * 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
+ * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+ * | Source Port | Destination Port |
+ * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+ * | Sequence Number |
+ * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+ * | Acknowledgment Number |
+ * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+ * | Data | |U|A|P|R|S|F| |
+ * | Offset| Reserved |R|C|S|S|Y|I| Window |
+ * | | |G|K|H|T|N|N| |
+ * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+ * | Checksum | Urgent Pointer |
+ * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+ * | Options | Padding |
+ * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+ * | data |
+ * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+ */
+public class TcpHeader extends Struct {
+ @Field(order = 0, type = Type.U16)
+ public final int srcPort;
+ @Field(order = 1, type = Type.U16)
+ public final int dstPort;
+ @Field(order = 2, type = Type.U32)
+ public final long seq;
+ @Field(order = 3, type = Type.U32)
+ public final long ack;
+ @Field(order = 4, type = Type.S16)
+ // data Offset (4 bits), reserved (6 bits), control bits (6 bits)
+ // TODO: update with bitfields once class Struct supports it
+ public final short dataOffsetAndControlBits;
+ @Field(order = 5, type = Type.U16)
+ public final int window;
+ @Field(order = 6, type = Type.S16)
+ public final short checksum;
+ @Field(order = 7, type = Type.U16)
+ public final int urgentPointer;
+
+ public TcpHeader(final int srcPort, final int dstPort, final long seq, final long ack,
+ final short dataOffsetAndControlBits, final int window, final short checksum,
+ final int urgentPointer) {
+ this.srcPort = srcPort;
+ this.dstPort = dstPort;
+ this.seq = seq;
+ this.ack = ack;
+ this.dataOffsetAndControlBits = dataOffsetAndControlBits;
+ this.window = window;
+ this.checksum = checksum;
+ this.urgentPointer = urgentPointer;
+ }
+}
diff --git a/staticlibs/device/com/android/net/module/util/structs/UdpHeader.java b/staticlibs/device/com/android/net/module/util/structs/UdpHeader.java
new file mode 100644
index 0000000..8b0316b
--- /dev/null
+++ b/staticlibs/device/com/android/net/module/util/structs/UdpHeader.java
@@ -0,0 +1,53 @@
+/*
+ * Copyright (C) 2021 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.net.module.util.structs;
+
+import com.android.net.module.util.Struct;
+import com.android.net.module.util.Struct.Field;
+import com.android.net.module.util.Struct.Type;
+
+/**
+ * L4 UDP header as per https://tools.ietf.org/html/rfc768.
+ *
+ * 0 1 2 3
+ * 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
+ * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+ * | Source Port | Destination Port |
+ * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+ * | Length | Checksum |
+ * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+ * | data octets ...
+ * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ ...
+ */
+public class UdpHeader extends Struct {
+ @Field(order = 0, type = Type.U16)
+ public final int srcPort;
+ @Field(order = 1, type = Type.U16)
+ public final int dstPort;
+ @Field(order = 2, type = Type.U16)
+ public final int length;
+ @Field(order = 3, type = Type.S16)
+ public final short checksum;
+
+ public UdpHeader(final int srcPort, final int dstPort, final int length,
+ final short checksum) {
+ this.srcPort = srcPort;
+ this.dstPort = dstPort;
+ this.length = length;
+ this.checksum = checksum;
+ }
+}
diff --git a/staticlibs/framework/com/android/net/module/util/NetworkStackConstants.java b/staticlibs/framework/com/android/net/module/util/NetworkStackConstants.java
index f7151d7..353fe69 100644
--- a/staticlibs/framework/com/android/net/module/util/NetworkStackConstants.java
+++ b/staticlibs/framework/com/android/net/module/util/NetworkStackConstants.java
@@ -89,9 +89,11 @@
public static final int IPV4_MAX_MTU = 65_535;
public static final int IPV4_HEADER_MIN_LEN = 20;
public static final int IPV4_IHL_MASK = 0xf;
+ public static final int IPV4_LENGTH_OFFSET = 2;
public static final int IPV4_FLAGS_OFFSET = 6;
public static final int IPV4_FRAGMENT_MASK = 0x1fff;
public static final int IPV4_PROTOCOL_OFFSET = 9;
+ public static final int IPV4_CHECKSUM_OFFSET = 10;
public static final int IPV4_SRC_ADDR_OFFSET = 12;
public static final int IPV4_DST_ADDR_OFFSET = 16;
public static final int IPV4_ADDR_LEN = 4;
@@ -164,13 +166,29 @@
public static final byte PIO_FLAG_AUTONOMOUS = (byte) (1 << 6);
/**
+ * TCP constants.
+ *
+ * See also:
+ * - https://tools.ietf.org/html/rfc793
+ */
+ public static final int TCP_HEADER_MIN_LEN = 20;
+ public static final int TCP_CHECKSUM_OFFSET = 16;
+ public static final byte TCPHDR_FIN = (byte) (1 << 0);
+ public static final byte TCPHDR_SYN = (byte) (1 << 1);
+ public static final byte TCPHDR_RST = (byte) (1 << 2);
+ public static final byte TCPHDR_PSH = (byte) (1 << 3);
+ public static final byte TCPHDR_ACK = (byte) (1 << 4);
+ public static final byte TCPHDR_URG = (byte) (1 << 5);
+
+ /**
* UDP constants.
*
* See also:
* - https://tools.ietf.org/html/rfc768
*/
public static final int UDP_HEADER_LEN = 8;
-
+ public static final int UDP_LENGTH_OFFSET = 4;
+ public static final int UDP_CHECKSUM_OFFSET = 6;
/**
* DHCP constants.
diff --git a/staticlibs/native/README.md b/staticlibs/native/README.md
index 18d19c4..1f505c4 100644
--- a/staticlibs/native/README.md
+++ b/staticlibs/native/README.md
@@ -22,6 +22,9 @@
* Each module creates a native library in their directory, which statically links against the
common native library (e.g. libnet_utils_device_common_bpf), and calls the native registered
- function by hardcoding the post-jarjar class_name.
-
-
+ function by hardcoding the post-jarjar class_name. Linkage *MUST* be static because common
+ functions in the file (e.g., `register_com_android_net_module_util_BpfMap`) will appear in the
+ library (`.so`) file, and different versions of the library loaded in the same process by
+ different modules will in general have different versions. It's important that each of these
+ libraries loads the common function from its own library. Static linkage should guarantee this
+ because static linkage resolves symbols at build time, not runtime.
\ No newline at end of file
diff --git a/staticlibs/native/bpf_map_utils/Android.bp b/staticlibs/native/bpf_map_utils/Android.bp
index e291bbc..e14c259 100644
--- a/staticlibs/native/bpf_map_utils/Android.bp
+++ b/staticlibs/native/bpf_map_utils/Android.bp
@@ -12,6 +12,10 @@
// See the License for the specific language governing permissions and
// limitations under the License.
+package {
+ default_applicable_licenses: ["Android-Apache-2.0"],
+}
+
cc_library_headers {
name: "bpf_map_utils",
vendor_available: true,
diff --git a/staticlibs/native/ip_checksum/Android.bp b/staticlibs/native/ip_checksum/Android.bp
new file mode 100644
index 0000000..d7e195d
--- /dev/null
+++ b/staticlibs/native/ip_checksum/Android.bp
@@ -0,0 +1,40 @@
+// Copyright (C) 2021 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 {
+ default_applicable_licenses: ["Android-Apache-2.0"],
+}
+
+cc_library_static {
+ name: "libip_checksum",
+
+ srcs: [
+ "checksum.c",
+ ],
+
+ cflags: [
+ "-Wall",
+ "-Werror",
+ ],
+
+ export_include_dirs: ["."],
+
+ // Needed because libnetutils depends on libip_checksum, and libnetutils has
+ // vendor_available = true. Making this library vendor_available does not create any maintenance
+ // burden or version skew issues because this library is only static, not dynamic, and thus is
+ // not installed on the device.
+ //
+ // TODO: delete libnetutils from the VNDK in T, and remove this.
+ vendor_available: true,
+}
diff --git a/staticlibs/native/ip_checksum/checksum.c b/staticlibs/native/ip_checksum/checksum.c
new file mode 100644
index 0000000..04217a7
--- /dev/null
+++ b/staticlibs/native/ip_checksum/checksum.c
@@ -0,0 +1,145 @@
+/*
+ * Copyright 2011 Daniel Drown
+ *
+ * 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.
+ *
+ * checksum.c - ipv4/ipv6 checksum calculation
+ */
+#include <netinet/icmp6.h>
+#include <netinet/in.h>
+#include <netinet/ip.h>
+#include <netinet/ip6.h>
+#include <netinet/ip_icmp.h>
+#include <netinet/tcp.h>
+#include <netinet/udp.h>
+
+#include "checksum.h"
+
+/* function: ip_checksum_add
+ * adds data to a checksum. only known to work on little-endian hosts
+ * current - the current checksum (or 0 to start a new checksum)
+ * data - the data to add to the checksum
+ * len - length of data
+ */
+uint32_t ip_checksum_add(uint32_t current, const void* data, int len) {
+ uint32_t checksum = current;
+ int left = len;
+ const uint16_t* data_16 = data;
+
+ while (left > 1) {
+ checksum += *data_16;
+ data_16++;
+ left -= 2;
+ }
+ if (left) {
+ checksum += *(uint8_t*)data_16;
+ }
+
+ return checksum;
+}
+
+/* function: ip_checksum_fold
+ * folds a 32-bit partial checksum into 16 bits
+ * temp_sum - sum from ip_checksum_add
+ * returns: the folded checksum in network byte order
+ */
+uint16_t ip_checksum_fold(uint32_t temp_sum) {
+ while (temp_sum > 0xffff) {
+ temp_sum = (temp_sum >> 16) + (temp_sum & 0xFFFF);
+ }
+ return temp_sum;
+}
+
+/* function: ip_checksum_finish
+ * folds and closes the checksum
+ * temp_sum - sum from ip_checksum_add
+ * returns: a header checksum value in network byte order
+ */
+uint16_t ip_checksum_finish(uint32_t temp_sum) {
+ return ~ip_checksum_fold(temp_sum);
+}
+
+/* function: ip_checksum
+ * combined ip_checksum_add and ip_checksum_finish
+ * data - data to checksum
+ * len - length of data
+ */
+uint16_t ip_checksum(const void* data, int len) {
+ // TODO: consider starting from 0xffff so the checksum of a buffer entirely consisting of zeros
+ // is correctly calculated as 0.
+ uint32_t temp_sum;
+
+ temp_sum = ip_checksum_add(0, data, len);
+ return ip_checksum_finish(temp_sum);
+}
+
+/* function: ipv6_pseudo_header_checksum
+ * calculate the pseudo header checksum for use in tcp/udp/icmp headers
+ * ip6 - the ipv6 header
+ * len - the transport length (transport header + payload)
+ * protocol - the transport layer protocol, can be different from ip6->ip6_nxt for fragments
+ */
+uint32_t ipv6_pseudo_header_checksum(const struct ip6_hdr* ip6, uint32_t len, uint8_t protocol) {
+ uint32_t checksum_len = htonl(len);
+ uint32_t checksum_next = htonl(protocol);
+
+ uint32_t current = 0;
+
+ current = ip_checksum_add(current, &(ip6->ip6_src), sizeof(struct in6_addr));
+ current = ip_checksum_add(current, &(ip6->ip6_dst), sizeof(struct in6_addr));
+ current = ip_checksum_add(current, &checksum_len, sizeof(checksum_len));
+ current = ip_checksum_add(current, &checksum_next, sizeof(checksum_next));
+
+ return current;
+}
+
+/* function: ipv4_pseudo_header_checksum
+ * calculate the pseudo header checksum for use in tcp/udp headers
+ * ip - the ipv4 header
+ * len - the transport length (transport header + payload)
+ */
+uint32_t ipv4_pseudo_header_checksum(const struct iphdr* ip, uint16_t len) {
+ uint16_t temp_protocol, temp_length;
+
+ temp_protocol = htons(ip->protocol);
+ temp_length = htons(len);
+
+ uint32_t current = 0;
+
+ current = ip_checksum_add(current, &(ip->saddr), sizeof(uint32_t));
+ current = ip_checksum_add(current, &(ip->daddr), sizeof(uint32_t));
+ current = ip_checksum_add(current, &temp_protocol, sizeof(uint16_t));
+ current = ip_checksum_add(current, &temp_length, sizeof(uint16_t));
+
+ return current;
+}
+
+/* function: ip_checksum_adjust
+ * calculates a new checksum given a previous checksum and the old and new pseudo-header checksums
+ * checksum - the header checksum in the original packet in network byte order
+ * old_hdr_sum - the pseudo-header checksum of the original packet
+ * new_hdr_sum - the pseudo-header checksum of the translated packet
+ * returns: the new header checksum in network byte order
+ */
+uint16_t ip_checksum_adjust(uint16_t checksum, uint32_t old_hdr_sum, uint32_t new_hdr_sum) {
+ // Algorithm suggested in RFC 1624.
+ // http://tools.ietf.org/html/rfc1624#section-3
+ checksum = ~checksum;
+ uint16_t folded_sum = ip_checksum_fold(checksum + new_hdr_sum);
+ uint16_t folded_old = ip_checksum_fold(old_hdr_sum);
+ if (folded_sum > folded_old) {
+ return ~(folded_sum - folded_old);
+ } else {
+ return ~(folded_sum - folded_old - 1); // end-around borrow
+ }
+}
diff --git a/staticlibs/native/ip_checksum/checksum.h b/staticlibs/native/ip_checksum/checksum.h
new file mode 100644
index 0000000..868217c
--- /dev/null
+++ b/staticlibs/native/ip_checksum/checksum.h
@@ -0,0 +1,34 @@
+/*
+ * Copyright 2011 Daniel Drown
+ *
+ * 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.
+ *
+ * checksum.h - checksum functions
+ */
+#ifndef __CHECKSUM_H__
+#define __CHECKSUM_H__
+
+#include <netinet/ip.h>
+#include <netinet/ip6.h>
+#include <stdint.h>
+
+uint32_t ip_checksum_add(uint32_t current, const void* data, int len);
+uint16_t ip_checksum_finish(uint32_t temp_sum);
+uint16_t ip_checksum(const void* data, int len);
+
+uint32_t ipv6_pseudo_header_checksum(const struct ip6_hdr* ip6, uint32_t len, uint8_t protocol);
+uint32_t ipv4_pseudo_header_checksum(const struct iphdr* ip, uint16_t len);
+
+uint16_t ip_checksum_adjust(uint16_t checksum, uint32_t old_hdr_sum, uint32_t new_hdr_sum);
+
+#endif /* __CHECKSUM_H__ */
diff --git a/staticlibs/netd/libnetdutils/Android.bp b/staticlibs/netd/libnetdutils/Android.bp
index 9f4d6e3..732e37d 100644
--- a/staticlibs/netd/libnetdutils/Android.bp
+++ b/staticlibs/netd/libnetdutils/Android.bp
@@ -18,6 +18,7 @@
"Syscalls.cpp",
"UniqueFd.cpp",
"UniqueFile.cpp",
+ "Utils.cpp",
],
defaults: ["netd_defaults"],
cflags: ["-Wall", "-Werror"],
diff --git a/staticlibs/netd/libnetdutils/Utils.cpp b/staticlibs/netd/libnetdutils/Utils.cpp
new file mode 100644
index 0000000..16ec882
--- /dev/null
+++ b/staticlibs/netd/libnetdutils/Utils.cpp
@@ -0,0 +1,61 @@
+/*
+ * Copyright (C) 2021 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.
+ */
+
+#include <map>
+
+#include <net/if.h>
+
+#include "dirent.h"
+#include "netdutils/Status.h"
+#include "netdutils/Utils.h"
+
+namespace android {
+namespace netdutils {
+
+StatusOr<std::vector<std::string>> getIfaceNames() {
+ std::vector<std::string> ifaceNames;
+ DIR* d;
+ struct dirent* de;
+
+ if (!(d = opendir("/sys/class/net"))) {
+ return statusFromErrno(errno, "Cannot open iface directory");
+ }
+ while ((de = readdir(d))) {
+ if ((de->d_type != DT_DIR) && (de->d_type != DT_LNK)) continue;
+ if (de->d_name[0] == '.') continue;
+ ifaceNames.push_back(std::string(de->d_name));
+ }
+ closedir(d);
+ return ifaceNames;
+}
+
+StatusOr<std::map<std::string, uint32_t>> getIfaceList() {
+ std::map<std::string, uint32_t> ifacePairs;
+
+ ASSIGN_OR_RETURN(auto ifaceNames, getIfaceNames());
+
+ for (const auto& name : ifaceNames) {
+ uint32_t ifaceIndex = if_nametoindex(name.c_str());
+ if (ifaceIndex) {
+ ifacePairs.insert(std::pair<std::string, uint32_t>(name, ifaceIndex));
+ }
+ }
+ return ifacePairs;
+}
+
+} // namespace netdutils
+} // namespace android
diff --git a/staticlibs/netd/libnetdutils/include/netdutils/Utils.h b/staticlibs/netd/libnetdutils/include/netdutils/Utils.h
new file mode 100644
index 0000000..83c583b
--- /dev/null
+++ b/staticlibs/netd/libnetdutils/include/netdutils/Utils.h
@@ -0,0 +1,32 @@
+/*
+ * Copyright (C) 2021 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.
+ */
+
+#ifndef NETUTILS_UTILS_H
+#define NETUTILS_UTILS_H
+
+#include "netdutils/StatusOr.h"
+
+namespace android {
+namespace netdutils {
+
+StatusOr<std::vector<std::string>> getIfaceNames();
+
+StatusOr<std::map<std::string, uint32_t>> getIfaceList();
+
+} // namespace netdutils
+} // namespace android
+
+#endif /* NETUTILS_UTILS_H */
diff --git a/staticlibs/tests/unit/src/com/android/net/module/util/PacketBuilderTest.java b/staticlibs/tests/unit/src/com/android/net/module/util/PacketBuilderTest.java
new file mode 100644
index 0000000..8f9a1f9
--- /dev/null
+++ b/staticlibs/tests/unit/src/com/android/net/module/util/PacketBuilderTest.java
@@ -0,0 +1,532 @@
+/*
+ * Copyright (C) 2021 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.net.module.util;
+
+import static android.system.OsConstants.IPPROTO_IP;
+import static android.system.OsConstants.IPPROTO_TCP;
+import static android.system.OsConstants.IPPROTO_UDP;
+
+import static com.android.net.module.util.NetworkStackConstants.ETHER_TYPE_IPV4;
+import static com.android.net.module.util.NetworkStackConstants.IPV4_HEADER_MIN_LEN;
+import static com.android.net.module.util.NetworkStackConstants.TCPHDR_ACK;
+import static com.android.net.module.util.NetworkStackConstants.TCP_HEADER_MIN_LEN;
+import static com.android.net.module.util.NetworkStackConstants.UDP_HEADER_LEN;
+import static com.android.testutils.MiscAsserts.assertThrows;
+
+import static org.junit.Assert.assertArrayEquals;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.fail;
+
+import android.net.InetAddresses;
+import android.net.MacAddress;
+
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+import androidx.test.filters.SmallTest;
+import androidx.test.runner.AndroidJUnit4;
+
+import com.android.net.module.util.structs.EthernetHeader;
+import com.android.net.module.util.structs.Ipv4Header;
+import com.android.net.module.util.structs.TcpHeader;
+import com.android.net.module.util.structs.UdpHeader;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import java.io.IOException;
+import java.net.Inet4Address;
+import java.nio.ByteBuffer;
+
+@RunWith(AndroidJUnit4.class)
+@SmallTest
+public class PacketBuilderTest {
+ private static final MacAddress SRC_MAC = MacAddress.fromString("11:22:33:44:55:66");
+ private static final MacAddress DST_MAC = MacAddress.fromString("aa:bb:cc:dd:ee:ff");
+ private static final Inet4Address IPV4_SRC_ADDR = addr("192.0.2.1");
+ private static final Inet4Address IPV4_DST_ADDR = addr("198.51.100.1");
+ private static final short SRC_PORT = 9876;
+ private static final short DST_PORT = 433;
+ private static final short SEQ_NO = 13579;
+ private static final short ACK_NO = 24680;
+ private static final byte TYPE_OF_SERVICE = 0;
+ private static final short ID = 27149;
+ private static final short FLAGS_AND_FRAGMENT_OFFSET = (short) 0x4000; // flags=DF, offset=0
+ private static final byte TIME_TO_LIVE = (byte) 0x40;
+ private static final short WINDOW = (short) 0x2000;
+ private static final short URGENT_POINTER = 0;
+ private static final ByteBuffer DATA = ByteBuffer.wrap(new byte[] {
+ (byte) 0xde, (byte) 0xad, (byte) 0xbe, (byte) 0xef
+ });
+
+ private static final byte[] TEST_PACKET_ETHERHDR_IPV4HDR_TCPHDR =
+ new byte[] {
+ // packet = (scapy.Ether(src="11:22:33:44:55:66", dst="aa:bb:cc:dd:ee:ff",
+ // type='IPv4') /
+ // scapy.IP(src="192.0.2.1", dst="198.51.100.1",
+ // tos=0, id=27149, flags='DF') /
+ // scapy.TCP(sport=9876, dport=433, seq=13579, ack=24680,
+ // flags='A', window=8192, urgptr=0))
+ // Ether header
+ (byte) 0xaa, (byte) 0xbb, (byte) 0xcc, (byte) 0xdd,
+ (byte) 0xee, (byte) 0xff, (byte) 0x11, (byte) 0x22,
+ (byte) 0x33, (byte) 0x44, (byte) 0x55, (byte) 0x66,
+ (byte) 0x08, (byte) 0x00,
+ // IPv4 header
+ (byte) 0x45, (byte) 0x00, (byte) 0x00, (byte) 0x28,
+ (byte) 0x6a, (byte) 0x0d, (byte) 0x40, (byte) 0x00,
+ (byte) 0x40, (byte) 0x06, (byte) 0xe4, (byte) 0x8c,
+ (byte) 0xc0, (byte) 0x00, (byte) 0x02, (byte) 0x01,
+ (byte) 0xc6, (byte) 0x33, (byte) 0x64, (byte) 0x01,
+ // TCP header
+ (byte) 0x26, (byte) 0x94, (byte) 0x01, (byte) 0xb1,
+ (byte) 0x00, (byte) 0x00, (byte) 0x35, (byte) 0x0b,
+ (byte) 0x00, (byte) 0x00, (byte) 0x60, (byte) 0x68,
+ (byte) 0x50, (byte) 0x10, (byte) 0x20, (byte) 0x00,
+ (byte) 0xe5, (byte) 0xe5, (byte) 0x00, (byte) 0x00
+ };
+
+ private static final byte[] TEST_PACKET_ETHERHDR_IPV4HDR_TCPHDR_DATA =
+ new byte[] {
+ // packet = (scapy.Ether(src="11:22:33:44:55:66", dst="aa:bb:cc:dd:ee:ff",
+ // type='IPv4') /
+ // scapy.IP(src="192.0.2.1", dst="198.51.100.1",
+ // tos=0, id=27149, flags='DF') /
+ // scapy.TCP(sport=9876, dport=433, seq=13579, ack=24680,
+ // flags='A', window=8192, urgptr=0) /
+ // b'\xde\xad\xbe\xef')
+ // Ether header
+ (byte) 0xaa, (byte) 0xbb, (byte) 0xcc, (byte) 0xdd,
+ (byte) 0xee, (byte) 0xff, (byte) 0x11, (byte) 0x22,
+ (byte) 0x33, (byte) 0x44, (byte) 0x55, (byte) 0x66,
+ (byte) 0x08, (byte) 0x00,
+ // IPv4 header
+ (byte) 0x45, (byte) 0x00, (byte) 0x00, (byte) 0x2c,
+ (byte) 0x6a, (byte) 0x0d, (byte) 0x40, (byte) 0x00,
+ (byte) 0x40, (byte) 0x06, (byte) 0xe4, (byte) 0x88,
+ (byte) 0xc0, (byte) 0x00, (byte) 0x02, (byte) 0x01,
+ (byte) 0xc6, (byte) 0x33, (byte) 0x64, (byte) 0x01,
+ // TCP header
+ (byte) 0x26, (byte) 0x94, (byte) 0x01, (byte) 0xb1,
+ (byte) 0x00, (byte) 0x00, (byte) 0x35, (byte) 0x0b,
+ (byte) 0x00, (byte) 0x00, (byte) 0x60, (byte) 0x68,
+ (byte) 0x50, (byte) 0x10, (byte) 0x20, (byte) 0x00,
+ (byte) 0x48, (byte) 0x44, (byte) 0x00, (byte) 0x00,
+ // Data
+ (byte) 0xde, (byte) 0xad, (byte) 0xbe, (byte) 0xef
+ };
+
+ private static final byte[] TEST_PACKET_IPV4HDR_TCPHDR =
+ new byte[] {
+ // packet = (scapy.IP(src="192.0.2.1", dst="198.51.100.1",
+ // tos=0, id=27149, flags='DF') /
+ // scapy.TCP(sport=9876, dport=433, seq=13579, ack=24680,
+ // flags='A', window=8192, urgptr=0))
+ // IPv4 header
+ (byte) 0x45, (byte) 0x00, (byte) 0x00, (byte) 0x28,
+ (byte) 0x6a, (byte) 0x0d, (byte) 0x40, (byte) 0x00,
+ (byte) 0x40, (byte) 0x06, (byte) 0xe4, (byte) 0x8c,
+ (byte) 0xc0, (byte) 0x00, (byte) 0x02, (byte) 0x01,
+ (byte) 0xc6, (byte) 0x33, (byte) 0x64, (byte) 0x01,
+ // TCP header
+ (byte) 0x26, (byte) 0x94, (byte) 0x01, (byte) 0xb1,
+ (byte) 0x00, (byte) 0x00, (byte) 0x35, (byte) 0x0b,
+ (byte) 0x00, (byte) 0x00, (byte) 0x60, (byte) 0x68,
+ (byte) 0x50, (byte) 0x10, (byte) 0x20, (byte) 0x00,
+ (byte) 0xe5, (byte) 0xe5, (byte) 0x00, (byte) 0x00
+ };
+
+ private static final byte[] TEST_PACKET_IPV4HDR_TCPHDR_DATA =
+ new byte[] {
+ // packet = (scapy.IP(src="192.0.2.1", dst="198.51.100.1",
+ // tos=0, id=27149, flags='DF') /
+ // scapy.TCP(sport=9876, dport=433, seq=13579, ack=24680,
+ // flags='A', window=8192, urgptr=0) /
+ // b'\xde\xad\xbe\xef')
+ // IPv4 header
+ (byte) 0x45, (byte) 0x00, (byte) 0x00, (byte) 0x2c,
+ (byte) 0x6a, (byte) 0x0d, (byte) 0x40, (byte) 0x00,
+ (byte) 0x40, (byte) 0x06, (byte) 0xe4, (byte) 0x88,
+ (byte) 0xc0, (byte) 0x00, (byte) 0x02, (byte) 0x01,
+ (byte) 0xc6, (byte) 0x33, (byte) 0x64, (byte) 0x01,
+ // TCP header
+ (byte) 0x26, (byte) 0x94, (byte) 0x01, (byte) 0xb1,
+ (byte) 0x00, (byte) 0x00, (byte) 0x35, (byte) 0x0b,
+ (byte) 0x00, (byte) 0x00, (byte) 0x60, (byte) 0x68,
+ (byte) 0x50, (byte) 0x10, (byte) 0x20, (byte) 0x00,
+ (byte) 0x48, (byte) 0x44, (byte) 0x00, (byte) 0x00,
+ // Data
+ (byte) 0xde, (byte) 0xad, (byte) 0xbe, (byte) 0xef
+ };
+
+ private static final byte[] TEST_PACKET_ETHERHDR_IPV4HDR_UDPHDR =
+ new byte[] {
+ // packet = (scapy.Ether(src="11:22:33:44:55:66", dst="aa:bb:cc:dd:ee:ff",
+ // type='IPv4') /
+ // scapy.IP(src="192.0.2.1", dst="198.51.100.1",
+ // tos=0, id=27149, flags='DF') /
+ // scapy.UDP(sport=9876, dport=433))
+ // Ether header
+ (byte) 0xaa, (byte) 0xbb, (byte) 0xcc, (byte) 0xdd,
+ (byte) 0xee, (byte) 0xff, (byte) 0x11, (byte) 0x22,
+ (byte) 0x33, (byte) 0x44, (byte) 0x55, (byte) 0x66,
+ (byte) 0x08, (byte) 0x00,
+ // IP header
+ (byte) 0x45, (byte) 0x00, (byte) 0x00, (byte) 0x1c,
+ (byte) 0x6a, (byte) 0x0d, (byte) 0x40, (byte) 0x00,
+ (byte) 0x40, (byte) 0x11, (byte) 0xe4, (byte) 0x8d,
+ (byte) 0xc0, (byte) 0x00, (byte) 0x02, (byte) 0x01,
+ (byte) 0xc6, (byte) 0x33, (byte) 0x64, (byte) 0x01,
+ // UDP header
+ (byte) 0x26, (byte) 0x94, (byte) 0x01, (byte) 0xb1,
+ (byte) 0x00, (byte) 0x08, (byte) 0xeb, (byte) 0x62
+ };
+
+ private static final byte[] TEST_PACKET_ETHERHDR_IPV4HDR_UDPHDR_DATA =
+ new byte[] {
+ // packet = (scapy.Ether(src="11:22:33:44:55:66", dst="aa:bb:cc:dd:ee:ff",
+ // type='IPv4') /
+ // scapy.IP(src="192.0.2.1", dst="198.51.100.1",
+ // tos=0, id=27149, flags='DF') /
+ // scapy.UDP(sport=9876, dport=433) /
+ // b'\xde\xad\xbe\xef')
+ // Ether header
+ (byte) 0xaa, (byte) 0xbb, (byte) 0xcc, (byte) 0xdd,
+ (byte) 0xee, (byte) 0xff, (byte) 0x11, (byte) 0x22,
+ (byte) 0x33, (byte) 0x44, (byte) 0x55, (byte) 0x66,
+ (byte) 0x08, (byte) 0x00,
+ // IP header
+ (byte) 0x45, (byte) 0x00, (byte) 0x00, (byte) 0x20,
+ (byte) 0x6a, (byte) 0x0d, (byte) 0x40, (byte) 0x00,
+ (byte) 0x40, (byte) 0x11, (byte) 0xe4, (byte) 0x89,
+ (byte) 0xc0, (byte) 0x00, (byte) 0x02, (byte) 0x01,
+ (byte) 0xc6, (byte) 0x33, (byte) 0x64, (byte) 0x01,
+ // UDP header
+ (byte) 0x26, (byte) 0x94, (byte) 0x01, (byte) 0xb1,
+ (byte) 0x00, (byte) 0x0c, (byte) 0x4d, (byte) 0xbd,
+ // Data
+ (byte) 0xde, (byte) 0xad, (byte) 0xbe, (byte) 0xef
+ };
+
+ private static final byte[] TEST_PACKET_IPV4HDR_UDPHDR =
+ new byte[] {
+ // packet = (scapy.IP(src="192.0.2.1", dst="198.51.100.1",
+ // tos=0, id=27149, flags='DF') /
+ // scapy.UDP(sport=9876, dport=433))
+ // IP header
+ (byte) 0x45, (byte) 0x00, (byte) 0x00, (byte) 0x1c,
+ (byte) 0x6a, (byte) 0x0d, (byte) 0x40, (byte) 0x00,
+ (byte) 0x40, (byte) 0x11, (byte) 0xe4, (byte) 0x8d,
+ (byte) 0xc0, (byte) 0x00, (byte) 0x02, (byte) 0x01,
+ (byte) 0xc6, (byte) 0x33, (byte) 0x64, (byte) 0x01,
+ // UDP header
+ (byte) 0x26, (byte) 0x94, (byte) 0x01, (byte) 0xb1,
+ (byte) 0x00, (byte) 0x08, (byte) 0xeb, (byte) 0x62
+ };
+
+ private static final byte[] TEST_PACKET_IPV4HDR_UDPHDR_DATA =
+ new byte[] {
+ // packet = (scapy.IP(src="192.0.2.1", dst="198.51.100.1",
+ // tos=0, id=27149, flags='DF') /
+ // scapy.UDP(sport=9876, dport=433) /
+ // b'\xde\xad\xbe\xef')
+ // IP header
+ (byte) 0x45, (byte) 0x00, (byte) 0x00, (byte) 0x20,
+ (byte) 0x6a, (byte) 0x0d, (byte) 0x40, (byte) 0x00,
+ (byte) 0x40, (byte) 0x11, (byte) 0xe4, (byte) 0x89,
+ (byte) 0xc0, (byte) 0x00, (byte) 0x02, (byte) 0x01,
+ (byte) 0xc6, (byte) 0x33, (byte) 0x64, (byte) 0x01,
+ // UDP header
+ (byte) 0x26, (byte) 0x94, (byte) 0x01, (byte) 0xb1,
+ (byte) 0x00, (byte) 0x0c, (byte) 0x4d, (byte) 0xbd,
+ // Data
+ (byte) 0xde, (byte) 0xad, (byte) 0xbe, (byte) 0xef
+ };
+
+ /**
+ * Build an IPv4 packet which has ether header, IPv4 header, TCP/UDP header and data.
+ * The ethernet header and data are optional. Note that both source mac address and
+ * destination mac address are required for ethernet header.
+ *
+ * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+ * | Layer 2 header (EthernetHeader) | (optional)
+ * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+ * | Layer 3 header (Ipv4Header) |
+ * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+ * | Layer 4 header (TcpHeader, UdpHeader) |
+ * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+ * | Payload | (optional)
+ * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+ *
+ * @param srcMac source MAC address. used by L2 ether header.
+ * @param dstMac destination MAC address. used by L2 ether header.
+ * @param l4proto the layer 4 protocol. support either IPPROTO_TCP or IPPROTO_UDP.
+ * @param payload the payload.
+ */
+ @NonNull
+ private ByteBuffer buildIpv4Packet(@Nullable final MacAddress srcMac,
+ @Nullable final MacAddress dstMac, final int l4proto,
+ @Nullable final ByteBuffer payload)
+ throws Exception {
+ if (l4proto != IPPROTO_TCP && l4proto != IPPROTO_UDP) {
+ fail("Unsupported layer 4 protocol " + l4proto);
+ }
+
+ final boolean hasEther = (srcMac != null && dstMac != null);
+ final int payloadLen = (payload == null) ? 0 : payload.limit();
+ final ByteBuffer buffer = PacketBuilder.allocate(hasEther, IPPROTO_IP, l4proto,
+ payloadLen);
+ final PacketBuilder packetBuilder = new PacketBuilder(buffer);
+
+ if (hasEther) packetBuilder.writeL2Header(srcMac, dstMac, (short) ETHER_TYPE_IPV4);
+ packetBuilder.writeIpv4Header(TYPE_OF_SERVICE, ID, FLAGS_AND_FRAGMENT_OFFSET,
+ TIME_TO_LIVE, (byte) l4proto, IPV4_SRC_ADDR, IPV4_DST_ADDR);
+ if (l4proto == IPPROTO_TCP) {
+ packetBuilder.writeTcpHeader(SRC_PORT, DST_PORT, SEQ_NO, ACK_NO,
+ TCPHDR_ACK, WINDOW, URGENT_POINTER);
+ } else if (l4proto == IPPROTO_UDP) {
+ packetBuilder.writeUdpHeader(SRC_PORT, DST_PORT);
+ }
+ if (payload != null) {
+ buffer.put(payload);
+ // in case data might be reused by caller, restore the position and
+ // limit of bytebuffer.
+ payload.clear();
+ }
+
+ return packetBuilder.finalizePacket();
+ }
+
+ /**
+ * Check ethernet header.
+ *
+ * @param actual the packet to check.
+ */
+ private void checkEtherHeader(final ByteBuffer actual) {
+ final EthernetHeader eth = Struct.parse(EthernetHeader.class, actual);
+ assertEquals(SRC_MAC, eth.srcMac);
+ assertEquals(DST_MAC, eth.dstMac);
+ assertEquals(ETHER_TYPE_IPV4, eth.etherType);
+ }
+
+ /**
+ * Check IPv4 header.
+ *
+ * @param l4proto the layer 4 protocol. support either IPPROTO_TCP or IPPROTO_UDP.
+ * @param hasData true if the packet has data payload; false otherwise.
+ * @param actual the packet to check.
+ */
+ private void checkIpv4Header(final int l4proto, final boolean hasData,
+ final ByteBuffer actual) {
+ if (l4proto != IPPROTO_TCP && l4proto != IPPROTO_UDP) {
+ fail("Unsupported layer 4 protocol " + l4proto);
+ }
+
+ final Ipv4Header ipv4Header = Struct.parse(Ipv4Header.class, actual);
+ assertEquals(Ipv4Header.IPHDR_VERSION_IHL, ipv4Header.vi);
+ assertEquals(TYPE_OF_SERVICE, ipv4Header.tos);
+ assertEquals(ID, ipv4Header.id);
+ assertEquals(FLAGS_AND_FRAGMENT_OFFSET, ipv4Header.flagsAndFragmentOffset);
+ assertEquals(TIME_TO_LIVE, ipv4Header.ttl);
+ assertEquals(IPV4_SRC_ADDR, ipv4Header.srcIp);
+ assertEquals(IPV4_DST_ADDR, ipv4Header.dstIp);
+
+ final int dataLength = hasData ? DATA.limit() : 0;
+ if (l4proto == IPPROTO_TCP) {
+ assertEquals(IPV4_HEADER_MIN_LEN + TCP_HEADER_MIN_LEN + dataLength,
+ ipv4Header.totalLength);
+ assertEquals((byte) IPPROTO_TCP, ipv4Header.protocol);
+ assertEquals(hasData ? (short) 0xe488 : (short) 0xe48c, ipv4Header.checksum);
+ } else if (l4proto == IPPROTO_UDP) {
+ assertEquals(IPV4_HEADER_MIN_LEN + UDP_HEADER_LEN + dataLength,
+ ipv4Header.totalLength);
+ assertEquals((byte) IPPROTO_UDP, ipv4Header.protocol);
+ assertEquals(hasData ? (short) 0xe489 : (short) 0xe48d, ipv4Header.checksum);
+ }
+ }
+
+ /**
+ * Check TCPv4 packet.
+ *
+ * @param hasEther true if the packet has ether header; false otherwise.
+ * @param hasData true if the packet has data payload; false otherwise.
+ * @param actual the packet to check.
+ */
+ private void checkTcpv4Packet(final boolean hasEther, final boolean hasData,
+ final ByteBuffer actual) {
+ if (hasEther) {
+ checkEtherHeader(actual);
+ }
+ checkIpv4Header(IPPROTO_TCP, hasData, actual);
+
+ final TcpHeader tcpHeader = Struct.parse(TcpHeader.class, actual);
+ assertEquals(SRC_PORT, tcpHeader.srcPort);
+ assertEquals(DST_PORT, tcpHeader.dstPort);
+ assertEquals(SEQ_NO, tcpHeader.seq);
+ assertEquals(ACK_NO, tcpHeader.ack);
+ assertEquals((short) 0x5010 /* offset=5(*4bytes), control bits=ACK */,
+ tcpHeader.dataOffsetAndControlBits);
+ assertEquals(WINDOW, tcpHeader.window);
+ assertEquals(hasData ? (short) 0x4844 : (short) 0xe5e5, tcpHeader.checksum);
+ assertEquals(URGENT_POINTER, tcpHeader.urgentPointer);
+
+ if (hasData) {
+ assertEquals(0xdeadbeef, actual.getInt());
+ }
+ }
+
+ /**
+ * Check UDPv4 packet.
+ *
+ * @param hasEther true if the packet has ether header; false otherwise.
+ * @param hasData true if the packet has data payload; false otherwise.
+ * @param actual the packet to check.
+ */
+ private void checkUdpv4Packet(final boolean hasEther, final boolean hasData,
+ final ByteBuffer actual) {
+ if (hasEther) {
+ checkEtherHeader(actual);
+ }
+ checkIpv4Header(IPPROTO_UDP, hasData, actual);
+
+ final UdpHeader udpHeader = Struct.parse(UdpHeader.class, actual);
+ assertEquals(SRC_PORT, udpHeader.srcPort);
+ assertEquals(DST_PORT, udpHeader.dstPort);
+ final int dataLength = hasData ? DATA.limit() : 0;
+ assertEquals(UDP_HEADER_LEN + dataLength, udpHeader.length);
+ assertEquals(hasData ? (short) 0x4dbd : (short) 0xeb62, udpHeader.checksum);
+
+ if (hasData) {
+ assertEquals(0xdeadbeef, actual.getInt());
+ }
+ }
+
+ @Test
+ public void testBuildPacketEtherIPv4Tcp() throws Exception {
+ final ByteBuffer packet = buildIpv4Packet(SRC_MAC, DST_MAC, IPPROTO_TCP, null /* data */);
+ checkTcpv4Packet(true /* hasEther */, false /* hasData */, packet);
+ assertArrayEquals(TEST_PACKET_ETHERHDR_IPV4HDR_TCPHDR, packet.array());
+ }
+
+ @Test
+ public void testBuildPacketEtherIPv4TcpData() throws Exception {
+ final ByteBuffer packet = buildIpv4Packet(SRC_MAC, DST_MAC, IPPROTO_TCP, DATA);
+ checkTcpv4Packet(true /* hasEther */, true /* hasData */, packet);
+ assertArrayEquals(TEST_PACKET_ETHERHDR_IPV4HDR_TCPHDR_DATA,
+ packet.array());
+ }
+
+ @Test
+ public void testBuildPacketIPv4Tcp() throws Exception {
+ final ByteBuffer packet = buildIpv4Packet(null /* srcMac */, null /* dstMac */,
+ IPPROTO_TCP, null /* data */);
+ checkTcpv4Packet(false /* hasEther */, false /* hasData */, packet);
+ assertArrayEquals(TEST_PACKET_IPV4HDR_TCPHDR, packet.array());
+ }
+
+ @Test
+ public void testBuildPacketIPv4TcpData() throws Exception {
+ final ByteBuffer packet = buildIpv4Packet(null /* srcMac */, null /* dstMac */,
+ IPPROTO_TCP, DATA);
+ checkTcpv4Packet(false /* hasEther */, true /* hasData */, packet);
+ assertArrayEquals(TEST_PACKET_IPV4HDR_TCPHDR_DATA, packet.array());
+ }
+
+ @Test
+ public void testBuildPacketEtherIPv4Udp() throws Exception {
+ final ByteBuffer packet = buildIpv4Packet(SRC_MAC, DST_MAC, IPPROTO_UDP, null /* data */);
+ checkUdpv4Packet(true /* hasEther */, false /* hasData */, packet);
+ assertArrayEquals(TEST_PACKET_ETHERHDR_IPV4HDR_UDPHDR, packet.array());
+ }
+
+ @Test
+ public void testBuildPacketEtherIPv4UdpData() throws Exception {
+ final ByteBuffer packet = buildIpv4Packet(SRC_MAC, DST_MAC, IPPROTO_UDP, DATA);
+ checkUdpv4Packet(true /* hasEther */, true /* hasData */, packet);
+ assertArrayEquals(TEST_PACKET_ETHERHDR_IPV4HDR_UDPHDR_DATA, packet.array());
+ }
+
+ @Test
+ public void testBuildPacketIPv4Udp() throws Exception {
+ final ByteBuffer packet = buildIpv4Packet(null /* srcMac */, null /* dstMac */,
+ IPPROTO_UDP, null /*data*/);
+ checkUdpv4Packet(false /* hasEther */, false /* hasData */, packet);
+ assertArrayEquals(TEST_PACKET_IPV4HDR_UDPHDR, packet.array());
+ }
+
+ @Test
+ public void testBuildPacketIPv4UdpData() throws Exception {
+ final ByteBuffer packet = buildIpv4Packet(null /* srcMac */, null /* dstMac */,
+ IPPROTO_UDP, DATA);
+ checkUdpv4Packet(false /* hasEther */, true /* hasData */, packet);
+ assertArrayEquals(TEST_PACKET_IPV4HDR_UDPHDR_DATA, packet.array());
+ }
+
+ @Test
+ public void testFinalizePacketWithoutIpv4Header() throws Exception {
+ final ByteBuffer buffer = PacketBuilder.allocate(false /* hasEther */, IPPROTO_IP,
+ IPPROTO_TCP, 0 /* payloadLen */);
+ final PacketBuilder packetBuilder = new PacketBuilder(buffer);
+ packetBuilder.writeTcpHeader(SRC_PORT, DST_PORT, SEQ_NO, ACK_NO,
+ TCPHDR_ACK, WINDOW, URGENT_POINTER);
+ assertThrows("java.io.IOException: Packet is missing IPv4 header", IOException.class,
+ () -> packetBuilder.finalizePacket());
+ }
+
+ @Test
+ public void testFinalizePacketWithoutL4Header() throws Exception {
+ final ByteBuffer buffer = PacketBuilder.allocate(false /* hasEther */, IPPROTO_IP,
+ IPPROTO_TCP, 0 /* payloadLen */);
+ final PacketBuilder packetBuilder = new PacketBuilder(buffer);
+ packetBuilder.writeIpv4Header(TYPE_OF_SERVICE, ID, FLAGS_AND_FRAGMENT_OFFSET,
+ TIME_TO_LIVE, (byte) IPPROTO_TCP, IPV4_SRC_ADDR, IPV4_DST_ADDR);
+ assertThrows("java.io.IOException: Packet is missing neither TCP nor UDP header",
+ IOException.class, () -> packetBuilder.finalizePacket());
+ }
+
+ @Test
+ public void testWriteL2HeaderToInsufficientBuffer() throws Exception {
+ final PacketBuilder packetBuilder = new PacketBuilder(ByteBuffer.allocate(1));
+ assertThrows(IOException.class,
+ () -> packetBuilder.writeL2Header(SRC_MAC, DST_MAC, (short) ETHER_TYPE_IPV4));
+ }
+
+ @Test
+ public void testWriteIpv4HeaderToInsufficientBuffer() throws Exception {
+ final PacketBuilder packetBuilder = new PacketBuilder(ByteBuffer.allocate(1));
+ assertThrows(IOException.class,
+ () -> packetBuilder.writeIpv4Header(TYPE_OF_SERVICE, ID, FLAGS_AND_FRAGMENT_OFFSET,
+ TIME_TO_LIVE, (byte) IPPROTO_TCP, IPV4_SRC_ADDR, IPV4_DST_ADDR));
+ }
+
+ @Test
+ public void testWriteTcpHeaderToInsufficientBuffer() throws Exception {
+ final PacketBuilder packetBuilder = new PacketBuilder(ByteBuffer.allocate(1));
+ assertThrows(IOException.class,
+ () -> packetBuilder.writeTcpHeader(SRC_PORT, DST_PORT, SEQ_NO, ACK_NO,
+ TCPHDR_ACK, WINDOW, URGENT_POINTER));
+ }
+
+ @Test
+ public void testWriteUdpHeaderToInsufficientBuffer() throws Exception {
+ final PacketBuilder packetBuilder = new PacketBuilder(ByteBuffer.allocate(1));
+ assertThrows(IOException.class, () -> packetBuilder.writeUdpHeader(SRC_PORT, DST_PORT));
+ }
+
+ private static Inet4Address addr(String addr) {
+ return (Inet4Address) InetAddresses.parseNumericAddress(addr);
+ }
+}
diff --git a/staticlibs/tests/unit/src/com/android/net/module/util/netlink/StructNlAttrTest.java b/staticlibs/tests/unit/src/com/android/net/module/util/netlink/StructNlAttrTest.java
index 72e179b..af3fac2 100644
--- a/staticlibs/tests/unit/src/com/android/net/module/util/netlink/StructNlAttrTest.java
+++ b/staticlibs/tests/unit/src/com/android/net/module/util/netlink/StructNlAttrTest.java
@@ -16,6 +16,7 @@
package com.android.net.module.util.netlink;
+import static com.android.net.module.util.netlink.RtNetlinkAddressMessage.IFA_FLAGS;
import static com.android.net.module.util.netlink.RtNetlinkLinkMessage.IFLA_ADDRESS;
import static com.android.net.module.util.netlink.RtNetlinkLinkMessage.IFLA_IFNAME;
@@ -35,6 +36,7 @@
public class StructNlAttrTest {
private static final MacAddress TEST_MAC_ADDRESS = MacAddress.fromString("00:11:22:33:44:55");
private static final String TEST_INTERFACE_NAME = "wlan0";
+ private static final int TEST_ADDR_FLAGS = 0x80;
@Test
public void testGetValueAsMacAddress() {
@@ -65,4 +67,29 @@
final String str2 = attr2.getValueAsString();
assertEquals(str2, TEST_INTERFACE_NAME);
}
+
+ @Test
+ public void testGetValueAsIntger() {
+ final StructNlAttr attr1 = new StructNlAttr(IFA_FLAGS, TEST_ADDR_FLAGS);
+ final Integer integer1 = attr1.getValueAsInteger();
+ final int int1 = attr1.getValueAsInt(0x08 /* default value */);
+ assertEquals(integer1, new Integer(TEST_ADDR_FLAGS));
+ assertEquals(int1, TEST_ADDR_FLAGS);
+
+ // Malformed attribute.
+ final byte[] malformed_int = new byte[] { (byte) 0x0, (byte) 0x0, (byte) 0x80, };
+ final StructNlAttr attr2 = new StructNlAttr(IFA_FLAGS, malformed_int);
+ final Integer integer2 = attr2.getValueAsInteger();
+ final int int2 = attr2.getValueAsInt(0x08 /* default value */);
+ assertNull(integer2);
+ assertEquals(int2, 0x08 /* default value */);
+
+ // Null attribute value.
+ final byte[] null_int = null;
+ final StructNlAttr attr3 = new StructNlAttr(IFA_FLAGS, null_int);
+ final Integer integer3 = attr3.getValueAsInteger();
+ final int int3 = attr3.getValueAsInt(0x08 /* default value */);
+ assertNull(integer3);
+ assertEquals(int3, 0x08 /* default value */);
+ }
}
diff --git a/staticlibs/testutils/devicetests/com/android/testutils/TestableNetworkAgent.kt b/staticlibs/testutils/devicetests/com/android/testutils/TestableNetworkAgent.kt
new file mode 100644
index 0000000..40fb773
--- /dev/null
+++ b/staticlibs/testutils/devicetests/com/android/testutils/TestableNetworkAgent.kt
@@ -0,0 +1,200 @@
+/*
+ * Copyright (C) 2021 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 android.content.Context
+import android.net.KeepalivePacketData
+import android.net.LinkProperties
+import android.net.NetworkAgent
+import android.net.NetworkAgentConfig
+import android.net.NetworkCapabilities
+import android.net.NetworkProvider
+import android.net.QosFilter
+import android.net.Uri
+import android.os.Looper
+import com.android.net.module.util.ArrayTrackRecord
+import com.android.testutils.TestableNetworkAgent.CallbackEntry.OnAddKeepalivePacketFilter
+import com.android.testutils.TestableNetworkAgent.CallbackEntry.OnAutomaticReconnectDisabled
+import com.android.testutils.TestableNetworkAgent.CallbackEntry.OnBandwidthUpdateRequested
+import com.android.testutils.TestableNetworkAgent.CallbackEntry.OnNetworkCreated
+import com.android.testutils.TestableNetworkAgent.CallbackEntry.OnNetworkDestroyed
+import com.android.testutils.TestableNetworkAgent.CallbackEntry.OnNetworkUnwanted
+import com.android.testutils.TestableNetworkAgent.CallbackEntry.OnRegisterQosCallback
+import com.android.testutils.TestableNetworkAgent.CallbackEntry.OnRemoveKeepalivePacketFilter
+import com.android.testutils.TestableNetworkAgent.CallbackEntry.OnSaveAcceptUnvalidated
+import com.android.testutils.TestableNetworkAgent.CallbackEntry.OnSignalStrengthThresholdsUpdated
+import com.android.testutils.TestableNetworkAgent.CallbackEntry.OnStartSocketKeepalive
+import com.android.testutils.TestableNetworkAgent.CallbackEntry.OnStopSocketKeepalive
+import com.android.testutils.TestableNetworkAgent.CallbackEntry.OnUnregisterQosCallback
+import com.android.testutils.TestableNetworkAgent.CallbackEntry.OnValidationStatus
+import java.time.Duration
+import kotlin.test.assertEquals
+import kotlin.test.assertNotNull
+import kotlin.test.assertNull
+import kotlin.test.assertTrue
+import org.junit.Assert.assertArrayEquals
+
+// Any legal score (0~99) for the test network would do, as it is going to be kept up by the
+// requests filed by the test and should never match normal internet requests. 70 is the default
+// score of Ethernet networks, it's as good a value as any other.
+private const val TEST_NETWORK_SCORE = 70
+
+private class Provider(context: Context, looper: Looper) :
+ NetworkProvider(context, looper, "NetworkAgentTest NetworkProvider")
+
+public open class TestableNetworkAgent(
+ context: Context,
+ looper: Looper,
+ val nc: NetworkCapabilities,
+ val lp: LinkProperties,
+ conf: NetworkAgentConfig
+) : NetworkAgent(context, looper, TestableNetworkAgent::class.java.simpleName /* tag */,
+ nc, lp, TEST_NETWORK_SCORE, conf, Provider(context, looper)) {
+
+ val DEFAULT_TIMEOUT_MS = 5000L
+
+ val history = ArrayTrackRecord<CallbackEntry>().newReadHead()
+
+ sealed class CallbackEntry {
+ object OnBandwidthUpdateRequested : CallbackEntry()
+ object OnNetworkUnwanted : CallbackEntry()
+ data class OnAddKeepalivePacketFilter(
+ val slot: Int,
+ val packet: KeepalivePacketData
+ ) : CallbackEntry()
+ data class OnRemoveKeepalivePacketFilter(val slot: Int) : CallbackEntry()
+ data class OnStartSocketKeepalive(
+ val slot: Int,
+ val interval: Int,
+ val packet: KeepalivePacketData
+ ) : CallbackEntry()
+ data class OnStopSocketKeepalive(val slot: Int) : CallbackEntry()
+ data class OnSaveAcceptUnvalidated(val accept: Boolean) : CallbackEntry()
+ object OnAutomaticReconnectDisabled : CallbackEntry()
+ data class OnValidationStatus(val status: Int, val uri: Uri?) : CallbackEntry()
+ data class OnSignalStrengthThresholdsUpdated(val thresholds: IntArray) : CallbackEntry()
+ object OnNetworkCreated : CallbackEntry()
+ object OnNetworkDestroyed : CallbackEntry()
+ data class OnRegisterQosCallback(
+ val callbackId: Int,
+ val filter: QosFilter
+ ) : CallbackEntry()
+ data class OnUnregisterQosCallback(val callbackId: Int) : CallbackEntry()
+ }
+
+ override fun onBandwidthUpdateRequested() {
+ history.add(OnBandwidthUpdateRequested)
+ }
+
+ override fun onNetworkUnwanted() {
+ history.add(OnNetworkUnwanted)
+ }
+
+ override fun onAddKeepalivePacketFilter(slot: Int, packet: KeepalivePacketData) {
+ history.add(OnAddKeepalivePacketFilter(slot, packet))
+ }
+
+ override fun onRemoveKeepalivePacketFilter(slot: Int) {
+ history.add(OnRemoveKeepalivePacketFilter(slot))
+ }
+
+ override fun onStartSocketKeepalive(
+ slot: Int,
+ interval: Duration,
+ packet: KeepalivePacketData
+ ) {
+ history.add(OnStartSocketKeepalive(slot, interval.seconds.toInt(), packet))
+ }
+
+ override fun onStopSocketKeepalive(slot: Int) {
+ history.add(OnStopSocketKeepalive(slot))
+ }
+
+ override fun onSaveAcceptUnvalidated(accept: Boolean) {
+ history.add(OnSaveAcceptUnvalidated(accept))
+ }
+
+ override fun onAutomaticReconnectDisabled() {
+ history.add(OnAutomaticReconnectDisabled)
+ }
+
+ override fun onSignalStrengthThresholdsUpdated(thresholds: IntArray) {
+ history.add(OnSignalStrengthThresholdsUpdated(thresholds))
+ }
+
+ fun expectSignalStrengths(thresholds: IntArray? = intArrayOf()) {
+ expectCallback<OnSignalStrengthThresholdsUpdated>().let {
+ assertArrayEquals(thresholds, it.thresholds)
+ }
+ }
+
+ override fun onQosCallbackRegistered(qosCallbackId: Int, filter: QosFilter) {
+ history.add(OnRegisterQosCallback(qosCallbackId, filter))
+ }
+
+ override fun onQosCallbackUnregistered(qosCallbackId: Int) {
+ history.add(OnUnregisterQosCallback(qosCallbackId))
+ }
+
+ override fun onValidationStatus(status: Int, uri: Uri?) {
+ history.add(OnValidationStatus(status, uri))
+ }
+
+ override fun onNetworkCreated() {
+ history.add(OnNetworkCreated)
+ }
+
+ override fun onNetworkDestroyed() {
+ history.add(OnNetworkDestroyed)
+ }
+
+ // Expects the initial validation event that always occurs immediately after registering
+ // a NetworkAgent whose network does not require validation (which test networks do
+ // not, since they lack the INTERNET capability). It always contains the default argument
+ // for the URI.
+ fun expectValidationBypassedStatus() = expectCallback<OnValidationStatus>().let {
+ assertEquals(it.status, VALID_NETWORK)
+ // The returned Uri is parsed from the empty string, which means it's an
+ // instance of the (private) Uri.StringUri. There are no real good ways
+ // to check this, the least bad is to just convert it to a string and
+ // make sure it's empty.
+ assertEquals("", it.uri.toString())
+ }
+
+ inline fun <reified T : CallbackEntry> expectCallback(): T {
+ val foundCallback = history.poll(DEFAULT_TIMEOUT_MS)
+ assertTrue(foundCallback is T, "Expected ${T::class} but found $foundCallback")
+ return foundCallback
+ }
+
+ inline fun <reified T : CallbackEntry> expectCallback(valid: (T) -> Boolean) {
+ val foundCallback = history.poll(DEFAULT_TIMEOUT_MS)
+ assertTrue(foundCallback is T, "Expected ${T::class} but found $foundCallback")
+ assertTrue(valid(foundCallback), "Unexpected callback : $foundCallback")
+ }
+
+ inline fun <reified T : CallbackEntry> eventuallyExpect() =
+ history.poll(DEFAULT_TIMEOUT_MS) { it is T }.also {
+ assertNotNull(it, "Callback ${T::class} not received")
+ } as T
+
+ fun assertNoCallback() {
+ assertTrue(waitForIdle(DEFAULT_TIMEOUT_MS),
+ "Handler didn't became idle after ${DEFAULT_TIMEOUT_MS}ms")
+ assertNull(history.peek())
+ }
+}
\ No newline at end of file