Add a class to build IPv4 TCP packet

Note that IPv4 and TCP options are not supported.

Test: atest NetworkStaticLibTests

Change-Id: Ie62b95bf759b74d72a5e85d312d17978422f633b
This commit is contained in:
Hungming Chen 2021-09-14 20:26:27 +08:00
commit 435833f2cd
4 changed files with 576 additions and 0 deletions

View file

@ -139,6 +139,7 @@ java_library {
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",
],

View file

@ -0,0 +1,222 @@
/*
* 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 com.android.net.module.util.IpUtils.ipChecksum;
import static com.android.net.module.util.IpUtils.tcpChecksum;
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 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 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) |
* +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
* | 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;
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);
}
}
/**
* 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 */));
// Populate the TCP header checksum field.
if (mTcpHeaderOffset > 0) {
mBuffer.putShort(mTcpHeaderOffset + TCP_CHECKSUM_OFFSET, tcpChecksum(mBuffer,
mIpv4HeaderOffset /* ipOffset */, mTcpHeaderOffset /* transportOffset */,
mBuffer.position() - mTcpHeaderOffset /* transportLen */));
} else { // TODO: add support for UDP
throw new IOException("Packet is missing TCP 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} 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) {
// TODO: add support for 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 += Struct.getSize(TcpHeader.class);
packetLen += payloadLen;
return ByteBuffer.allocate(packetLen);
}
}

View file

@ -89,9 +89,11 @@ public final class NetworkStackConstants {
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;
@ -163,6 +165,21 @@ public final class NetworkStackConstants {
public static final byte PIO_FLAG_ON_LINK = (byte) (1 << 7);
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.
*

View file

@ -0,0 +1,336 @@
/*
* 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 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.testutils.MiscAsserts.assertThrows;
import static org.junit.Assert.assertArrayEquals;
import static org.junit.Assert.assertEquals;
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 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
};
/**
* Build a TCPv4 packet which has ether header, IPv4 header, TCP 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) |
* +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
* | Payload | (optional)
* +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
*
* @param srcMac source MAC address. used by L2 ether header.
* @param dstMac destination MAC address. used by L2 ether header.
* @param payload the payload.
*/
@NonNull
private ByteBuffer buildTcpv4Packet(@Nullable final MacAddress srcMac,
@Nullable final MacAddress dstMac, @Nullable final ByteBuffer payload)
throws Exception {
final boolean hasEther = (srcMac != null && dstMac != null);
final int payloadLen = (payload == null) ? 0 : payload.limit();
final ByteBuffer buffer = PacketBuilder.allocate(hasEther, IPPROTO_IP, IPPROTO_TCP,
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) IPPROTO_TCP, IPV4_SRC_ADDR, IPV4_DST_ADDR);
packetBuilder.writeTcpHeader(SRC_PORT, DST_PORT, SEQ_NO, ACK_NO,
TCPHDR_ACK, WINDOW, URGENT_POINTER);
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();
}
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);
}
private void checkIpv4Header(final boolean hasData, final ByteBuffer actual) {
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;
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);
}
private void checkTcpv4Packet(final boolean hasEther, final boolean hasData,
final ByteBuffer actual) {
if (hasEther) {
checkEtherHeader(actual);
}
checkIpv4Header(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);
}
@Test
public void testBuildPacketEtherIPv4Tcp() throws Exception {
final ByteBuffer packet = buildTcpv4Packet(SRC_MAC, DST_MAC, 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 = buildTcpv4Packet(SRC_MAC, DST_MAC, 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 = buildTcpv4Packet(null /* srcMac */, null /* dstMac */,
null /* data */);
checkTcpv4Packet(false /* hasEther */, false /* hasData */, packet);
assertArrayEquals(TEST_PACKET_IPV4HDR_TCPHDR, packet.array());
}
@Test
public void testBuildPacketIPv4TcpData() throws Exception {
final ByteBuffer packet = buildTcpv4Packet(null /* srcMac */, null /* dstMac */, DATA);
checkTcpv4Packet(false /* hasEther */, true /* hasData */, packet);
assertArrayEquals(TEST_PACKET_IPV4HDR_TCPHDR_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 testFinalizePacketWithoutTcpHeader() 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 TCP headers", 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));
}
private static Inet4Address addr(String addr) {
return (Inet4Address) InetAddresses.parseNumericAddress(addr);
}
}