/* * Copyright (c) 2013 Cisco Systems, Inc. and others. All rights reserved. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License v1.0 which accompanies this distribution, * and is available at http://www.eclipse.org/legal/epl-v10.html */ /** * */ package org.opendaylight.controller.sal.packet; import java.net.InetAddress; import java.util.HashMap; import java.util.LinkedHashMap; import java.util.Map; import java.util.Random; import org.apache.commons.lang3.tuple.ImmutablePair; import org.apache.commons.lang3.tuple.Pair; import org.opendaylight.controller.sal.utils.IPProtocols; import org.opendaylight.controller.sal.utils.NetUtils; import org.slf4j.Logger; import org.slf4j.LoggerFactory; /** * Class that represents the IPv4 packet objects */ public class IPv4 extends Packet { protected static final Logger logger = LoggerFactory .getLogger(IPv4.class); private static final String VERSION = "Version"; private static final String HEADERLENGTH = "HeaderLength"; private static final String DIFFSERV = "DiffServ"; private static final String ECN = "ECN"; private static final String TOTLENGTH = "TotalLength"; private static final String IDENTIFICATION = "Identification"; private static final String FLAGS = "Flags"; private static final String FRAGOFFSET = "FragmentOffset"; private static final String TTL = "TTL"; private static final String PROTOCOL = "Protocol"; private static final String CHECKSUM = "Checksum"; private static final String SIP = "SourceIPAddress"; private static final String DIP = "DestinationIPAddress"; private static final String OPTIONS = "Options"; private static final int UNIT_SIZE_SHIFT = 2; private static final int UNIT_SIZE = (1 << UNIT_SIZE_SHIFT); private static final int MIN_HEADER_SIZE = 20; public static final Map> protocolClassMap; static { protocolClassMap = new HashMap>(); protocolClassMap.put(IPProtocols.ICMP.byteValue(), ICMP.class); protocolClassMap.put(IPProtocols.UDP.byteValue(), UDP.class); protocolClassMap.put(IPProtocols.TCP.byteValue(), TCP.class); } private static Map> fieldCoordinates = new LinkedHashMap>() { private static final long serialVersionUID = 1L; { put(VERSION, new ImmutablePair(0, 4)); put(HEADERLENGTH, new ImmutablePair(4, 4)); put(DIFFSERV, new ImmutablePair(8, 6)); put(ECN, new ImmutablePair(14, 2)); put(TOTLENGTH, new ImmutablePair(16, 16)); put(IDENTIFICATION, new ImmutablePair(32, 16)); put(FLAGS, new ImmutablePair(48, 3)); put(FRAGOFFSET, new ImmutablePair(51, 13)); put(TTL, new ImmutablePair(64, 8)); put(PROTOCOL, new ImmutablePair(72, 8)); put(CHECKSUM, new ImmutablePair(80, 16)); put(SIP, new ImmutablePair(96, 32)); put(DIP, new ImmutablePair(128, 32)); put(OPTIONS, new ImmutablePair(160, 0)); } }; private final Map fieldValues; /** * Default constructor that sets the version to 4, headerLength to 5, * and flags to 2. The default value for the identification is set to a * random number and the remaining fields are set to 0. */ public IPv4() { super(); fieldValues = new HashMap(); hdrFieldCoordMap = fieldCoordinates; hdrFieldsMap = fieldValues; corrupted = false; setVersion((byte) 4); setHeaderLength((byte) 5); setDiffServ((byte) 0); setECN((byte) 0); setIdentification(generateId()); setFlags((byte) 2); setFragmentOffset((short) 0); } /** * The write access to the packet is set in this constructor. * Constructor that sets the version to 4, headerLength to 5, * and flags to 2. The default value for the identification is set to a * random number and the remaining fields are set to 0. * @param boolean */ public IPv4(boolean writeAccess) { super(writeAccess); fieldValues = new HashMap(); hdrFieldCoordMap = fieldCoordinates; hdrFieldsMap = fieldValues; corrupted = false; setVersion((byte) 4); setHeaderLength((byte) 5); setDiffServ((byte) 0); setECN((byte) 0); setIdentification(generateId()); setFlags((byte) 2); setFragmentOffset((short) 0); } /** * Gets the IP version stored * @return the version */ public byte getVersion() { return (BitBufferHelper.getByte(fieldValues.get(VERSION))); } /** * Gets the IP header length stored * @return the headerLength in bytes */ public int getHeaderLen() { return (4 * BitBufferHelper.getByte(fieldValues.get(HEADERLENGTH))); } /** * Gets the header size in bits * @return The number of bits constituting the header */ @Override public int getHeaderSize() { int headerLen = this.getHeaderLen(); if (headerLen == 0) { headerLen = MIN_HEADER_SIZE; } return headerLen * NetUtils.NumBitsInAByte; } /** * Gets the differential services value stored * @return the diffServ */ public byte getDiffServ() { return BitBufferHelper.getByte(fieldValues.get(DIFFSERV)); } /** * Gets the ecn bits stored * @return the ecn bits */ public byte getECN() { return BitBufferHelper.getByte(fieldValues.get(ECN)); } /** * Gets the total length of the IP header in bytes * @return the totalLength */ public short getTotalLength() { return (BitBufferHelper.getShort(fieldValues.get(TOTLENGTH))); } /** * Gets the identification value stored * @return the identification */ public short getIdentification() { return (BitBufferHelper.getShort(fieldValues.get(IDENTIFICATION))); } /** * Gets the flag values stored * @return the flags */ public byte getFlags() { return (BitBufferHelper.getByte(fieldValues.get(FLAGS))); } /** * Gets the TTL value stored * @return the ttl */ public byte getTtl() { return (BitBufferHelper.getByte(fieldValues.get(TTL))); } /** * Gets the protocol value stored * @return the protocol */ public byte getProtocol() { return (BitBufferHelper.getByte(fieldValues.get(PROTOCOL))); } /** * Gets the checksum value stored * @return the checksum */ public short getChecksum() { return (BitBufferHelper.getShort(fieldValues.get(CHECKSUM))); } /** * Gets the fragment offset stored * @return the fragmentOffset */ public short getFragmentOffset() { return (BitBufferHelper.getShort(fieldValues.get(FRAGOFFSET))); } /** * Gets the source IP address stored * @return the sourceAddress */ public int getSourceAddress() { return (BitBufferHelper.getInt(fieldValues.get(SIP))); } /** * Gets the destination IP address stored * @return the destinationAddress */ public int getDestinationAddress() { return (BitBufferHelper.getInt(fieldValues.get(DIP))); } /** * gets the Options stored * @return the options */ public byte[] getOptions() { return (fieldValues.get(OPTIONS)); } @Override /** * Stores the value of fields read from data stream * Variable header value like payload protocol, is stored here */ public void setHeaderField(String headerField, byte[] readValue) { if (headerField.equals(PROTOCOL)) { payloadClass = protocolClassMap.get(readValue[0]); } else if (headerField.equals(OPTIONS) && (readValue == null || readValue.length == 0)) { hdrFieldsMap.remove(headerField); return; } hdrFieldsMap.put(headerField, readValue); } /** * Stores the IP version from the header * @param version the version to set * @return @IPv4 */ public IPv4 setVersion(byte ipVersion) { byte[] version = BitBufferHelper.toByteArray(ipVersion); fieldValues.put(VERSION, version); return this; } /** * Stores the length of IP header in words (4 bytes) * @param headerLength the headerLength to set * @return IPv4 */ public IPv4 setHeaderLength(byte ipheaderLength) { byte[] headerLength = BitBufferHelper.toByteArray(ipheaderLength); fieldValues.put(HEADERLENGTH, headerLength); return this; } /** * Stores the differential services value from the IP header * @param diffServ the diffServ to set * @return IPv4 */ public IPv4 setDiffServ(byte ipdiffServ) { byte[] diffServ = BitBufferHelper.toByteArray(ipdiffServ); fieldValues.put(DIFFSERV, diffServ); return this; } /** * Stores the ECN bits from the header * @param ECN bits to set * @return IPv4 */ public IPv4 setECN(byte ecn) { byte[] ecnbytes = BitBufferHelper.toByteArray(ecn); fieldValues.put(ECN, ecnbytes); return this; } /** * Stores the total length of IP header in bytes * @param totalLength the totalLength to set * @return IPv4 */ public IPv4 setTotalLength(short iptotalLength) { byte[] totalLength = BitBufferHelper.toByteArray(iptotalLength); fieldValues.put(TOTLENGTH, totalLength); return this; } /** * Stores the identification number from the header * @param identification the identification to set * @return IPv4 */ public IPv4 setIdentification(short ipIdentification) { byte[] identification = BitBufferHelper.toByteArray(ipIdentification); fieldValues.put(IDENTIFICATION, identification); return this; } /** * Stores the IP flags value * @param flags the flags to set * @return IPv4 */ public IPv4 setFlags(byte ipFlags) { byte[] flags = { ipFlags }; fieldValues.put(FLAGS, flags); return this; } /** * Stores the IP fragmentation offset value * @param fragmentOffset the fragmentOffset to set * @return IPv4 */ public IPv4 setFragmentOffset(short ipFragmentOffset) { byte[] fragmentOffset = BitBufferHelper.toByteArray(ipFragmentOffset); fieldValues.put(FRAGOFFSET, fragmentOffset); return this; } /** * Stores the TTL value * @param ttl the ttl to set * @return IPv4 */ public IPv4 setTtl(byte ipTtl) { byte[] ttl = BitBufferHelper.toByteArray(ipTtl); fieldValues.put(TTL, ttl); return this; } /** * Stores the protocol value of the IP payload * @param protocol the protocol to set * @return IPv4 */ public IPv4 setProtocol(byte ipProtocol) { byte[] protocol = BitBufferHelper.toByteArray(ipProtocol); fieldValues.put(PROTOCOL, protocol); return this; } /** * @param checksum the checksum to set */ /*public IPv4 setChecksum() { short ipChecksum = computeChecksum(); byte[] checksum = BitBufferHelper.toByteArray(ipChecksum); fieldValues.put(CHECKSUM, checksum); return this; }*/ /** * Stores the IP source address from the header * @param sourceAddress the sourceAddress to set * @return IPv4 */ public IPv4 setSourceAddress(InetAddress ipSourceAddress) { byte[] sourceAddress = ipSourceAddress.getAddress(); fieldValues.put(SIP, sourceAddress); return this; } /** * Stores the IP destination address from the header * @param the destination Address to set * @return IPv4 */ public IPv4 setDestinationAddress(InetAddress ipDestinationAddress) { byte[] sourceAddress = ipDestinationAddress.getAddress(); fieldValues.put(DIP, sourceAddress); return this; } /** * Stores the IP destination address from the header * @param destinationAddress the destinationAddress to set * @return IPv4 */ public IPv4 setDestinationAddress(int ipDestinationAddress) { byte[] destinationAddress = BitBufferHelper .toByteArray(ipDestinationAddress); fieldValues.put(DIP, destinationAddress); return this; } /** * Generate a random number to set the Identification field * in IPv4 Header * @return short */ private short generateId() { Random randomgen = new Random(); return (short) (randomgen.nextInt(Short.MAX_VALUE + 1)); } /** * Store the options from IP header * @param options - byte[] * @return IPv4 */ public IPv4 setOptions(byte[] options) { byte newIHL = (byte)(MIN_HEADER_SIZE >>> UNIT_SIZE_SHIFT); if (options == null || options.length == 0) { fieldValues.remove(OPTIONS); } else { int len = options.length; int rlen = (len + (UNIT_SIZE - 1)) & ~(UNIT_SIZE - 1); if (rlen > len) { // Padding is required. byte[] newopt = new byte[rlen]; System.arraycopy(options, 0, newopt, 0, len); options = newopt; len = rlen; } fieldValues.put(OPTIONS, options); newIHL += (len >>> UNIT_SIZE_SHIFT); } setHeaderLength(newIHL); return this; } /** * Computes the IPv4 header checksum on the passed stream of bytes * representing the packet * * @param data * The byte stream * @param offset * The byte offset from where the IPv4 packet starts * @return The computed checksum */ short computeChecksum(byte[] data, int start) { int end = start + getHeaderLen(); short checkSum = (short) 0; int sum = 0, carry = 0, finalSum = 0; int wordData; int checksumStart = start + (getfieldOffset(CHECKSUM) / NetUtils.NumBitsInAByte); for (int i = start; i <= (end - 1); i = i + 2) { // Skip, if the current bytes are checkSum bytes if (i == checksumStart) { continue; } wordData = ((data[i] << 8) & 0xFF00) + (data[i + 1] & 0xFF); sum = sum + wordData; } carry = (sum >> 16) & 0xFF; finalSum = (sum & 0xFFFF) + carry; checkSum = (short) ~((short) finalSum & 0xFFFF); return checkSum; } @Override /** * Gets the number of bits for the fieldname specified * If the fieldname has variable length like "Options", then this value is computed using the header length * @param fieldname - String * @return number of bits for fieldname - int */ public int getfieldnumBits(String fieldName) { if (fieldName.equals(OPTIONS)) { return (getHeaderLen() - MIN_HEADER_SIZE) * NetUtils.NumBitsInAByte; } return hdrFieldCoordMap.get(fieldName).getRight(); } @Override /** * Method to perform post serialization - like computation of checksum of serialized header * @param data * @return void * @Exception throws PacketException */ protected void postSerializeCustomOperation(byte[] data) throws PacketException { // Recompute the total length field here byte[] totalLength = BitBufferHelper.toByteArray((short) data.length); try { BitBufferHelper.setBytes(data, totalLength, getfieldOffset(TOTLENGTH), getfieldnumBits(TOTLENGTH)); } catch (BufferException e) { throw new PacketException(e.getMessage()); } // Now compute the Header Checksum byte[] checkSum = BitBufferHelper.toByteArray(computeChecksum(data, 0)); try { BitBufferHelper.setBytes(data, checkSum, getfieldOffset(CHECKSUM), getfieldnumBits(CHECKSUM)); } catch (BufferException e) { throw new PacketException(e.getMessage()); } } @Override /** * Stores the payload of IP, serializes it and stores the length of serialized payload * bytes in Total Length * @param payload - Packet */ /** * Set the total length field in the IPv4 Object * Note: this field will get overwritten during serialization phase. */ public void setPayload(Packet payload) { this.payload = payload; /* * Deriving the Total Length here */ int payloadLength = 0; if (payload != null) { try { payloadLength = payload.serialize().length; } catch (PacketException e) { logger.error("", e); } } this.setTotalLength((short) (this.getHeaderLen() + payloadLength)); } /** * Method to perform post deserialization - like compare computed checksum with * the one obtained from IP header */ @Override protected void postDeserializeCustomOperation(byte[] data, int startBitOffset) { int start = startBitOffset / NetUtils.NumBitsInAByte; short computedChecksum = computeChecksum(data, start); short actualChecksum = BitBufferHelper.getShort(fieldValues.get(CHECKSUM)); if (computedChecksum != actualChecksum) { corrupted = true; } } }