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:
parent
d2c8b5dbf0
commit
435833f2cd
4 changed files with 576 additions and 0 deletions
|
|
@ -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",
|
||||
],
|
||||
|
|
|
|||
222
staticlibs/device/com/android/net/module/util/PacketBuilder.java
Normal file
222
staticlibs/device/com/android/net/module/util/PacketBuilder.java
Normal 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);
|
||||
}
|
||||
}
|
||||
|
|
@ -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.
|
||||
*
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
Loading…
Reference in a new issue