/* This file is part of PacketDotNet PacketDotNet is free software: you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. PacketDotNet is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details. You should have received a copy of the GNU Lesser General Public License along with PacketDotNet. If not, see . */ /* * Copyright 2009 Chris Morgan */ using System; using System.IO; using System.Text; using PacketDotNet.Utils; using System.Linq; namespace PacketDotNet { /// /// Base class for all packet types. /// Defines helper methods and accessors for the architecture that underlies how /// packets interact and store their data. /// public abstract class Packet { // NOTE: No need to warn about lack of use, the compiler won't // put any calls to 'log' here but we need 'log' to exist to compile #pragma warning disable 0169, 0649 private static readonly ILogInactive log; #pragma warning restore 0169, 0649 /// /// Used internally when building new packet dissectors /// protected ByteArraySegment header; /// /// Used internally when building new packet dissectors /// protected PacketOrByteArraySegment payloadPacketOrData = new PacketOrByteArraySegment(); /// /// The parent packet. Accessible via the 'ParentPacket' property /// private Packet parentPacket; /// /// Gets the total length of the packet. /// Recursively finds the length of this packet and all of the packets /// encapsulated by this packet /// /// /// The total length of the packet. /// protected int TotalPacketLength { get { int totalLength = 0; totalLength += header.Length; if(payloadPacketOrData.Type == PayloadType.Bytes) { totalLength += payloadPacketOrData.TheByteArraySegment.Length; } else if(payloadPacketOrData.Type == PayloadType.Packet) { totalLength += payloadPacketOrData.ThePacket.TotalPacketLength; } return totalLength; } } /// /// Returns true if we already have a contiguous byte[] in either /// of these conditions: /// /// - This packet's header byte[] and payload byte[] are the same instance /// or /// - This packet's header byte[] and this packet's payload packet /// are the same instance and the offsets indicate that the bytes /// are contiguous /// protected bool SharesMemoryWithSubPackets { get { log.Debug(""); switch(payloadPacketOrData.Type) { case PayloadType.Bytes: // is the byte array payload the same byte[] and does the offset indicate // that the bytes are contiguous? if((header.Bytes == payloadPacketOrData.TheByteArraySegment.Bytes) && ((header.Offset + header.Length) == payloadPacketOrData.TheByteArraySegment.Offset)) { log.Debug("PayloadType.Bytes returning true"); return true; } else { log.Debug("PayloadType.Bytes returning false"); return false; } case PayloadType.Packet: // is the byte array payload the same as the payload packet header and does // the offset indicate that the bytes are contiguous? if((header.Bytes == payloadPacketOrData.ThePacket.header.Bytes) && ((header.Offset + header.Length) == payloadPacketOrData.ThePacket.header.Offset)) { // and does the sub packet share memory with its sub packets? var retval = payloadPacketOrData.ThePacket.SharesMemoryWithSubPackets; log.DebugFormat("PayloadType.Packet retval {0}", retval); return retval; } else { log.Debug("PayloadType.Packet returning false"); return false; } case PayloadType.None: // no payload data or packet thus we must share memory with // our non-existent sub packets log.Debug("PayloadType.None, returning true"); return true; default: throw new System.NotImplementedException(); } } } /// /// The packet that is carrying this one /// public virtual Packet ParentPacket { get { return parentPacket; } set { parentPacket = value; } } /// /// Returns a /// public virtual byte[] Header { get { return this.header.ActualBytes(); } } /// /// Packet that this packet carries if one is present. /// Note that the packet MAY have a null PayloadPacket but /// a non-null PayloadData /// public virtual Packet PayloadPacket { get { return payloadPacketOrData.ThePacket; } set { if (this == value) throw new InvalidOperationException("A packet cannot have itself as its payload."); payloadPacketOrData.ThePacket = value; payloadPacketOrData.ThePacket.ParentPacket = this; } } /// /// Payload byte[] if one is present. /// Note that the packet MAY have a null PayloadData but a /// non-null PayloadPacket /// public byte[] PayloadData { get { if(payloadPacketOrData.TheByteArraySegment == null) { log.Debug("returning null"); return null; } else { var retval = payloadPacketOrData.TheByteArraySegment.ActualBytes(); log.DebugFormat("retval.Length: {0}", retval.Length); return retval; } } set { log.DebugFormat("value.Length {0}", value.Length); payloadPacketOrData.TheByteArraySegment = new ByteArraySegment(value, 0, value.Length); } } /// /// byte[] containing this packet and its payload /// NOTE: Use 'public virtual ByteArraySegment BytesHighPerformance' for highest performance /// public virtual byte[] Bytes { get { log.Debug(""); // Retrieve the byte array container var ba = BytesHighPerformance; // ActualBytes() will copy bytes if necessary but will avoid a copy in the // case where our offset is zero and the byte[] length matches the // encapsulated Length return ba.ActualBytes(); } } /// /// The option to return a ByteArraySegment means that this method /// is higher performance as the data can start at an offset other than /// the first byte. /// public virtual ByteArraySegment BytesHighPerformance { get { log.Debug(""); // ensure calculated values are properly updated //RecursivelyUpdateCalculatedValues(); // if we share memory with all of our sub packets we can take a // higher performance path to retrieve the bytes if(SharesMemoryWithSubPackets) { // The high performance path that is often taken because it is called on // packets that have not had their header, or any of their sub packets, resized var newByteArraySegment = new ByteArraySegment(header.Bytes, header.Offset, header.BytesLength - header.Offset); log.DebugFormat("SharesMemoryWithSubPackets, returning byte array {0}", newByteArraySegment.ToString()); return newByteArraySegment; } else // need to rebuild things from scratch { log.Debug("rebuilding the byte array"); var ms = new MemoryStream(); // TODO: not sure if this is a performance gain or if // the compiler is smart enough to not call the get accessor for Header // twice, once when retrieving the header and again when retrieving the Length var theHeader = Header; ms.Write(theHeader, 0, theHeader.Length); payloadPacketOrData.AppendToMemoryStream(ms); var newBytes = ms.ToArray(); return new ByteArraySegment(newBytes, 0, newBytes.Length); } } } /// /// Constructor /// public Packet() { } /// /// Parse bytes into a packet /// /// /// A /// /// /// A /// /// /// A /// public static Packet ParsePacket(LinkLayers LinkLayer, byte[] PacketData) { Packet p; var bas = new ByteArraySegment(PacketData); log.DebugFormat("LinkLayer {0}", LinkLayer); switch(LinkLayer) { case LinkLayers.Ethernet: p = new EthernetPacket(bas); break; case LinkLayers.LinuxSLL: p = new LinuxSLLPacket(bas); break; case LinkLayers.Ppp: p = new PPPPacket(bas); break; case LinkLayers.Ieee80211: p = Ieee80211.MacFrame.ParsePacket(bas); break; case LinkLayers.Ieee80211_Radio: p = new Ieee80211.RadioPacket(bas); break; case LinkLayers.PerPacketInformation: p = new Ieee80211.PpiPacket(bas); break; default: throw new System.NotImplementedException("LinkLayer of " + LinkLayer + " is not implemented"); } return p; } /// /// Used to ensure that values like checksums and lengths are /// properly updated /// public void RecursivelyUpdateCalculatedValues(EthernetPacketType[] supportedEthernetPacketTypes = null, IPProtocolType[] supportedIPProtocols = null) { RecursivelyUpdateCalculatedValues(0, supportedEthernetPacketTypes, supportedIPProtocols, false); } protected void RecursivelyUpdateCalculatedValues(int depth, EthernetPacketType[] supportedEthernetPacketTypes, IPProtocolType[] supportedIPProtocols, bool skipLevel) { // call the possibly overridden method if(!skipLevel) { UpdateCalculatedValues(); } // if the packet contains another packet, try it if(payloadPacketOrData.Type == PayloadType.Packet && payloadPacketOrData.ThePacket != this) { var skip = (this is EthernetPacket && !supportedEthernetPacketTypes.Contains(((EthernetPacket)this).Type)) || (this is IpPacket && !supportedIPProtocols.Contains(((IpPacket)this).NextHeader)); payloadPacketOrData.ThePacket.RecursivelyUpdateCalculatedValues(depth +1, supportedEthernetPacketTypes, supportedIPProtocols, skip); } } /// /// Called to ensure that calculated values are updated before /// the packet bytes are retrieved /// /// Classes should override this method to update things like /// checksums and lengths that take too much time or are too complex /// to update for each packet parameter change /// public virtual void UpdateCalculatedValues() { } /// Output this packet as a readable string public override System.String ToString() { return ToString(StringOutputType.Normal); } /// /// /// Output the packet information in the specified format /// Normal - outputs the packet info to a single line /// Colored - outputs the packet info to a single line with coloring /// Verbose - outputs detailed info about the packet /// VerboseColored - outputs detailed info about the packet with coloring /// /// /// /// public virtual string ToString(StringOutputType outputFormat) { if(payloadPacketOrData.Type == PayloadType.Packet) { return payloadPacketOrData.ThePacket.ToString(outputFormat); } else { return String.Empty; } } /// /// Prints the Packet PayloadData in Hex format /// With the 16-byte segment number, raw bytes, and parsed ascii output /// Ex: /// 0010 00 18 82 6c 7c 7f 00 c0 9f 77 a3 b0 88 64 11 00 ...1|... .w...d.. /// /// /// A /// public string PrintHex() { byte[] data = BytesHighPerformance.Bytes; var buffer = new StringBuilder(); string segmentNumber = ""; string bytes = ""; string ascii = ""; buffer.AppendLine("Data: ******* Raw Hex Output - length=" + data.Length + " bytes"); buffer.AppendLine("Data: Segment: Bytes: Ascii:"); buffer.AppendLine("Data: --------------------------------------------------------------------------"); // parse the raw data for(int i = 1; i <= data.Length; i++) { // add the current byte to the bytes hex string bytes += (data[i-1].ToString("x")).PadLeft(2, '0') + " "; // add the current byte to the asciiBytes array for later processing if(data[i-1] < 0x21 || data[i-1] > 0x7e) { ascii += "."; } else { ascii += Encoding.ASCII.GetString(new byte[1] { data[i-1] }); } // add an additional space to split the bytes into // two groups of 8 bytes if(i % 16 != 0 && i % 8 == 0) { bytes += " "; ascii += " "; } // append the output string if(i % 16 == 0) { // add the 16 byte segment number segmentNumber = ((((i - 16) / 16) * 10).ToString()).PadLeft(4, '0'); // build the line buffer.AppendLine("Data: " + segmentNumber + " " + bytes + " " + ascii); // reset for the next line bytes = ""; ascii = ""; continue; } // handle the last pass if(i == data.Length) { // add the 16 byte segment number segmentNumber = (((((i - 16) / 16) + 1) * 10).ToString()).PadLeft(4, '0'); // build the line buffer.AppendLine("Data: " + (segmentNumber.ToString()).PadLeft(4, '0') + " " + bytes.PadRight(49, ' ') + " " + ascii); } } return buffer.ToString(); } /// /// Extract a packet of a specific type or null if a packet of the given type isn't found /// NOTE: a 'dynamic' return type is possible here but costs ~7.8% in performance /// /// /// Type. /// public Packet Extract(System.Type type) { var p = this; // search for a packet type that matches the given one do { if(type.IsAssignableFrom(p.GetType ())) { return p; } // move to the PayloadPacket p = p.PayloadPacket; } while(p != null); return null; } /// /// Color used when generating the text description of a packet /// public virtual System.String Color { get { return AnsiEscapeSequences.Black; } } } }