From 2a65575cf89829aca8dc4684dcf3669d630366a0 Mon Sep 17 00:00:00 2001 From: Jonathan Hart Date: Tue, 7 Apr 2015 16:46:33 -0700 Subject: [PATCH] Improve the resiliency of the packet deserialization code. Packet deserializers now check for malformed input while reading the byte stream. Deserializers are re-implemented as functions that take a byte array and return a packet object. The old IPacket.deserialize(...) methods have been deprecated with the goal of eventually moving to immutable packet objects. Unit tests have been implemented for all Deserializer functions. ONOS-1589 Change-Id: I9073d5e6e7991e15d43830cfd810989256b71c56 --- .../onosproject/bgprouter/IcmpHandler.java | 6 +- .../segmentrouting/IcmpHandler.java | 7 +- .../net/host/impl/HostMonitorTest.java | 16 +- .../DefaultOpenFlowPacketContext.java | 13 +- .../impl/OpenFlowCorePacketContext.java | 20 ++- utils/misc/pom.xml | 5 + .../src/main/java/org/onlab/packet/ARP.java | 48 +++++- .../src/main/java/org/onlab/packet/DHCP.java | 134 +++++++++++++---- .../src/main/java/org/onlab/packet/Data.java | 28 +++- .../packet/DeserializationException.java | 32 ++++ .../java/org/onlab/packet/Deserializer.java | 36 +++++ .../main/java/org/onlab/packet/EthType.java | 26 ++-- .../main/java/org/onlab/packet/Ethernet.java | 92 +++++++++--- .../src/main/java/org/onlab/packet/ICMP.java | 52 +++++-- .../src/main/java/org/onlab/packet/ICMP6.java | 89 ++++++++---- .../main/java/org/onlab/packet/IPacket.java | 6 + .../src/main/java/org/onlab/packet/IPv4.java | 101 ++++++++++--- .../src/main/java/org/onlab/packet/IPv6.java | 88 +++++++---- .../src/main/java/org/onlab/packet/LLC.java | 27 +++- .../src/main/java/org/onlab/packet/LLDP.java | 78 +++++++++- .../onlab/packet/LLDPOrganizationalTLV.java | 13 +- .../main/java/org/onlab/packet/LLDPTLV.java | 17 ++- .../src/main/java/org/onlab/packet/MPLS.java | 64 ++++++-- .../java/org/onlab/packet/PacketUtils.java | 84 +++++++++++ .../src/main/java/org/onlab/packet/TCP.java | 103 +++++++++---- .../src/main/java/org/onlab/packet/UDP.java | 95 ++++++++---- .../org/onlab/packet/ipv6/Authentication.java | 66 +++++++-- .../org/onlab/packet/ipv6/BaseOptions.java | 66 +++++++-- .../packet/ipv6/EncapSecurityPayload.java | 29 +++- .../java/org/onlab/packet/ipv6/Fragment.java | 62 ++++++-- .../java/org/onlab/packet/ipv6/Routing.java | 70 +++++++-- .../packet/ndp/NeighborAdvertisement.java | 35 +++++ .../packet/ndp/NeighborDiscoveryOptions.java | 55 ++++++- .../packet/ndp/NeighborSolicitation.java | 30 ++++ .../java/org/onlab/packet/ndp/Redirect.java | 32 ++++ .../onlab/packet/ndp/RouterAdvertisement.java | 36 +++++ .../onlab/packet/ndp/RouterSolicitation.java | 32 +++- .../test/java/org/onlab/packet/ArpTest.java | 88 +++++++++++ .../test/java/org/onlab/packet/DhcpTest.java | 137 ++++++++++++++++++ .../java/org/onlab/packet/EthernetTest.java | 103 +++++++++++++ .../test/java/org/onlab/packet/ICMP6Test.java | 15 +- .../test/java/org/onlab/packet/ICMPTest.java | 70 +++++++++ .../test/java/org/onlab/packet/IPv4Test.java | 99 +++++++++++++ .../test/java/org/onlab/packet/IPv6Test.java | 33 ++++- .../test/java/org/onlab/packet/LLCTest.java | 70 +++++++++ .../test/java/org/onlab/packet/LLDPTest.java | 115 +++++++++++++++ .../test/java/org/onlab/packet/MplsTest.java | 74 ++++++++++ .../org/onlab/packet/PacketTestUtils.java | 98 +++++++++++++ .../test/java/org/onlab/packet/TCPTest.java | 19 ++- .../test/java/org/onlab/packet/UDPTest.java | 19 ++- .../onlab/packet/ipv6/AuthenticationTest.java | 14 +- .../onlab/packet/ipv6/BaseOptionsTest.java | 14 +- .../packet/ipv6/EncapSecurityPayloadTest.java | 16 +- .../org/onlab/packet/ipv6/FragmentTest.java | 15 +- .../org/onlab/packet/ipv6/RoutingTest.java | 15 +- .../packet/ndp/NeighborAdvertisementTest.java | 10 +- .../packet/ndp/NeighborSolicitationTest.java | 10 +- .../org/onlab/packet/ndp/RedirectTest.java | 9 +- .../packet/ndp/RouterAdvertisementTest.java | 10 +- .../packet/ndp/RouterSolicitationTest.java | 20 ++- 60 files changed, 2496 insertions(+), 370 deletions(-) create mode 100644 utils/misc/src/main/java/org/onlab/packet/DeserializationException.java create mode 100644 utils/misc/src/main/java/org/onlab/packet/Deserializer.java create mode 100644 utils/misc/src/main/java/org/onlab/packet/PacketUtils.java create mode 100644 utils/misc/src/test/java/org/onlab/packet/ArpTest.java create mode 100644 utils/misc/src/test/java/org/onlab/packet/DhcpTest.java create mode 100644 utils/misc/src/test/java/org/onlab/packet/EthernetTest.java create mode 100644 utils/misc/src/test/java/org/onlab/packet/ICMPTest.java create mode 100644 utils/misc/src/test/java/org/onlab/packet/IPv4Test.java create mode 100644 utils/misc/src/test/java/org/onlab/packet/LLCTest.java create mode 100644 utils/misc/src/test/java/org/onlab/packet/LLDPTest.java create mode 100644 utils/misc/src/test/java/org/onlab/packet/MplsTest.java create mode 100644 utils/misc/src/test/java/org/onlab/packet/PacketTestUtils.java diff --git a/apps/bgprouter/src/main/java/org/onosproject/bgprouter/IcmpHandler.java b/apps/bgprouter/src/main/java/org/onosproject/bgprouter/IcmpHandler.java index 1af3fd9a44..e068c940f8 100644 --- a/apps/bgprouter/src/main/java/org/onosproject/bgprouter/IcmpHandler.java +++ b/apps/bgprouter/src/main/java/org/onosproject/bgprouter/IcmpHandler.java @@ -15,8 +15,6 @@ */ package org.onosproject.bgprouter; -import java.nio.ByteBuffer; - import org.onlab.packet.Ethernet; import org.onlab.packet.ICMP; import org.onlab.packet.IPv4; @@ -36,6 +34,8 @@ import org.onosproject.routing.config.RoutingConfigurationService; import org.slf4j.Logger; import org.slf4j.LoggerFactory; +import java.nio.ByteBuffer; + public class IcmpHandler { private static final Logger log = LoggerFactory.getLogger(IcmpHandler.class); @@ -100,7 +100,7 @@ public class IcmpHandler { icmpReplyIpv4.setTtl((byte) 64); icmpReplyIpv4.setChecksum((short) 0); - ICMP icmpReply = (ICMP) icmpRequestIpv4.getPayload().clone(); + ICMP icmpReply = new ICMP(); icmpReply.setIcmpType(ICMP.TYPE_ECHO_REPLY); icmpReply.setIcmpCode(ICMP.SUBTYPE_ECHO_REPLY); icmpReply.setChecksum((short) 0); diff --git a/apps/segmentrouting/src/main/java/org/onosproject/segmentrouting/IcmpHandler.java b/apps/segmentrouting/src/main/java/org/onosproject/segmentrouting/IcmpHandler.java index b3da8dcb23..0f8fa59d74 100644 --- a/apps/segmentrouting/src/main/java/org/onosproject/segmentrouting/IcmpHandler.java +++ b/apps/segmentrouting/src/main/java/org/onosproject/segmentrouting/IcmpHandler.java @@ -15,8 +15,6 @@ */ package org.onosproject.segmentrouting; -import java.nio.ByteBuffer; -import java.util.List; import org.onlab.packet.Ethernet; import org.onlab.packet.ICMP; import org.onlab.packet.IPv4; @@ -33,6 +31,9 @@ import org.onosproject.net.packet.OutboundPacket; import org.slf4j.Logger; import org.slf4j.LoggerFactory; +import java.nio.ByteBuffer; +import java.util.List; + import static com.google.common.base.Preconditions.checkNotNull; public class IcmpHandler { @@ -109,7 +110,7 @@ public class IcmpHandler { icmpReplyIpv4.setTtl((byte) 64); icmpReplyIpv4.setChecksum((short) 0); - ICMP icmpReply = (ICMP) icmpRequestIpv4.getPayload().clone(); + ICMP icmpReply = new ICMP(); icmpReply.setIcmpType(ICMP.TYPE_ECHO_REPLY); icmpReply.setIcmpCode(ICMP.SUBTYPE_ECHO_REPLY); icmpReply.setChecksum((short) 0); diff --git a/core/net/src/test/java/org/onosproject/net/host/impl/HostMonitorTest.java b/core/net/src/test/java/org/onosproject/net/host/impl/HostMonitorTest.java index 679a888eeb..90cf6b4e28 100644 --- a/core/net/src/test/java/org/onosproject/net/host/impl/HostMonitorTest.java +++ b/core/net/src/test/java/org/onosproject/net/host/impl/HostMonitorTest.java @@ -49,8 +49,14 @@ import java.util.Collections; import java.util.List; import java.util.Set; -import static org.easymock.EasyMock.*; -import static org.junit.Assert.*; +import static org.easymock.EasyMock.createMock; +import static org.easymock.EasyMock.expect; +import static org.easymock.EasyMock.expectLastCall; +import static org.easymock.EasyMock.replay; +import static org.easymock.EasyMock.verify; +import static org.junit.Assert.assertArrayEquals; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertTrue; public class HostMonitorTest { @@ -151,10 +157,9 @@ public class HostMonitorTest { assertEquals(portNum, oi.port()); // Check the output packet is correct (well the important bits anyway) - Ethernet eth = new Ethernet(); final byte[] pktData = new byte[packet.data().remaining()]; packet.data().get(pktData); - eth.deserialize(pktData, 0, pktData.length); + Ethernet eth = Ethernet.deserializer().deserialize(pktData, 0, pktData.length); assertEquals(Ethernet.VLAN_UNTAGGED, eth.getVlanID()); ARP arp = (ARP) eth.getPayload(); assertArrayEquals(SOURCE_ADDR.toOctets(), @@ -220,10 +225,9 @@ public class HostMonitorTest { assertEquals(portNum, oi.port()); // Check the output packet is correct (well the important bits anyway) - Ethernet eth = new Ethernet(); final byte[] pktData = new byte[packet.data().remaining()]; packet.data().get(pktData); - eth.deserialize(pktData, 0, pktData.length); + Ethernet eth = Ethernet.deserializer().deserialize(pktData, 0, pktData.length); assertEquals(vlan, eth.getVlanID()); ARP arp = (ARP) eth.getPayload(); assertArrayEquals(SOURCE_ADDR.toOctets(), diff --git a/openflow/api/src/main/java/org/onosproject/openflow/controller/DefaultOpenFlowPacketContext.java b/openflow/api/src/main/java/org/onosproject/openflow/controller/DefaultOpenFlowPacketContext.java index be7e4b0872..f4ca827ccd 100644 --- a/openflow/api/src/main/java/org/onosproject/openflow/controller/DefaultOpenFlowPacketContext.java +++ b/openflow/api/src/main/java/org/onosproject/openflow/controller/DefaultOpenFlowPacketContext.java @@ -15,7 +15,7 @@ */ package org.onosproject.openflow.controller; - +import org.onlab.packet.DeserializationException; import org.onlab.packet.Ethernet; import org.onosproject.core.Permission; import org.projectfloodlight.openflow.protocol.OFPacketIn; @@ -26,6 +26,8 @@ import org.projectfloodlight.openflow.protocol.action.OFActionOutput; import org.projectfloodlight.openflow.protocol.match.MatchField; import org.projectfloodlight.openflow.types.OFBufferId; import org.projectfloodlight.openflow.types.OFPort; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; import java.nio.BufferUnderflowException; import java.util.Collections; @@ -97,11 +99,12 @@ public final class DefaultOpenFlowPacketContext implements OpenFlowPacketContext public Ethernet parsed() { checkPermission(Permission.PACKET_READ); - Ethernet eth = new Ethernet(); try { - eth.deserialize(pktin.getData(), 0, pktin.getData().length); - return eth; - } catch (BufferUnderflowException | NullPointerException e) { + return Ethernet.deserializer().deserialize(pktin.getData(), 0, pktin.getData().length); + } catch (BufferUnderflowException | NullPointerException | + DeserializationException e) { + Logger log = LoggerFactory.getLogger(getClass()); + log.warn("packet deserialization problem"); return null; } } diff --git a/providers/openflow/packet/src/main/java/org/onosproject/provider/of/packet/impl/OpenFlowCorePacketContext.java b/providers/openflow/packet/src/main/java/org/onosproject/provider/of/packet/impl/OpenFlowCorePacketContext.java index 5d44c91fb2..6d1531036b 100644 --- a/providers/openflow/packet/src/main/java/org/onosproject/provider/of/packet/impl/OpenFlowCorePacketContext.java +++ b/providers/openflow/packet/src/main/java/org/onosproject/provider/of/packet/impl/OpenFlowCorePacketContext.java @@ -15,6 +15,8 @@ */ package org.onosproject.provider.of.packet.impl; +import org.onlab.packet.DeserializationException; +import org.onlab.packet.Ethernet; import org.onosproject.net.PortNumber; import org.onosproject.net.flow.instructions.Instruction; import org.onosproject.net.flow.instructions.Instruction.Type; @@ -23,8 +25,9 @@ import org.onosproject.net.packet.DefaultPacketContext; import org.onosproject.net.packet.InboundPacket; import org.onosproject.net.packet.OutboundPacket; import org.onosproject.openflow.controller.OpenFlowPacketContext; -import org.onlab.packet.Ethernet; import org.projectfloodlight.openflow.types.OFPort; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; import java.util.List; @@ -33,6 +36,8 @@ import java.util.List; */ public class OpenFlowCorePacketContext extends DefaultPacketContext { + private static final Logger log = LoggerFactory.getLogger(OpenFlowCorePacketContext.class); + private final OpenFlowPacketContext ofPktCtx; /** @@ -57,12 +62,15 @@ public class OpenFlowCorePacketContext extends DefaultPacketContext { if (outPacket() == null) { sendPacket(null); } else { - Ethernet eth = new Ethernet(); - eth.deserialize(outPacket().data().array(), 0, - outPacket().data().array().length); - sendPacket(eth); + try { + Ethernet eth = Ethernet.deserializer() + .deserialize(outPacket().data().array(), 0, + outPacket().data().array().length); + sendPacket(eth); + } catch (DeserializationException e) { + log.warn("Unable to deserialize packet"); + } } - } } diff --git a/utils/misc/pom.xml b/utils/misc/pom.xml index e3635f493a..1795121504 100644 --- a/utils/misc/pom.xml +++ b/utils/misc/pom.xml @@ -41,6 +41,11 @@ onlab-junit test + + org.easymock + easymock + test + io.netty netty diff --git a/utils/misc/src/main/java/org/onlab/packet/ARP.java b/utils/misc/src/main/java/org/onlab/packet/ARP.java index 88e1d4ff49..dc3c07f159 100644 --- a/utils/misc/src/main/java/org/onlab/packet/ARP.java +++ b/utils/misc/src/main/java/org/onlab/packet/ARP.java @@ -21,6 +21,8 @@ package org.onlab.packet; import java.nio.ByteBuffer; import java.util.Arrays; +import static org.onlab.packet.PacketUtils.*; + /** * * @@ -35,6 +37,8 @@ public class ARP extends BasePacket { public static final short OP_RARP_REQUEST = 0x3; public static final short OP_RARP_REPLY = 0x4; + public static final short INITIAL_HEADER_LENGTH = 8; + protected short hardwareType; protected short protocolType; protected byte hardwareAddressLength; @@ -247,7 +251,7 @@ public class ARP extends BasePacket { @Override public IPacket deserialize(final byte[] data, final int offset, - final int length) { + final int length) { final ByteBuffer bb = ByteBuffer.wrap(data, offset, length); this.hardwareType = bb.getShort(); this.protocolType = bb.getShort(); @@ -386,10 +390,50 @@ public class ARP extends BasePacket { arp.setTargetHardwareAddress(request.getSourceMACAddress()); arp.setTargetProtocolAddress(((ARP) request.getPayload()) - .getSenderProtocolAddress()); + .getSenderProtocolAddress()); arp.setSenderProtocolAddress(srcIp.toInt()); eth.setPayload(arp); return eth; } + + /** + * Deserializer function for ARP packets. + * + * @return deserializer function + */ + public static Deserializer deserializer() { + return (data, offset, length) -> { + checkInput(data, offset, length, INITIAL_HEADER_LENGTH); + + ARP arp = new ARP(); + final ByteBuffer bb = ByteBuffer.wrap(data, offset, length); + arp.setHardwareType(bb.getShort()); + arp.setProtocolType(bb.getShort()); + + byte hwAddressLength = bb.get(); + arp.setHardwareAddressLength(hwAddressLength); + + byte protocolAddressLength = bb.get(); + arp.setProtocolAddressLength(protocolAddressLength); + arp.setOpCode(bb.getShort()); + + // Check we have enough space for the addresses + checkHeaderLength(length, INITIAL_HEADER_LENGTH + + 2 * hwAddressLength + + 2 * protocolAddressLength); + + arp.senderHardwareAddress = new byte[0xff & hwAddressLength]; + bb.get(arp.senderHardwareAddress, 0, arp.senderHardwareAddress.length); + arp.senderProtocolAddress = new byte[0xff & protocolAddressLength]; + bb.get(arp.senderProtocolAddress, 0, arp.senderProtocolAddress.length); + arp.targetHardwareAddress = new byte[0xff & hwAddressLength]; + bb.get(arp.targetHardwareAddress, 0, arp.targetHardwareAddress.length); + arp.targetProtocolAddress = new byte[0xff & protocolAddressLength]; + bb.get(arp.targetProtocolAddress, 0, arp.targetProtocolAddress.length); + + return arp; + }; + } + } diff --git a/utils/misc/src/main/java/org/onlab/packet/DHCP.java b/utils/misc/src/main/java/org/onlab/packet/DHCP.java index 8dba13c593..40a7745d3c 100644 --- a/utils/misc/src/main/java/org/onlab/packet/DHCP.java +++ b/utils/misc/src/main/java/org/onlab/packet/DHCP.java @@ -24,6 +24,8 @@ import java.util.ArrayList; import java.util.List; import java.util.ListIterator; +import static org.onlab.packet.PacketUtils.*; + /** * */ @@ -429,33 +431,9 @@ public class DHCP extends BasePacket { return data; } - protected void writeString(final String string, final ByteBuffer bb, - final int maxLength) { - if (string == null) { - for (int i = 0; i < maxLength; ++i) { - bb.put((byte) 0x0); - } - } else { - byte[] bytes = null; - try { - bytes = string.getBytes("ascii"); - } catch (final UnsupportedEncodingException e) { - throw new RuntimeException("Failure encoding server name", e); - } - int writeLength = bytes.length; - if (writeLength > maxLength) { - writeLength = maxLength; - } - bb.put(bytes, 0, writeLength); - for (int i = writeLength; i < maxLength; ++i) { - bb.put((byte) 0x0); - } - } - } - @Override public IPacket deserialize(final byte[] data, final int offset, - final int length) { + final int length) { final ByteBuffer bb = ByteBuffer.wrap(data, offset, length); if (bb.remaining() < DHCP.MIN_HEADER_LENGTH) { return this; @@ -529,7 +507,31 @@ public class DHCP extends BasePacket { return this; } - protected String readString(final ByteBuffer bb, final int maxLength) { + protected void writeString(final String string, final ByteBuffer bb, + final int maxLength) { + if (string == null) { + for (int i = 0; i < maxLength; ++i) { + bb.put((byte) 0x0); + } + } else { + byte[] bytes = null; + try { + bytes = string.getBytes("ascii"); + } catch (final UnsupportedEncodingException e) { + throw new RuntimeException("Failure encoding server name", e); + } + int writeLength = bytes.length; + if (writeLength > maxLength) { + writeLength = maxLength; + } + bb.put(bytes, 0, writeLength); + for (int i = writeLength; i < maxLength; ++i) { + bb.put((byte) 0x0); + } + } + } + + private static String readString(final ByteBuffer bb, final int maxLength) { final byte[] bytes = new byte[maxLength]; bb.get(bytes); String result = null; @@ -540,4 +542,84 @@ public class DHCP extends BasePacket { } return result; } + + /** + * Deserializer function for DHCP packets. + * + * @return deserializer function + */ + public static Deserializer deserializer() { + return (data, offset, length) -> { + checkInput(data, offset, length, MIN_HEADER_LENGTH); + + ByteBuffer bb = ByteBuffer.wrap(data, offset, length); + DHCP dhcp = new DHCP(); + + dhcp.opCode = bb.get(); + dhcp.hardwareType = bb.get(); + dhcp.hardwareAddressLength = bb.get(); + dhcp.hops = bb.get(); + dhcp.transactionId = bb.getInt(); + dhcp.seconds = bb.getShort(); + dhcp.flags = bb.getShort(); + dhcp.clientIPAddress = bb.getInt(); + dhcp.yourIPAddress = bb.getInt(); + dhcp.serverIPAddress = bb.getInt(); + dhcp.gatewayIPAddress = bb.getInt(); + final int hardwareAddressLength = 0xff & dhcp.hardwareAddressLength; + dhcp.clientHardwareAddress = new byte[hardwareAddressLength]; + + bb.get(dhcp.clientHardwareAddress); + for (int i = hardwareAddressLength; i < 16; ++i) { + bb.get(); + } + dhcp.serverName = readString(bb, 64); + dhcp.bootFileName = readString(bb, 128); + // read the magic cookie + // magic cookie + bb.get(); + bb.get(); + bb.get(); + bb.get(); + + // read options + boolean foundEndOptionsMarker = false; + while (bb.hasRemaining()) { + final DHCPOption option = new DHCPOption(); + int code = 0xff & bb.get(); // convert signed byte to int in range + // [0,255] + option.setCode((byte) code); + if (code == 0) { + // skip these + continue; + } else if (code != 255) { + if (bb.hasRemaining()) { + final int l = 0xff & bb.get(); // convert signed byte to + // int in range [0,255] + option.setLength((byte) l); + if (bb.remaining() >= l) { + final byte[] optionData = new byte[l]; + bb.get(optionData); + option.setData(optionData); + dhcp.options.add(option); + } else { + throw new DeserializationException( + "Buffer underflow while reading DHCP option"); + } + } + } else if (code == 255) { + // remaining bytes are supposed to be 0, but ignore them just in + // case + foundEndOptionsMarker = true; + break; + } + } + + if (!foundEndOptionsMarker) { + throw new DeserializationException("DHCP End options marker was missing"); + } + + return dhcp; + }; + } } diff --git a/utils/misc/src/main/java/org/onlab/packet/Data.java b/utils/misc/src/main/java/org/onlab/packet/Data.java index f3a10929a4..79abcba1fc 100644 --- a/utils/misc/src/main/java/org/onlab/packet/Data.java +++ b/utils/misc/src/main/java/org/onlab/packet/Data.java @@ -20,6 +20,8 @@ package org.onlab.packet; import java.util.Arrays; +import static org.onlab.packet.PacketUtils.*; + /** * */ @@ -30,6 +32,7 @@ public class Data extends BasePacket { * */ public Data() { + data = new byte[0]; } /** @@ -63,7 +66,7 @@ public class Data extends BasePacket { @Override public IPacket deserialize(final byte[] data, final int offset, - final int length) { + final int length) { this.data = Arrays.copyOfRange(data, offset, data.length); return this; } @@ -103,4 +106,27 @@ public class Data extends BasePacket { } return true; } + + /** + * Deserializer function for generic payload data. + * + * @return deserializer function + */ + public static Deserializer deserializer() { + return (data, offset, length) -> { + // Allow zero-length data for now + if (length == 0) { + return new Data(); + } + + checkInput(data, offset, length, 1); + + Data dataObject = new Data(); + + dataObject.data = Arrays.copyOfRange(data, offset, data.length); + + return dataObject; + }; + } + } diff --git a/utils/misc/src/main/java/org/onlab/packet/DeserializationException.java b/utils/misc/src/main/java/org/onlab/packet/DeserializationException.java new file mode 100644 index 0000000000..03a8aa35d6 --- /dev/null +++ b/utils/misc/src/main/java/org/onlab/packet/DeserializationException.java @@ -0,0 +1,32 @@ +/* + * Copyright 2015 Open Networking Laboratory + * + * 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 org.onlab.packet; + +/** + * Signals that an error occurred during deserialization of a packet. + */ +public class DeserializationException extends Exception { + + /** + * Creates a new deserialization exception with the given message. + * + * @param message exception message + */ + public DeserializationException(String message) { + super(message); + } +} diff --git a/utils/misc/src/main/java/org/onlab/packet/Deserializer.java b/utils/misc/src/main/java/org/onlab/packet/Deserializer.java new file mode 100644 index 0000000000..e0ef381b1a --- /dev/null +++ b/utils/misc/src/main/java/org/onlab/packet/Deserializer.java @@ -0,0 +1,36 @@ +/* + * Copyright 2015 Open Networking Laboratory + * + * 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 org.onlab.packet; + +/** + * Function to deserialize a packet from a byte-based input stream. + */ +@FunctionalInterface +public interface Deserializer { + + /** + * Deserialize a packet object from a byte array. + * + * @param data input array to take packet bytes from + * @param offset index where this packet header begins in the byte array + * @param length length of the packet header + * @return a deserialized packet object + * @throws DeserializationException if the packet cannot be deserialized + * from the input + */ + U deserialize(byte[] data, int offset, int length) throws DeserializationException; +} diff --git a/utils/misc/src/main/java/org/onlab/packet/EthType.java b/utils/misc/src/main/java/org/onlab/packet/EthType.java index 4f52b7d54f..7573ae896d 100644 --- a/utils/misc/src/main/java/org/onlab/packet/EthType.java +++ b/utils/misc/src/main/java/org/onlab/packet/EthType.java @@ -93,25 +93,27 @@ public class EthType { public static enum EtherType { - ARP(0x806, "arp", ARP.class), - RARP(0x8035, "rarp", null), - IPV4(0x800, "ipv4", IPv4.class), - IPV6(0x86dd, "ipv6", IPv6.class), - LLDP(0x88cc, "lldp", LLDP.class), - VLAN(0x8100, "vlan", null), - BDDP(0x8942, "bddp", LLDP.class), - MPLS_UNICAST(0x8847, "mpls_unicast", null), - MPLS_MULTICAST(0x8848, "mpls_unicast", null); + ARP(0x806, "arp", ARP.class, org.onlab.packet.ARP.deserializer()), + RARP(0x8035, "rarp", null, org.onlab.packet.ARP.deserializer()), + IPV4(0x800, "ipv4", IPv4.class, org.onlab.packet.IPv4.deserializer()), + IPV6(0x86dd, "ipv6", IPv6.class, org.onlab.packet.IPv6.deserializer()), + LLDP(0x88cc, "lldp", LLDP.class, org.onlab.packet.LLDP.deserializer()), + VLAN(0x8100, "vlan", null, null), + BDDP(0x8942, "bddp", LLDP.class, org.onlab.packet.LLDP.deserializer()), + MPLS_UNICAST(0x8847, "mpls_unicast", null, org.onlab.packet.MPLS.deserializer()), + MPLS_MULTICAST(0x8848, "mpls_unicast", null, org.onlab.packet.MPLS.deserializer()); private final Class clazz; private EthType ethType; private String type; + private Deserializer deserializer; - EtherType(int ethType, String type, Class clazz) { + EtherType(int ethType, String type, Class clazz, Deserializer deserializer) { this.ethType = new EthType(ethType); this.type = type; this.clazz = clazz; + this.deserializer = deserializer; } public EthType ethType() { @@ -127,6 +129,8 @@ public class EthType { return clazz; } - + public Deserializer deserializer() { + return deserializer; + } } } diff --git a/utils/misc/src/main/java/org/onlab/packet/Ethernet.java b/utils/misc/src/main/java/org/onlab/packet/Ethernet.java index 779d96cfc4..700bdfc416 100644 --- a/utils/misc/src/main/java/org/onlab/packet/Ethernet.java +++ b/utils/misc/src/main/java/org/onlab/packet/Ethernet.java @@ -18,13 +18,14 @@ package org.onlab.packet; -import static com.google.common.base.Preconditions.checkNotNull; - import java.nio.ByteBuffer; import java.util.Arrays; import java.util.HashMap; import java.util.Map; +import static com.google.common.base.Preconditions.checkNotNull; +import static org.onlab.packet.PacketUtils.checkHeaderLength; +import static org.onlab.packet.PacketUtils.checkInput; /** * @@ -42,15 +43,21 @@ public class Ethernet extends BasePacket { public static final short MPLS_UNICAST = EthType.MPLS_UNICAST; public static final short MPLS_MULTICAST = EthType.MPLS_MULTICAST; + public static final short VLAN_UNTAGGED = (short) 0xffff; + + public static final short ETHERNET_HEADER_LENGTH = 14; // bytes + public static final short VLAN_HEADER_LENGTH = 4; // bytes + public static final short DATALAYER_ADDRESS_LENGTH = 6; // bytes - public static final Map> ETHER_TYPE_CLASS_MAP = - new HashMap<>(); + + private static final Map> ETHERTYPE_DESERIALIZER_MAP = + new HashMap<>(); static { for (EthType.EtherType ethType : EthType.EtherType.values()) { if (ethType.clazz() != null) { - ETHER_TYPE_CLASS_MAP.put(ethType.ethType().toShort(), ethType.clazz()); + ETHERTYPE_DESERIALIZER_MAP.put(ethType.ethType().toShort(), ethType.deserializer()); } } } @@ -300,7 +307,7 @@ public class Ethernet extends BasePacket { @Override public IPacket deserialize(final byte[] data, final int offset, - final int length) { + final int length) { if (length <= 0) { return null; } @@ -331,21 +338,19 @@ public class Ethernet extends BasePacket { this.etherType = ethType; IPacket payload; - if (Ethernet.ETHER_TYPE_CLASS_MAP.containsKey(this.etherType)) { - final Class clazz = Ethernet.ETHER_TYPE_CLASS_MAP - .get(this.etherType); - try { - payload = clazz.newInstance(); - } catch (final Exception e) { - throw new RuntimeException( - "Error parsing payload for Ethernet packet", e); - } + Deserializer deserializer; + if (Ethernet.ETHERTYPE_DESERIALIZER_MAP.containsKey(ethType)) { + deserializer = Ethernet.ETHERTYPE_DESERIALIZER_MAP.get(ethType); } else { - payload = new Data(); + deserializer = Data.deserializer(); + } + try { + this.payload = deserializer.deserialize(data, bb.position(), + bb.limit() - bb.position()); + this.payload.setParent(this); + } catch (DeserializationException e) { + return this; } - this.payload = payload.deserialize(data, bb.position(), - bb.limit() - bb.position()); - this.payload.setParent(this); return this; } @@ -567,4 +572,53 @@ public class Ethernet extends BasePacket { return builder.toString(); } + /** + * Deserializer function for Ethernet packets. + * + * @return deserializer function + */ + public static Deserializer deserializer() { + return (data, offset, length) -> { + checkInput(data, offset, length, ETHERNET_HEADER_LENGTH); + + byte[] addressBuffer = new byte[DATALAYER_ADDRESS_LENGTH]; + + ByteBuffer bb = ByteBuffer.wrap(data, offset, length); + Ethernet eth = new Ethernet(); + // Read destination MAC address into buffer + bb.get(addressBuffer); + eth.setDestinationMACAddress(addressBuffer); + + // Read source MAC address into buffer + bb.get(addressBuffer); + eth.setSourceMACAddress(addressBuffer); + + short ethType = bb.getShort(); + if (ethType == TYPE_VLAN) { + checkHeaderLength(length, ETHERNET_HEADER_LENGTH + VLAN_HEADER_LENGTH); + final short tci = bb.getShort(); + eth.setPriorityCode((byte) (tci >> 13 & 0x07)); + eth.setVlanID((short) (tci & 0x0fff)); + ethType = bb.getShort(); + } else { + eth.setVlanID(Ethernet.VLAN_UNTAGGED); + } + eth.setEtherType(ethType); + + IPacket payload; + Deserializer deserializer; + if (Ethernet.ETHERTYPE_DESERIALIZER_MAP.containsKey(ethType)) { + deserializer = Ethernet.ETHERTYPE_DESERIALIZER_MAP.get(ethType); + } else { + deserializer = Data.deserializer(); + } + payload = deserializer.deserialize(data, bb.position(), + bb.limit() - bb.position()); + payload.setParent(eth); + eth.setPayload(payload); + + return eth; + }; + } + } diff --git a/utils/misc/src/main/java/org/onlab/packet/ICMP.java b/utils/misc/src/main/java/org/onlab/packet/ICMP.java index 46814cd136..d07a9ba483 100644 --- a/utils/misc/src/main/java/org/onlab/packet/ICMP.java +++ b/utils/misc/src/main/java/org/onlab/packet/ICMP.java @@ -20,6 +20,8 @@ package org.onlab.packet; import java.nio.ByteBuffer; +import static org.onlab.packet.PacketUtils.*; + /** * Implements ICMP packet format. * @@ -33,6 +35,8 @@ public class ICMP extends BasePacket { public static final byte TYPE_ECHO_REPLY = 0x00; public static final byte SUBTYPE_ECHO_REPLY = 0x00; + public static final short ICMP_HEADER_LENGTH = 4; + /** * @return the icmpType */ @@ -134,6 +138,21 @@ public class ICMP extends BasePacket { return data; } + @Override + public IPacket deserialize(final byte[] data, final int offset, + final int length) { + final ByteBuffer bb = ByteBuffer.wrap(data, offset, length); + this.icmpType = bb.get(); + this.icmpCode = bb.get(); + this.checksum = bb.getShort(); + + this.payload = new Data(); + this.payload = this.payload.deserialize(data, bb.position(), bb.limit() + - bb.position()); + this.payload.setParent(this); + return this; + } + /* * (non-Javadoc) * @@ -178,18 +197,27 @@ public class ICMP extends BasePacket { return true; } - @Override - public IPacket deserialize(final byte[] data, final int offset, - final int length) { - final ByteBuffer bb = ByteBuffer.wrap(data, offset, length); - this.icmpType = bb.get(); - this.icmpCode = bb.get(); - this.checksum = bb.getShort(); + /** + * Deserializer function for ICMP packets. + * + * @return deserializer function + */ + public static Deserializer deserializer() { + return (data, offset, length) -> { + checkInput(data, offset, length, ICMP_HEADER_LENGTH); - this.payload = new Data(); - this.payload = this.payload.deserialize(data, bb.position(), bb.limit() - - bb.position()); - this.payload.setParent(this); - return this; + ICMP icmp = new ICMP(); + + final ByteBuffer bb = ByteBuffer.wrap(data, offset, length); + icmp.icmpType = bb.get(); + icmp.icmpCode = bb.get(); + icmp.checksum = bb.getShort(); + + icmp.payload = Data.deserializer() + .deserialize(data, bb.position(), bb.limit() + - bb.position()); + icmp.payload.setParent(icmp); + return icmp; + }; } } diff --git a/utils/misc/src/main/java/org/onlab/packet/ICMP6.java b/utils/misc/src/main/java/org/onlab/packet/ICMP6.java index 7c9b4b989c..c8981302ce 100644 --- a/utils/misc/src/main/java/org/onlab/packet/ICMP6.java +++ b/utils/misc/src/main/java/org/onlab/packet/ICMP6.java @@ -24,10 +24,13 @@ import org.onlab.packet.ndp.NeighborSolicitation; import org.onlab.packet.ndp.Redirect; import org.onlab.packet.ndp.RouterAdvertisement; import org.onlab.packet.ndp.RouterSolicitation; + import java.nio.ByteBuffer; import java.util.HashMap; import java.util.Map; +import static org.onlab.packet.PacketUtils.checkInput; + /** * Implements ICMPv6 packet format. (RFC 4443) */ @@ -96,15 +99,15 @@ public class ICMP6 extends BasePacket { /** Unrecognized IPv6 option encountered. */ public static final byte IPV6_OPT_ERR = (byte) 0x01; - public static final Map> PROTOCOL_CLASS_MAP = + public static final Map> TYPE_DESERIALIZER_MAP = new HashMap<>(); static { - ICMP6.PROTOCOL_CLASS_MAP.put(ICMP6.ROUTER_SOLICITATION, RouterSolicitation.class); - ICMP6.PROTOCOL_CLASS_MAP.put(ICMP6.ROUTER_ADVERTISEMENT, RouterAdvertisement.class); - ICMP6.PROTOCOL_CLASS_MAP.put(ICMP6.NEIGHBOR_SOLICITATION, NeighborSolicitation.class); - ICMP6.PROTOCOL_CLASS_MAP.put(ICMP6.NEIGHBOR_ADVERTISEMENT, NeighborAdvertisement.class); - ICMP6.PROTOCOL_CLASS_MAP.put(ICMP6.REDIRECT, Redirect.class); + ICMP6.TYPE_DESERIALIZER_MAP.put(ICMP6.ROUTER_SOLICITATION, RouterSolicitation.deserializer()); + ICMP6.TYPE_DESERIALIZER_MAP.put(ICMP6.ROUTER_ADVERTISEMENT, RouterAdvertisement.deserializer()); + ICMP6.TYPE_DESERIALIZER_MAP.put(ICMP6.NEIGHBOR_SOLICITATION, NeighborSolicitation.deserializer()); + ICMP6.TYPE_DESERIALIZER_MAP.put(ICMP6.NEIGHBOR_ADVERTISEMENT, NeighborAdvertisement.deserializer()); + ICMP6.TYPE_DESERIALIZER_MAP.put(ICMP6.REDIRECT, Redirect.deserializer()); } protected byte icmpType; @@ -261,6 +264,31 @@ public class ICMP6 extends BasePacket { return data; } + @Override + public IPacket deserialize(final byte[] data, final int offset, + final int length) { + final ByteBuffer bb = ByteBuffer.wrap(data, offset, length); + this.icmpType = bb.get(); + this.icmpCode = bb.get(); + this.checksum = bb.getShort(); + + Deserializer deserializer; + if (ICMP6.TYPE_DESERIALIZER_MAP.containsKey(icmpType)) { + deserializer = TYPE_DESERIALIZER_MAP.get(icmpType); + } else { + deserializer = Data.deserializer(); + } + try { + this.payload = deserializer.deserialize(data, bb.position(), + bb.limit() - bb.position()); + this.payload.setParent(this); + } catch (DeserializationException e) { + return this; + } + + return this; + } + /* * (non-Javadoc) * @@ -305,31 +333,34 @@ public class ICMP6 extends BasePacket { return true; } - @Override - public IPacket deserialize(final byte[] data, final int offset, - final int length) { - final ByteBuffer bb = ByteBuffer.wrap(data, offset, length); - this.icmpType = bb.get(); - this.icmpCode = bb.get(); - this.checksum = bb.getShort(); + /** + * Deserializer function for ICMPv6 packets. + * + * @return deserializer function + */ + public static Deserializer deserializer() { + return (data, offset, length) -> { + checkInput(data, offset, length, HEADER_LENGTH); - IPacket payload; - if (ICMP6.PROTOCOL_CLASS_MAP.containsKey(this.icmpType)) { - final Class clazz = ICMP6.PROTOCOL_CLASS_MAP - .get(this.icmpType); - try { - payload = clazz.newInstance(); - } catch (final Exception e) { - throw new RuntimeException( - "Error parsing payload for ICMP6 packet", e); + ICMP6 icmp6 = new ICMP6(); + + ByteBuffer bb = ByteBuffer.wrap(data, offset, length); + + icmp6.icmpType = bb.get(); + icmp6.icmpCode = bb.get(); + icmp6.checksum = bb.getShort(); + + Deserializer deserializer; + if (ICMP6.TYPE_DESERIALIZER_MAP.containsKey(icmp6.icmpType)) { + deserializer = TYPE_DESERIALIZER_MAP.get(icmp6.icmpType); + } else { + deserializer = Data.deserializer(); } - } else { - payload = new Data(); - } - this.payload = payload.deserialize(data, bb.position(), - bb.limit() - bb.position()); - this.payload.setParent(this); + icmp6.payload = deserializer.deserialize(data, bb.position(), + bb.limit() - bb.position()); + icmp6.payload.setParent(icmp6); - return this; + return icmp6; + }; } } diff --git a/utils/misc/src/main/java/org/onlab/packet/IPacket.java b/utils/misc/src/main/java/org/onlab/packet/IPacket.java index ac6ae6098f..38684ebc46 100644 --- a/utils/misc/src/main/java/org/onlab/packet/IPacket.java +++ b/utils/misc/src/main/java/org/onlab/packet/IPacket.java @@ -64,6 +64,11 @@ public interface IPacket { /** * Deserializes this packet layer and all possible payloads. * + * NOTE: This method has been deprecated and will be removed in a future + * release. It is now recommended to use the Deserializer function provided + * by the deserialize() method on each packet to deserialize them. The + * Deserializer functions are robust to malformed input. + * * @param data bytes to deserialize * @param offset * offset to start deserializing from @@ -71,6 +76,7 @@ public interface IPacket { * length of the data to deserialize * @return the deserialized data */ + @Deprecated IPacket deserialize(byte[] data, int offset, int length); /** diff --git a/utils/misc/src/main/java/org/onlab/packet/IPv4.java b/utils/misc/src/main/java/org/onlab/packet/IPv4.java index d1563ebbff..0d75245730 100644 --- a/utils/misc/src/main/java/org/onlab/packet/IPv4.java +++ b/utils/misc/src/main/java/org/onlab/packet/IPv4.java @@ -25,6 +25,8 @@ import java.util.Collection; import java.util.HashMap; import java.util.Map; +import static org.onlab.packet.PacketUtils.*; + /** * */ @@ -32,19 +34,21 @@ public class IPv4 extends BasePacket { public static final byte PROTOCOL_ICMP = 0x1; public static final byte PROTOCOL_TCP = 0x6; public static final byte PROTOCOL_UDP = 0x11; - public static final Map> PROTOCOL_CLASS_MAP = + public static final Map> PROTOCOL_DESERIALIZER_MAP = new HashMap<>(); static { - IPv4.PROTOCOL_CLASS_MAP.put(IPv4.PROTOCOL_ICMP, ICMP.class); - IPv4.PROTOCOL_CLASS_MAP.put(IPv4.PROTOCOL_TCP, TCP.class); - IPv4.PROTOCOL_CLASS_MAP.put(IPv4.PROTOCOL_UDP, UDP.class); + IPv4.PROTOCOL_DESERIALIZER_MAP.put(IPv4.PROTOCOL_ICMP, ICMP.deserializer()); + IPv4.PROTOCOL_DESERIALIZER_MAP.put(IPv4.PROTOCOL_TCP, TCP.deserializer()); + IPv4.PROTOCOL_DESERIALIZER_MAP.put(IPv4.PROTOCOL_UDP, UDP.deserializer()); } private static final byte DSCP_MASK = 0x3f; private static final byte DSCP_OFFSET = 2; private static final byte ECN_MASK = 0x3; + private static final short HEADER_LENGTH = 20; + protected byte version; protected byte headerLength; protected byte diffServ; @@ -414,7 +418,7 @@ s */ @Override public IPacket deserialize(final byte[] data, final int offset, - final int length) { + final int length) { final ByteBuffer bb = ByteBuffer.wrap(data, offset, length); short sscratch; @@ -439,29 +443,26 @@ s */ bb.get(this.options); } - IPacket payload; - if (IPv4.PROTOCOL_CLASS_MAP.containsKey(this.protocol)) { - final Class clazz = IPv4.PROTOCOL_CLASS_MAP - .get(this.protocol); - try { - payload = clazz.newInstance(); - } catch (final Exception e) { - throw new RuntimeException( - "Error parsing payload for IPv4 packet", e); - } - } else { - payload = new Data(); - } - this.payload = payload.deserialize(data, bb.position(), - bb.limit() - bb.position()); - this.payload.setParent(this); - if (this.totalLength != length) { this.isTruncated = true; } else { this.isTruncated = false; } + Deserializer deserializer; + if (IPv4.PROTOCOL_DESERIALIZER_MAP.containsKey(this.protocol)) { + deserializer = IPv4.PROTOCOL_DESERIALIZER_MAP.get(this.protocol); + } else { + deserializer = Data.deserializer(); + } + try { + this.payload = deserializer.deserialize(data, bb.position(), + bb.limit() - bb.position()); + this.payload.setParent(this); + } catch (DeserializationException e) { + return this; + } + return this; } @@ -669,4 +670,60 @@ s */ } return true; } + + /** + * Deserializer function for IPv4 packets. + * + * @return deserializer function + */ + public static Deserializer deserializer() { + return (data, offset, length) -> { + checkInput(data, offset, length, HEADER_LENGTH); + + IPv4 ipv4 = new IPv4(); + + final ByteBuffer bb = ByteBuffer.wrap(data, offset, length); + + byte versionByte = bb.get(); + ipv4.headerLength = (byte) (versionByte & 0xf); + ipv4.setVersion((byte) (versionByte >> 4 & 0xf)); + ipv4.setDiffServ(bb.get()); + ipv4.totalLength = bb.getShort(); + ipv4.identification = bb.getShort(); + short flagsFragment = bb.getShort(); + ipv4.flags = (byte) (flagsFragment >> 13 & 0x7); + ipv4.fragmentOffset = (short) (flagsFragment & 0x1fff); + ipv4.ttl = bb.get(); + ipv4.protocol = bb.get(); + ipv4.checksum = bb.getShort(); + ipv4.sourceAddress = bb.getInt(); + ipv4.destinationAddress = bb.getInt(); + + if (ipv4.headerLength > 5) { + checkHeaderLength(length, ipv4.headerLength * 4); + + int optionsLength = (ipv4.headerLength - 5) * 4; + ipv4.options = new byte[optionsLength]; + bb.get(ipv4.options); + } + + Deserializer deserializer; + if (IPv4.PROTOCOL_DESERIALIZER_MAP.containsKey(ipv4.protocol)) { + deserializer = IPv4.PROTOCOL_DESERIALIZER_MAP.get(ipv4.protocol); + } else { + deserializer = Data.deserializer(); + } + ipv4.payload = deserializer.deserialize(data, bb.position(), + bb.limit() - bb.position()); + ipv4.payload.setParent(ipv4); + + if (ipv4.totalLength != length) { + ipv4.isTruncated = true; + } else { + ipv4.isTruncated = false; + } + + return ipv4; + }; + } } diff --git a/utils/misc/src/main/java/org/onlab/packet/IPv6.java b/utils/misc/src/main/java/org/onlab/packet/IPv6.java index 3bb35c539e..2e59632adb 100644 --- a/utils/misc/src/main/java/org/onlab/packet/IPv6.java +++ b/utils/misc/src/main/java/org/onlab/packet/IPv6.java @@ -22,14 +22,17 @@ import org.onlab.packet.ipv6.Authentication; import org.onlab.packet.ipv6.DestinationOptions; import org.onlab.packet.ipv6.EncapSecurityPayload; import org.onlab.packet.ipv6.Fragment; -import org.onlab.packet.ipv6.IExtensionHeader; import org.onlab.packet.ipv6.HopByHopOptions; +import org.onlab.packet.ipv6.IExtensionHeader; import org.onlab.packet.ipv6.Routing; + import java.nio.ByteBuffer; import java.util.Arrays; import java.util.HashMap; import java.util.Map; +import static org.onlab.packet.PacketUtils.checkInput; + /** * Implements IPv6 packet format. (RFC 2460) */ @@ -47,19 +50,19 @@ public class IPv6 extends BasePacket implements IExtensionHeader { public static final byte PROTOCOL_DSTOPT = 0x3C; - public static final Map> PROTOCOL_CLASS_MAP = + public static final Map> PROTOCOL_DESERIALIZER_MAP = new HashMap<>(); static { - IPv6.PROTOCOL_CLASS_MAP.put(IPv6.PROTOCOL_ICMP6, ICMP6.class); - IPv6.PROTOCOL_CLASS_MAP.put(IPv6.PROTOCOL_TCP, TCP.class); - IPv6.PROTOCOL_CLASS_MAP.put(IPv6.PROTOCOL_UDP, UDP.class); - IPv6.PROTOCOL_CLASS_MAP.put(IPv6.PROTOCOL_HOPOPT, HopByHopOptions.class); - IPv6.PROTOCOL_CLASS_MAP.put(IPv6.PROTOCOL_ROUTING, Routing.class); - IPv6.PROTOCOL_CLASS_MAP.put(IPv6.PROTOCOL_FRAG, Fragment.class); - IPv6.PROTOCOL_CLASS_MAP.put(IPv6.PROTOCOL_ESP, EncapSecurityPayload.class); - IPv6.PROTOCOL_CLASS_MAP.put(IPv6.PROTOCOL_AH, Authentication.class); - IPv6.PROTOCOL_CLASS_MAP.put(IPv6.PROTOCOL_DSTOPT, DestinationOptions.class); + IPv6.PROTOCOL_DESERIALIZER_MAP.put(IPv6.PROTOCOL_ICMP6, ICMP6.deserializer()); + IPv6.PROTOCOL_DESERIALIZER_MAP.put(IPv6.PROTOCOL_TCP, TCP.deserializer()); + IPv6.PROTOCOL_DESERIALIZER_MAP.put(IPv6.PROTOCOL_UDP, UDP.deserializer()); + IPv6.PROTOCOL_DESERIALIZER_MAP.put(IPv6.PROTOCOL_HOPOPT, HopByHopOptions.deserializer()); + IPv6.PROTOCOL_DESERIALIZER_MAP.put(IPv6.PROTOCOL_ROUTING, Routing.deserializer()); + IPv6.PROTOCOL_DESERIALIZER_MAP.put(IPv6.PROTOCOL_FRAG, Fragment.deserializer()); + IPv6.PROTOCOL_DESERIALIZER_MAP.put(IPv6.PROTOCOL_ESP, EncapSecurityPayload.deserializer()); + IPv6.PROTOCOL_DESERIALIZER_MAP.put(IPv6.PROTOCOL_AH, Authentication.deserializer()); + IPv6.PROTOCOL_DESERIALIZER_MAP.put(IPv6.PROTOCOL_DSTOPT, DestinationOptions.deserializer()); } protected byte version; @@ -256,22 +259,19 @@ public class IPv6 extends BasePacket implements IExtensionHeader { bb.get(this.sourceAddress, 0, Ip6Address.BYTE_LENGTH); bb.get(this.destinationAddress, 0, Ip6Address.BYTE_LENGTH); - IPacket payload; - if (IPv6.PROTOCOL_CLASS_MAP.containsKey(this.nextHeader)) { - final Class clazz = IPv6.PROTOCOL_CLASS_MAP - .get(this.nextHeader); - try { - payload = clazz.newInstance(); - } catch (final Exception e) { - throw new RuntimeException( - "Error parsing payload for IPv6 packet", e); - } + Deserializer deserializer; + if (IPv6.PROTOCOL_DESERIALIZER_MAP.containsKey(this.nextHeader)) { + deserializer = IPv6.PROTOCOL_DESERIALIZER_MAP.get(this.nextHeader); } else { - payload = new Data(); + deserializer = Data.deserializer(); + } + try { + this.payload = deserializer.deserialize(data, bb.position(), + bb.limit() - bb.position()); + this.payload.setParent(this); + } catch (DeserializationException e) { + return this; } - this.payload = payload.deserialize(data, bb.position(), - bb.limit() - bb.position()); - this.payload.setParent(this); return this; } @@ -343,4 +343,42 @@ public class IPv6 extends BasePacket implements IExtensionHeader { } return true; } + + /** + * Deserializer function for IPv6 packets. + * + * @return deserializer function + */ + public static Deserializer deserializer() { + return (data, offset, length) -> { + checkInput(data, offset, length, FIXED_HEADER_LENGTH); + + IPv6 ipv6 = new IPv6(); + + ByteBuffer bb = ByteBuffer.wrap(data, offset, length); + + int iscratch = bb.getInt(); + + ipv6.version = (byte) (iscratch >> 28 & 0xf); + ipv6.trafficClass = (byte) (iscratch >> 20 & 0xff); + ipv6.flowLabel = iscratch & 0xfffff; + ipv6.payloadLength = bb.getShort(); + ipv6.nextHeader = bb.get(); + ipv6.hopLimit = bb.get(); + bb.get(ipv6.sourceAddress, 0, Ip6Address.BYTE_LENGTH); + bb.get(ipv6.destinationAddress, 0, Ip6Address.BYTE_LENGTH); + + Deserializer deserializer; + if (IPv6.PROTOCOL_DESERIALIZER_MAP.containsKey(ipv6.nextHeader)) { + deserializer = IPv6.PROTOCOL_DESERIALIZER_MAP.get(ipv6.nextHeader); + } else { + deserializer = Data.deserializer(); + } + ipv6.payload = deserializer.deserialize(data, bb.position(), + bb.limit() - bb.position()); + ipv6.payload.setParent(ipv6); + + return ipv6; + }; + } } diff --git a/utils/misc/src/main/java/org/onlab/packet/LLC.java b/utils/misc/src/main/java/org/onlab/packet/LLC.java index 035f396d37..78b4f3fa8f 100644 --- a/utils/misc/src/main/java/org/onlab/packet/LLC.java +++ b/utils/misc/src/main/java/org/onlab/packet/LLC.java @@ -20,6 +20,8 @@ package org.onlab.packet; import java.nio.ByteBuffer; +import static org.onlab.packet.PacketUtils.*; + /** * This class represents an Link Local Control header that is used in Ethernet * 802.3. @@ -27,6 +29,9 @@ import java.nio.ByteBuffer; * */ public class LLC extends BasePacket { + + public static final byte LLC_HEADER_LENGTH = 3; + private byte dsap = 0; private byte ssap = 0; private byte ctrl = 0; @@ -67,11 +72,31 @@ public class LLC extends BasePacket { @Override public IPacket deserialize(final byte[] data, final int offset, - final int length) { + final int length) { final ByteBuffer bb = ByteBuffer.wrap(data, offset, length); this.dsap = bb.get(); this.ssap = bb.get(); this.ctrl = bb.get(); return this; } + + /** + * Deserializer function for LLC packets. + * + * @return deserializer function + */ + public static Deserializer deserializer() { + return (data, offset, length) -> { + checkInput(data, offset, length, LLC_HEADER_LENGTH); + + ByteBuffer bb = ByteBuffer.wrap(data, offset, length); + LLC llc = new LLC(); + + llc.dsap = bb.get(); + llc.ssap = bb.get(); + llc.ctrl = bb.get(); + + return llc; + }; + } } diff --git a/utils/misc/src/main/java/org/onlab/packet/LLDP.java b/utils/misc/src/main/java/org/onlab/packet/LLDP.java index 339bc3a7a5..ae9d717393 100644 --- a/utils/misc/src/main/java/org/onlab/packet/LLDP.java +++ b/utils/misc/src/main/java/org/onlab/packet/LLDP.java @@ -20,13 +20,26 @@ package org.onlab.packet; import java.nio.ByteBuffer; -import java.util.ArrayList; +import java.util.LinkedList; import java.util.List; +import static org.onlab.packet.PacketUtils.*; + /** * */ public class LLDP extends BasePacket { + public static final byte CHASSIS_TLV_TYPE = 1; + public static final short CHASSIS_TLV_SIZE = 7; + public static final byte CHASSIS_TLV_SUBTYPE = 4; + + public static final byte PORT_TLV_TYPE = 2; + public static final short PORT_TLV_SIZE = 5; + public static final byte PORT_TLV_SUBTYPE = 2; + + public static final byte TTL_TLV_TYPE = 3; + public static final short TTL_TLV_SIZE = 2; + protected LLDPTLV chassisId; protected LLDPTLV portId; protected LLDPTLV ttl; @@ -34,7 +47,7 @@ public class LLDP extends BasePacket { protected short ethType; public LLDP() { - this.optionalTLVList = new ArrayList(); + this.optionalTLVList = new LinkedList<>(); this.ethType = Ethernet.TYPE_LLDP; } @@ -134,11 +147,15 @@ public class LLDP extends BasePacket { @Override public IPacket deserialize(final byte[] data, final int offset, - final int length) { + final int length) { final ByteBuffer bb = ByteBuffer.wrap(data, offset, length); LLDPTLV tlv; do { - tlv = new LLDPOrganizationalTLV().deserialize(bb); + try { + tlv = new LLDPOrganizationalTLV().deserialize(bb); + } catch (DeserializationException e) { + break; + } // if there was a failure to deserialize stop processing TLVs if (tlv == null) { @@ -227,4 +244,57 @@ public class LLDP extends BasePacket { } return true; } + + /** + * Deserializer function for LLDP packets. + * + * @return deserializer function + */ + public static Deserializer deserializer() { + return (data, offset, length) -> { + checkInput(data, offset, length, 0); + + LLDP lldp = new LLDP(); + + int currentIndex = 0; + + ByteBuffer bb = ByteBuffer.wrap(data, offset, length); + LLDPTLV tlv; + do { + // Each new TLV must be a minimum of 2 bytes + // (containing the type and length fields). + currentIndex += 2; + checkHeaderLength(length, currentIndex); + + tlv = new LLDPOrganizationalTLV().deserialize(bb); + + // if there was a failure to deserialize stop processing TLVs + if (tlv == null) { + break; + } + switch (tlv.getType()) { + case 0x0: + // can throw this one away, it's just an end delimiter + break; + case 0x1: + lldp.chassisId = tlv; + break; + case 0x2: + lldp.portId = tlv; + break; + case 0x3: + lldp.ttl = tlv; + break; + default: + lldp.optionalTLVList.add(tlv); + break; + } + + currentIndex += tlv.getLength(); + } while (tlv.getType() != 0); + + return lldp; + }; + } + } diff --git a/utils/misc/src/main/java/org/onlab/packet/LLDPOrganizationalTLV.java b/utils/misc/src/main/java/org/onlab/packet/LLDPOrganizationalTLV.java index 497e0ad716..bedf439f54 100644 --- a/utils/misc/src/main/java/org/onlab/packet/LLDPOrganizationalTLV.java +++ b/utils/misc/src/main/java/org/onlab/packet/LLDPOrganizationalTLV.java @@ -154,10 +154,15 @@ public class LLDPOrganizationalTLV extends LLDPTLV { } @Override - public LLDPTLV deserialize(final ByteBuffer bb) { - LLDPTLV tlv = super.deserialize(bb); - if (tlv.getType() != LLDPOrganizationalTLV.ORGANIZATIONAL_TLV_TYPE) { - return tlv; + public LLDPTLV deserialize(final ByteBuffer bb) throws DeserializationException { + super.deserialize(bb); + if (this.getType() != LLDPOrganizationalTLV.ORGANIZATIONAL_TLV_TYPE) { + return this; + } + + if (this.getLength() <= OUI_LENGTH + SUBTYPE_LENGTH) { + throw new DeserializationException( + "TLV length is less than required for organizational TLV"); } final ByteBuffer optionalField = ByteBuffer.wrap(this.value); diff --git a/utils/misc/src/main/java/org/onlab/packet/LLDPTLV.java b/utils/misc/src/main/java/org/onlab/packet/LLDPTLV.java index b5fe83313b..77efe1b719 100644 --- a/utils/misc/src/main/java/org/onlab/packet/LLDPTLV.java +++ b/utils/misc/src/main/java/org/onlab/packet/LLDPTLV.java @@ -95,18 +95,23 @@ public class LLDPTLV { return data; } - public LLDPTLV deserialize(final ByteBuffer bb) { - short sscratch; - sscratch = bb.getShort(); - this.type = (byte) (sscratch >> 9 & 0x7f); - this.length = (short) (sscratch & 0x1ff); + public LLDPTLV deserialize(final ByteBuffer bb) throws DeserializationException { + if (bb.remaining() < 2) { + throw new DeserializationException( + "Not enough bytes to deserialize TLV type and length"); + } + short typeLength; + typeLength = bb.getShort(); + this.type = (byte) (typeLength >> 9 & 0x7f); + this.length = (short) (typeLength & 0x1ff); if (this.length > 0) { this.value = new byte[this.length]; // if there is an underrun just toss the TLV if (bb.remaining() < this.length) { - return null; + throw new DeserializationException( + "Remaining bytes are less then the length of the TLV"); } bb.get(this.value); } diff --git a/utils/misc/src/main/java/org/onlab/packet/MPLS.java b/utils/misc/src/main/java/org/onlab/packet/MPLS.java index 4ba6a0c756..47dbeed2f7 100644 --- a/utils/misc/src/main/java/org/onlab/packet/MPLS.java +++ b/utils/misc/src/main/java/org/onlab/packet/MPLS.java @@ -4,16 +4,19 @@ import java.nio.ByteBuffer; import java.util.HashMap; import java.util.Map; +import static org.onlab.packet.PacketUtils.checkInput; + public class MPLS extends BasePacket { - public static final int ADDRESS_LENGTH = 4; + public static final int HEADER_LENGTH = 4; + public static final byte PROTOCOL_IPV4 = 0x1; public static final byte PROTOCOL_MPLS = 0x6; - public static final Map> PROTOCOL_CLASS_MAP; + static Map> protocolDeserializerMap + = new HashMap<>(); static { - PROTOCOL_CLASS_MAP = new HashMap>(); - PROTOCOL_CLASS_MAP.put(PROTOCOL_IPV4, IPv4.class); - PROTOCOL_CLASS_MAP.put(PROTOCOL_MPLS, MPLS.class); + protocolDeserializerMap.put(PROTOCOL_IPV4, IPv4.deserializer()); + protocolDeserializerMap.put(PROTOCOL_MPLS, MPLS.deserializer()); } protected int label; //20bits @@ -59,19 +62,18 @@ public class MPLS extends BasePacket { this.bos = (byte) (mplsheader & 0x000000ff); this.protocol = (this.bos == 1) ? PROTOCOL_IPV4 : PROTOCOL_MPLS; - IPacket payload; - if (IPv4.PROTOCOL_CLASS_MAP.containsKey(this.protocol)) { - Class clazz = IPv4.PROTOCOL_CLASS_MAP.get(this.protocol); - try { - payload = clazz.newInstance(); - } catch (Exception e) { - throw new RuntimeException("Error parsing payload for MPLS packet", e); - } + Deserializer deserializer; + if (protocolDeserializerMap.containsKey(this.protocol)) { + deserializer = protocolDeserializerMap.get(this.protocol); } else { - payload = new Data(); + deserializer = Data.deserializer(); + } + try { + this.payload = deserializer.deserialize(data, bb.position(), bb.limit() - bb.position()); + this.payload.setParent(this); + } catch (DeserializationException e) { + return this; } - this.payload = payload.deserialize(data, bb.position(), bb.limit() - bb.position()); - this.payload.setParent(this); return this; } @@ -112,4 +114,34 @@ public class MPLS extends BasePacket { this.ttl = ttl; } + /** + * Deserializer function for MPLS packets. + * + * @return deserializer function + */ + public static Deserializer deserializer() { + return (data, offset, length) -> { + checkInput(data, offset, length, HEADER_LENGTH); + + MPLS mpls = new MPLS(); + ByteBuffer bb = ByteBuffer.wrap(data, offset, length); + + int mplsheader = bb.getInt(); + mpls.label = ((mplsheader & 0xfffff000) >>> 12); + mpls.bos = (byte) ((mplsheader & 0x00000100) >> 8); + mpls.ttl = (byte) (mplsheader & 0x000000ff); + mpls.protocol = (mpls.bos == 1) ? PROTOCOL_IPV4 : PROTOCOL_MPLS; + + Deserializer deserializer; + if (protocolDeserializerMap.containsKey(mpls.protocol)) { + deserializer = protocolDeserializerMap.get(mpls.protocol); + } else { + deserializer = Data.deserializer(); + } + mpls.payload = deserializer.deserialize(data, bb.position(), bb.limit() - bb.position()); + mpls.payload.setParent(mpls); + + return mpls; + }; + } } diff --git a/utils/misc/src/main/java/org/onlab/packet/PacketUtils.java b/utils/misc/src/main/java/org/onlab/packet/PacketUtils.java new file mode 100644 index 0000000000..c3bede2fd8 --- /dev/null +++ b/utils/misc/src/main/java/org/onlab/packet/PacketUtils.java @@ -0,0 +1,84 @@ +/* + * Copyright 2015 Open Networking Laboratory + * + * 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 org.onlab.packet; + +import static com.google.common.base.Preconditions.checkNotNull; + +/** + * Utilities for working with packet headers. + */ +public final class PacketUtils { + + private PacketUtils() { + } + + /** + * Check the length of the input buffer is appropriate given the offset and + * length parameters. + * + * @param byteLength length of the input buffer array + * @param offset offset given to begin reading bytes from + * @param length length given to read up until + * @throws DeserializationException if the input parameters don't match up (i.e + * we can't read that many bytes from the buffer at the given offest) + */ + public static void checkBufferLength(int byteLength, int offset, int length) + throws DeserializationException { + boolean ok = (offset >= 0 && offset < byteLength); + ok = ok & (length >= 0 && offset + length <= byteLength); + + if (!ok) { + throw new DeserializationException("Unable to read " + length + " bytes from a " + + byteLength + " byte array starting at offset " + offset); + } + } + + /** + * Check that there are enough bytes in the buffer to read some number of + * bytes that we need to read a full header. + * + * @param givenLength given size of the buffer + * @param requiredLength number of bytes we need to read some header fully + * @throws DeserializationException if there aren't enough bytes + */ + public static void checkHeaderLength(int givenLength, int requiredLength) + throws DeserializationException { + if (requiredLength > givenLength) { + throw new DeserializationException(requiredLength + + " bytes are needed to continue deserialization, however only " + + givenLength + " remain in buffer"); + } + } + + /** + * Check the input parameters are sane and there's enough bytes to read + * the required length. + * + * @param data input byte buffer + * @param offset offset of the start of the header + * @param length length given to deserialize the header + * @param requiredLength length needed to deserialize header + * @throws DeserializationException if we're unable to deserialize the + * packet based on the input parameters + */ + public static void checkInput(byte[] data, int offset, int length, int requiredLength) + throws DeserializationException { + checkNotNull(data); + checkBufferLength(data.length, offset, length); + checkHeaderLength(length, requiredLength); + } +} diff --git a/utils/misc/src/main/java/org/onlab/packet/TCP.java b/utils/misc/src/main/java/org/onlab/packet/TCP.java index 3b92c83fc9..b13b53c488 100644 --- a/utils/misc/src/main/java/org/onlab/packet/TCP.java +++ b/utils/misc/src/main/java/org/onlab/packet/TCP.java @@ -20,11 +20,16 @@ package org.onlab.packet; import java.nio.ByteBuffer; +import static org.onlab.packet.PacketUtils.*; + /** * Implements TCP packet format. */ public class TCP extends BasePacket { + + private static final short TCP_HEADER_LENGTH = 20; + protected short sourcePort; protected short destinationPort; protected int sequence; @@ -339,6 +344,40 @@ public class TCP extends BasePacket { return data; } + @Override + public IPacket deserialize(final byte[] data, final int offset, + final int length) { + final ByteBuffer bb = ByteBuffer.wrap(data, offset, length); + this.sourcePort = bb.getShort(); + this.destinationPort = bb.getShort(); + this.sequence = bb.getInt(); + this.acknowledge = bb.getInt(); + this.flags = bb.getShort(); + this.dataOffset = (byte) (this.flags >> 12 & 0xf); + this.flags = (short) (this.flags & 0x1ff); + this.windowSize = bb.getShort(); + this.checksum = bb.getShort(); + this.urgentPointer = bb.getShort(); + if (this.dataOffset > 5) { + int optLength = (this.dataOffset << 2) - 20; + if (bb.limit() < bb.position() + optLength) { + optLength = bb.limit() - bb.position(); + } + try { + this.options = new byte[optLength]; + bb.get(this.options, 0, optLength); + } catch (final IndexOutOfBoundsException e) { + this.options = null; + } + } + + this.payload = new Data(); + this.payload = this.payload.deserialize(data, bb.position(), bb.limit() + - bb.position()); + this.payload.setParent(this); + return this; + } + /* * (non-Javadoc) * @@ -384,37 +423,39 @@ public class TCP extends BasePacket { && (this.dataOffset == 5 || this.options.equals(other.options)); } - @Override - public IPacket deserialize(final byte[] data, final int offset, - final int length) { - final ByteBuffer bb = ByteBuffer.wrap(data, offset, length); - this.sourcePort = bb.getShort(); - this.destinationPort = bb.getShort(); - this.sequence = bb.getInt(); - this.acknowledge = bb.getInt(); - this.flags = bb.getShort(); - this.dataOffset = (byte) (this.flags >> 12 & 0xf); - this.flags = (short) (this.flags & 0x1ff); - this.windowSize = bb.getShort(); - this.checksum = bb.getShort(); - this.urgentPointer = bb.getShort(); - if (this.dataOffset > 5) { - int optLength = (this.dataOffset << 2) - 20; - if (bb.limit() < bb.position() + optLength) { - optLength = bb.limit() - bb.position(); - } - try { - this.options = new byte[optLength]; - bb.get(this.options, 0, optLength); - } catch (final IndexOutOfBoundsException e) { - this.options = null; - } - } + /** + * Deserializer function for TCP packets. + * + * @return deserializer function + */ + public static Deserializer deserializer() { + return (data, offset, length) -> { + checkInput(data, offset, length, TCP_HEADER_LENGTH); - this.payload = new Data(); - this.payload = this.payload.deserialize(data, bb.position(), bb.limit() - - bb.position()); - this.payload.setParent(this); - return this; + TCP tcp = new TCP(); + + final ByteBuffer bb = ByteBuffer.wrap(data, offset, length); + tcp.sourcePort = bb.getShort(); + tcp.destinationPort = bb.getShort(); + tcp.sequence = bb.getInt(); + tcp.acknowledge = bb.getInt(); + tcp.flags = bb.getShort(); + tcp.dataOffset = (byte) (tcp.flags >> 12 & 0xf); + tcp.flags = (short) (tcp.flags & 0x1ff); + tcp.windowSize = bb.getShort(); + tcp.checksum = bb.getShort(); + tcp.urgentPointer = bb.getShort(); + if (tcp.dataOffset > 5) { + int optLength = (tcp.dataOffset << 2) - 20; + checkHeaderLength(length, TCP_HEADER_LENGTH + tcp.dataOffset); + tcp.options = new byte[optLength]; + bb.get(tcp.options, 0, optLength); + } + + tcp.payload = Data.deserializer() + .deserialize(data, bb.position(), bb.limit() - bb.position()); + tcp.payload.setParent(tcp); + return tcp; + }; } } diff --git a/utils/misc/src/main/java/org/onlab/packet/UDP.java b/utils/misc/src/main/java/org/onlab/packet/UDP.java index c743f09c51..32432e6edb 100644 --- a/utils/misc/src/main/java/org/onlab/packet/UDP.java +++ b/utils/misc/src/main/java/org/onlab/packet/UDP.java @@ -22,23 +22,27 @@ import java.nio.ByteBuffer; import java.util.HashMap; import java.util.Map; +import static org.onlab.packet.PacketUtils.*; + /** * */ public class UDP extends BasePacket { - public static final Map> DECODE_MAP = + public static final Map> PORT_DESERIALIZER_MAP = new HashMap<>(); public static final short DHCP_SERVER_PORT = (short) 67; public static final short DHCP_CLIENT_PORT = (short) 68; + private static final short UDP_HEADER_LENGTH = 8; + static { /* * Disable DHCP until the deserialize code is hardened to deal with * garbage input */ - UDP.DECODE_MAP.put(UDP.DHCP_SERVER_PORT, DHCP.class); - UDP.DECODE_MAP.put(UDP.DHCP_CLIENT_PORT, DHCP.class); + UDP.PORT_DESERIALIZER_MAP.put(UDP.DHCP_SERVER_PORT, DHCP.deserializer()); + UDP.PORT_DESERIALIZER_MAP.put(UDP.DHCP_CLIENT_PORT, DHCP.deserializer()); } @@ -192,6 +196,34 @@ public class UDP extends BasePacket { return data; } + @Override + public IPacket deserialize(final byte[] data, final int offset, + final int length) { + final ByteBuffer bb = ByteBuffer.wrap(data, offset, length); + this.sourcePort = bb.getShort(); + this.destinationPort = bb.getShort(); + this.length = bb.getShort(); + this.checksum = bb.getShort(); + + Deserializer deserializer; + if (UDP.PORT_DESERIALIZER_MAP.containsKey(this.destinationPort)) { + deserializer = UDP.PORT_DESERIALIZER_MAP.get(this.destinationPort); + } else if (UDP.PORT_DESERIALIZER_MAP.containsKey(this.sourcePort)) { + deserializer = UDP.PORT_DESERIALIZER_MAP.get(this.sourcePort); + } else { + deserializer = Data.deserializer(); + } + + try { + this.payload = deserializer.deserialize(data, bb.position(), + bb.limit() - bb.position()); + this.payload.setParent(this); + } catch (DeserializationException e) { + return this; + } + return this; + } + /* * (non-Javadoc) * @@ -240,35 +272,36 @@ public class UDP extends BasePacket { return true; } - @Override - public IPacket deserialize(final byte[] data, final int offset, - final int length) { - final ByteBuffer bb = ByteBuffer.wrap(data, offset, length); - this.sourcePort = bb.getShort(); - this.destinationPort = bb.getShort(); - this.length = bb.getShort(); - this.checksum = bb.getShort(); + /** + * Deserializer function for UDP packets. + * + * @return deserializer function + */ + public static Deserializer deserializer() { + return (data, offset, length) -> { + checkInput(data, offset, length, UDP_HEADER_LENGTH); - if (UDP.DECODE_MAP.containsKey(this.destinationPort)) { - try { - this.payload = UDP.DECODE_MAP.get(this.destinationPort) - .getConstructor().newInstance(); - } catch (final Exception e) { - throw new RuntimeException("Failure instantiating class", e); + UDP udp = new UDP(); + + ByteBuffer bb = ByteBuffer.wrap(data, offset, length); + udp.sourcePort = bb.getShort(); + udp.destinationPort = bb.getShort(); + udp.length = bb.getShort(); + udp.checksum = bb.getShort(); + + Deserializer deserializer; + if (UDP.PORT_DESERIALIZER_MAP.containsKey(udp.destinationPort)) { + deserializer = UDP.PORT_DESERIALIZER_MAP.get(udp.destinationPort); + } else if (UDP.PORT_DESERIALIZER_MAP.containsKey(udp.sourcePort)) { + deserializer = UDP.PORT_DESERIALIZER_MAP.get(udp.sourcePort); + } else { + deserializer = Data.deserializer(); } - } else if (UDP.DECODE_MAP.containsKey(this.sourcePort)) { - try { - this.payload = UDP.DECODE_MAP.get(this.sourcePort) - .getConstructor().newInstance(); - } catch (final Exception e) { - throw new RuntimeException("Failure instantiating class", e); - } - } else { - this.payload = new Data(); - } - this.payload = this.payload.deserialize(data, bb.position(), bb.limit() - - bb.position()); - this.payload.setParent(this); - return this; + + udp.payload = deserializer.deserialize(data, bb.position(), + bb.limit() - bb.position()); + udp.payload.setParent(udp); + return udp; + }; } } diff --git a/utils/misc/src/main/java/org/onlab/packet/ipv6/Authentication.java b/utils/misc/src/main/java/org/onlab/packet/ipv6/Authentication.java index d4c741fa18..ec04a812c5 100644 --- a/utils/misc/src/main/java/org/onlab/packet/ipv6/Authentication.java +++ b/utils/misc/src/main/java/org/onlab/packet/ipv6/Authentication.java @@ -18,11 +18,16 @@ package org.onlab.packet.ipv6; import org.onlab.packet.BasePacket; import org.onlab.packet.Data; +import org.onlab.packet.DeserializationException; +import org.onlab.packet.Deserializer; import org.onlab.packet.IPacket; import org.onlab.packet.IPv6; + import java.nio.ByteBuffer; import java.util.Arrays; +import static org.onlab.packet.PacketUtils.checkInput; + /** * Implements IPv6 authentication extension header format. (RFC 4302) */ @@ -186,22 +191,20 @@ public class Authentication extends BasePacket implements IExtensionHeader { this.integrityCheck = new byte[icvLength]; bb.get(this.integrityCheck, 0, icvLength); - IPacket payload; - if (IPv6.PROTOCOL_CLASS_MAP.containsKey(this.nextHeader)) { - final Class clazz = IPv6.PROTOCOL_CLASS_MAP - .get(this.nextHeader); - try { - payload = clazz.newInstance(); - } catch (final Exception e) { - throw new RuntimeException( - "Error parsing payload for Authentication packet", e); - } + Deserializer deserializer; + if (IPv6.PROTOCOL_DESERIALIZER_MAP.containsKey(this.nextHeader)) { + deserializer = IPv6.PROTOCOL_DESERIALIZER_MAP.get(this.nextHeader); } else { - payload = new Data(); + deserializer = Data.deserializer(); + } + + try { + this.payload = deserializer.deserialize(data, bb.position(), + bb.limit() - bb.position()); + this.payload.setParent(this); + } catch (DeserializationException e) { + return this; } - this.payload = payload.deserialize(data, bb.position(), - bb.limit() - bb.position()); - this.payload.setParent(this); return this; } @@ -259,4 +262,39 @@ public class Authentication extends BasePacket implements IExtensionHeader { } return true; } + + /** + * Deserializer function for authentication headers. + * + * @return deserializer function + */ + public static Deserializer deserializer() { + return (data, offset, length) -> { + checkInput(data, offset, length, FIXED_HEADER_LENGTH); + + Authentication authentication = new Authentication(); + + ByteBuffer bb = ByteBuffer.wrap(data, offset, length); + authentication.nextHeader = bb.get(); + authentication.payloadLength = bb.get(); + bb.getShort(); + authentication.securityParamIndex = bb.getInt(); + authentication.sequence = bb.getInt(); + int icvLength = (authentication.payloadLength + MINUS) * LENGTH_UNIT - FIXED_HEADER_LENGTH; + authentication.integrityCheck = new byte[icvLength]; + bb.get(authentication.integrityCheck, 0, icvLength); + + Deserializer deserializer; + if (IPv6.PROTOCOL_DESERIALIZER_MAP.containsKey(authentication.nextHeader)) { + deserializer = IPv6.PROTOCOL_DESERIALIZER_MAP.get(authentication.nextHeader); + } else { + deserializer = Data.deserializer(); + } + authentication.payload = deserializer.deserialize(data, bb.position(), + bb.limit() - bb.position()); + authentication.payload.setParent(authentication); + + return authentication; + }; + } } diff --git a/utils/misc/src/main/java/org/onlab/packet/ipv6/BaseOptions.java b/utils/misc/src/main/java/org/onlab/packet/ipv6/BaseOptions.java index 6fd81cf7d0..f57b756ee8 100644 --- a/utils/misc/src/main/java/org/onlab/packet/ipv6/BaseOptions.java +++ b/utils/misc/src/main/java/org/onlab/packet/ipv6/BaseOptions.java @@ -18,12 +18,17 @@ package org.onlab.packet.ipv6; import org.onlab.packet.BasePacket; import org.onlab.packet.Data; +import org.onlab.packet.DeserializationException; +import org.onlab.packet.Deserializer; import org.onlab.packet.IPacket; import org.onlab.packet.IPv6; import java.nio.ByteBuffer; import java.util.Arrays; +import static org.onlab.packet.PacketUtils.checkHeaderLength; +import static org.onlab.packet.PacketUtils.checkInput; + /** * Base class for hop-by-hop options and destination options. */ @@ -151,22 +156,19 @@ public class BaseOptions extends BasePacket implements IExtensionHeader { this.options = new byte[optionLength]; bb.get(this.options, 0, optionLength); - IPacket payload; - if (IPv6.PROTOCOL_CLASS_MAP.containsKey(this.nextHeader)) { - final Class clazz = IPv6.PROTOCOL_CLASS_MAP - .get(this.nextHeader); - try { - payload = clazz.newInstance(); - } catch (final Exception e) { - throw new RuntimeException( - "Error parsing payload for BaseOptions packet", e); - } + Deserializer deserializer; + if (IPv6.PROTOCOL_DESERIALIZER_MAP.containsKey(this.nextHeader)) { + deserializer = IPv6.PROTOCOL_DESERIALIZER_MAP.get(this.nextHeader); } else { - payload = new Data(); + deserializer = Data.deserializer(); + } + try { + this.payload = deserializer.deserialize(data, bb.position(), + bb.limit() - bb.position()); + this.payload.setParent(this); + } catch (DeserializationException e) { + return this; } - this.payload = payload.deserialize(data, bb.position(), - bb.limit() - bb.position()); - this.payload.setParent(this); return this; } @@ -219,4 +221,40 @@ public class BaseOptions extends BasePacket implements IExtensionHeader { } return true; } + + /** + * Deserializer function for IPv6 base options. + * + * @return deserializer function + */ + public static Deserializer deserializer() { + return (data, offset, length) -> { + checkInput(data, offset, length, FIXED_HEADER_LENGTH); + + BaseOptions baseOptions = new BaseOptions(); + + ByteBuffer bb = ByteBuffer.wrap(data, offset, length); + baseOptions.nextHeader = bb.get(); + baseOptions.headerExtLength = bb.get(); + int optionLength = + FIXED_OPTIONS_LENGTH + LENGTH_UNIT * baseOptions.headerExtLength; + + checkHeaderLength(bb.remaining(), optionLength); + + baseOptions.options = new byte[optionLength]; + bb.get(baseOptions.options, 0, optionLength); + + Deserializer deserializer; + if (IPv6.PROTOCOL_DESERIALIZER_MAP.containsKey(baseOptions.nextHeader)) { + deserializer = IPv6.PROTOCOL_DESERIALIZER_MAP.get(baseOptions.nextHeader); + } else { + deserializer = Data.deserializer(); + } + baseOptions.payload = deserializer.deserialize(data, bb.position(), + bb.limit() - bb.position()); + baseOptions.payload.setParent(baseOptions); + + return baseOptions; + }; + } } diff --git a/utils/misc/src/main/java/org/onlab/packet/ipv6/EncapSecurityPayload.java b/utils/misc/src/main/java/org/onlab/packet/ipv6/EncapSecurityPayload.java index ffe552fec3..e46a126101 100644 --- a/utils/misc/src/main/java/org/onlab/packet/ipv6/EncapSecurityPayload.java +++ b/utils/misc/src/main/java/org/onlab/packet/ipv6/EncapSecurityPayload.java @@ -18,10 +18,14 @@ package org.onlab.packet.ipv6; import org.onlab.packet.BasePacket; import org.onlab.packet.Data; +import org.onlab.packet.Deserializer; import org.onlab.packet.IPacket; import org.onlab.packet.IPv6; + import java.nio.ByteBuffer; +import static org.onlab.packet.PacketUtils.checkInput; + /** * Implements IPv6 Encapsulating Security Payload (ESP) extension header format. * (RFC 4303) @@ -113,7 +117,7 @@ public class EncapSecurityPayload extends BasePacket { this.payload = new Data(); this.payload.deserialize(data, bb.position(), - bb.limit() - bb.position()); + bb.limit() - bb.position()); this.payload.setParent(this); return this; @@ -158,4 +162,27 @@ public class EncapSecurityPayload extends BasePacket { } return true; } + + /** + * Deserializer function for encapsulated security payload headers. + * + * @return deserializer function + */ + public static Deserializer deserializer() { + return (data, offset, length) -> { + checkInput(data, offset, length, HEADER_LENGTH); + + EncapSecurityPayload encapSecurityPayload = new EncapSecurityPayload(); + + ByteBuffer bb = ByteBuffer.wrap(data, offset, length); + encapSecurityPayload.securityParamIndex = bb.getInt(); + encapSecurityPayload.sequence = bb.getInt(); + + encapSecurityPayload.payload = Data.deserializer().deserialize( + data, bb.position(), bb.limit() - bb.position()); + encapSecurityPayload.payload.setParent(encapSecurityPayload); + + return encapSecurityPayload; + }; + } } diff --git a/utils/misc/src/main/java/org/onlab/packet/ipv6/Fragment.java b/utils/misc/src/main/java/org/onlab/packet/ipv6/Fragment.java index 5a0d4b4db0..68015d31a2 100644 --- a/utils/misc/src/main/java/org/onlab/packet/ipv6/Fragment.java +++ b/utils/misc/src/main/java/org/onlab/packet/ipv6/Fragment.java @@ -18,11 +18,15 @@ package org.onlab.packet.ipv6; import org.onlab.packet.BasePacket; import org.onlab.packet.Data; +import org.onlab.packet.DeserializationException; +import org.onlab.packet.Deserializer; import org.onlab.packet.IPacket; import org.onlab.packet.IPv6; import java.nio.ByteBuffer; +import static org.onlab.packet.PacketUtils.checkInput; + /** * Implements IPv6 fragment extension header format. (RFC 2460) */ @@ -149,22 +153,19 @@ public class Fragment extends BasePacket implements IExtensionHeader { this.moreFragment = (byte) (sscratch & 0x1); this.identification = bb.getInt(); - IPacket payload; - if (IPv6.PROTOCOL_CLASS_MAP.containsKey(this.nextHeader)) { - final Class clazz = IPv6.PROTOCOL_CLASS_MAP - .get(this.nextHeader); - try { - payload = clazz.newInstance(); - } catch (final Exception e) { - throw new RuntimeException( - "Error parsing payload for Fragment packet", e); - } + Deserializer deserializer; + if (IPv6.PROTOCOL_DESERIALIZER_MAP.containsKey(this.nextHeader)) { + deserializer = IPv6.PROTOCOL_DESERIALIZER_MAP.get(this.nextHeader); } else { - payload = new Data(); + deserializer = Data.deserializer(); + } + try { + this.payload = deserializer.deserialize(data, bb.position(), + bb.limit() - bb.position()); + this.payload.setParent(this); + } catch (DeserializationException e) { + return this; } - this.payload = payload.deserialize(data, bb.position(), - bb.limit() - bb.position()); - this.payload.setParent(this); return this; } @@ -216,4 +217,37 @@ public class Fragment extends BasePacket implements IExtensionHeader { } return true; } + + /** + * Deserializer function for fragment headers. + * + * @return deserializer function + */ + public static Deserializer deserializer() { + return (data, offset, length) -> { + checkInput(data, offset, length, HEADER_LENGTH); + + Fragment fragment = new Fragment(); + + ByteBuffer bb = ByteBuffer.wrap(data, offset, length); + fragment.nextHeader = bb.get(); + bb.get(); + short sscratch = bb.getShort(); + fragment.fragmentOffset = (short) (sscratch >> 3 & 0x1fff); + fragment.moreFragment = (byte) (sscratch & 0x1); + fragment.identification = bb.getInt(); + + Deserializer deserializer; + if (IPv6.PROTOCOL_DESERIALIZER_MAP.containsKey(fragment.nextHeader)) { + deserializer = IPv6.PROTOCOL_DESERIALIZER_MAP.get(fragment.nextHeader); + } else { + deserializer = Data.deserializer(); + } + fragment.payload = deserializer.deserialize(data, bb.position(), + bb.limit() - bb.position()); + fragment.payload.setParent(fragment); + + return fragment; + }; + } } diff --git a/utils/misc/src/main/java/org/onlab/packet/ipv6/Routing.java b/utils/misc/src/main/java/org/onlab/packet/ipv6/Routing.java index 808d2ecc63..d7d204a91b 100644 --- a/utils/misc/src/main/java/org/onlab/packet/ipv6/Routing.java +++ b/utils/misc/src/main/java/org/onlab/packet/ipv6/Routing.java @@ -18,12 +18,17 @@ package org.onlab.packet.ipv6; import org.onlab.packet.BasePacket; import org.onlab.packet.Data; +import org.onlab.packet.DeserializationException; +import org.onlab.packet.Deserializer; import org.onlab.packet.IPacket; import org.onlab.packet.IPv6; import java.nio.ByteBuffer; import java.util.Arrays; +import static org.onlab.packet.PacketUtils.checkHeaderLength; +import static org.onlab.packet.PacketUtils.checkInput; + /** * Implements IPv6 routing extension header format. (RFC 2460) */ @@ -175,22 +180,19 @@ public class Routing extends BasePacket implements IExtensionHeader { this.routingData = new byte[dataLength]; bb.get(this.routingData, 0, dataLength); - IPacket payload; - if (IPv6.PROTOCOL_CLASS_MAP.containsKey(this.nextHeader)) { - final Class clazz = IPv6.PROTOCOL_CLASS_MAP - .get(this.nextHeader); - try { - payload = clazz.newInstance(); - } catch (final Exception e) { - throw new RuntimeException( - "Error parsing payload for Routing packet", e); - } + Deserializer deserializer; + if (IPv6.PROTOCOL_DESERIALIZER_MAP.containsKey(this.nextHeader)) { + deserializer = IPv6.PROTOCOL_DESERIALIZER_MAP.get(this.nextHeader); } else { - payload = new Data(); + deserializer = new Data().deserializer(); + } + try { + this.payload = deserializer.deserialize(data, bb.position(), + bb.limit() - bb.position()); + this.payload.setParent(this); + } catch (DeserializationException e) { + return this; } - this.payload = payload.deserialize(data, bb.position(), - bb.limit() - bb.position()); - this.payload.setParent(this); return this; } @@ -248,4 +250,42 @@ public class Routing extends BasePacket implements IExtensionHeader { } return true; } -} \ No newline at end of file + + /** + * Deserializer function for routing headers. + * + * @return deserializer function + */ + public static Deserializer deserializer() { + return (data, offset, length) -> { + checkInput(data, offset, length, FIXED_HEADER_LENGTH); + + Routing routing = new Routing(); + + ByteBuffer bb = ByteBuffer.wrap(data, offset, length); + routing.nextHeader = bb.get(); + routing.headerExtLength = bb.get(); + routing.routingType = bb.get(); + routing.segmentsLeft = bb.get(); + int dataLength = + FIXED_ROUTING_DATA_LENGTH + LENGTH_UNIT * routing.headerExtLength; + + checkHeaderLength(bb.remaining(), dataLength); + + routing.routingData = new byte[dataLength]; + bb.get(routing.routingData, 0, dataLength); + + Deserializer deserializer; + if (IPv6.PROTOCOL_DESERIALIZER_MAP.containsKey(routing.nextHeader)) { + deserializer = IPv6.PROTOCOL_DESERIALIZER_MAP.get(routing.nextHeader); + } else { + deserializer = new Data().deserializer(); + } + routing.payload = deserializer.deserialize(data, bb.position(), + bb.limit() - bb.position()); + routing.payload.setParent(routing); + + return routing; + }; + } +} diff --git a/utils/misc/src/main/java/org/onlab/packet/ndp/NeighborAdvertisement.java b/utils/misc/src/main/java/org/onlab/packet/ndp/NeighborAdvertisement.java index ccb87e3fb3..08c749a1f9 100644 --- a/utils/misc/src/main/java/org/onlab/packet/ndp/NeighborAdvertisement.java +++ b/utils/misc/src/main/java/org/onlab/packet/ndp/NeighborAdvertisement.java @@ -16,6 +16,7 @@ package org.onlab.packet.ndp; import org.onlab.packet.BasePacket; +import org.onlab.packet.Deserializer; import org.onlab.packet.IPacket; import org.onlab.packet.Ip6Address; @@ -23,6 +24,8 @@ import java.nio.ByteBuffer; import java.util.Arrays; import java.util.List; +import static org.onlab.packet.PacketUtils.checkInput; + /** * Implements ICMPv6 Neighbor Advertisement packet format (RFC 4861). */ @@ -238,4 +241,36 @@ public class NeighborAdvertisement extends BasePacket { } return true; } + + /** + * Deserializer function for neighbor advertisement packets. + * + * @return deserializer function + */ + public static Deserializer deserializer() { + return (data, offset, length) -> { + checkInput(data, offset, length, HEADER_LENGTH); + + NeighborAdvertisement neighborAdvertisement = new NeighborAdvertisement(); + + ByteBuffer bb = ByteBuffer.wrap(data, offset, length); + + int iscratch; + + iscratch = bb.getInt(); + neighborAdvertisement.routerFlag = (byte) (iscratch >> 31 & 0x1); + neighborAdvertisement.solicitedFlag = (byte) (iscratch >> 30 & 0x1); + neighborAdvertisement.overrideFlag = (byte) (iscratch >> 29 & 0x1); + bb.get(neighborAdvertisement.targetAddress, 0, Ip6Address.BYTE_LENGTH); + + NeighborDiscoveryOptions options = NeighborDiscoveryOptions.deserializer() + .deserialize(data, bb.position(), bb.limit() - bb.position()); + + for (NeighborDiscoveryOptions.Option option : options.options()) { + neighborAdvertisement.addOption(option.type(), option.data()); + } + + return neighborAdvertisement; + }; + } } diff --git a/utils/misc/src/main/java/org/onlab/packet/ndp/NeighborDiscoveryOptions.java b/utils/misc/src/main/java/org/onlab/packet/ndp/NeighborDiscoveryOptions.java index b8561b1c3e..00a2606888 100644 --- a/utils/misc/src/main/java/org/onlab/packet/ndp/NeighborDiscoveryOptions.java +++ b/utils/misc/src/main/java/org/onlab/packet/ndp/NeighborDiscoveryOptions.java @@ -15,13 +15,17 @@ */ package org.onlab.packet.ndp; +import org.onlab.packet.BasePacket; +import org.onlab.packet.DeserializationException; +import org.onlab.packet.Deserializer; +import org.onlab.packet.IPacket; + import java.nio.ByteBuffer; import java.util.ArrayList; import java.util.Arrays; import java.util.List; -import org.onlab.packet.BasePacket; -import org.onlab.packet.IPacket; +import static org.onlab.packet.PacketUtils.checkInput; /** * Neighbor Discovery Protocol packet options. @@ -33,6 +37,11 @@ public class NeighborDiscoveryOptions extends BasePacket { public static final byte TYPE_REDIRECTED_HEADER = 4; public static final byte TYPE_MTU = 5; + public static final byte INITIAL_HEADER_REQUIRED = 2; + + private static final String BUFFER_UNDERFLOW_ERROR = + "Not enough bytes in buffer to read option"; + private final List