PacketBuilder: add IPv6 support
Support IPv6 packet builder and verify with UDP packet unit test.
Bug: 215655463
Test: atest NetworkStaticLibTests
Change-Id: I5edf0ffbc9b37ecf0e0a6da0dc7d04716fc4c38c
diff --git a/staticlibs/device/com/android/net/module/util/PacketBuilder.java b/staticlibs/device/com/android/net/module/util/PacketBuilder.java
index c908528..8acb296 100644
--- a/staticlibs/device/com/android/net/module/util/PacketBuilder.java
+++ b/staticlibs/device/com/android/net/module/util/PacketBuilder.java
@@ -17,6 +17,7 @@
package com.android.net.module.util;
import static android.system.OsConstants.IPPROTO_IP;
+import static android.system.OsConstants.IPPROTO_IPV6;
import static android.system.OsConstants.IPPROTO_TCP;
import static android.system.OsConstants.IPPROTO_UDP;
@@ -25,6 +26,7 @@
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.IPV6_LEN_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;
@@ -35,11 +37,13 @@
import com.android.net.module.util.structs.EthernetHeader;
import com.android.net.module.util.structs.Ipv4Header;
+import com.android.net.module.util.structs.Ipv6Header;
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.net.Inet6Address;
import java.nio.BufferOverflowException;
import java.nio.ByteBuffer;
@@ -49,7 +53,7 @@
* +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
* | Layer 2 header (EthernetHeader) | (optional)
* +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
- * | Layer 3 header (Ipv4Header) |
+ * | Layer 3 header (Ipv4Header, Ipv6Header) |
* +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
* | Layer 4 header (TcpHeader, UdpHeader) |
* +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
@@ -74,11 +78,14 @@
* sendPacket(buf);
*/
public class PacketBuilder {
+ private static final int INVALID_OFFSET = -1;
+
private final ByteBuffer mBuffer;
- private int mIpv4HeaderOffset = -1;
- private int mTcpHeaderOffset = -1;
- private int mUdpHeaderOffset = -1;
+ private int mIpv4HeaderOffset = INVALID_OFFSET;
+ private int mIpv6HeaderOffset = INVALID_OFFSET;
+ private int mTcpHeaderOffset = INVALID_OFFSET;
+ private int mUdpHeaderOffset = INVALID_OFFSET;
public PacketBuilder(@NonNull ByteBuffer buffer) {
mBuffer = buffer;
@@ -130,6 +137,31 @@
}
/**
+ * Write an IPv6 header.
+ * The IP header length is calculated and written back in #finalizePacket.
+ *
+ * @param vtf version, traffic class and flow label
+ * @param nextHeader the transport layer protocol
+ * @param hopLimit hop limit
+ * @param srcIp source IP address
+ * @param dstIp destination IP address
+ */
+ public void writeIpv6Header(int vtf, byte nextHeader, short hopLimit,
+ @NonNull final Inet6Address srcIp, @NonNull final Inet6Address dstIp)
+ throws IOException {
+ mIpv6HeaderOffset = mBuffer.position();
+ final Ipv6Header ipv6Header = new Ipv6Header(vtf,
+ (short) 0 /* payloadLength, calculate in #finalizePacket */, nextHeader,
+ hopLimit, srcIp, dstIp);
+
+ try {
+ ipv6Header.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.
*
@@ -186,32 +218,42 @@
*/
@NonNull
public ByteBuffer finalizePacket() throws IOException {
- if (mIpv4HeaderOffset < 0) {
- // TODO: add support for IPv6
- throw new IOException("Packet is missing IPv4 header");
+ // Finalize IPv4 or IPv6 header.
+ int ipHeaderOffset = INVALID_OFFSET;
+ if (mIpv4HeaderOffset != INVALID_OFFSET) {
+ ipHeaderOffset = mIpv4HeaderOffset;
+
+ // 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 */));
+ } else if (mIpv6HeaderOffset != INVALID_OFFSET) {
+ ipHeaderOffset = mIpv6HeaderOffset;
+
+ // Populate the IPv6 payloadLength field.
+ mBuffer.putShort(mIpv6HeaderOffset + IPV6_LEN_OFFSET,
+ (short) (mBuffer.position() - mIpv6HeaderOffset));
+ } else {
+ throw new IOException("Packet is missing neither IPv4 nor IPv6 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) {
+ // Finalize TCP or UDP header.
+ if (mTcpHeaderOffset != INVALID_OFFSET) {
// Populate the TCP header checksum field.
mBuffer.putShort(mTcpHeaderOffset + TCP_CHECKSUM_OFFSET, tcpChecksum(mBuffer,
- mIpv4HeaderOffset /* ipOffset */, mTcpHeaderOffset /* transportOffset */,
+ ipHeaderOffset /* ipOffset */, mTcpHeaderOffset /* transportOffset */,
mBuffer.position() - mTcpHeaderOffset /* transportLen */));
- } else if (mUdpHeaderOffset > 0) {
+ } else if (mUdpHeaderOffset != INVALID_OFFSET) {
// 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 */));
+ ipHeaderOffset /* ipOffset */, mUdpHeaderOffset /* transportOffset */));
} else {
throw new IOException("Packet is missing neither TCP nor UDP header");
}
@@ -225,15 +267,15 @@
*
* @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 l3proto the layer 3 protocol. Only {@code IPPROTO_IP} and {@code IPPROTO_IPV6}
+ * 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
+ if (l3proto != IPPROTO_IP && l3proto != IPPROTO_IPV6) {
throw new IllegalArgumentException("Unsupported layer 3 protocol " + l3proto);
}
@@ -247,7 +289,8 @@
int packetLen = 0;
if (hasEther) packetLen += Struct.getSize(EthernetHeader.class);
- packetLen += Struct.getSize(Ipv4Header.class);
+ packetLen += (l3proto == IPPROTO_IP) ? Struct.getSize(Ipv4Header.class)
+ : Struct.getSize(Ipv6Header.class);
packetLen += (l4proto == IPPROTO_TCP) ? Struct.getSize(TcpHeader.class)
: Struct.getSize(UdpHeader.class);
packetLen += payloadLen;
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
index 8f9a1f9..c02763a 100644
--- a/staticlibs/tests/unit/src/com/android/net/module/util/PacketBuilderTest.java
+++ b/staticlibs/tests/unit/src/com/android/net/module/util/PacketBuilderTest.java
@@ -17,11 +17,14 @@
package com.android.net.module.util;
import static android.system.OsConstants.IPPROTO_IP;
+import static android.system.OsConstants.IPPROTO_IPV6;
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.ETHER_TYPE_IPV6;
import static com.android.net.module.util.NetworkStackConstants.IPV4_HEADER_MIN_LEN;
+import static com.android.net.module.util.NetworkStackConstants.IPV6_HEADER_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;
@@ -41,6 +44,7 @@
import com.android.net.module.util.structs.EthernetHeader;
import com.android.net.module.util.structs.Ipv4Header;
+import com.android.net.module.util.structs.Ipv6Header;
import com.android.net.module.util.structs.TcpHeader;
import com.android.net.module.util.structs.UdpHeader;
@@ -49,6 +53,7 @@
import java.io.IOException;
import java.net.Inet4Address;
+import java.net.Inet6Address;
import java.nio.ByteBuffer;
@RunWith(AndroidJUnit4.class)
@@ -56,8 +61,10 @@
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 Inet4Address IPV4_SRC_ADDR = addr4("192.0.2.1");
+ private static final Inet4Address IPV4_DST_ADDR = addr4("198.51.100.1");
+ private static final Inet6Address IPV6_SRC_ADDR = addr6("2001:db8::1");
+ private static final Inet6Address IPV6_DST_ADDR = addr6("2001:db8::2");
private static final short SRC_PORT = 9876;
private static final short DST_PORT = 433;
private static final short SEQ_NO = 13579;
@@ -68,6 +75,9 @@
private static final byte TIME_TO_LIVE = (byte) 0x40;
private static final short WINDOW = (short) 0x2000;
private static final short URGENT_POINTER = 0;
+ // version=6, traffic class=0x80, flowlabel=0x515ca;
+ private static final int VERSION_TRAFFICCLASS_FLOWLABEL = 0x680515ca;
+ private static final short HOP_LIMIT = 0x40;
private static final ByteBuffer DATA = ByteBuffer.wrap(new byte[] {
(byte) 0xde, (byte) 0xad, (byte) 0xbe, (byte) 0xef
});
@@ -256,15 +266,123 @@
(byte) 0xde, (byte) 0xad, (byte) 0xbe, (byte) 0xef
};
+ private static final byte[] TEST_PACKET_ETHERHDR_IPV6HDR_UDPHDR =
+ new byte[] {
+ // packet = (scapy.Ether(src="11:22:33:44:55:66", dst="aa:bb:cc:dd:ee:ff",
+ // type='IPv6') /
+ // scapy.IPv6(src="2001:db8::1", dst="2001:db8::2", tc=0x80,
+ // fl=0x515ca, plen=48, hlim=0x40) /
+ // scapy.UDP(sport=9876, dport=433))
+ // Note that plen(48) = ipv6hdr(40) + udphdr(8).
+ // 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) 0x86, (byte) 0xdd,
+ // IP header
+ (byte) 0x68, (byte) 0x05, (byte) 0x15, (byte) 0xca,
+ (byte) 0x00, (byte) 0x30, (byte) 0x11, (byte) 0x40,
+ (byte) 0x20, (byte) 0x01, (byte) 0x0d, (byte) 0xb8,
+ (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00,
+ (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00,
+ (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x01,
+ (byte) 0x20, (byte) 0x01, (byte) 0x0d, (byte) 0xb8,
+ (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00,
+ (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00,
+ (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x02,
+ // UDP header
+ (byte) 0x26, (byte) 0x94, (byte) 0x01, (byte) 0xb1,
+ (byte) 0x00, (byte) 0x08, (byte) 0x7c, (byte) 0x24
+ };
+
+ private static final byte[] TEST_PACKET_ETHERHDR_IPV6HDR_UDPHDR_DATA =
+ new byte[] {
+ // packet = (scapy.Ether(src="11:22:33:44:55:66", dst="aa:bb:cc:dd:ee:ff",
+ // type='IPv6') /
+ // scapy.IPv6(src="2001:db8::1", dst="2001:db8::2", tc=0x80,
+ // fl=0x515ca, plen=52, hlim=0x40) /
+ // scapy.UDP(sport=9876, dport=433) /
+ // b'\xde\xad\xbe\xef')
+ // Note that plen(52) = ipv6hdr(40) + udphdr(8) + data(4).
+ // 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) 0x86, (byte) 0xdd,
+ // IP header
+ (byte) 0x68, (byte) 0x05, (byte) 0x15, (byte) 0xca,
+ (byte) 0x00, (byte) 0x34, (byte) 0x11, (byte) 0x40,
+ (byte) 0x20, (byte) 0x01, (byte) 0x0d, (byte) 0xb8,
+ (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00,
+ (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00,
+ (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x01,
+ (byte) 0x20, (byte) 0x01, (byte) 0x0d, (byte) 0xb8,
+ (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00,
+ (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00,
+ (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x02,
+ // UDP header
+ (byte) 0x26, (byte) 0x94, (byte) 0x01, (byte) 0xb1,
+ (byte) 0x00, (byte) 0x0c, (byte) 0xde, (byte) 0x7e,
+ // Data
+ (byte) 0xde, (byte) 0xad, (byte) 0xbe, (byte) 0xef
+ };
+
+ private static final byte[] TEST_PACKET_IPV6HDR_UDPHDR =
+ new byte[] {
+ // packet = (scapy.IPv6(src="2001:db8::1", dst="2001:db8::2", tc=0x80,
+ // fl=0x515ca, plen=48, hlim=0x40) /
+ // scapy.UDP(sport=9876, dport=433))
+ // Note that plen(48) = ipv6hdr(40) + udphdr(8).
+ // IP header
+ (byte) 0x68, (byte) 0x05, (byte) 0x15, (byte) 0xca,
+ (byte) 0x00, (byte) 0x30, (byte) 0x11, (byte) 0x40,
+ (byte) 0x20, (byte) 0x01, (byte) 0x0d, (byte) 0xb8,
+ (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00,
+ (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00,
+ (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x01,
+ (byte) 0x20, (byte) 0x01, (byte) 0x0d, (byte) 0xb8,
+ (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00,
+ (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00,
+ (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x02,
+ // UDP header
+ (byte) 0x26, (byte) 0x94, (byte) 0x01, (byte) 0xb1,
+ (byte) 0x00, (byte) 0x08, (byte) 0x7c, (byte) 0x24
+ };
+
+ private static final byte[] TEST_PACKET_IPV6HDR_UDPHDR_DATA =
+ new byte[] {
+ // packet = (scapy.IPv6(src="2001:db8::1", dst="2001:db8::2", tc=0x80,
+ // fl=0x515ca, plen=52, hlim=0x40) /
+ // scapy.UDP(sport=9876, dport=433) /
+ // b'\xde\xad\xbe\xef')
+ // Note that plen(52) = ipv6hdr(40) + udphdr(8) + data(4).
+ // IP header
+ (byte) 0x68, (byte) 0x05, (byte) 0x15, (byte) 0xca,
+ (byte) 0x00, (byte) 0x34, (byte) 0x11, (byte) 0x40,
+ (byte) 0x20, (byte) 0x01, (byte) 0x0d, (byte) 0xb8,
+ (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00,
+ (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00,
+ (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x01,
+ (byte) 0x20, (byte) 0x01, (byte) 0x0d, (byte) 0xb8,
+ (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00,
+ (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00,
+ (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x02,
+ // UDP header
+ (byte) 0x26, (byte) 0x94, (byte) 0x01, (byte) 0xb1,
+ (byte) 0x00, (byte) 0x0c, (byte) 0xde, (byte) 0x7e,
+ // Data
+ (byte) 0xde, (byte) 0xad, (byte) 0xbe, (byte) 0xef
+ };
+
/**
- * Build an IPv4 packet which has ether header, IPv4 header, TCP/UDP header and data.
+ * Build a packet which has ether header, IP 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 3 header (Ipv4Header, Ipv6Header) |
* +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
* | Layer 4 header (TcpHeader, UdpHeader) |
* +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
@@ -273,33 +391,55 @@
*
* @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 l3proto the layer 3 protocol. Only {@code IPPROTO_IP} and {@code IPPROTO_IPV6}
+ * currently supported.
+ * @param l4proto the layer 4 protocol. Only {@code IPPROTO_TCP} and {@code IPPROTO_UDP}
+ * currently supported.
* @param payload the payload.
*/
@NonNull
- private ByteBuffer buildIpv4Packet(@Nullable final MacAddress srcMac,
- @Nullable final MacAddress dstMac, final int l4proto,
+ private ByteBuffer buildPacket(@Nullable final MacAddress srcMac,
+ @Nullable final MacAddress dstMac, final int l3proto, final int l4proto,
@Nullable final ByteBuffer payload)
throws Exception {
+ if (l3proto != IPPROTO_IP && l3proto != IPPROTO_IPV6) {
+ fail("Unsupported layer 3 protocol " + l3proto);
+ }
+
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,
+ final ByteBuffer buffer = PacketBuilder.allocate(hasEther, l3proto, 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);
+ // [1] Build ether header.
+ if (hasEther) {
+ final int etherType = (l3proto == IPPROTO_IP) ? ETHER_TYPE_IPV4 : ETHER_TYPE_IPV6;
+ packetBuilder.writeL2Header(srcMac, dstMac, (short) etherType);
+ }
+
+ // [2] Build IP header.
+ if (l3proto == IPPROTO_IP) {
+ packetBuilder.writeIpv4Header(TYPE_OF_SERVICE, ID, FLAGS_AND_FRAGMENT_OFFSET,
+ TIME_TO_LIVE, (byte) l4proto, IPV4_SRC_ADDR, IPV4_DST_ADDR);
+ } else if (l3proto == IPPROTO_IPV6) {
+ packetBuilder.writeIpv6Header(VERSION_TRAFFICCLASS_FLOWLABEL,
+ (byte) l4proto, HOP_LIMIT, IPV6_SRC_ADDR, IPV6_DST_ADDR);
+ }
+
+ // [3] Build TCP or UDP header.
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);
}
+
+ // [4] Build payload.
if (payload != null) {
buffer.put(payload);
// in case data might be reused by caller, restore the position and
@@ -313,19 +453,27 @@
/**
* Check ethernet header.
*
+ * @param l3proto the layer 3 protocol. Only {@code IPPROTO_IP} and {@code IPPROTO_IPV6}
+ * currently supported.
* @param actual the packet to check.
*/
- private void checkEtherHeader(final ByteBuffer actual) {
+ private void checkEtherHeader(final int l3proto, final ByteBuffer actual) {
+ if (l3proto != IPPROTO_IP && l3proto != IPPROTO_IPV6) {
+ fail("Unsupported layer 3 protocol " + l3proto);
+ }
+
final EthernetHeader eth = Struct.parse(EthernetHeader.class, actual);
assertEquals(SRC_MAC, eth.srcMac);
assertEquals(DST_MAC, eth.dstMac);
- assertEquals(ETHER_TYPE_IPV4, eth.etherType);
+ final int expectedEtherType = (l3proto == IPPROTO_IP) ? ETHER_TYPE_IPV4 : ETHER_TYPE_IPV6;
+ assertEquals(expectedEtherType, eth.etherType);
}
/**
* Check IPv4 header.
*
- * @param l4proto the layer 4 protocol. support either IPPROTO_TCP or IPPROTO_UDP.
+ * @param l4proto the layer 4 protocol. Only {@code IPPROTO_TCP} and {@code IPPROTO_UDP}
+ * currently supported.
* @param hasData true if the packet has data payload; false otherwise.
* @param actual the packet to check.
*/
@@ -359,16 +507,57 @@
}
/**
- * Check TCPv4 packet.
+ * Check IPv6 header.
*
- * @param hasEther true if the packet has ether header; false otherwise.
+ * @param l4proto the layer 4 protocol. Only {@code IPPROTO_TCP} and {@code IPPROTO_UDP}
+ * currently supported.
* @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,
+ private void checkIpv6Header(final int l4proto, final boolean hasData,
final ByteBuffer actual) {
+ if (l4proto != IPPROTO_TCP && l4proto != IPPROTO_UDP) {
+ fail("Unsupported layer 4 protocol " + l4proto);
+ }
+
+ final Ipv6Header ipv6Header = Struct.parse(Ipv6Header.class, actual);
+
+ assertEquals(VERSION_TRAFFICCLASS_FLOWLABEL, ipv6Header.vtf);
+ assertEquals(HOP_LIMIT, ipv6Header.hopLimit);
+ assertEquals(IPV6_SRC_ADDR, ipv6Header.srcIp);
+ assertEquals(IPV6_DST_ADDR, ipv6Header.dstIp);
+
+ final int dataLength = hasData ? DATA.limit() : 0;
+ if (l4proto == IPPROTO_TCP) {
+ assertEquals(IPV6_HEADER_LEN + TCP_HEADER_MIN_LEN + dataLength,
+ ipv6Header.payloadLength);
+ assertEquals((byte) IPPROTO_TCP, ipv6Header.nextHeader);
+ } else if (l4proto == IPPROTO_UDP) {
+ assertEquals(IPV6_HEADER_LEN + UDP_HEADER_LEN + dataLength,
+ ipv6Header.payloadLength);
+ assertEquals((byte) IPPROTO_UDP, ipv6Header.nextHeader);
+ }
+ }
+
+ /**
+ * Check TCPv4 packet.
+ *
+ * @param hasEther true if the packet has ether header; false otherwise.
+ * @param l3proto the layer 3 protocol. Only {@code IPPROTO_IP} and {@code IPPROTO_IPV6}
+ * currently supported.
+ * @param hasData true if the packet has data payload; false otherwise.
+ * @param actual the packet to check.
+ *
+ * TODO: support IPv6
+ */
+ private void checkTcpv4Packet(final boolean hasEther, final int l3proto, final boolean hasData,
+ final ByteBuffer actual) {
+ if (l3proto != IPPROTO_IP && l3proto != IPPROTO_IPV6) {
+ fail("Unsupported layer 3 protocol " + l3proto);
+ }
+
if (hasEther) {
- checkEtherHeader(actual);
+ checkEtherHeader(l3proto, actual);
}
checkIpv4Header(IPPROTO_TCP, hasData, actual);
@@ -389,26 +578,45 @@
}
/**
- * Check UDPv4 packet.
+ * Check UDP packet.
*
* @param hasEther true if the packet has ether header; false otherwise.
+ * @param l3proto the layer 3 protocol. Only {@code IPPROTO_IP} and {@code IPPROTO_IPV6}
+ * currently supported.
* @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,
+ private void checkUdpPacket(final boolean hasEther, final int l3proto, final boolean hasData,
final ByteBuffer actual) {
- if (hasEther) {
- checkEtherHeader(actual);
+ if (l3proto != IPPROTO_IP && l3proto != IPPROTO_IPV6) {
+ fail("Unsupported layer 3 protocol " + l3proto);
}
- checkIpv4Header(IPPROTO_UDP, hasData, actual);
+ // [1] Check ether header.
+ if (hasEther) {
+ checkEtherHeader(l3proto, actual);
+ }
+
+ // [2] Check IP header.
+ if (l3proto == IPPROTO_IP) {
+ checkIpv4Header(IPPROTO_UDP, hasData, actual);
+ } else if (l3proto == IPPROTO_IPV6) {
+ checkIpv6Header(IPPROTO_UDP, hasData, actual);
+ }
+
+ // [3] Check UDP header.
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 (l3proto == IPPROTO_IP) {
+ assertEquals(hasData ? (short) 0x4dbd : (short) 0xeb62, udpHeader.checksum);
+ } else if (l3proto == IPPROTO_IPV6) {
+ assertEquals(hasData ? (short) 0xde7e : (short) 0x7c24, udpHeader.checksum);
+ }
+ // [4] Check payload.
if (hasData) {
assertEquals(0xdeadbeef, actual.getInt());
}
@@ -416,66 +624,100 @@
@Test
public void testBuildPacketEtherIPv4Tcp() throws Exception {
- final ByteBuffer packet = buildIpv4Packet(SRC_MAC, DST_MAC, IPPROTO_TCP, null /* data */);
- checkTcpv4Packet(true /* hasEther */, false /* hasData */, packet);
+ final ByteBuffer packet = buildPacket(SRC_MAC, DST_MAC, IPPROTO_IP, IPPROTO_TCP,
+ null /* data */);
+ checkTcpv4Packet(true /* hasEther */, IPPROTO_IP, 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);
+ final ByteBuffer packet = buildPacket(SRC_MAC, DST_MAC, IPPROTO_IP, IPPROTO_TCP, DATA);
+ checkTcpv4Packet(true /* hasEther */, IPPROTO_IP, 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);
+ final ByteBuffer packet = buildPacket(null /* srcMac */, null /* dstMac */,
+ IPPROTO_IP, IPPROTO_TCP, null /* data */);
+ checkTcpv4Packet(false /* hasEther */, IPPROTO_IP, 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);
+ final ByteBuffer packet = buildPacket(null /* srcMac */, null /* dstMac */,
+ IPPROTO_IP, IPPROTO_TCP, DATA);
+ checkTcpv4Packet(false /* hasEther */, IPPROTO_IP, 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);
+ final ByteBuffer packet = buildPacket(SRC_MAC, DST_MAC, IPPROTO_IP, IPPROTO_UDP,
+ null /* data */);
+ checkUdpPacket(true /* hasEther */, IPPROTO_IP, 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);
+ final ByteBuffer packet = buildPacket(SRC_MAC, DST_MAC, IPPROTO_IP, IPPROTO_UDP, DATA);
+ checkUdpPacket(true /* hasEther */, IPPROTO_IP, 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);
+ final ByteBuffer packet = buildPacket(null /* srcMac */, null /* dstMac */,
+ IPPROTO_IP, IPPROTO_UDP, null /*data*/);
+ checkUdpPacket(false /* hasEther */, IPPROTO_IP, 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);
+ final ByteBuffer packet = buildPacket(null /* srcMac */, null /* dstMac */,
+ IPPROTO_IP, IPPROTO_UDP, DATA);
+ checkUdpPacket(false /* hasEther */, IPPROTO_IP, true /* hasData */, packet);
assertArrayEquals(TEST_PACKET_IPV4HDR_UDPHDR_DATA, packet.array());
}
@Test
+ public void testBuildPacketEtherIPv6Udp() throws Exception {
+ final ByteBuffer packet = buildPacket(SRC_MAC, DST_MAC, IPPROTO_IPV6, IPPROTO_UDP,
+ null /* data */);
+ checkUdpPacket(true /* hasEther */, IPPROTO_IPV6, false /* hasData */, packet);
+ assertArrayEquals(TEST_PACKET_ETHERHDR_IPV6HDR_UDPHDR, packet.array());
+ }
+
+ @Test
+ public void testBuildPacketEtherIPv6UdpData() throws Exception {
+ final ByteBuffer packet = buildPacket(SRC_MAC, DST_MAC, IPPROTO_IPV6, IPPROTO_UDP,
+ DATA);
+ checkUdpPacket(true /* hasEther */, IPPROTO_IPV6, true /* hasData */, packet);
+ assertArrayEquals(TEST_PACKET_ETHERHDR_IPV6HDR_UDPHDR_DATA, packet.array());
+ }
+
+ @Test
+ public void testBuildPacketIPv6Udp() throws Exception {
+ final ByteBuffer packet = buildPacket(null /* srcMac */, null /* dstMac */,
+ IPPROTO_IPV6, IPPROTO_UDP, null /*data*/);
+ checkUdpPacket(false /* hasEther */, IPPROTO_IPV6, false /* hasData */, packet);
+ assertArrayEquals(TEST_PACKET_IPV6HDR_UDPHDR, packet.array());
+ }
+
+ @Test
+ public void testBuildPacketIPv6UdpData() throws Exception {
+ final ByteBuffer packet = buildPacket(null /* srcMac */, null /* dstMac */,
+ IPPROTO_IPV6, IPPROTO_UDP, DATA);
+ checkUdpPacket(false /* hasEther */, IPPROTO_IPV6, true /* hasData */, packet);
+ assertArrayEquals(TEST_PACKET_IPV6HDR_UDPHDR_DATA, packet.array());
+ }
+
+ @Test
public void testFinalizePacketWithoutIpv4Header() throws Exception {
final ByteBuffer buffer = PacketBuilder.allocate(false /* hasEther */, IPPROTO_IP,
IPPROTO_TCP, 0 /* payloadLen */);
@@ -526,7 +768,11 @@
assertThrows(IOException.class, () -> packetBuilder.writeUdpHeader(SRC_PORT, DST_PORT));
}
- private static Inet4Address addr(String addr) {
+ private static Inet4Address addr4(String addr) {
return (Inet4Address) InetAddresses.parseNumericAddress(addr);
}
+
+ private static Inet6Address addr6(String addr) {
+ return (Inet6Address) InetAddresses.parseNumericAddress(addr);
+ }
}