From 05548f13d35e26a7b0b2806fc146ef6e886c2984 Mon Sep 17 00:00:00 2001 From: seuffert Date: Mon, 21 Feb 2011 23:20:14 +0100 Subject: [PATCH] adding PacketDotNet library --- PacketDotNet/ARPFields.cs | 85 +++ PacketDotNet/ARPOperation.cs | 39 + PacketDotNet/ARPPacket.cs | 397 ++++++++++ PacketDotNet/ApplicationPacket.cs | 36 + PacketDotNet/AssemblyInfo.cs | 26 + PacketDotNet/DataLinkPacket.cs | 36 + PacketDotNet/EthernetFields.cs | 56 ++ PacketDotNet/EthernetPacket.cs | 333 +++++++++ PacketDotNet/EthernetPacketType.cs | 174 +++++ PacketDotNet/ICMPv4Fields.cs | 57 ++ PacketDotNet/ICMPv4Packet.cs | 250 +++++++ PacketDotNet/ICMPv4TypeCodes.cs | 52 ++ PacketDotNet/ICMPv6Fields.cs | 50 ++ PacketDotNet/ICMPv6Packet.cs | 200 +++++ PacketDotNet/ICMPv6Types.cs | 51 ++ PacketDotNet/IGMPMessageType.cs | 37 + PacketDotNet/IGMPv2Fields.cs | 53 ++ PacketDotNet/IGMPv2Packet.cs | 162 ++++ PacketDotNet/ILogInactive.cs | 256 +++++++ PacketDotNet/IPProtocol.cs | 67 ++ PacketDotNet/IPProtocolType.cs | 78 ++ PacketDotNet/IPv4Fields.cs | 105 +++ PacketDotNet/IPv4Packet.cs | 664 +++++++++++++++++ PacketDotNet/IPv6Fields.cs | 102 +++ PacketDotNet/IPv6Packet.cs | 506 +++++++++++++ PacketDotNet/InternetLinkLayerPacket.cs | 87 +++ PacketDotNet/InternetPacket.cs | 41 ++ PacketDotNet/IpPacket.cs | 362 +++++++++ PacketDotNet/IpPort.cs | 76 ++ PacketDotNet/IpVersion.cs | 34 + PacketDotNet/LLDP/AddressFamily.cs | 27 + PacketDotNet/LLDP/CapabilityOptions.cs | 40 + PacketDotNet/LLDP/ChassisID.cs | 351 +++++++++ PacketDotNet/LLDP/ChassisSubTypes.cs | 29 + PacketDotNet/LLDP/EndOfLLDPDU.cs | 54 ++ PacketDotNet/LLDP/InterfaceNumbering.cs | 16 + PacketDotNet/LLDP/ManagementAddress.cs | 329 +++++++++ PacketDotNet/LLDP/NetworkAddress.cs | 198 +++++ PacketDotNet/LLDP/OrganizationSpecific.cs | 173 +++++ PacketDotNet/LLDP/PortDescription.cs | 65 ++ PacketDotNet/LLDP/PortID.cs | 241 ++++++ PacketDotNet/LLDP/PortSubTypes.cs | 30 + PacketDotNet/LLDP/StringTLV.cs | 101 +++ PacketDotNet/LLDP/SystemCapabilities.cs | 158 ++++ PacketDotNet/LLDP/SystemDescription.cs | 65 ++ PacketDotNet/LLDP/SystemName.cs | 51 ++ PacketDotNet/LLDP/TLV.cs | 151 ++++ PacketDotNet/LLDP/TLVCollection.cs | 74 ++ PacketDotNet/LLDP/TLVTypeLength.cs | 121 +++ PacketDotNet/LLDP/TLVTypes.cs | 98 +++ PacketDotNet/LLDP/TimeToLive.cs | 104 +++ PacketDotNet/LLDPPacket.cs | 382 ++++++++++ PacketDotNet/Libraries/log4net.dll | Bin 0 -> 243712 bytes PacketDotNet/LinkLayers.cs | 90 +++ PacketDotNet/LinuxSLLFields.cs | 94 +++ PacketDotNet/LinuxSLLPacket.cs | 227 ++++++ PacketDotNet/LinuxSLLType.cs | 56 ++ .../Conversion/BigEndianBitConverter.cs | 67 ++ .../MiscUtil/Conversion/DoubleConverter.cs | 219 ++++++ .../MiscUtil/Conversion/EndianBitConverter.cs | 696 ++++++++++++++++++ .../MiscUtil/Conversion/Endianness.cs | 18 + .../Conversion/LittleEndianBitConverter.cs | 66 ++ .../MiscUtil/IO/EndianBinaryReader.cs | 599 +++++++++++++++ .../MiscUtil/IO/EndianBinaryWriter.cs | 392 ++++++++++ PacketDotNet/PPPFields.cs | 45 ++ PacketDotNet/PPPPacket.cs | 201 +++++ PacketDotNet/PPPProtocol.cs | 39 + PacketDotNet/PPPoECode.cs | 61 ++ PacketDotNet/PPPoEFields.cs | 69 ++ PacketDotNet/PPPoEPacket.cs | 288 ++++++++ PacketDotNet/Packet.cs | 419 +++++++++++ PacketDotNet/PacketDotNet.csproj | 182 +++++ PacketDotNet/PacketDotNet.csproj.user | 13 + PacketDotNet/PacketOrByteArraySegment.cs | 104 +++ PacketDotNet/PayloadType.cs | 35 + PacketDotNet/PosixTimeval.cs | 279 +++++++ PacketDotNet/RawPacket.cs | 67 ++ PacketDotNet/SessionPacket.cs | 39 + PacketDotNet/TcpFields.cs | 95 +++ PacketDotNet/TcpPacket.cs | 645 ++++++++++++++++ PacketDotNet/TransportPacket.cs | 124 ++++ PacketDotNet/TransportProtocols.cs | 10 + PacketDotNet/UdpFields.cs | 53 ++ PacketDotNet/UdpPacket.cs | 349 +++++++++ PacketDotNet/Utils/AnsiEscapeSequences.cs | 104 +++ PacketDotNet/Utils/ByteArraySegment.cs | 167 +++++ PacketDotNet/Utils/ChecksumUtils.cs | 119 +++ PacketDotNet/Utils/HexPrinter.cs | 41 ++ PacketDotNet/Utils/RandomUtils.cs | 40 + 89 files changed, 13442 insertions(+) create mode 100644 PacketDotNet/ARPFields.cs create mode 100644 PacketDotNet/ARPOperation.cs create mode 100644 PacketDotNet/ARPPacket.cs create mode 100644 PacketDotNet/ApplicationPacket.cs create mode 100644 PacketDotNet/AssemblyInfo.cs create mode 100644 PacketDotNet/DataLinkPacket.cs create mode 100644 PacketDotNet/EthernetFields.cs create mode 100644 PacketDotNet/EthernetPacket.cs create mode 100644 PacketDotNet/EthernetPacketType.cs create mode 100644 PacketDotNet/ICMPv4Fields.cs create mode 100644 PacketDotNet/ICMPv4Packet.cs create mode 100644 PacketDotNet/ICMPv4TypeCodes.cs create mode 100644 PacketDotNet/ICMPv6Fields.cs create mode 100644 PacketDotNet/ICMPv6Packet.cs create mode 100644 PacketDotNet/ICMPv6Types.cs create mode 100644 PacketDotNet/IGMPMessageType.cs create mode 100644 PacketDotNet/IGMPv2Fields.cs create mode 100644 PacketDotNet/IGMPv2Packet.cs create mode 100644 PacketDotNet/ILogInactive.cs create mode 100644 PacketDotNet/IPProtocol.cs create mode 100644 PacketDotNet/IPProtocolType.cs create mode 100644 PacketDotNet/IPv4Fields.cs create mode 100644 PacketDotNet/IPv4Packet.cs create mode 100644 PacketDotNet/IPv6Fields.cs create mode 100644 PacketDotNet/IPv6Packet.cs create mode 100644 PacketDotNet/InternetLinkLayerPacket.cs create mode 100644 PacketDotNet/InternetPacket.cs create mode 100644 PacketDotNet/IpPacket.cs create mode 100644 PacketDotNet/IpPort.cs create mode 100644 PacketDotNet/IpVersion.cs create mode 100644 PacketDotNet/LLDP/AddressFamily.cs create mode 100644 PacketDotNet/LLDP/CapabilityOptions.cs create mode 100644 PacketDotNet/LLDP/ChassisID.cs create mode 100644 PacketDotNet/LLDP/ChassisSubTypes.cs create mode 100644 PacketDotNet/LLDP/EndOfLLDPDU.cs create mode 100644 PacketDotNet/LLDP/InterfaceNumbering.cs create mode 100644 PacketDotNet/LLDP/ManagementAddress.cs create mode 100644 PacketDotNet/LLDP/NetworkAddress.cs create mode 100644 PacketDotNet/LLDP/OrganizationSpecific.cs create mode 100644 PacketDotNet/LLDP/PortDescription.cs create mode 100644 PacketDotNet/LLDP/PortID.cs create mode 100644 PacketDotNet/LLDP/PortSubTypes.cs create mode 100644 PacketDotNet/LLDP/StringTLV.cs create mode 100644 PacketDotNet/LLDP/SystemCapabilities.cs create mode 100644 PacketDotNet/LLDP/SystemDescription.cs create mode 100644 PacketDotNet/LLDP/SystemName.cs create mode 100644 PacketDotNet/LLDP/TLV.cs create mode 100644 PacketDotNet/LLDP/TLVCollection.cs create mode 100644 PacketDotNet/LLDP/TLVTypeLength.cs create mode 100644 PacketDotNet/LLDP/TLVTypes.cs create mode 100644 PacketDotNet/LLDP/TimeToLive.cs create mode 100644 PacketDotNet/LLDPPacket.cs create mode 100644 PacketDotNet/Libraries/log4net.dll create mode 100644 PacketDotNet/LinkLayers.cs create mode 100644 PacketDotNet/LinuxSLLFields.cs create mode 100644 PacketDotNet/LinuxSLLPacket.cs create mode 100644 PacketDotNet/LinuxSLLType.cs create mode 100644 PacketDotNet/MiscUtil/Conversion/BigEndianBitConverter.cs create mode 100644 PacketDotNet/MiscUtil/Conversion/DoubleConverter.cs create mode 100644 PacketDotNet/MiscUtil/Conversion/EndianBitConverter.cs create mode 100644 PacketDotNet/MiscUtil/Conversion/Endianness.cs create mode 100644 PacketDotNet/MiscUtil/Conversion/LittleEndianBitConverter.cs create mode 100644 PacketDotNet/MiscUtil/IO/EndianBinaryReader.cs create mode 100644 PacketDotNet/MiscUtil/IO/EndianBinaryWriter.cs create mode 100644 PacketDotNet/PPPFields.cs create mode 100644 PacketDotNet/PPPPacket.cs create mode 100644 PacketDotNet/PPPProtocol.cs create mode 100644 PacketDotNet/PPPoECode.cs create mode 100644 PacketDotNet/PPPoEFields.cs create mode 100644 PacketDotNet/PPPoEPacket.cs create mode 100644 PacketDotNet/Packet.cs create mode 100644 PacketDotNet/PacketDotNet.csproj create mode 100644 PacketDotNet/PacketDotNet.csproj.user create mode 100644 PacketDotNet/PacketOrByteArraySegment.cs create mode 100644 PacketDotNet/PayloadType.cs create mode 100644 PacketDotNet/PosixTimeval.cs create mode 100644 PacketDotNet/RawPacket.cs create mode 100644 PacketDotNet/SessionPacket.cs create mode 100644 PacketDotNet/TcpFields.cs create mode 100644 PacketDotNet/TcpPacket.cs create mode 100644 PacketDotNet/TransportPacket.cs create mode 100644 PacketDotNet/TransportProtocols.cs create mode 100644 PacketDotNet/UdpFields.cs create mode 100644 PacketDotNet/UdpPacket.cs create mode 100644 PacketDotNet/Utils/AnsiEscapeSequences.cs create mode 100644 PacketDotNet/Utils/ByteArraySegment.cs create mode 100644 PacketDotNet/Utils/ChecksumUtils.cs create mode 100644 PacketDotNet/Utils/HexPrinter.cs create mode 100644 PacketDotNet/Utils/RandomUtils.cs diff --git a/PacketDotNet/ARPFields.cs b/PacketDotNet/ARPFields.cs new file mode 100644 index 0000000..0b58c6e --- /dev/null +++ b/PacketDotNet/ARPFields.cs @@ -0,0 +1,85 @@ +/* +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 + */ +namespace PacketDotNet +{ + /// IP protocol field encoding information. + /// + /// + /// FIXME: These fields are partially broken because they assume the offset for + /// several fields and the offset is actually based on the accumulated offset + /// into the structure determined by the fields that indicate sizes + public class ARPFields + { + /// Type code for ethernet addresses. + public readonly static int EthernetProtocolType = 0x0001; + /// Type code for MAC addresses. + public readonly static int IPv4ProtocolType = 0x0800; + + /// Operation type length in bytes. + public readonly static int OperationLength = 2; + /// + /// The length of the address type fields in bytes, + /// eg. the length of hardware type or protocol type + /// + public readonly static int AddressTypeLength = 2; + /// + /// The length of the address length fields in bytes. + /// + public readonly static int AddressLengthLength = 1; + /// Position of the hardware address type. + public readonly static int HardwareAddressTypePosition = 0; + /// Position of the protocol address type. + public readonly static int ProtocolAddressTypePosition; + /// Position of the hardware address length. + public readonly static int HardwareAddressLengthPosition; + /// Position of the protocol address length. + public readonly static int ProtocolAddressLengthPosition; + /// Position of the operation type. + public readonly static int OperationPosition; + /// Position of the sender hardware address. + public readonly static int SenderHardwareAddressPosition; + /// Position of the sender protocol address. + public readonly static int SenderProtocolAddressPosition; + /// Position of the target hardware address. + public readonly static int TargetHardwareAddressPosition; + /// Position of the target protocol address. + public readonly static int TargetProtocolAddressPosition; + /// Total length in bytes of an ARP header. + public readonly static int HeaderLength; // == 28 + + static ARPFields() + { + // NOTE: We use IPv4Fields_Fields.IP_ADDRESS_WIDTH because arp packets are + // only used in IPv4 networks. Neighbor discovery is used with IPv6 + //FIXME: we really should use the sizes given by the length fields to determine + // the position offsets here instead of assuming the hw address is an ethernet mac address + ProtocolAddressTypePosition = HardwareAddressTypePosition + AddressTypeLength; + HardwareAddressLengthPosition = ProtocolAddressTypePosition + AddressTypeLength; + ProtocolAddressLengthPosition = HardwareAddressLengthPosition + AddressLengthLength; + OperationPosition = ProtocolAddressLengthPosition + AddressLengthLength; + SenderHardwareAddressPosition = OperationPosition + OperationLength; + SenderProtocolAddressPosition = SenderHardwareAddressPosition + EthernetFields.MacAddressLength; + TargetHardwareAddressPosition = SenderProtocolAddressPosition + IPv4Fields.AddressLength; + TargetProtocolAddressPosition = TargetHardwareAddressPosition + EthernetFields.MacAddressLength; + + HeaderLength = TargetProtocolAddressPosition + IPv4Fields.AddressLength; + } + } +} diff --git a/PacketDotNet/ARPOperation.cs b/PacketDotNet/ARPOperation.cs new file mode 100644 index 0000000..6e665d5 --- /dev/null +++ b/PacketDotNet/ARPOperation.cs @@ -0,0 +1,39 @@ +/* +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 2010 Chris Morgan + */ +using System; + +namespace PacketDotNet +{ + /// + /// The possible ARP operation values + /// + public enum ARPOperation : ushort + { + /// + /// Arp request + /// + Request = 0x1, + + /// + /// Arp response + /// + Response = 0x2 + } +} diff --git a/PacketDotNet/ARPPacket.cs b/PacketDotNet/ARPPacket.cs new file mode 100644 index 0000000..ee42172 --- /dev/null +++ b/PacketDotNet/ARPPacket.cs @@ -0,0 +1,397 @@ +/* +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.Net.NetworkInformation; +using MiscUtil.Conversion; +using PacketDotNet.Utils; + +namespace PacketDotNet +{ + /// + /// An ARP protocol packet. + /// + public class ARPPacket : InternetLinkLayerPacket + { +#if DEBUG + private static readonly log4net.ILog log = log4net.LogManager.GetLogger(System.Reflection.MethodBase.GetCurrentMethod().DeclaringType); +#else + // 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 + private static readonly ILogInactive log; +#pragma warning restore 0169 +#endif + + /// + /// Also known as HardwareType + /// + virtual public LinkLayers HardwareAddressType + { + get + { + return (LinkLayers)EndianBitConverter.Big.ToUInt16(header.Bytes, + header.Offset + ARPFields.HardwareAddressTypePosition); + } + + set + { + var theValue = (UInt16)value; + EndianBitConverter.Big.CopyBytes(theValue, + header.Bytes, + header.Offset + ARPFields.HardwareAddressTypePosition); + } + } + + /// + /// Also known as ProtocolType + /// + virtual public EthernetPacketType ProtocolAddressType + { + get + { + return (EthernetPacketType)EndianBitConverter.Big.ToUInt16(header.Bytes, + header.Offset + ARPFields.ProtocolAddressTypePosition); + } + + set + { + var theValue = (UInt16)value; + EndianBitConverter.Big.CopyBytes(theValue, + header.Bytes, + header.Offset + ARPFields.ProtocolAddressTypePosition); + } + } + + /// + /// Hardware address length field + /// + virtual public int HardwareAddressLength + { + get + { + return header.Bytes[header.Offset + ARPFields.HardwareAddressLengthPosition]; + } + + set + { + header.Bytes[header.Offset + ARPFields.HardwareAddressLengthPosition] = (byte)value; + } + } + + /// + /// Protocol address length field + /// + virtual public int ProtocolAddressLength + { + get + { + return header.Bytes[header.Offset + ARPFields.ProtocolAddressLengthPosition]; + } + + set + { + header.Bytes[header.Offset + ARPFields.ProtocolAddressLengthPosition] = (byte)value; + } + } + + /// Fetch the operation code. + /// Usually one of ARPFields.{ARP_OP_REQ_CODE, ARP_OP_REP_CODE}. + /// + /// Sets the operation code. + /// Usually one of ARPFields.{ARP_OP_REQ_CODE, ARP_OP_REP_CODE}. + /// + virtual public ARPOperation Operation + { + get + { + return (ARPOperation)EndianBitConverter.Big.ToInt16(header.Bytes, + header.Offset + ARPFields.OperationPosition); + } + + set + { + var theValue = (Int16)value; + EndianBitConverter.Big.CopyBytes(theValue, + header.Bytes, + header.Offset + ARPFields.OperationPosition); + } + } + + /// + /// Upper layer protocol address of the sender, typically an IPv4 or IPv6 address + /// + virtual public System.Net.IPAddress SenderProtocolAddress + { + get + { + return IpPacket.GetIPAddress(System.Net.Sockets.AddressFamily.InterNetwork, + header.Offset + ARPFields.SenderProtocolAddressPosition, + header.Bytes); + } + + set + { + byte[] address = value.GetAddressBytes(); + Array.Copy(address, 0, + header.Bytes, header.Offset + ARPFields.SenderProtocolAddressPosition, + address.Length); + } + } + + /// + /// Upper layer protocol address of the target, typically an IPv4 or IPv6 address + /// + virtual public System.Net.IPAddress TargetProtocolAddress + { + get + { + return IpPacket.GetIPAddress(System.Net.Sockets.AddressFamily.InterNetwork, + header.Offset + ARPFields.TargetProtocolAddressPosition, + header.Bytes); + } + + set + { + byte[] address = value.GetAddressBytes(); + Array.Copy(address, 0, + header.Bytes, header.Offset + ARPFields.TargetProtocolAddressPosition, + address.Length); + } + } + + /// + /// Sender hardware address, usually an ethernet mac address + /// + public virtual PhysicalAddress SenderHardwareAddress + { + get + { + //FIXME: this code is broken because it assumes that the address position is + // a fixed position + byte[] hwAddress = new byte[HardwareAddressLength]; + Array.Copy(header.Bytes, header.Offset + ARPFields.SenderHardwareAddressPosition, + hwAddress, 0, hwAddress.Length); + return new PhysicalAddress(hwAddress); + } + + set + { + byte[] hwAddress = value.GetAddressBytes(); + + // for now we only support ethernet addresses even though the arp protocol + // makes provisions for varying length addresses + if(hwAddress.Length != EthernetFields.MacAddressLength) + { + throw new System.InvalidOperationException("expected physical address length of " + + EthernetFields.MacAddressLength + + " but it was " + + hwAddress.Length); + } + + Array.Copy(hwAddress, 0, + header.Bytes, header.Offset + ARPFields.SenderHardwareAddressPosition, + hwAddress.Length); + } + } + + /// + /// Target hardware address, usually an ethernet mac address + /// + public virtual PhysicalAddress TargetHardwareAddress + { + get + { + //FIXME: this code is broken because it assumes that the address position is + // a fixed position + byte[] hwAddress = new byte[HardwareAddressLength]; + Array.Copy(header.Bytes, header.Offset + ARPFields.TargetHardwareAddressPosition, + hwAddress, 0, + hwAddress.Length); + return new PhysicalAddress(hwAddress); + } + set + { + byte[] hwAddress = value.GetAddressBytes(); + + // for now we only support ethernet addresses even though the arp protocol + // makes provisions for varying length addresses + if(hwAddress.Length != EthernetFields.MacAddressLength) + { + throw new System.InvalidOperationException("expected physical address length of " + + EthernetFields.MacAddressLength + + " but it was " + + hwAddress.Length); + } + + Array.Copy(hwAddress, 0, + header.Bytes, header.Offset + ARPFields.TargetHardwareAddressPosition, + hwAddress.Length); + } + } + + /// Fetch ascii escape sequence of the color associated with this packet type. + override public System.String Color + { + get + { + return AnsiEscapeSequences.Purple; + } + } + + /// + /// Create an ARPPacket from values + /// + /// + /// A + /// + /// + /// A + /// + /// + /// A + /// + /// + /// A + /// + /// + /// A + /// + public ARPPacket(ARPOperation Operation, + PhysicalAddress TargetHardwareAddress, + System.Net.IPAddress TargetProtocolAddress, + PhysicalAddress SenderHardwareAddress, + System.Net.IPAddress SenderProtocolAddress) + : base(new PosixTimeval()) + { + log.Debug(""); + + // allocate memory for this packet + int offset = 0; + int length = ARPFields.HeaderLength; + var headerBytes = new byte[length]; + header = new ByteArraySegment(headerBytes, offset, length); + + this.Operation = Operation; + this.TargetHardwareAddress = TargetHardwareAddress; + this.TargetProtocolAddress = TargetProtocolAddress; + this.SenderHardwareAddress = SenderHardwareAddress; + this.SenderProtocolAddress = SenderProtocolAddress; + + // set some internal properties to fully define the packet + this.HardwareAddressType = LinkLayers.Ethernet; + this.HardwareAddressLength = EthernetFields.MacAddressLength; + + this.ProtocolAddressType = EthernetPacketType.IpV4; + this.ProtocolAddressLength = IPv4Fields.AddressLength; + } + + /// + /// byte[]/int Offset constructor + /// + /// + /// A + /// + /// + /// A + /// + public ARPPacket(byte[] Bytes, int Offset) : + this(Bytes, Offset, new PosixTimeval()) + { } + + /// + /// byte[]/int offset/PosixTimeval constructor + /// + /// + /// A + /// + /// + /// A + /// + /// + /// A + /// + public ARPPacket(byte[] Bytes, int Offset, PosixTimeval Timeval) : + base(Timeval) + { + header = new ByteArraySegment(Bytes, Offset, ARPFields.HeaderLength); + + // NOTE: no need to set the payloadPacketOrData field, arp packets have + // no payload + } + + /// Convert this ARP packet to a readable string. + public override System.String ToString() + { + return ToColoredString(false); + } + + /// Generate string with contents describing this ARP packet. + /// whether or not the string should contain ansi + /// color escape sequences. + /// + public override System.String ToColoredString(bool colored) + { + System.Text.StringBuilder buffer = new System.Text.StringBuilder(); + buffer.Append('['); + if (colored) + buffer.Append(Color); + buffer.Append("ARPPacket"); + if (colored) + buffer.Append(AnsiEscapeSequences.Reset); + buffer.Append(": "); + buffer.Append(Operation); + buffer.Append(' '); + buffer.Append(SenderHardwareAddress + " -> " + TargetHardwareAddress); + buffer.Append(", "); + buffer.Append(SenderProtocolAddress + " -> " + TargetProtocolAddress); + //buffer.append(" l=" + header.length + "," + data.length); + buffer.Append(']'); + + // append the base string output + buffer.Append(base.ToColoredString(colored)); + + return buffer.ToString(); + } + + /// + /// Returns the encapsulated ARPPacket of the Packet p or null if + /// there is no encapsulated packet + /// + /// + /// A + /// + /// + /// A + /// + public static ARPPacket GetEncapsulated(Packet p) + { + if(p is InternetLinkLayerPacket) + { + var payload = InternetLinkLayerPacket.GetInnerPayload((InternetLinkLayerPacket)p); + if(payload is ARPPacket) + { + return (ARPPacket)payload; + } + } + + return null; + } + } +} diff --git a/PacketDotNet/ApplicationPacket.cs b/PacketDotNet/ApplicationPacket.cs new file mode 100644 index 0000000..d135496 --- /dev/null +++ b/PacketDotNet/ApplicationPacket.cs @@ -0,0 +1,36 @@ +/* +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 . +*/ + +using PacketDotNet.Utils; + +namespace PacketDotNet +{ + /// + /// Represents an application layer packet as described at http://en.wikipedia.org/wiki/Application_Layer + /// + public abstract class ApplicationPacket : Packet + { + /// + /// ApplicationPacket constructor + /// + /// + /// A + /// + public ApplicationPacket(PosixTimeval Timeval) : base(Timeval) + {} + } +} diff --git a/PacketDotNet/AssemblyInfo.cs b/PacketDotNet/AssemblyInfo.cs new file mode 100644 index 0000000..de77789 --- /dev/null +++ b/PacketDotNet/AssemblyInfo.cs @@ -0,0 +1,26 @@ +using System.Reflection; +using System.Runtime.CompilerServices; + +// Information about this assembly is defined by the following attributes. +// Change them to the values specific to your project. + +[assembly: AssemblyTitle("PacketDotNet")] +[assembly: AssemblyDescription(".Net assembly for dissecting and constructing network packets")] +[assembly: AssemblyConfiguration("")] +[assembly: AssemblyCompany("")] +[assembly: AssemblyProduct("")] +[assembly: AssemblyCopyright("Chris Morgan (chmorgan@gmail.com)")] +[assembly: AssemblyTrademark("")] +[assembly: AssemblyCulture("")] + +// The assembly version has the format "{Major}.{Minor}.{Build}.{Revision}". +// The form "{Major}.{Minor}.*" will automatically update the build and revision, +// and "{Major}.{Minor}.{Build}.*" will update just the revision. + +[assembly: AssemblyVersion("0.5.0")] + +// The following attributes are used to specify the signing key for the assembly, +// if desired. See the Mono documentation for more information about signing. + +[assembly: AssemblyDelaySign(false)] +[assembly: AssemblyKeyFile("")] diff --git a/PacketDotNet/DataLinkPacket.cs b/PacketDotNet/DataLinkPacket.cs new file mode 100644 index 0000000..4f6a7e0 --- /dev/null +++ b/PacketDotNet/DataLinkPacket.cs @@ -0,0 +1,36 @@ +/* +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 . +*/ +using System; +using PacketDotNet.Utils; + +namespace PacketDotNet +{ + /// + /// Represents a Layer 2 protocol. + /// + public abstract class DataLinkPacket : Packet + { + /// + /// DataLinkPacket constructor + /// + /// + /// A + /// + public DataLinkPacket(PosixTimeval Timeval) : base(Timeval) + {} + } +} diff --git a/PacketDotNet/EthernetFields.cs b/PacketDotNet/EthernetFields.cs new file mode 100644 index 0000000..d748fea --- /dev/null +++ b/PacketDotNet/EthernetFields.cs @@ -0,0 +1,56 @@ +/* +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; + +namespace PacketDotNet +{ + /// + /// Ethernet protocol field encoding information. + /// + public class EthernetFields + { + /// Width of the ethernet type code in bytes. + public readonly static int TypeLength = 2; + + /// Position of the destination MAC address within the ethernet header. + public readonly static int DestinationMacPosition = 0; + + /// Position of the source MAC address within the ethernet header. + public readonly static int SourceMacPosition; + + /// Position of the ethernet type field within the ethernet header. + public readonly static int TypePosition; + + /// Total length of an ethernet header in bytes. + public readonly static int HeaderLength; // == 14 + + static EthernetFields() + { + SourceMacPosition = EthernetFields.MacAddressLength; + TypePosition = EthernetFields.MacAddressLength * 2; + HeaderLength = EthernetFields.TypePosition + EthernetFields.TypeLength; + } + + /// + /// size of an ethernet mac address in bytes + /// + public readonly static int MacAddressLength = 6; + } +} diff --git a/PacketDotNet/EthernetPacket.cs b/PacketDotNet/EthernetPacket.cs new file mode 100644 index 0000000..43836d0 --- /dev/null +++ b/PacketDotNet/EthernetPacket.cs @@ -0,0 +1,333 @@ +/* +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.Net.NetworkInformation; +using PacketDotNet.Utils; +using MiscUtil.Conversion; + +namespace PacketDotNet +{ + /// + /// See http://en.wikipedia.org/wiki/Ethernet#Ethernet_frame_types_and_the_EtherType_field + /// + public class EthernetPacket : InternetLinkLayerPacket + { +#if DEBUG + private static readonly log4net.ILog log = log4net.LogManager.GetLogger(System.Reflection.MethodBase.GetCurrentMethod().DeclaringType); +#else + // 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 + private static readonly ILogInactive log; +#pragma warning restore 0169 +#endif + + /// + /// Payload packet, overridden to set the 'Type' field based on + /// the type of packet being used here if the PayloadPacket is being set + /// + public override Packet PayloadPacket + { + get + { + return base.PayloadPacket; + } + + set + { + base.PayloadPacket = value; + + // set Type based on the type of the payload + if(value is IPv4Packet) + { + Type = EthernetPacketType.IpV4; + } else if(value is IPv6Packet) + { + Type = EthernetPacketType.IpV6; + } else if(value is ARPPacket) + { + Type = EthernetPacketType.Arp; + } + else if(value is LLDPPacket) + { + Type = EthernetPacketType.LLDP; + } + else // NOTE: new types should be inserted here + { + Type = EthernetPacketType.None; + } + } + } + + /// MAC address of the host where the packet originated from. + public virtual PhysicalAddress SourceHwAddress + { + get + { + byte[] hwAddress = new byte[EthernetFields.MacAddressLength]; + Array.Copy(header.Bytes, header.Offset + EthernetFields.SourceMacPosition, + hwAddress, 0, hwAddress.Length); + return new PhysicalAddress(hwAddress); + } + + set + { + byte[] hwAddress = value.GetAddressBytes(); + if(hwAddress.Length != EthernetFields.MacAddressLength) + { + throw new System.InvalidOperationException("address length " + hwAddress.Length + + " not equal to the expected length of " + + EthernetFields.MacAddressLength); + } + + Array.Copy(hwAddress, 0, header.Bytes, header.Offset + EthernetFields.SourceMacPosition, + hwAddress.Length); + } + } + + /// MAC address of the host where the packet originated from. + public virtual PhysicalAddress DestinationHwAddress + { + get + { + byte[] hwAddress = new byte[EthernetFields.MacAddressLength]; + Array.Copy(header.Bytes, header.Offset + EthernetFields.DestinationMacPosition, + hwAddress, 0, hwAddress.Length); + return new PhysicalAddress(hwAddress); + } + + set + { + byte[] hwAddress = value.GetAddressBytes(); + if(hwAddress.Length != EthernetFields.MacAddressLength) + { + throw new System.InvalidOperationException("address length " + hwAddress.Length + + " not equal to the expected length of " + + EthernetFields.MacAddressLength); + } + + Array.Copy(hwAddress, 0, header.Bytes, header.Offset + EthernetFields.DestinationMacPosition, + hwAddress.Length); + } + } + + /// + /// Type of packet that this ethernet packet encapsulates + /// + public virtual EthernetPacketType Type + { + get + { + return (EthernetPacketType)EndianBitConverter.Big.ToInt16(header.Bytes, + header.Offset + EthernetFields.TypePosition); + } + + set + { + Int16 val = (Int16)value; + EndianBitConverter.Big.CopyBytes(val, + header.Bytes, + header.Offset + EthernetFields.TypePosition); + } + } + + /// + /// Construct a new ethernet packet from source and destination mac addresses + /// + public EthernetPacket(PhysicalAddress SourceHwAddress, + PhysicalAddress DestinationHwAddress, + EthernetPacketType ethernetPacketType) + : base(new PosixTimeval()) + { + log.Debug(""); + + // allocate memory for this packet + int offset = 0; + int length = EthernetFields.HeaderLength; + var headerBytes = new byte[length]; + header = new ByteArraySegment(headerBytes, offset, length); + + // set the instance values + this.SourceHwAddress = SourceHwAddress; + this.DestinationHwAddress = DestinationHwAddress; + this.Type = ethernetPacketType; + } + + /// + /// Create an EthernetPacket from a byte array + /// + /// + /// A + /// + /// + /// A + /// + public EthernetPacket(byte[] Bytes, int Offset) : + this(Bytes, Offset, new PosixTimeval()) + { + log.Debug(""); + } + + /// + /// Create an EthernetPacket from a byte array and a Timeval + /// + /// + /// A + /// + /// + /// A + /// + /// + /// A + /// + public EthernetPacket(byte[] Bytes, int Offset, PosixTimeval Timeval) : + base(Timeval) + { + log.Debug(""); + + // slice off the header portion + header = new ByteArraySegment(Bytes, Offset, EthernetFields.HeaderLength); + + // parse the encapsulated bytes + payloadPacketOrData = ParseEncapsulatedBytes(header, Type, Timeval); + } + + /// + /// Used by the EthernetPacket constructor. Located here because the LinuxSLL constructor + /// also needs to perform the same operations as it contains an ethernet type + /// + /// + /// A + /// + /// + /// A + /// + /// + /// A + /// + /// + /// A + /// + internal static PacketOrByteArraySegment ParseEncapsulatedBytes(ByteArraySegment Header, + EthernetPacketType Type, + PosixTimeval Timeval) + { + // slice off the payload + var payload = Header.EncapsulatedBytes(); + log.DebugFormat("payload {0}", payload.ToString()); + + var payloadPacketOrData = new PacketOrByteArraySegment(); + + // parse the encapsulated bytes + switch(Type) + { + case EthernetPacketType.IpV4: + payloadPacketOrData.ThePacket = new IPv4Packet(payload.Bytes, payload.Offset, Timeval); + break; + case EthernetPacketType.IpV6: + payloadPacketOrData.ThePacket = new IPv6Packet(payload.Bytes, payload.Offset, Timeval); + break; + case EthernetPacketType.Arp: + payloadPacketOrData.ThePacket = new ARPPacket(payload.Bytes, payload.Offset, Timeval); + break; + case EthernetPacketType.LLDP: + payloadPacketOrData.ThePacket = new LLDPPacket(payload.Bytes, payload.Offset, Timeval); + break; + case EthernetPacketType.PointToPointProtocolOverEthernetSessionStage: + payloadPacketOrData.ThePacket = new PPPoEPacket(payload.Bytes, payload.Offset, Timeval); + break; + default: // consider the sub-packet to be a byte array + payloadPacketOrData.TheByteArraySegment = payload; + break; + } + + return payloadPacketOrData; + } + + /// Fetch ascii escape sequence of the color associated with this packet type. + public override System.String Color + { + get + { + return AnsiEscapeSequences.DarkGray; + } + } + + /// Convert this ethernet packet to a readable string. + public override System.String ToString() + { + return ToColoredString(false); + } + + /// Generate string with contents describing this ethernet packet. + /// whether or not the string should contain ansi + /// color escape sequences. + /// + public override System.String ToColoredString(bool colored) + { + System.Text.StringBuilder buffer = new System.Text.StringBuilder(); + buffer.Append('['); + if (colored) + buffer.Append(Color); + buffer.Append("EthernetPacket"); + if (colored) + buffer.Append(AnsiEscapeSequences.Reset); + buffer.Append(": "); + buffer.Append(SourceHwAddress + " -> " + DestinationHwAddress); + buffer.Append(" proto=" + Type.ToString() + " (0x" + System.Convert.ToString((ushort)Type, 16) + ")"); + buffer.Append(" l=" + EthernetFields.HeaderLength); // + "," + data.length); + buffer.Append(']'); + + // append the base output + buffer.Append(base.ToColoredString(colored)); + + return buffer.ToString(); + } + + /// Convert a more verbose string. + public override System.String ToColoredVerboseString(bool colored) + { + //TODO: just output the colored output for now + return ToColoredString(colored); + } + + /// + /// Generate a random EthernetPacket + /// TODO: could improve this routine to set a random payload as well + /// + /// + /// A + /// + public static EthernetPacket RandomPacket() + { + var rnd = new Random(); + + byte[] srcPhysicalAddress = new byte[EthernetFields.MacAddressLength]; + byte[] dstPhysicalAddress = new byte[EthernetFields.MacAddressLength]; + + rnd.NextBytes(srcPhysicalAddress); + rnd.NextBytes(dstPhysicalAddress); + + return new EthernetPacket(new PhysicalAddress(srcPhysicalAddress), + new PhysicalAddress(dstPhysicalAddress), + EthernetPacketType.None); + } + } +} diff --git a/PacketDotNet/EthernetPacketType.cs b/PacketDotNet/EthernetPacketType.cs new file mode 100644 index 0000000..e0d1792 --- /dev/null +++ b/PacketDotNet/EthernetPacketType.cs @@ -0,0 +1,174 @@ +/* +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 . +*/ +using System; + +namespace PacketDotNet +{ + /// Copied from Pcap.Net @ 20091117 + /// + /// + /// Code constants for well-defined ethernet protocols. + /// + /// EtherType is a two-octet field in an Ethernet frame, as defined by the Ethernet II framing networking standard. + /// It is used to indicate which protocol is encapsulated in the payload. + /// + /// Also contains entries taken from linux/if_ether.h and tcpdump/ethertype.h + /// + public enum EthernetPacketType : ushort + { + /// + /// No Ethernet type + /// + None = 0x0000, + /// + /// Internet Protocol, Version 4 (IPv4) + /// + IpV4 = 0x0800, + /// + /// Address Resolution Protocol (ARP) + /// + Arp = 0x0806, + /// + /// Reverse Address Resolution Protocol (RARP) + /// + ReverseArp = 0x8035, + /// + /// AppleTalk (Ethertalk) + /// + AppleTalk = 0x809B, + /// + /// AppleTalk Address Resolution Protocol (AARP) + /// + AppleTalkArp = 0x80F3, + /// + /// VLAN-tagged frame (IEEE 802.1Q) + /// + VLanTaggedFrame = 0x8100, + /// + /// Novell IPX (alt) + /// + NovellInternetworkPacketExchange = 0x8137, + /// + /// Novell + /// + Novell = 0x8138, + /// + /// Internet Protocol, Version 6 (IPv6) + /// + IpV6 = 0x86DD, + /// + /// MAC Control + /// + MacControl = 0x8808, + /// + /// CobraNet + /// + CobraNet = 0x8819, + /// + /// MPLS unicast + /// + MultiprotocolLabelSwitchingUnicast = 0x8847, + /// + /// MPLS multicast + /// + MultiprotocolLabelSwitchingMulticast = 0x8848, + /// + /// PPPoE Discovery Stage + /// + PointToPointProtocolOverEthernetDiscoveryStage = 0x8863, + /// + /// PPPoE Session Stage + /// + PointToPointProtocolOverEthernetSessionStage = 0x8864, + /// + /// EAP over LAN (IEEE 802.1X) + /// + ExtensibleAuthenticationProtocolOverLan = 0x888E, + /// + /// HyperSCSI (SCSI over Ethernet) + /// + HyperScsi = 0x889A, + /// + /// ATA over Ethernet + /// + AtaOverEthernet = 0x88A2, + /// + /// EtherCAT Protocol + /// + EtherCatProtocol = 0x88A4, + /// + /// Provider Bridging (IEEE 802.1ad) + /// + ProviderBridging = 0x88A8, + /// + /// AVB Transport Protocol (AVBTP) + /// + AvbTransportProtocol = 0x88B5, + /// + /// Link Layer Discovery Protocol (LLDP) + /// + LLDP = 0x88CC, + /// + /// SERCOS III + /// + SerialRealTimeCommunicationSystemIii = 0x88CD, + /// + /// Circuit Emulation Services over Ethernet (MEF-8) + /// + CircuitEmulationServicesOverEthernet = 0x88D8, + /// + /// HomePlug + /// + HomePlug = 0x88E1, + /// + /// MAC security (IEEE 802.1AE) + /// + MacSecurity = 0x88E5, + /// + /// Precision Time Protocol (IEEE 1588) + /// + PrecisionTimeProtocol = 0x88f7, + /// + /// IEEE 802.1ag Connectivity Fault Management (CFM) Protocol / ITU-T Recommendation Y.1731 (OAM) + /// + ConnectivityFaultManagementOrOperationsAdministrationManagement = 0x8902, + /// + /// Fibre Channel over Ethernet + /// + FibreChannelOverEthernet = 0x8906, + /// + /// FCoE Initialization Protocol + /// + FibreChannelOverEthernetInitializationProtocol = 0x8914, + /// + /// Q-in-Q + /// + QInQ = 0x9100, + /// + /// Veritas Low Latency Transport (LLT) + /// + VeritasLowLatencyTransport = 0xCAFE, + /// + /// Ethernet loopback packet + /// + Loop = 0x0060, + /// + /// Ethernet echo packet + /// + Echo = 0x0200 + } +} diff --git a/PacketDotNet/ICMPv4Fields.cs b/PacketDotNet/ICMPv4Fields.cs new file mode 100644 index 0000000..8c08f32 --- /dev/null +++ b/PacketDotNet/ICMPv4Fields.cs @@ -0,0 +1,57 @@ +/* +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 + */ +namespace PacketDotNet +{ + /// + /// ICMP protocol field encoding information. + /// See http://en.wikipedia.org/wiki/ICMPv6 + /// + public class ICMPv4Fields + { + /// Length of the ICMP message type code in bytes. + public readonly static int TypeCodeLength = 2; + /// Length of the ICMP header checksum in bytes. + public readonly static int ChecksumLength = 2; + /// Length of the ICMP ID field in bytes. + public readonly static int IDLength = 2; + /// Length of the ICMP Sequence field in bytes + public readonly static int SequenceLength = 2; + + /// Position of the ICMP message type/code. + public readonly static int TypeCodePosition = 0; + /// Position of the ICMP header checksum. + public readonly static int ChecksumPosition; + /// Position of the ICMP ID field + public readonly static int IDPosition; + /// Position of the Sequence field + public readonly static int SequencePosition; + /// Length in bytes of an ICMP header. + public readonly static int HeaderLength; + + static ICMPv4Fields() + { + TypeCodePosition = 0; + ChecksumPosition = TypeCodePosition + TypeCodeLength; + IDPosition = ChecksumPosition + ChecksumLength; + SequencePosition = IDPosition + IDLength; + HeaderLength = SequencePosition + SequenceLength; + } + } +} diff --git a/PacketDotNet/ICMPv4Packet.cs b/PacketDotNet/ICMPv4Packet.cs new file mode 100644 index 0000000..87b6b3a --- /dev/null +++ b/PacketDotNet/ICMPv4Packet.cs @@ -0,0 +1,250 @@ +/* +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 2010 Chris Morgan + */ +using System; +using MiscUtil.Conversion; +using PacketDotNet.Utils; + +namespace PacketDotNet +{ + /// + /// An ICMP packet + /// See http://en.wikipedia.org/wiki/Internet_Control_Message_Protocol + /// + [Serializable] + public class ICMPv4Packet : InternetPacket + { +#if DEBUG + private static readonly log4net.ILog log = log4net.LogManager.GetLogger(System.Reflection.MethodBase.GetCurrentMethod().DeclaringType); +#else + // 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 + private static readonly ILogInactive log; +#pragma warning restore 0169 +#endif + + /// + /// The Type/Code enum value + /// + virtual public ICMPv4TypeCodes TypeCode + { + get + { + var val = EndianBitConverter.Big.ToUInt16(header.Bytes, + header.Offset + ICMPv4Fields.TypeCodePosition); + + //TODO: how to handle a mismatch in the mapping? maybe throw here? + if(Enum.IsDefined(typeof(ICMPv4TypeCodes), val)) + return (ICMPv4TypeCodes)val; + else + throw new System.NotImplementedException("TypeCode of " + val + " is not defined in ICMPv4TypeCode"); + } + + set + { + var theValue = (UInt16)value; + EndianBitConverter.Big.CopyBytes(theValue, + header.Bytes, + header.Offset + ICMPv4Fields.TypeCodePosition); + } + } + + /// + /// Checksum value + /// + public ushort Checksum + { + get + { + return EndianBitConverter.Big.ToUInt16(header.Bytes, + header.Offset + ICMPv4Fields.ChecksumPosition); + } + + set + { + var theValue = value; + EndianBitConverter.Big.CopyBytes(theValue, + header.Bytes, + header.Offset + ICMPv4Fields.ChecksumPosition); + } + } + + /// + /// ID field + /// + public ushort ID + { + get + { + return EndianBitConverter.Big.ToUInt16(header.Bytes, + header.Offset + ICMPv4Fields.IDPosition); + } + + set + { + var theValue = value; + EndianBitConverter.Big.CopyBytes(theValue, + header.Bytes, + header.Offset + ICMPv4Fields.IDPosition); + } + } + + /// + /// Sequence field + /// + public ushort Sequence + { + get + { + return EndianBitConverter.Big.ToUInt16(header.Bytes, + header.Offset + ICMPv4Fields.SequencePosition); + } + + set + { + EndianBitConverter.Big.CopyBytes(value, + header.Bytes, + header.Offset + ICMPv4Fields.SequencePosition); + } + } + + /// + /// Contents of the ICMP packet + /// + public byte[] Data + { + get + { + return payloadPacketOrData.TheByteArraySegment.ActualBytes(); + } + + set + { + payloadPacketOrData.TheByteArraySegment = new ByteArraySegment(value, 0, value.Length); + } + } + + /// + /// byte[]/int offset constructor + /// + /// + /// A + /// + /// + /// A + /// + public ICMPv4Packet(byte[] Bytes, int Offset) : + this(Bytes, Offset, new PosixTimeval()) + {} + + /// + /// byte[]/int Offset/PosixTimeval constructor + /// + /// + /// A + /// + /// + /// A + /// + /// + /// A + /// + public ICMPv4Packet(byte[] Bytes, int Offset, PosixTimeval Timeval) : + base(Timeval) + { + log.Debug(""); + + header = new ByteArraySegment(Bytes, Offset, ICMPv4Fields.HeaderLength); + + // store the payload bytes + payloadPacketOrData = new PacketOrByteArraySegment(); + payloadPacketOrData.TheByteArraySegment = header.EncapsulatedBytes(); + + + } + + /// Fetch ascii escape sequence of the color associated with this packet type. + override public System.String Color + { + get + { + return AnsiEscapeSequences.LightBlue; + } + } + + /// Convert this ICMP packet to a readable string. + public override System.String ToString() + { + return ToColoredString(false); + } + + /// Generate string with contents describing this ICMP packet. + /// whether or not the string should contain ansi + /// color escape sequences. + /// + public override System.String ToColoredString(bool colored) + { + System.Text.StringBuilder buffer = new System.Text.StringBuilder(); + buffer.Append('['); + if (colored) + buffer.Append(Color); + buffer.Append("ICMPPacket"); + if (colored) + buffer.Append(AnsiEscapeSequences.Reset); + buffer.Append(": "); + buffer.Append(TypeCode); + buffer.Append(", "); + buffer.Append(" l=" + header.Length); + buffer.Append(']'); + + return buffer.ToString(); + } + + /// + /// Returns the ICMPv4Packet inside of Packet p or null if + /// there is no encapsulated ICMPv4Packet + /// + /// + /// A + /// + /// + /// A + /// + public static ICMPv4Packet GetEncapsulated(Packet p) + { + log.Debug(""); + + if(p is InternetLinkLayerPacket) + { + var payload = InternetLinkLayerPacket.GetInnerPayload((InternetLinkLayerPacket)p); + if(payload is IpPacket) + { + var payload2 = payload.PayloadPacket; + if(payload2 is ICMPv4Packet) + { + return (ICMPv4Packet)payload2; + } + } + } + + return null; + } + } +} diff --git a/PacketDotNet/ICMPv4TypeCodes.cs b/PacketDotNet/ICMPv4TypeCodes.cs new file mode 100644 index 0000000..ffb6367 --- /dev/null +++ b/PacketDotNet/ICMPv4TypeCodes.cs @@ -0,0 +1,52 @@ +/* +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 + */ +namespace PacketDotNet +{ + /// + /// Code constants for ICMP message types. + /// From http://en.wikipedia.org/wiki/Internet_Control_Message_Protocol#List_of_permitted_control_messages_.28incomplete_list.29 + /// Note that these values represent the combined + /// type and code fields, where the type field is the upper byte + /// + public enum ICMPv4TypeCodes : ushort + { +#pragma warning disable 1591 + EchoReply = 0x0000, + + // Destination Unreachable replies + DestinationNetworkUnreachable = 0x0300, + DestinationHostUnreachable = 0x0301, + DestinationProtocolUnreachable = 0x0302, + DestinationPortUnreachable = 0x303, + FragmentationRequiredAndDFFlagSet = 0x0304, + SourceRouteFailed = 0x0305, + DestinationNetworkUnknown = 0x0306, + DestinationHostUnknown = 0x0307, + SourceHostIsolated = 0x0308, + NetworkAdministrativelyProhibited = 0x0309, + NetworkUnreachableForTos = 0x030A, + HostUnreachableForTos = 0x030B, + CommunicationAdministrativelyProhibited = 0x030C, + EchoRequest = 0x0800, +#pragma warning restore 1591 + + //TODO: continue this list as user requested + } +} diff --git a/PacketDotNet/ICMPv6Fields.cs b/PacketDotNet/ICMPv6Fields.cs new file mode 100644 index 0000000..c7376ac --- /dev/null +++ b/PacketDotNet/ICMPv6Fields.cs @@ -0,0 +1,50 @@ +/* +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 + */ +namespace PacketDotNet +{ + /// + /// ICMP protocol field encoding information. + /// See http://en.wikipedia.org/wiki/ICMPv6 + /// + public class ICMPv6Fields + { + /// Length of the ICMP message type code in bytes. + public readonly static int TypeLength = 1; + /// Length of the ICMP subcode in bytes. + public readonly static int CodeLength = 1; + /// Length of the ICMP header checksum in bytes. + public readonly static int ChecksumLength = 2; + /// Position of the ICMP message type. + public readonly static int TypePosition = 0; + /// Position of the ICMP message subcode. + public readonly static int CodePosition; + /// Position of the ICMP header checksum. + public readonly static int ChecksumPosition; + /// Length in bytes of an ICMP header. + public readonly static int HeaderLength; // == 4 + + static ICMPv6Fields() + { + CodePosition = TypePosition + TypeLength; + ChecksumPosition = CodePosition + CodeLength; + HeaderLength = ChecksumPosition + ChecksumLength; + } + } +} diff --git a/PacketDotNet/ICMPv6Packet.cs b/PacketDotNet/ICMPv6Packet.cs new file mode 100644 index 0000000..ce576b1 --- /dev/null +++ b/PacketDotNet/ICMPv6Packet.cs @@ -0,0 +1,200 @@ +/* +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 MiscUtil.Conversion; +using PacketDotNet.Utils; + +namespace PacketDotNet +{ + /// + /// An ICMP packet. + /// See http://en.wikipedia.org/wiki/ICMPv6 + /// + [Serializable] + public class ICMPv6Packet : InternetPacket + { +#if DEBUG + private static readonly log4net.ILog log = log4net.LogManager.GetLogger(System.Reflection.MethodBase.GetCurrentMethod().DeclaringType); +#else + // 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 + private static readonly ILogInactive log; +#pragma warning restore 0169 +#endif + + /// + /// The Type value + /// + virtual public ICMPv6Types Type + { + get + { + var val = header.Bytes[header.Offset + ICMPv6Fields.TypePosition]; + + //TODO: how to handle a mismatch in the mapping? maybe throw here? + if(Enum.IsDefined(typeof(ICMPv6Types), val)) + return (ICMPv6Types)val; + else + throw new System.NotImplementedException("Type of " + val + " is not defined in ICMPv6Types"); + } + + set + { + header.Bytes[header.Offset + ICMPv6Fields.TypePosition] = (byte)value; + } + } + + /// Fetch the ICMP code + virtual public byte Code + { + get + { + return header.Bytes[header.Offset + ICMPv6Fields.CodePosition]; + } + + set + { + header.Bytes[header.Offset + ICMPv6Fields.CodePosition] = (byte)value; + } + } + + /// + /// Checksum value + /// + public ushort Checksum + { + get + { + return EndianBitConverter.Big.ToUInt16(header.Bytes, + header.Offset + ICMPv6Fields.ChecksumPosition); + } + + set + { + var theValue = value; + EndianBitConverter.Big.CopyBytes(theValue, + header.Bytes, + header.Offset + ICMPv6Fields.ChecksumPosition); + } + } + + /// + /// byte[]/int offset constructor + /// + /// + /// A + /// + /// + /// A + /// + public ICMPv6Packet(byte[] Bytes, int Offset) : + this(Bytes, Offset, new PosixTimeval()) + {} + + /// + /// byte[]/int Offset/PosixTimeval constructor + /// + /// + /// A + /// + /// + /// A + /// + /// + /// A + /// + public ICMPv6Packet(byte[] Bytes, int Offset, PosixTimeval Timeval) : + base(Timeval) + { + log.Debug(""); + + header = new ByteArraySegment(Bytes, Offset, Bytes.Length - Offset); + } + + /// Fetch ascii escape sequence of the color associated with this packet type. + override public System.String Color + { + get + { + return AnsiEscapeSequences.LightBlue; + } + } + + /// Convert this ICMP packet to a readable string. + public override System.String ToString() + { + return ToColoredString(false); + } + + /// Generate string with contents describing this ICMP packet. + /// whether or not the string should contain ansi + /// color escape sequences. + /// + public override System.String ToColoredString(bool colored) + { + System.Text.StringBuilder buffer = new System.Text.StringBuilder(); + buffer.Append('['); + if (colored) + buffer.Append(Color); + buffer.Append("ICMPPacket"); + if (colored) + buffer.Append(AnsiEscapeSequences.Reset); + buffer.Append(": "); + buffer.Append(Type); + buffer.Append(Code); + buffer.Append(", "); + buffer.Append(" l=" + header.Length); + buffer.Append(']'); + + return buffer.ToString(); + } + + /// + /// Returns the ICMPv6Packet inside of Packet p or null if + /// there is no encapsulated ICMPv6Packet + /// + /// + /// A + /// + /// + /// A + /// + public static ICMPv6Packet GetEncapsulated(Packet p) + { + log.Debug(""); + + if(p is InternetLinkLayerPacket) + { + var payload = InternetLinkLayerPacket.GetInnerPayload((InternetLinkLayerPacket)p); + if(payload is IpPacket) + { + var payload2 = payload.PayloadPacket; + if(payload2 is ICMPv6Packet) + { + return (ICMPv6Packet)payload2; + } + } + } + + return null; + } + } +} diff --git a/PacketDotNet/ICMPv6Types.cs b/PacketDotNet/ICMPv6Types.cs new file mode 100644 index 0000000..188b916 --- /dev/null +++ b/PacketDotNet/ICMPv6Types.cs @@ -0,0 +1,51 @@ +/* +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 2010 Chris Morgan + */ + +using System; + +namespace PacketDotNet +{ + /// + /// ICMPv6 types, see http://en.wikipedia.org/wiki/ICMPv6 and + /// http://www.iana.org/assignments/icmpv6-parameters + /// + public enum ICMPv6Types : byte + { +#pragma warning disable 1591 + #region ICMPv6 Error Messages + DestinationUnreachable = 1, + PacketTooBig = 2, + TimeExceeded = 3, + ParameterProblem = 4, + PrivateExperimentation1 = 100, + PrivateExperimentation2 = 101, + ReservedForExpansion1 = 127, + #endregion + #region ICMPv6 Informational Messages + EchoRequest = 128, + EchoReply = 129, + RouterSolicitation = 133, + PrivateExperimentation3 = 200, + PrivateExperimentation4 = 201, + ReservedForExpansion2 = 255 + #endregion +#pragma warning restore 1591 + } +} diff --git a/PacketDotNet/IGMPMessageType.cs b/PacketDotNet/IGMPMessageType.cs new file mode 100644 index 0000000..9cbfe98 --- /dev/null +++ b/PacketDotNet/IGMPMessageType.cs @@ -0,0 +1,37 @@ +/* +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 + */ +namespace PacketDotNet +{ + /// Code constants for IGMP message types. + /// + /// From RFC #2236. + /// + /// + public enum IGMPMessageType : byte + { +#pragma warning disable 1591 + MembershipQuery = 0x11, + MembershipReportIGMPv1 = 0x12, + MembershipReportIGMPv2 = 0x16, + MembershipReportIGMPv3 = 0x22, + LeaveGroup = 0x17, +#pragma warning restore 1591 + } +} diff --git a/PacketDotNet/IGMPv2Fields.cs b/PacketDotNet/IGMPv2Fields.cs new file mode 100644 index 0000000..8a06f5b --- /dev/null +++ b/PacketDotNet/IGMPv2Fields.cs @@ -0,0 +1,53 @@ +/* +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 + */ +namespace PacketDotNet +{ + /// IGMP protocol field encoding information. + public class IGMPv2Fields + { + /// Length of the IGMP message type code in bytes. + public readonly static int TypeLength = 1; + /// Length of the IGMP max response code in bytes. + public readonly static int MaxResponseTimeLength = 1; + /// Length of the IGMP header checksum in bytes. + public readonly static int ChecksumLength = 2; + /// Length of group address in bytes. + public readonly static int GroupAddressLength; + /// Position of the IGMP message type. + public readonly static int TypePosition = 0; + /// Position of the IGMP max response code. + public readonly static int MaxResponseTimePosition; + /// Position of the IGMP header checksum. + public readonly static int ChecksumPosition; + /// Position of the IGMP group address. + public readonly static int GroupAddressPosition; + /// Length in bytes of an IGMP header. + public readonly static int HeaderLength; // 8 + + static IGMPv2Fields() + { + GroupAddressLength = IPv4Fields.AddressLength; + MaxResponseTimePosition = TypePosition + TypeLength; + ChecksumPosition = MaxResponseTimePosition + MaxResponseTimeLength; + GroupAddressPosition = ChecksumPosition + ChecksumLength; + HeaderLength = GroupAddressPosition + GroupAddressLength; + } + } +} diff --git a/PacketDotNet/IGMPv2Packet.cs b/PacketDotNet/IGMPv2Packet.cs new file mode 100644 index 0000000..d2bf74f --- /dev/null +++ b/PacketDotNet/IGMPv2Packet.cs @@ -0,0 +1,162 @@ +/* +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 MiscUtil.Conversion; +using PacketDotNet.Utils; + +namespace PacketDotNet +{ + /// + /// An IGMP packet. + /// + [Serializable] + public class IGMPv2Packet : InternetPacket + { + /// + /// The type of IGMP message + /// + virtual public IGMPMessageType Type + { + get + { + return (IGMPMessageType)header.Bytes[header.Offset + IGMPv2Fields.TypePosition]; + } + + set + { + header.Bytes[header.Offset + IGMPv2Fields.TypePosition] = (byte)value; + } + } + + /// Fetch the IGMP max response time. + virtual public int MaxResponseTime + { + get + { + return header.Bytes[header.Offset + IGMPv2Fields.MaxResponseTimePosition]; + } + + set + { + header.Bytes[header.Offset + IGMPv2Fields.MaxResponseTimePosition] = (byte)value; + } + } + + /// Fetch the IGMP header checksum. + virtual public int Checksum + { + get + { + return EndianBitConverter.Big.ToInt16(header.Bytes, + header.Offset + IGMPv2Fields.ChecksumPosition); + } + + set + { + var theValue = (Int16)value; + EndianBitConverter.Big.CopyBytes(theValue, + header.Bytes, + header.Offset + IGMPv2Fields.ChecksumPosition); + } + } + + /// Fetch the IGMP group address. + virtual public System.Net.IPAddress GroupAddress + { + get + { + return IpPacket.GetIPAddress(System.Net.Sockets.AddressFamily.InterNetwork, + header.Offset + IGMPv2Fields.GroupAddressPosition, + header.Bytes); + } + + } + + /// Fetch ascii escape sequence of the color associated with this packet type. + override public System.String Color + { + get + { + return AnsiEscapeSequences.Brown; + } + + } + + /// + /// byte[]/int Offset constructor + /// + /// + /// A + /// + /// + /// A + /// + public IGMPv2Packet(byte[] Bytes, int Offset) : + this(Bytes, Offset, new PosixTimeval()) + { } + + /// + /// byte[]/int offset/PosixTimeval constructor + /// + /// + /// A + /// + /// + /// A + /// + /// + /// A + /// + public IGMPv2Packet(byte[] Bytes, int Offset, PosixTimeval Timeval) : + base(Timeval) + { + throw new System.NotImplementedException(); + } + + /// Convert this IGMP packet to a readable string. + public override System.String ToString() + { + return ToColoredString(false); + } + + /// Generate string with contents describing this IGMP packet. + /// whether or not the string should contain ansi + /// color escape sequences. + /// + public override System.String ToColoredString(bool colored) + { + System.Text.StringBuilder buffer = new System.Text.StringBuilder(); + buffer.Append('['); + if (colored) + buffer.Append(Color); + buffer.Append("IGMPPacket"); + if (colored) + buffer.Append(AnsiEscapeSequences.Reset); + buffer.Append(": "); + buffer.Append(Type); + buffer.Append(", "); + buffer.Append(GroupAddress + ": "); + buffer.Append(" l=" + IGMPv2Fields.HeaderLength); + buffer.Append(']'); + + return buffer.ToString(); + } + } +} \ No newline at end of file diff --git a/PacketDotNet/ILogInactive.cs b/PacketDotNet/ILogInactive.cs new file mode 100644 index 0000000..e1d88ff --- /dev/null +++ b/PacketDotNet/ILogInactive.cs @@ -0,0 +1,256 @@ +using System; +#if DEBUG +using log4net.Core; +#endif + +namespace PacketDotNet +{ +#if !DEBUG + // For Release builds we disable logging by using this class + // in place of a log4net logger + internal class ILogInactive + { +#if false + public bool IsDebugEnabled + { + get { return false; } + } + + public bool IsInfoEnabled + { + get { return false; } + } + + public bool IsWarnEnabled + { + get { return false; } + } + + public bool IsErrorEnabled + { + get { return false; } + } + + public bool IsFatalEnabled + { + get { return false; } + } +#endif + + [System.Diagnostics.ConditionalAttribute("DEBUG")] + public void Debug (object message) + { + throw new System.NotImplementedException(); + } + + [System.Diagnostics.ConditionalAttribute("DEBUG")] + public void Debug (object message, System.Exception exception) + { + throw new System.NotImplementedException (); + } + + [System.Diagnostics.ConditionalAttribute("DEBUG")] + public void DebugFormat(string format, params object[] args) + { + throw new System.NotImplementedException (); + } + + [System.Diagnostics.ConditionalAttribute("DEBUG")] + public void DebugFormat (System.IFormatProvider provider, string format, params object[] args) + { + throw new System.NotImplementedException (); + } + + [System.Diagnostics.ConditionalAttribute("DEBUG")] + public void DebugFormat (string format, object arg0) + { + throw new System.NotImplementedException (); + } + + [System.Diagnostics.ConditionalAttribute("DEBUG")] + public void DebugFormat (string format, object arg0, object arg1) + { + throw new System.NotImplementedException (); + } + + [System.Diagnostics.ConditionalAttribute("DEBUG")] + public void DebugFormat (string format, object arg0, object arg1, object arg2) + { + throw new System.NotImplementedException (); + } + + [System.Diagnostics.ConditionalAttribute("DEBUG")] + public void Error (object message) + { + throw new System.NotImplementedException (); + } + + [System.Diagnostics.ConditionalAttribute("DEBUG")] + public void Error (object message, System.Exception exception) + { + throw new System.NotImplementedException (); + } + + [System.Diagnostics.ConditionalAttribute("DEBUG")] + public void ErrorFormat(string format, params object[] args) + { + throw new System.NotImplementedException (); + } + + [System.Diagnostics.ConditionalAttribute("DEBUG")] + public void ErrorFormat (System.IFormatProvider provider, string format, params object[] args) + { + throw new System.NotImplementedException (); + } + + [System.Diagnostics.ConditionalAttribute("DEBUG")] + public void ErrorFormat (string format, object arg0) + { + throw new System.NotImplementedException (); + } + + [System.Diagnostics.ConditionalAttribute("DEBUG")] + public void ErrorFormat (string format, object arg0, object arg1) + { + throw new System.NotImplementedException (); + } + + [System.Diagnostics.ConditionalAttribute("DEBUG")] + public void ErrorFormat (string format, object arg0, object arg1, object arg2) + { + throw new System.NotImplementedException (); + } + + [System.Diagnostics.ConditionalAttribute("DEBUG")] + public void Fatal (object message) + { + throw new System.NotImplementedException (); + } + + [System.Diagnostics.ConditionalAttribute("DEBUG")] + public void Fatal (object message, System.Exception exception) + { + throw new System.NotImplementedException (); + } + + [System.Diagnostics.ConditionalAttribute("DEBUG")] + public void FatalFormat(string format, params object[] args) + { + throw new System.NotImplementedException (); + } + + [System.Diagnostics.ConditionalAttribute("DEBUG")] + public void FatalFormat (System.IFormatProvider provider, string format, params object[] args) + { + throw new System.NotImplementedException (); + } + + [System.Diagnostics.ConditionalAttribute("DEBUG")] + public void FatalFormat (string format, object arg0) + { + throw new System.NotImplementedException (); + } + + [System.Diagnostics.ConditionalAttribute("DEBUG")] + public void FatalFormat (string format, object arg0, object arg1) + { + throw new System.NotImplementedException (); + } + + [System.Diagnostics.ConditionalAttribute("DEBUG")] + public void FatalFormat (string format, object arg0, object arg1, object arg2) + { + throw new System.NotImplementedException (); + } + + [System.Diagnostics.ConditionalAttribute("DEBUG")] + public void Info (object message) + { + throw new System.NotImplementedException (); + } + + [System.Diagnostics.ConditionalAttribute("DEBUG")] + public void Info (object message, System.Exception exception) + { + throw new System.NotImplementedException (); + } + + [System.Diagnostics.ConditionalAttribute("DEBUG")] + public void InfoFormat(string format, params object[] args) + { + throw new System.NotImplementedException (); + } + + [System.Diagnostics.ConditionalAttribute("DEBUG")] + public void InfoFormat (System.IFormatProvider provider, string format, params object[] args) + { + throw new System.NotImplementedException (); + } + + [System.Diagnostics.ConditionalAttribute("DEBUG")] + public void InfoFormat (string format, object arg0) + { + throw new System.NotImplementedException (); + } + + [System.Diagnostics.ConditionalAttribute("DEBUG")] + public void InfoFormat (string format, object arg0, object arg1) + { + throw new System.NotImplementedException (); + } + + [System.Diagnostics.ConditionalAttribute("DEBUG")] + public void InfoFormat (string format, object arg0, object arg1, object arg2) + { + throw new System.NotImplementedException (); + } + + [System.Diagnostics.ConditionalAttribute("DEBUG")] + public void Warn (object message) + { + throw new System.NotImplementedException (); + } + + [System.Diagnostics.ConditionalAttribute("DEBUG")] + public void Warn (object message, System.Exception exception) + { + throw new System.NotImplementedException (); + } + + [System.Diagnostics.ConditionalAttribute("DEBUG")] + public void WarnFormat(string format, params object[] args) + { + throw new System.NotImplementedException (); + } + + [System.Diagnostics.ConditionalAttribute("DEBUG")] + public void WarnFormat (System.IFormatProvider provider, string format, params object[] args) + { + throw new System.NotImplementedException (); + } + + [System.Diagnostics.ConditionalAttribute("DEBUG")] + public void WarnFormat (string format, object arg0) + { + throw new System.NotImplementedException (); + } + + [System.Diagnostics.ConditionalAttribute("DEBUG")] + public void WarnFormat (string format, object arg0, object arg1) + { + throw new System.NotImplementedException (); + } + + [System.Diagnostics.ConditionalAttribute("DEBUG")] + public void WarnFormat (string format, object arg0, object arg1, object arg2) + { + throw new System.NotImplementedException (); + } + + public static ILogInactive GetLogger(Type type) + { + return new ILogInactive(); + } + } +#endif +} diff --git a/PacketDotNet/IPProtocol.cs b/PacketDotNet/IPProtocol.cs new file mode 100644 index 0000000..2c33ba0 --- /dev/null +++ b/PacketDotNet/IPProtocol.cs @@ -0,0 +1,67 @@ +// ************************************************************************ +// Copyright (C) 2001, Patrick Charles and Jonas Lehmann * +// Distributed under the Mozilla Public License * +// http://www.mozilla.org/NPL/MPL-1.1.txt * +// ************************************************************************* + +using System; + +namespace PacketDotNet +{ + /// + /// String representation of an IP protocol value + /// + public class IPProtocol + { + /// Fetch a protocol description. + /// the code associated with the message. + /// + /// a message describing the significance of the IP protocol. + /// + public static System.String getDescription(int code) + { + System.Int32 c = (System.Int32) code; + if (messages.ContainsKey(c)) + { + return (System.String) messages[c]; + } else + { + return "unknown"; + } + } + + /// 'Human-readable' IP protocol descriptions. + private static System.Collections.Hashtable messages = new System.Collections.Hashtable(); + + static IPProtocol() + { + messages[(System.Int32) IPProtocolType.IP] = "Dummy protocol for TCP"; + messages[(System.Int32) IPProtocolType.HOPOPTS] = "IPv6 Hop-by-Hop options"; + messages[(System.Int32) IPProtocolType.ICMP] = "Internet Control Message Protocol"; + messages[(System.Int32) IPProtocolType.IGMP] = "Internet Group Management Protocol"; + messages[(System.Int32) IPProtocolType.IPIP] = "IPIP tunnels"; + messages[(System.Int32) IPProtocolType.TCP] = "Transmission Control Protocol"; + messages[(System.Int32) IPProtocolType.EGP] = "Exterior Gateway Protocol"; + messages[(System.Int32) IPProtocolType.PUP] = "PUP protocol"; + messages[(System.Int32) IPProtocolType.UDP] = "User Datagram Protocol"; + messages[(System.Int32) IPProtocolType.IDP] = "XNS IDP protocol"; + messages[(System.Int32) IPProtocolType.TP] = "SO Transport Protocol Class 4"; + messages[(System.Int32) IPProtocolType.IPV6] = "IPv6 header"; + messages[(System.Int32) IPProtocolType.ROUTING] = "IPv6 routing header"; + messages[(System.Int32) IPProtocolType.FRAGMENT] = "IPv6 fragmentation header"; + messages[(System.Int32) IPProtocolType.RSVP] = "Reservation Protocol"; + messages[(System.Int32) IPProtocolType.GRE] = "General Routing Encapsulation"; + messages[(System.Int32) IPProtocolType.ESP] = "encapsulating security payload"; + messages[(System.Int32) IPProtocolType.AH] = "authentication header"; + messages[(System.Int32) IPProtocolType.ICMPV6] = "ICMPv6"; + messages[(System.Int32) IPProtocolType.NONE] = "IPv6 no next header"; + messages[(System.Int32) IPProtocolType.DSTOPTS] = "IPv6 destination options"; + messages[(System.Int32) IPProtocolType.MTP] = "Multicast Transport Protocol"; + messages[(System.Int32) IPProtocolType.ENCAP] = "Encapsulation Header"; + messages[(System.Int32) IPProtocolType.PIM] = "Protocol Independent Multicast"; + messages[(System.Int32) IPProtocolType.COMP] = "Compression Header Protocol"; + messages[(System.Int32) IPProtocolType.RAW] = "Raw IP Packet"; +// messages[(System.Int32) IPProtocolType.INVALID] = "INVALID IP"; + } + } +} diff --git a/PacketDotNet/IPProtocolType.cs b/PacketDotNet/IPProtocolType.cs new file mode 100644 index 0000000..0233605 --- /dev/null +++ b/PacketDotNet/IPProtocolType.cs @@ -0,0 +1,78 @@ +// ************************************************************************ +// Copyright (C) 2001, Patrick Charles and Jonas Lehmann * +// Distributed under the Mozilla Public License * +// http://www.mozilla.org/NPL/MPL-1.1.txt * +// ************************************************************************* + +using System; + +namespace PacketDotNet +{ + /// + /// The protocol encapsulated inside of the IP packet + /// + public enum IPProtocolType + { + /// Dummy protocol for TCP. + IP = 0, + /// IPv6 Hop-by-Hop options. + HOPOPTS = 0, + /// Internet Control Message Protocol. + ICMP = 1, + /// Internet Group Management Protocol. + IGMP = 2, + /// IPIP tunnels (older KA9Q tunnels use 94). + IPIP = 4, + /// Transmission Control Protocol. + TCP = 6, + /// Exterior Gateway Protocol. + EGP = 8, + /// PUP protocol. + PUP = 12, + /// User Datagram Protocol. + UDP = 17, + /// XNS IDP protocol. + IDP = 22, + /// SO Transport Protocol Class 4. + TP = 29, + /// IPv6 header. + IPV6 = 41, + /// IPv6 routing header. + ROUTING = 43, + /// IPv6 fragmentation header. + FRAGMENT = 44, + /// Reservation Protocol. + RSVP = 46, + /// General Routing Encapsulation. + GRE = 47, + /// encapsulating security payload. + ESP = 50, + /// authentication header. + AH = 51, + /// ICMPv6. + ICMPV6 = 58, + /// IPv6 no next header. + NONE = 59, + /// IPv6 destination options. + DSTOPTS = 60, + /// Multicast Transport Protocol. + MTP = 92, + /// Encapsulation Header. + ENCAP = 98, + /// Protocol Independent Multicast. + PIM = 103, + /// Compression Header Protocol. + COMP = 108, + /// Raw IP packets. + RAW = 255, + + /// Unrecognized IP protocol. + /// WARNING: this only works because the int storage for the protocol + /// code has more bits than the field in the IP header where it is stored. + /// + INVALID = -1, + + /// IP protocol mask. + MASK = 0xff + } +} diff --git a/PacketDotNet/IPv4Fields.cs b/PacketDotNet/IPv4Fields.cs new file mode 100644 index 0000000..dc0494e --- /dev/null +++ b/PacketDotNet/IPv4Fields.cs @@ -0,0 +1,105 @@ +/* +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 + */ +namespace PacketDotNet +{ + /// + /// IP protocol field encoding information. + /// + public struct IPv4Fields + { + /// Width of the IP version and header length field in bytes. + public readonly static int VersionAndHeaderLengthLength = 1; + + /// Width of the Differentiated Services / Type of service field in bytes. + public readonly static int DifferentiatedServicesLength = 1; + + /// Width of the total length field in bytes. + public readonly static int TotalLengthLength = 2; + + /// Width of the ID field in bytes. + public readonly static int IdLength = 2; + + /// Width of the fragment offset bits and offset field in bytes. + public readonly static int FragmentOffsetAndFlagsLength = 2; + + /// Width of the TTL field in bytes. + public readonly static int TtlLength = 1; + + /// Width of the IP protocol code in bytes. + public readonly static int ProtocolLength = 1; + + /// Width of the IP checksum in bytes. + public readonly static int ChecksumLength = 2; + + /// Position of the version code and header length within the IP header. + public readonly static int VersionAndHeaderLengthPosition = 0; + + /// Position of the differentiated services value within the IP header. + public readonly static int DifferentiatedServicesPosition; + + /// Position of the header length within the IP header. + public readonly static int TotalLengthPosition; + + /// Position of the packet ID within the IP header. + public readonly static int IdPosition; + + /// Position of the flag bits and fragment offset within the IP header. + public readonly static int FragmentOffsetAndFlagsPosition; + + /// Position of the ttl within the IP header. + public readonly static int TtlPosition; + + /// + /// Position of the protocol used within the IP data + /// + public readonly static int ProtocolPosition; + + /// Position of the checksum within the IP header. + public readonly static int ChecksumPosition; + + /// Position of the source IP address within the IP header. + public readonly static int SourcePosition; + + /// Position of the destination IP address within a packet. + public readonly static int DestinationPosition; + + /// Length in bytes of an IP header, excluding options. + public readonly static int HeaderLength; // == 20 + + /// + /// Number of bytes in an IPv4 address + /// + public readonly static int AddressLength = 4; + + static IPv4Fields() + { + DifferentiatedServicesPosition = VersionAndHeaderLengthPosition + VersionAndHeaderLengthLength; + TotalLengthPosition = DifferentiatedServicesPosition + DifferentiatedServicesLength; + IdPosition = TotalLengthPosition + TotalLengthLength; + FragmentOffsetAndFlagsPosition = IdPosition + IdLength; + TtlPosition = FragmentOffsetAndFlagsPosition + FragmentOffsetAndFlagsLength; + ProtocolPosition = TtlPosition + TtlLength; + ChecksumPosition = ProtocolPosition + ProtocolLength; + SourcePosition = ChecksumPosition + ChecksumLength; + DestinationPosition = SourcePosition + AddressLength; + HeaderLength = DestinationPosition + AddressLength; + } + } +} \ No newline at end of file diff --git a/PacketDotNet/IPv4Packet.cs b/PacketDotNet/IPv4Packet.cs new file mode 100644 index 0000000..0039c30 --- /dev/null +++ b/PacketDotNet/IPv4Packet.cs @@ -0,0 +1,664 @@ +/* +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 MiscUtil.Conversion; +using PacketDotNet.Utils; + +namespace PacketDotNet +{ + /// + /// IPv4 packet + /// See http://en.wikipedia.org/wiki/IPv4 for into + /// + public class IPv4Packet : IpPacket + { +#if DEBUG + private static readonly log4net.ILog log = log4net.LogManager.GetLogger(System.Reflection.MethodBase.GetCurrentMethod().DeclaringType); +#else + // 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 + private static readonly ILogInactive log; +#pragma warning restore 0169 +#endif + + /// + /// Number of bytes in the smallest valid ipv4 packet + /// + public const int HeaderMinimumLength = 20; + + /// Type of service code constants for IP. Type of service describes + /// how a packet should be handled. + ///

+ /// TOS is an 8-bit record in an IP header which contains a 3-bit + /// precendence field, 4 TOS bit fields and a 0 bit. + ///

+ ///

+ /// The following constants are bit masks which can be logically and'ed + /// with the 8-bit IP TOS field to determine what type of service is set. + ///

+ ///

+ /// Taken from TCP/IP Illustrated V1 by Richard Stevens, p34. + ///

+ ///
+ public struct TypesOfService_Fields + { +#pragma warning disable 1591 + public readonly static int MINIMIZE_DELAY = 0x10; + public readonly static int MAXIMIZE_THROUGHPUT = 0x08; + public readonly static int MAXIMIZE_RELIABILITY = 0x04; + public readonly static int MINIMIZE_MONETARY_COST = 0x02; + public readonly static int UNUSED = 0x01; +#pragma warning restore 1591 + } + + /// + /// Version number of the IP protocol being used + /// + public static IpVersion ipVersion = IpVersion.IPv4; + + /// Get the IP version code. + public override IpVersion Version + { + get + { + return (IpVersion)((header.Bytes[header.Offset + IPv4Fields.VersionAndHeaderLengthPosition] >> 4) & 0x0F); + } + + set + { + // read the original value + var theByte = header.Bytes[header.Offset + IPv4Fields.VersionAndHeaderLengthPosition]; + + // mask in the version bits + theByte = (byte)((theByte & 0x0F) | (((byte)value << 4) & 0xF0)); + + // write back the modified value + header.Bytes[header.Offset + IPv4Fields.VersionAndHeaderLengthPosition] = theByte; + } + } + + /// + /// Forwards compatibility IPv6.PayloadLength property + /// + public override ushort PayloadLength + { + get + { + return (ushort)(TotalLength - (HeaderLength * 4)); + } + + set + { + TotalLength = value + (HeaderLength * 4); + } + } + + /// + /// The IP header length field. At most, this can be a + /// four-bit value. The high order bits beyond the fourth bit + /// will be ignored. + /// + /// The length of the IP header in 32-bit words. + /// + public override int HeaderLength + { + get + { + return (header.Bytes[header.Offset + IPv4Fields.VersionAndHeaderLengthPosition]) & 0x0F; + } + + set + { + // read the original value + var theByte = header.Bytes[header.Offset + IPv4Fields.VersionAndHeaderLengthPosition]; + + // mask in the header length bits + theByte = (byte)((theByte & 0xF0) | (((byte)value) & 0x0F)); + + // write back the modified value + header.Bytes[header.Offset + IPv4Fields.VersionAndHeaderLengthPosition] = theByte; + } + } + + /// + /// The unique ID of this IP datagram. The ID normally + /// increments by one each time a datagram is sent by a host. + /// A 16-bit unsigned integer. + /// + virtual public ushort Id + { + get + { + return EndianBitConverter.Big.ToUInt16(header.Bytes, + header.Offset + IPv4Fields.IdPosition); + } + + set + { + EndianBitConverter.Big.CopyBytes(value, + header.Bytes, + header.Offset + IPv4Fields.IdPosition); + } + } + + /// + /// Fragmentation offset + /// The offset specifies a number of octets (i.e., bytes). + /// A 13-bit unsigned integer. + /// + virtual public int FragmentOffset + { + get + { + var fragmentOffsetAndFlags = EndianBitConverter.Big.ToInt16(header.Bytes, + header.Offset + IPv4Fields.FragmentOffsetAndFlagsPosition); + + // mask off the high flag bits + return (fragmentOffsetAndFlags & 0x1FFF); + } + + set + { + // retrieve the value + var fragmentOffsetAndFlags = EndianBitConverter.Big.ToInt16(header.Bytes, + header.Offset + IPv4Fields.FragmentOffsetAndFlagsPosition); + + // mask the fragementation offset in + fragmentOffsetAndFlags = (short)((fragmentOffsetAndFlags & 0xE000) | (value & 0x1FFF)); + + EndianBitConverter.Big.CopyBytes(fragmentOffsetAndFlags, + header.Bytes, + header.Offset + IPv4Fields.FragmentOffsetAndFlagsPosition); + } + } + + /// Fetch the IP address of the host where the packet originated from. + public override System.Net.IPAddress SourceAddress + { + get + { + return IpPacket.GetIPAddress(System.Net.Sockets.AddressFamily.InterNetwork, + header.Offset + IPv4Fields.SourcePosition, header.Bytes); + } + + set + { + byte[] address = value.GetAddressBytes(); + Array.Copy(address, 0, + header.Bytes, header.Offset + IPv4Fields.SourcePosition, + address.Length); + } + } + + /// Fetch the IP address of the host where the packet is destined. + public override System.Net.IPAddress DestinationAddress + { + get + { + return IpPacket.GetIPAddress(System.Net.Sockets.AddressFamily.InterNetwork, + header.Offset + IPv4Fields.DestinationPosition, header.Bytes); + } + + set + { + byte[] address = value.GetAddressBytes(); + Array.Copy(address, 0, + header.Bytes, header.Offset + IPv4Fields.DestinationPosition, + address.Length); + } + } + + /// Fetch the header checksum. + virtual public int Checksum + { + get + { + return EndianBitConverter.Big.ToInt16(header.Bytes, + header.Offset + IPv4Fields.ChecksumPosition); + } + + set + { + var val = (Int16)value; + EndianBitConverter.Big.CopyBytes(val, + header.Bytes, + header.Offset + IPv4Fields.ChecksumPosition); + } + } + + /// Check if the IP packet is valid, checksum-wise. + virtual public bool ValidChecksum + { + get + { + return ValidIPChecksum; + } + + } + + /// + /// Check if the IP packet header is valid, checksum-wise. + /// + public bool ValidIPChecksum + { + get + { + log.Debug(""); + + // first validate other information about the packet. if this stuff + // is not true, the packet (and therefore the checksum) is invalid + // - ip_hl >= 5 (ip_hl is the length in 4-byte words) + if (Header.Length < IPv4Fields.HeaderLength) + { + log.DebugFormat("invalid length, returning false"); + return false; + } + else + { + var headerOnesSum = ChecksumUtils.OnesSum(Header); + log.DebugFormat(HexPrinter.GetString(Header, 0, Header.Length)); + const int expectedHeaderOnesSum = 0xffff; + var retval = (headerOnesSum == expectedHeaderOnesSum); + log.DebugFormat("headerOnesSum: {0}, expectedHeaderOnesSum {1}, returning {2}", + headerOnesSum, + expectedHeaderOnesSum, + retval); + log.DebugFormat("Header.Length {0}", Header.Length); + return retval; + } + } + } + + /// Fetch ascii escape sequence of the color associated with this packet type. + override public System.String Color + { + get + { + return AnsiEscapeSequences.White; + } + } + + /// Fetch the type of service. + public int DifferentiatedServices + { + get + { + return header.Bytes[header.Offset + IPv4Fields.DifferentiatedServicesPosition]; + } + + set + { + header.Bytes[header.Offset + IPv4Fields.DifferentiatedServicesPosition] = (byte)value; + } + } + + /// + /// Renamed to DifferentiatedServices in IPv6 but present here + /// for backwards compatibility + /// + public int TypeOfService + { + get { return DifferentiatedServices; } + set { DifferentiatedServices = value; } + } + + /// + /// The entire datagram size including header and data + /// + public override int TotalLength + { + get + { + return EndianBitConverter.Big.ToInt16(header.Bytes, + header.Offset + IPv4Fields.TotalLengthPosition); + } + + set + { + var theValue = (Int16)value; + EndianBitConverter.Big.CopyBytes(theValue, + header.Bytes, + header.Offset + IPv4Fields.TotalLengthPosition); + } + } + + /// Fetch fragment flags. + /// A 3-bit unsigned integer. + public virtual int FragmentFlags + { + get + { + var fragmentOffsetAndFlags = EndianBitConverter.Big.ToInt16(header.Bytes, + header.Offset + IPv4Fields.FragmentOffsetAndFlagsPosition); + + // shift off the fragment offset bits + return fragmentOffsetAndFlags >> (16 - 3); + } + + set + { + // retrieve the value + var fragmentOffsetAndFlags = EndianBitConverter.Big.ToInt16(header.Bytes, + header.Offset + IPv4Fields.FragmentOffsetAndFlagsPosition); + + // mask the flags in + fragmentOffsetAndFlags = (short)((fragmentOffsetAndFlags & 0x1FFF) | ((value & 0x07) << (16 - 3))); + + EndianBitConverter.Big.CopyBytes(fragmentOffsetAndFlags, + header.Bytes, + header.Offset + IPv4Fields.FragmentOffsetAndFlagsPosition); + } + } + + /// Fetch the time to live. TTL sets the upper limit on the number of + /// routers through which this IP datagram is allowed to pass. + /// Originally intended to be the number of seconds the packet lives it is now decremented + /// by one each time a router passes the packet on + /// + /// 8-bit value + /// + public override int TimeToLive + { + get + { + return header.Bytes[header.Offset + IPv4Fields.TtlPosition]; + } + + set + { + header.Bytes[header.Offset + IPv4Fields.TtlPosition] = (byte)value; + } + } + + /// Fetch the code indicating the type of protocol embedded in the IP + /// + /// + public override IPProtocolType Protocol + { + get + { + return (IPProtocolType)header.Bytes[header.Offset + IPv4Fields.ProtocolPosition]; + } + + set + { + header.Bytes[header.Offset + IPv4Fields.ProtocolPosition] = (byte)value; + } + } + + /// + /// Calculates the IP checksum, optionally updating the IP checksum header. + /// + /// The calculated IP checksum. + /// + public int CalculateIPChecksum() + { + //copy the ip header + var theHeader = Header; + byte[] ip = new byte[theHeader.Length]; + Array.Copy(theHeader, ip, theHeader.Length); + + //reset the checksum field (checksum is calculated when this field is zeroed) + var theValue = (UInt16)0; + EndianBitConverter.Big.CopyBytes(theValue, ip, IPv4Fields.ChecksumPosition); + + //calculate the one's complement sum of the ip header + int cs = ChecksumUtils.OnesComplementSum(ip, 0, ip.Length); + + return cs; + } + + /// + /// Update the checksum value + /// + public void UpdateIPChecksum () + { + this.Checksum = CalculateIPChecksum(); + } + + /// + /// Prepend to the given byte[] origHeader the portion of the IPv6 header used for + /// generating an tcp checksum + /// + /// http://en.wikipedia.org/wiki/Transmission_Control_Protocol#TCP_checksum_using_IPv4 + /// http://tools.ietf.org/html/rfc793 + /// + /// + /// A + /// + /// + /// A + /// + internal override byte[] AttachPseudoIPHeader(byte[] origHeader) + { + log.Debug(""); + + bool odd = origHeader.Length % 2 != 0; + int numberOfBytesFromIPHeaderUsedToGenerateChecksum = 12; + int headerSize = numberOfBytesFromIPHeaderUsedToGenerateChecksum + origHeader.Length; + if (odd) + headerSize++; + + byte[] headerForChecksum = new byte[headerSize]; + // 0-7: ip src+dest addr + Array.Copy(header.Bytes, + header.Offset + IPv4Fields.SourcePosition, + headerForChecksum, + 0, + IPv4Fields.AddressLength * 2); + // 8: always zero + headerForChecksum[8] = 0; + // 9: ip protocol + headerForChecksum[9] = (byte)Protocol; + // 10-11: header+data length + var length = (Int16)origHeader.Length; + EndianBitConverter.Big.CopyBytes(length, headerForChecksum, + 10); + + // prefix the pseudoHeader to the header+data + Array.Copy(origHeader, 0, + headerForChecksum, numberOfBytesFromIPHeaderUsedToGenerateChecksum, + origHeader.Length); + + //if not even length, pad with a zero + if (odd) + headerForChecksum[headerForChecksum.Length - 1] = 0; + + return headerForChecksum; + } + + /// + /// Construct an instance by values + /// + public IPv4Packet(System.Net.IPAddress SourceAddress, + System.Net.IPAddress DestinationAddress) + : base(new PosixTimeval()) + { + // allocate memory for this packet + int offset = 0; + int length = IPv4Fields.HeaderLength; + var headerBytes = new byte[length]; + header = new ByteArraySegment(headerBytes, offset, length); + + // set some default values to make this packet valid + PayloadLength = 0; + HeaderLength = (HeaderMinimumLength / 4); // NOTE: HeaderLength is the number of 32bit words in the header + TimeToLive = DefaultTimeToLive; + + // set instance values + this.SourceAddress = SourceAddress; + this.DestinationAddress = DestinationAddress; + this.Version = ipVersion; + } + + /// + /// Parse bytes into an IP packet + /// + /// + /// A + /// + /// + /// A + /// + public IPv4Packet(byte[] Bytes, int Offset) : + this(Bytes, Offset, new PosixTimeval()) + { + log.Debug(""); + } + + /// + /// Parse bytes into an IP packet + /// + /// + /// A + /// + /// + /// A + /// + /// + /// A + /// + public IPv4Packet(byte[] Bytes, int Offset, PosixTimeval Timeval) : + base(Timeval) + { + log.Debug(""); + + header = new ByteArraySegment(Bytes, Offset, Bytes.Length - Offset); + + // Check that the TotalLength is valid, at least HeaderMinimumLength long + if(TotalLength < HeaderMinimumLength) + { + throw new System.InvalidOperationException("TotalLength " + TotalLength + " < HeaderMinimumLength " + HeaderMinimumLength); + } + + // update the header length with the correct value + // NOTE: we take care to convert from 32bit words into bytes + // NOTE: we do this *after* setting header because we need header to be valid + // before we can retrieve the HeaderLength property + header.Length = HeaderLength * 4; + + log.DebugFormat("IPv4Packet HeaderLength {0}", HeaderLength); + log.DebugFormat("header {0}", header); + + // parse the payload + payloadPacketOrData = IpPacket.ParseEncapsulatedBytes(header, + NextHeader, + Timeval, + this); + } + + /// Convert this IP packet to a readable string. + public override System.String ToString() + { + return ToColoredString(false); + } + + /// Generate string with contents describing this IP packet. + /// whether or not the string should contain ansi + /// color escape sequences. + /// + public override System.String ToColoredString(bool colored) + { + System.Text.StringBuilder buffer = new System.Text.StringBuilder(); + buffer.Append('['); + if (colored) + buffer.Append(Color); + buffer.Append("IPv4Packet"); + if (colored) + buffer.Append(AnsiEscapeSequences.Reset); + buffer.Append(": "); + buffer.Append(SourceAddress + " -> " + DestinationAddress); + buffer.Append(" HeaderLength=" + HeaderLength); + buffer.Append(" Protocol=" + Protocol); + buffer.Append(" TimeToLive=" + TimeToLive); + // FIXME: what would we use for Length? +// buffer.Append(" l=" + HeaderLength + "," + Length); + buffer.Append(']'); + + // append the base class output + buffer.Append(base.ToColoredString(colored)); + + return buffer.ToString(); + } + + /// Convert this IP packet to a more verbose string. + public override System.String ToColoredVerboseString(bool colored) + { + System.Text.StringBuilder buffer = new System.Text.StringBuilder(); + buffer.Append('['); + if (colored) + buffer.Append(Color); + buffer.Append("IPv4Packet"); + if (colored) + buffer.Append(AnsiEscapeSequences.Reset); + buffer.Append(": "); + buffer.Append("version=" + Version + ", "); + buffer.Append("hlen=" + HeaderLength + ", "); + buffer.Append("tos=" + TypeOfService + ", "); + //FIXME: what to use for length here? +// buffer.Append("length=" + Length + ", "); + buffer.Append("id=" + Id + ", "); + buffer.Append("flags=0x" + System.Convert.ToString(FragmentFlags, 16) + ", "); + buffer.Append("offset=" + FragmentOffset + ", "); + buffer.Append("ttl=" + TimeToLive + ", "); + buffer.Append("proto=" + Protocol + ", "); + buffer.Append("sum=0x" + System.Convert.ToString(Checksum, 16)); +#if false + if (this.ValidChecksum) + buffer.Append(" (correct), "); + else + buffer.Append(" (incorrect, should be " + ComputeIPChecksum(false) + "), "); +#endif + buffer.Append("src=" + SourceAddress + ", "); + buffer.Append("dest=" + DestinationAddress); + buffer.Append(']'); + + // append the base class output + buffer.Append(base.ToColoredVerboseString(colored)); + + return buffer.ToString(); + } + + /// + /// Generate a random packet + /// + /// + /// A + /// + public static IPv4Packet RandomPacket() + { + var srcAddress = RandomUtils.GetIPAddress(ipVersion); + var dstAddress = RandomUtils.GetIPAddress(ipVersion); + return new IPv4Packet(srcAddress, dstAddress); + } + + /// + /// Update the length fields + /// + public override void UpdateCalculatedValues () + { + // update the length field based on the length of this packet header + // plus the length of all of the packets it contains + TotalLength = TotalPacketLength; + } + } +} diff --git a/PacketDotNet/IPv6Fields.cs b/PacketDotNet/IPv6Fields.cs new file mode 100644 index 0000000..32724b2 --- /dev/null +++ b/PacketDotNet/IPv6Fields.cs @@ -0,0 +1,102 @@ +/* +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 + */ +namespace PacketDotNet +{ + /// + /// A struct containing length and position information about IPv6 Fields. + /// + public struct IPv6Fields + { + /// + /// The IP Version, Traffic Class, and Flow Label field length. These must be in one + /// field due to boundary crossings. + /// + public readonly static int VersionTrafficClassFlowLabelLength = 4; + + /// + /// The payload length field length. + /// + public readonly static int PayloadLengthLength = 2; + + /// + /// The next header field length, identifies protocol encapsulated by the packet + /// + public readonly static int NextHeaderLength = 1; + + /// + /// The hop limit field length. + /// + public readonly static int HopLimitLength = 1; + + /// + /// Address field length + /// + public readonly static int AddressLength = 16; + + /// + /// The byte position of the field line in the IPv6 header. + /// This is where the IP version, Traffic Class, and Flow Label fields are. + /// + public readonly static int VersionTrafficClassFlowLabelPosition = 0; + + /// + /// The byte position of the payload length field. + /// + public readonly static int PayloadLengthPosition; + + /// + /// The byte position of the next header field. (Replaces the ipv4 protocol field) + /// + public readonly static int NextHeaderPosition; + + /// + /// The byte position of the hop limit field. + /// + public readonly static int HopLimitPosition; + + /// + /// The byte position of the source address field. + /// + public readonly static int SourceAddressPosition; + + /// + /// The byte position of the destination address field. + /// + public readonly static int DestinationAddressPosition; + + /// + /// The byte length of the IPv6 Header + /// + public readonly static int HeaderLength; // == 40 + + /// + /// Commutes the field positions. + /// + static IPv6Fields( ) + { + PayloadLengthPosition = VersionTrafficClassFlowLabelPosition + VersionTrafficClassFlowLabelLength; + NextHeaderPosition = PayloadLengthPosition + PayloadLengthLength; + HopLimitPosition = NextHeaderPosition + NextHeaderLength; + SourceAddressPosition = HopLimitPosition + HopLimitLength; + DestinationAddressPosition = SourceAddressPosition + AddressLength; + HeaderLength = DestinationAddressPosition + AddressLength; + } + } +} \ No newline at end of file diff --git a/PacketDotNet/IPv6Packet.cs b/PacketDotNet/IPv6Packet.cs new file mode 100644 index 0000000..85afb71 --- /dev/null +++ b/PacketDotNet/IPv6Packet.cs @@ -0,0 +1,506 @@ +/* +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 David Bond + * Copyright 2009 Chris Morgan + */ + +using System; +using System.IO; +using MiscUtil.Conversion; +using PacketDotNet.Utils; + +namespace PacketDotNet +{ + /// + /// IPv6 packet + /// + /// References + /// ---------- + /// http://tools.ietf.org/html/rfc2460 + /// http://en.wikipedia.org/wiki/IPv6 + /// + public class IPv6Packet : IpPacket + { +#if DEBUG + private static readonly log4net.ILog log = log4net.LogManager.GetLogger(System.Reflection.MethodBase.GetCurrentMethod().DeclaringType); +#else + // 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 + private static readonly ILogInactive log; +#pragma warning restore 0169 +#endif + + /// + /// Minimum number of bytes in an IPv6 header + /// + public const int HeaderMinimumLength = 40; + + /// + /// The version of the IP protocol. The '6' in IPv6 indicates the version of the protocol + /// + public static IpVersion ipVersion = IpVersion.IPv6; + + private Int32 VersionTrafficClassFlowLabel + { + get + { + return EndianBitConverter.Big.ToInt32(header.Bytes, + header.Offset + IPv6Fields.VersionTrafficClassFlowLabelPosition); + } + + set + { + EndianBitConverter.Big.CopyBytes(value, header.Bytes, header.Offset + IPv6Fields.VersionTrafficClassFlowLabelPosition); + } + } + + /// + /// The version field of the IPv6 Packet. + /// + public override IpVersion Version + { + get + { + return (IpVersion)((VersionTrafficClassFlowLabel >> 28) & 0xF); + } + + set + { + var theValue = (Int32)value; + + // read the existing value + var field = (UInt32)VersionTrafficClassFlowLabel; + + // mask the new field into place + field = (UInt32)((field & 0x0FFFFFFF) | ((theValue << 28) & 0xF0000000)); + + // write the updated value back + VersionTrafficClassFlowLabel = (int)field; + } + } + + /// + /// The traffic class field of the IPv6 Packet. + /// + public virtual int TrafficClass + { + get + { + return ((VersionTrafficClassFlowLabel >> 20) & 0xFF); + } + + set + { + // read the original value + var field = (UInt32)VersionTrafficClassFlowLabel; + + // mask in the new field + field = (UInt32)(((field & 0xF00FFFFF) | (((UInt32)value) << 20 ) & 0x0FF00000)); + + // write the updated value back + VersionTrafficClassFlowLabel = (int)field; + } + } + + /// + /// The flow label field of the IPv6 Packet. + /// + public virtual int FlowLabel + { + get + { + return (VersionTrafficClassFlowLabel & 0xFFFFF); + } + + set + { + // read the original value + var field = (UInt32)VersionTrafficClassFlowLabel; + + // make the value in + field = (UInt32)((field & 0xFFF00000) | ((UInt32)(value) & 0x000FFFFF)); + + // write the updated value back + VersionTrafficClassFlowLabel = (int)field; + } + } + + /// + /// The payload lengeth field of the IPv6 Packet + /// NOTE: Differs from the IPv4 'Total length' field that includes the length of the header as + /// payload length is ONLY the size of the payload. + /// + public override ushort PayloadLength + { + get + { + return EndianBitConverter.Big.ToUInt16(header.Bytes, + header.Offset + IPv6Fields.PayloadLengthPosition); + } + + set + { + EndianBitConverter.Big.CopyBytes(value, + header.Bytes, + header.Offset + IPv6Fields.PayloadLengthPosition); + } + } + + /// + /// Backwards compatibility property for IPv4.HeaderLength + /// NOTE: This field is the number of 32bit words + /// + public override int HeaderLength + { + get + { + return (IPv6Fields.HeaderLength / 4); + } + + set + { + throw new System.NotImplementedException (); + } + } + + /// + /// Backwards compatibility property for IPv4.TotalLength + /// + public override int TotalLength + { + get + { + return PayloadLength + (HeaderLength * 4); + } + + set + { + PayloadLength = (ushort)(value - (HeaderLength * 4)); + } + } + + /// + /// Identifies the protocol encapsulated by this packet + /// + /// Replaces IPv4's 'protocol' field, has compatible values + /// + public override IPProtocolType NextHeader + { + get + { + return (IPProtocolType)(header.Bytes[header.Offset + IPv6Fields.NextHeaderPosition]); + } + + set + { + header.Bytes[header.Offset + IPv6Fields.NextHeaderPosition] = (byte)value; + } + } + + /// + /// The protocol of the packet encapsulated in this ip packet + /// + public override IPProtocolType Protocol + { + get { return NextHeader; } + set { NextHeader = value; } + } + + /// + /// The hop limit field of the IPv6 Packet. + /// NOTE: Replaces the 'time to live' field of IPv4 + /// + /// 8-bit value + /// + public override int HopLimit + { + get + { + return header.Bytes[header.Offset + IPv6Fields.HopLimitPosition]; + } + + set + { + header.Bytes[header.Offset + IPv6Fields.HopLimitPosition] = (byte)value; + } + } + + /// + /// Helper alias for 'HopLimit' + /// + public override int TimeToLive + { + get { return HopLimit; } + set { HopLimit = value; } + } + + /// + /// The source address field of the IPv6 Packet. + /// + public override System.Net.IPAddress SourceAddress + { + get + { + return IpPacket.GetIPAddress(System.Net.Sockets.AddressFamily.InterNetworkV6, + header.Offset + IPv6Fields.SourceAddressPosition, + header.Bytes); + } + + set + { + byte[] address = value.GetAddressBytes(); + System.Array.Copy(address, 0, + header.Bytes, header.Offset + IPv6Fields.SourceAddressPosition, + address.Length); + } + } + + /// + /// The destination address field of the IPv6 Packet. + /// + public override System.Net.IPAddress DestinationAddress + { + get + { + return IpPacket.GetIPAddress(System.Net.Sockets.AddressFamily.InterNetworkV6, + header.Offset + IPv6Fields.DestinationAddressPosition, + header.Bytes); + } + + set + { + byte[] address = value.GetAddressBytes(); + System.Array.Copy(address, 0, + header.Bytes, header.Offset + IPv6Fields.DestinationAddressPosition, + address.Length); + } + } + + /// + /// Create an IPv6 packet from values + /// + /// + /// A + /// + /// + /// A + /// + public IPv6Packet(System.Net.IPAddress SourceAddress, + System.Net.IPAddress DestinationAddress) + : base(new PosixTimeval()) + { + log.Debug(""); + + // allocate memory for this packet + int offset = 0; + int length = IPv6Fields.HeaderLength; + var headerBytes = new byte[length]; + header = new ByteArraySegment(headerBytes, offset, length); + + // set some default values to make this packet valid + PayloadLength = 0; + TimeToLive = DefaultTimeToLive; + + // set instance values + this.SourceAddress = SourceAddress; + this.DestinationAddress = DestinationAddress; + this.Version = ipVersion; + } + + /// + /// byte[]/int offset constructor, timeval defaults to the current time + /// + /// + /// A + /// + /// + /// A + /// + public IPv6Packet(byte[] Bytes, int Offset) : + this(Bytes, Offset, new PosixTimeval()) + { + log.Debug(""); + } + + /// + /// byte[]/int offset/PosixTimeval constructor + /// + /// + /// A + /// + /// + /// A + /// + /// + /// A + /// + public IPv6Packet(byte[] Bytes, int Offset, PosixTimeval Timeval) : + base(Timeval) + { + log.DebugFormat("Bytes.Length {0}, Offset {1}", + Bytes.Length, + Offset); + + // slice off the header + header = new ByteArraySegment(Bytes, Offset, IPv6Packet.HeaderMinimumLength); + + // set the actual length, we need to do this because we need to set + // header to something valid above before we can retrieve the PayloadLength + log.DebugFormat("PayloadLength: {0}", PayloadLength); + header.Length = (Bytes.Length - Offset) - PayloadLength; + + // parse the payload + payloadPacketOrData = IpPacket.ParseEncapsulatedBytes(header, + NextHeader, + Timeval, + this); + } + + /// + /// Prepend to the given byte[] origHeader the portion of the IPv6 header used for + /// generating an tcp checksum + /// + /// http://en.wikipedia.org/wiki/Transmission_Control_Protocol#TCP_checksum_using_IPv6 + /// http://tools.ietf.org/html/rfc2460#page-27 + /// + /// + /// A + /// + /// + /// A + /// + internal override byte[] AttachPseudoIPHeader(byte[] origHeader) + { + MemoryStream ms = new MemoryStream(); + BinaryWriter bw = new BinaryWriter(ms); + + // 0-16: ip src addr + bw.Write(header.Bytes, header.Offset + IPv6Fields.SourceAddressPosition, + IPv6Fields.AddressLength); + + // 17-32: ip dst addr + bw.Write(header.Bytes, header.Offset + IPv6Fields.DestinationAddressPosition, + IPv6Fields.AddressLength); + + // 33-36: TCP length + bw.Write((UInt32)System.Net.IPAddress.HostToNetworkOrder((Int32)origHeader.Length)); + + // 37-39: 3 bytes of zeros + bw.Write((byte)0); + bw.Write((byte)0); + bw.Write((byte)0); + + // 40: Next header + bw.Write((byte)NextHeader); + + // prefix the pseudoHeader to the header+data + byte[] pseudoHeader = ms.ToArray(); + int headerSize = pseudoHeader.Length + origHeader.Length; + bool odd = origHeader.Length % 2 != 0; + if (odd) + headerSize++; + + byte[] finalData = new byte[headerSize]; + + // copy the pseudo header in + Array.Copy(pseudoHeader, 0, finalData, 0, pseudoHeader.Length); + + // copy the origHeader in + Array.Copy(origHeader, 0, finalData, pseudoHeader.Length, origHeader.Length); + + //if not even length, pad with a zero + if (odd) + finalData[finalData.Length - 1] = 0; + + return finalData; + } + + /// + /// Converts the packet to a string. + /// + /// + public override String ToString( ) + { + return base.ToString( ) + "\r\nIPv6 Packet [\r\n" + + "\tIPv6 Source Address: " + SourceAddress.ToString() + ", \r\n" + + "\tIPv6 Destination Address: " + DestinationAddress.ToString() + "\r\n" + + "]"; + // TODO Implement Better ToString + } + + /// Generate string with contents describing this IP packet. + /// whether or not the string should contain ansi + /// color escape sequences. + /// + public override System.String ToColoredString(bool colored) + { + System.Text.StringBuilder buffer = new System.Text.StringBuilder(); + buffer.Append('['); + if (colored) + buffer.Append(Color); + buffer.Append("IPv6Packet"); + if (colored) + buffer.Append(AnsiEscapeSequences.Reset); + buffer.Append(": "); + buffer.Append(SourceAddress + " -> " + DestinationAddress); + buffer.Append(" next header=" + NextHeader); + //FIXME: figure out what to use for the lengths +#if false + buffer.Append(" l=" + this.IPPayloadLength); + buffer.Append(" sum=" + this.IPPayloadLength); +#endif + buffer.Append(']'); + + // append the base class output + buffer.Append(base.ToColoredString(colored)); + + return buffer.ToString(); + } + + /// Convert this IP packet to a more verbose string. + public override System.String ToColoredVerboseString(bool colored) + { + throw new System.NotImplementedException(); + } + + /// + /// Converts the packet to a color string. TODO add a method for colored to string. + /// + override public String Color + { + get + { + return AnsiEscapeSequences.White; + } + } + + /// + /// Generate a random packet + /// + /// + /// A + /// + public static IPv6Packet RandomPacket() + { + var srcAddress = RandomUtils.GetIPAddress(ipVersion); + var dstAddress = RandomUtils.GetIPAddress(ipVersion); + return new IPv6Packet(srcAddress, dstAddress); + } + } +} diff --git a/PacketDotNet/InternetLinkLayerPacket.cs b/PacketDotNet/InternetLinkLayerPacket.cs new file mode 100644 index 0000000..dbda8a2 --- /dev/null +++ b/PacketDotNet/InternetLinkLayerPacket.cs @@ -0,0 +1,87 @@ +/* +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 PacketDotNet.Utils; + +namespace PacketDotNet +{ + /// + /// Internet Link layer packet + /// See http://en.wikipedia.org/wiki/Link_Layer + /// + public class InternetLinkLayerPacket : Packet + { +#if DEBUG + private static readonly log4net.ILog log = log4net.LogManager.GetLogger(System.Reflection.MethodBase.GetCurrentMethod().DeclaringType); +#else + // 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 + private static readonly ILogInactive log; +#pragma warning restore 0169 +#endif + + /// + /// Constructor + /// + /// + /// A + /// + public InternetLinkLayerPacket(PosixTimeval timeval) : base(timeval) + {} + + /// + /// Look for the innermost payload. This method is useful because + /// while some packets are LinuxSSL->IpPacket or + /// EthernetPacket->IpPacket, there are some packets that are + /// EthernetPacket->PPPoEPacket->PPPPacket->IpPacket, and for these cases + /// we really want to get to the IpPacket + /// + /// + /// A + /// + public static Packet GetInnerPayload(InternetLinkLayerPacket packet) + { + // is this an ethernet packet? + if(packet is EthernetPacket) + { + log.Debug("packet is EthernetPacket"); + + var thePayload = packet.PayloadPacket; + + // is this packets payload a PPPoEPacket? If so, + // the PPPoEPacket payload should be a PPPPacket and we want + // the payload of the PPPPpacket + if(thePayload is PPPoEPacket) + { + log.Debug("thePayload is PPPoEPacket"); + return thePayload.PayloadPacket.PayloadPacket; + } + + return thePayload; + } else + { + log.Debug("else"); + return packet.PayloadPacket; + } + } + } +} \ No newline at end of file diff --git a/PacketDotNet/InternetPacket.cs b/PacketDotNet/InternetPacket.cs new file mode 100644 index 0000000..8a59f9a --- /dev/null +++ b/PacketDotNet/InternetPacket.cs @@ -0,0 +1,41 @@ +/* +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 PacketDotNet.Utils; + +namespace PacketDotNet +{ + /// + /// Internet packets include IPv4, IPv6, IGMP etc, see + /// http://en.wikipedia.org/wiki/Internet_Layer + /// + public class InternetPacket : Packet + { + /// + /// Constructor + /// + /// + /// A + /// + public InternetPacket(PosixTimeval Timeval) : base(Timeval) + { } + } +} diff --git a/PacketDotNet/IpPacket.cs b/PacketDotNet/IpPacket.cs new file mode 100644 index 0000000..0bd7cfa --- /dev/null +++ b/PacketDotNet/IpPacket.cs @@ -0,0 +1,362 @@ +/* +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 2010 Chris Morgan + */ + +using System; +using System.Net; +using PacketDotNet.Utils; +using MiscUtil.Conversion; + +namespace PacketDotNet +{ + /// + /// Base class for IPv4 and IPv6 packets that exports the common + /// functionality that both of these classes has in common + /// + public abstract class IpPacket : InternetPacket + { +#if DEBUG + private static readonly log4net.ILog log = log4net.LogManager.GetLogger(System.Reflection.MethodBase.GetCurrentMethod().DeclaringType); +#else + // 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 + private static readonly ILogInactive log; +#pragma warning restore 0169 +#endif + + /// + /// The default time to live value for Ip packets being constructed + /// + protected int DefaultTimeToLive = 64; + + /// + /// Payload packet, overridden to set the NextHeader/Protocol based + /// on the type of payload packet when the payload packet is set + /// + public override Packet PayloadPacket + { + get + { + return base.PayloadPacket; + } + + set + { + base.PayloadPacket = value; + + // set NextHeader (Protocol) based on the type of this packet + if(value is TcpPacket) + { + NextHeader = IPProtocolType.TCP; + } else if(value is UdpPacket) + { + NextHeader = IPProtocolType.UDP; + } else if(value is ICMPv6Packet) + { + NextHeader = IPProtocolType.ICMPV6; + } else if(value is ICMPv4Packet) + { + NextHeader = IPProtocolType.ICMP; + } else // NOTE: new checks go here + { + NextHeader = IPProtocolType.NONE; + } + + // update the payload length based on the size + // of the payload packet + var newPayloadLength = (ushort)base.PayloadPacket.Bytes.Length; + log.DebugFormat("newPayloadLength {0}", newPayloadLength); + PayloadLength = newPayloadLength; + } + } + + /// + /// The destination address + /// + public abstract IPAddress DestinationAddress + { + get; + set; + } + + /// + /// The source address + /// + public abstract IPAddress SourceAddress + { + get; + set; + } + + /// + /// The IP version + /// + public abstract IpVersion Version + { + get; + set; + } + + /// + /// The protocol of the ip packet's payload + /// Named 'Protocol' in IPv4 + /// Named 'NextHeader' in IPv6' + /// + public abstract IPProtocolType Protocol + { + get; + set; + } + + /// + /// The protocol of the ip packet's payload + /// Included along side Protocol for user convienence + /// + public virtual IPProtocolType NextHeader + { + get { return Protocol; } + set { Protocol = value; } + } + + /// + /// The number of hops remaining before this packet is discarded + /// Named 'TimeToLive' in IPv4 + /// Named 'HopLimit' in IPv6 + /// + public abstract int TimeToLive + { + get; + set; + } + + /// + /// The number of hops remaining for this packet + /// Included along side of TimeToLive for user convienence + /// + public virtual int HopLimit + { + get { return TimeToLive; } + set { TimeToLive = value; } + } + + /// + /// ipv4 header length field, calculated for ipv6 packets + /// NOTE: This field is the number of 32bit words in the ip header, + /// ie. the number of bytes is 4x this value + /// + public abstract int HeaderLength + { + get; set; + } + + /// + /// ipv4 total number of bytes in the ipv4 header + payload, + /// ipv6 PayloadLength + IPv6Fields.HeaderLength + /// + public abstract int TotalLength + { + get; set; + } + + /// + /// ipv6 payload length in bytes, + /// calculate from ipv4.TotalLength - (ipv4.HeaderLength * 4) + /// + public abstract ushort PayloadLength + { + get; + set; + } + + /// + /// Adds a pseudo ip header to a given packet. Used to generate the full + /// byte array required to generate a udp or tcp checksum. + /// + /// + /// A + /// + /// + /// A + /// + internal abstract byte[] AttachPseudoIPHeader(byte[] origHeader); + + /// + /// Convert an ip address from a byte[] + /// + /// + /// A + /// + /// + /// A + /// + /// + /// A + /// + /// + /// A + /// + public static System.Net.IPAddress GetIPAddress(System.Net.Sockets.AddressFamily ipType, + int fieldOffset, + byte[] bytes) + { + byte[] address; + if(ipType == System.Net.Sockets.AddressFamily.InterNetwork) // ipv4 + { + address = new byte[IPv4Fields.AddressLength]; + } else if(ipType == System.Net.Sockets.AddressFamily.InterNetworkV6) + { + address = new byte[IPv6Fields.AddressLength]; + } else + { + throw new System.InvalidOperationException("ipType " + ipType + " unknown"); + } + + System.Array.Copy(bytes, fieldOffset, + address, 0, address.Length); + + return new System.Net.IPAddress(address); + } + + /// + /// IpPacket constructor + /// + /// + /// A + /// + public IpPacket(PosixTimeval Timeval) : base(Timeval) + {} + + /// + /// Called by IPv4 and IPv6 packets to parse their packet payload + /// + /// + /// A + /// + /// + /// A + /// + /// + /// A + /// + /// + /// A + /// + /// + /// A + /// + internal static PacketOrByteArraySegment ParseEncapsulatedBytes(ByteArraySegment Header, + IPProtocolType ProtocolType, + PosixTimeval Timeval, + Packet ParentPacket) + { + // slice off the payload + var payload = Header.EncapsulatedBytes(); + + log.DebugFormat("payload: {0}, ParentPacket.GetType() {1}", + payload, + ParentPacket.GetType()); + + var payloadPacketOrData = new PacketOrByteArraySegment(); + + switch(ProtocolType) + { + case IPProtocolType.TCP: + payloadPacketOrData.ThePacket = new TcpPacket(payload.Bytes, + payload.Offset, + Timeval, + ParentPacket); + break; + case IPProtocolType.UDP: + payloadPacketOrData.ThePacket = new UdpPacket(payload.Bytes, + payload.Offset, + Timeval, + ParentPacket); + break; + case IPProtocolType.ICMP: + payloadPacketOrData.ThePacket = new ICMPv4Packet(payload.Bytes, + payload.Offset, + Timeval); + break; + case IPProtocolType.ICMPV6: + payloadPacketOrData.ThePacket = new ICMPv6Packet(payload.Bytes, + payload.Offset, + Timeval); + break; + // NOTE: new payload parsing entries go here + default: + payloadPacketOrData.TheByteArraySegment = payload; + break; + } + + return payloadPacketOrData; + } + + /// + /// Returns the IpPacket inside of the Packet p or null if + /// there is no encapsulated packet + /// + /// + /// A + /// + /// + /// A + /// + public static IpPacket GetEncapsulated(Packet p) + { + log.Debug(""); + + if(p is InternetLinkLayerPacket) + { + var payload = InternetLinkLayerPacket.GetInnerPayload((InternetLinkLayerPacket)p); + if(payload is IpPacket) + { + return (IpPacket)payload; + } + } + + return null; + } + + /// + /// Generate a random packet of a specific ip version + /// + /// + /// A + /// + /// + /// A + /// + public static IpPacket RandomPacket(IpVersion version) + { + log.DebugFormat("version {0}", version); + + if(version == IpVersion.IPv4) + { + return IPv4Packet.RandomPacket(); + } else if(version == IpVersion.IPv6) + { + return IPv6Packet.RandomPacket(); + } else + { + throw new System.InvalidOperationException("Unknown version of " + version); + } + } + } +} diff --git a/PacketDotNet/IpPort.cs b/PacketDotNet/IpPort.cs new file mode 100644 index 0000000..16ea90a --- /dev/null +++ b/PacketDotNet/IpPort.cs @@ -0,0 +1,76 @@ +/* +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 + */ + +namespace PacketDotNet +{ + /// Code constants for ip ports. + public enum IpPort : ushort + { +#pragma warning disable 1591 + Echo = 7, + DayTime = 13, + FtpData = 20, + Ftp = 21, + /// + /// Secure shell + /// + Ssh = 22, + /// + /// Terminal protocol + /// + Telnet = 23, + /// + /// Simple mail transport protocol + /// + Smtp = 25, + Time = 37, + Whois = 63, + Tftp = 69, + Gopher = 70, + Finger = 79, + /// + /// Hyper text transfer protocol + /// + Http = 80, + /// + /// Same as Http + /// + Www = 80, + Kerberos = 88, + Pop3 = 110, + Ident = 113, + Auth = 113, + /// + /// Secure ftp + /// + Sftp = 115, + /// + /// Network time protocol + /// + Ntp = 123, + Imap = 143, + /// + /// Simple network management protocol + /// + Snmp = 161, + PrivilegedPortLimit = 1024 +#pragma warning restore 1591 + } +} diff --git a/PacketDotNet/IpVersion.cs b/PacketDotNet/IpVersion.cs new file mode 100644 index 0000000..0f14b1b --- /dev/null +++ b/PacketDotNet/IpVersion.cs @@ -0,0 +1,34 @@ +/* +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; + +namespace PacketDotNet +{ + /// Code constants for internet protocol versions. + /// + /// + public enum IpVersion + { + /// Internet protocol version 4. + IPv4 = 4, + /// Internet protocol version 6. + IPv6 = 6 + } +} diff --git a/PacketDotNet/LLDP/AddressFamily.cs b/PacketDotNet/LLDP/AddressFamily.cs new file mode 100644 index 0000000..ada8ae1 --- /dev/null +++ b/PacketDotNet/LLDP/AddressFamily.cs @@ -0,0 +1,27 @@ +namespace PacketDotNet.LLDP +{ + /// + /// The IANA (Internet Assigned Numbers Authority) Address Family + /// + /// Source http://www.iana.org/assignments/address-family-numbers/ + public enum AddressFamily + { + /// IP version 4 + IPv4 = 1, + /// IP version 6 + IPv6 = 2, + /// NSAP + NSAP = 3, + /// HDLC + HDLC = 4, + /// BBN 1822 + BBN1822 = 5, + /// 802 (includes all 802 media plus Ethernet "canonical format") + Eth802 = 6, + /// E.163 + E163 = 7 + // Add more if necessary + // See remarks for more info on where + // to find more info + } +} \ No newline at end of file diff --git a/PacketDotNet/LLDP/CapabilityOptions.cs b/PacketDotNet/LLDP/CapabilityOptions.cs new file mode 100644 index 0000000..cc627be --- /dev/null +++ b/PacketDotNet/LLDP/CapabilityOptions.cs @@ -0,0 +1,40 @@ +using System; + +namespace PacketDotNet.LLDP +{ + /// + /// The System Capabilities options + /// + [Flags] + public enum CapabilityOptions + { + /// + /// An Other Type of System + /// + Other = 0x01, + /// A Repeater + /// See IETF RFC 2108 + Repeater = 0x02, + /// A Bridge + /// IETF RFC 2674 + Bridge = 0x04, + /// A WLAN Access Point + /// IEEE 802.11 MIB + WLanAP = 0x08, + /// A Router + /// IETF RFC 1812 + Router = 0x10, + /// A Telephone + /// IETF RFC 2011 + Telephone = 0x20, + /// A DOCSIS Cable Device + /// + /// See IETF RFC 2669 + /// See IETF RFC 2670 + /// + DocsisCableDevice = 0x40, + /// A Station with no other capabilities + /// IETF RFC 2011 + StationOnly = 0x80, + }; +} \ No newline at end of file diff --git a/PacketDotNet/LLDP/ChassisID.cs b/PacketDotNet/LLDP/ChassisID.cs new file mode 100644 index 0000000..e7d00b3 --- /dev/null +++ b/PacketDotNet/LLDP/ChassisID.cs @@ -0,0 +1,351 @@ +using System; +using System.Net; +using System.Net.NetworkInformation; +using PacketDotNet.Utils; + +namespace PacketDotNet.LLDP +{ + /// + /// A Chassis ID TLV + /// + public class ChassisID : TLV + { +#if DEBUG + private static readonly log4net.ILog log = log4net.LogManager.GetLogger(System.Reflection.MethodBase.GetCurrentMethod().DeclaringType); +#else + // 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 + private static readonly ILogInactive log; +#pragma warning restore 0169 +#endif + + /// + /// Length of the sub type field in bytes + /// + private const int SubTypeLength = 1; + + #region Constructors + + /// + /// Creates a Chassis ID TLV by parsing a byte[] + /// + /// + /// + /// + /// The Chassis ID TLV's offset from the + /// origin of the LLDP + /// + public ChassisID(byte[] bytes, int offset) : + base(bytes, offset) + { + log.Debug(""); + } + + /// + /// Creates a Chassis ID TLV and sets it value + /// + /// + /// The ChassisID subtype + /// + /// + /// The subtype's value + /// + public ChassisID(ChassisSubTypes subType, object subTypeValue) + { + log.DebugFormat("subType {0}", subType); + + EmptyTLVDataInit(); + + Type = TLVTypes.ChassisID; + + SubType = subType; + + // method will resize the tlv + SubTypeValue = subTypeValue; + } + + /// + /// Create a ChassisID given a mac address + /// + /// + /// A + /// + public ChassisID(PhysicalAddress MACAddress) + { + log.DebugFormat("MACAddress {0}", MACAddress.ToString()); + + EmptyTLVDataInit(); + + Type = TLVTypes.ChassisID; + SubType = ChassisSubTypes.MACAddress; + + SubTypeValue = MACAddress; + } + + /// + /// Create a ChassisID given an interface name + /// http://tools.ietf.org/search/rfc2863 page 38 + /// + /// + /// A + /// + public ChassisID(string InterfaceName) + { + log.DebugFormat("InterfaceName {0}", InterfaceName); + + EmptyTLVDataInit(); + + Type = TLVTypes.ChassisID; + SubType = ChassisSubTypes.InterfaceName; + + SetSubTypeValue(InterfaceName); + } + + #endregion + + #region Properties + + /// + /// The type of the TLV subtype + /// + public ChassisSubTypes SubType + { + get + { + return (ChassisSubTypes)tlvData.Bytes[ValueOffset]; + } + + set + { + // set the subtype + tlvData.Bytes[ValueOffset] = (byte)value; + } + } + + /// + /// The TLV subtype value + /// + public object SubTypeValue + { + get { return GetSubTypeValue(); } + set { SetSubTypeValue(value); } + } + + /// + /// If SubType is ChassisComponent + /// + public byte[] ChassisComponent + { + get { return (byte[])GetSubTypeValue(); } + set + { + SubType = ChassisSubTypes.ChassisComponent; + SetSubTypeValue(value); + } + } + + /// + /// If SubType is InterfaceName the interface name + /// + public string InterfaceName + { + get { return (string)GetSubTypeValue(); } + set + { + SubType = ChassisSubTypes.InterfaceName; + SetSubTypeValue(value); + } + } + + /// + /// If SubType is MACAddress the mac address + /// + public PhysicalAddress MACAddress + { + get { return (PhysicalAddress)GetSubTypeValue(); } + set + { + SubType = ChassisSubTypes.MACAddress; + SetSubTypeValue(value); + } + } + + /// + /// If SubType is NetworkAddress the network address + /// + public LLDP.NetworkAddress NetworkAddress + { + get { return (LLDP.NetworkAddress)GetSubTypeValue(); } + set + { + SubType = ChassisSubTypes.NetworkAddress; + SetSubTypeValue(value); + } + } + + /// + /// If SubType is PortComponent + /// + public byte[] PortComponent + { + get { return (byte[])GetSubTypeValue(); } + set + { + SubType = ChassisSubTypes.PortComponent; + SetSubTypeValue(value); + } + } + + /// + /// If SubType is InterfaceAlias + /// + public byte[] InterfaceAlias + { + get { return (byte[])GetSubTypeValue(); } + set + { + SubType = ChassisSubTypes.InterfaceAlias; + SetSubTypeValue(value); + } + } + + #endregion + + #region Methods + + /// + /// Helper method to reduce duplication in type specific constructors + /// + private void EmptyTLVDataInit() + { + var length = TLVTypeLength.TypeLengthLength + SubTypeLength; + var bytes = new byte[length]; + int offset = 0; + tlvData = new ByteArraySegment(bytes, offset, length); + } + + private object GetSubTypeValue() + { + byte[] val; + int dataOffset = ValueOffset + SubTypeLength; + int dataLength = Length - SubTypeLength; + + switch (SubType) + { + case ChassisSubTypes.ChassisComponent: + case ChassisSubTypes.InterfaceAlias: + case ChassisSubTypes.LocallyAssigned: + case ChassisSubTypes.PortComponent: + val = new byte[dataLength]; + Array.Copy(tlvData.Bytes, dataOffset, + val, 0, + dataLength); + return val; + case ChassisSubTypes.NetworkAddress: + return new NetworkAddress(tlvData.Bytes, + dataOffset, + dataLength); + case ChassisSubTypes.MACAddress: + val = new byte[dataLength]; + Array.Copy(tlvData.Bytes, dataOffset, + val, 0, + dataLength); + return new PhysicalAddress(val); + case ChassisSubTypes.InterfaceName: + return System.Text.ASCIIEncoding.ASCII.GetString(tlvData.Bytes, dataOffset, dataLength); + default: + throw new ArgumentOutOfRangeException(); + } + } + + private void SetSubTypeValue(object val) + { + byte[] valBytes; + + // make sure we have the correct type + switch (SubType) + { + case ChassisSubTypes.ChassisComponent: + case ChassisSubTypes.InterfaceAlias: + case ChassisSubTypes.LocallyAssigned: + case ChassisSubTypes.PortComponent: + if(!(val is byte[])) + { + throw new ArgumentOutOfRangeException("expected byte[] for type"); + } + + valBytes = (byte[])val; + + SetSubTypeValue(valBytes); + break; + case ChassisSubTypes.NetworkAddress: + if(!(val is NetworkAddress)) + { + throw new ArgumentOutOfRangeException("expected NetworkAddress instance for NetworkAddress"); + } + + valBytes = ((NetworkAddress)val).Bytes; + + SetSubTypeValue(valBytes); + break; + case ChassisSubTypes.InterfaceName: + if(!(val is string)) + { + throw new ArgumentOutOfRangeException("expected string for InterfaceName"); + } + + var interfaceName = (string)val; + + valBytes = System.Text.ASCIIEncoding.ASCII.GetBytes(interfaceName); + + SetSubTypeValue(valBytes); + break; + case ChassisSubTypes.MACAddress: + if(!(val is PhysicalAddress)) + { + throw new ArgumentOutOfRangeException("expected PhysicalAddress for MACAddress"); + } + + var physicalAddress = (PhysicalAddress)val; + + SetSubTypeValue(physicalAddress.GetAddressBytes()); + break; + default: + throw new ArgumentOutOfRangeException(); + } + } + + private void SetSubTypeValue(byte[] subTypeValue) + { + // is the length different than the current length? + if(subTypeValue.Length != Length) + { + var headerLength = TLVTypeLength.TypeLengthLength + SubTypeLength; + var newTlvMemory = new byte[headerLength + subTypeValue.Length]; + + // copy the header data over + Array.Copy(tlvData.Bytes, tlvData.Offset, newTlvMemory, 0, headerLength); + + // update the tlv memory pointer, offset and length + tlvData = new ByteArraySegment(newTlvMemory, 0, newTlvMemory.Length); + } + + Array.Copy(subTypeValue, 0, tlvData.Bytes, ValueOffset + SubTypeLength, + subTypeValue.Length); + } + + /// + /// Convert this Chassis ID TLV to a string. + /// + /// + /// A human readable string + /// + public override string ToString () + { + return string.Format("[ChassisID: SubType={0}, SubTypeValue={1}]", SubType, SubTypeValue); + } + + #endregion + } +} \ No newline at end of file diff --git a/PacketDotNet/LLDP/ChassisSubTypes.cs b/PacketDotNet/LLDP/ChassisSubTypes.cs new file mode 100644 index 0000000..ac383bb --- /dev/null +++ b/PacketDotNet/LLDP/ChassisSubTypes.cs @@ -0,0 +1,29 @@ +namespace PacketDotNet.LLDP +{ + /// + /// The Chassis ID TLV subtypes + /// + public enum ChassisSubTypes + { + /// A Chassis Component identifier + /// See IETF RFC 2737 + ChassisComponent = 1, + /// An Interface Alias identifier + /// See IETF RFC 2863 + InterfaceAlias = 2, + /// A Port Component identifier + /// See IETF RFC 2737 + PortComponent = 3, + /// A MAC (Media Access Control) Address identifier + /// See IEEE Std 802 + MACAddress = 4, + /// A Network Address (IP Address) Identifier + /// See IEEE Std 802 + NetworkAddress = 5, + /// An Interface Name identifier + /// See IEEE Std 802 + InterfaceName = 6, + /// A Locally Assigned identifier + LocallyAssigned = 7 + }; +} \ No newline at end of file diff --git a/PacketDotNet/LLDP/EndOfLLDPDU.cs b/PacketDotNet/LLDP/EndOfLLDPDU.cs new file mode 100644 index 0000000..5ede052 --- /dev/null +++ b/PacketDotNet/LLDP/EndOfLLDPDU.cs @@ -0,0 +1,54 @@ +namespace PacketDotNet.LLDP +{ + /// + /// An End Of LLDPDU TLV + /// + public class EndOfLLDPDU : TLV + { + #region Constructors + + /// + /// Parses bytes into an End Of LLDPDU TLV + /// + /// + /// TLV bytes + /// + /// + /// The End Of LLDPDU TLV's offset from the + /// origin of the LLDP + /// + public EndOfLLDPDU(byte[] bytes, int offset) : + base(bytes, offset) + { + Type = 0; + Length = 0; + } + + /// + /// Creates an End Of LLDPDU TLV + /// + public EndOfLLDPDU() + { + var bytes = new byte[TLVTypeLength.TypeLengthLength]; + var offset = 0; + var length = bytes.Length; + tlvData = new PacketDotNet.Utils.ByteArraySegment(bytes, offset, length); + + Type = 0; + Length = 0; + } + + /// + /// Convert this TTL TLV to a string. + /// + /// + /// A human readable string + /// + public override string ToString () + { + return string.Format("[EndOfLLDPDU]"); + } + + #endregion + } +} \ No newline at end of file diff --git a/PacketDotNet/LLDP/InterfaceNumbering.cs b/PacketDotNet/LLDP/InterfaceNumbering.cs new file mode 100644 index 0000000..7bf94c1 --- /dev/null +++ b/PacketDotNet/LLDP/InterfaceNumbering.cs @@ -0,0 +1,16 @@ +namespace PacketDotNet.LLDP +{ + /// + /// Interface Numbering Types + /// + /// Source IETF RFC 802.1AB + public enum InterfaceNumbering + { + /// Unknown + Unknown, + /// Interface Index + ifIndex, + /// System Port Number + SystemPortNumber + }; +} \ No newline at end of file diff --git a/PacketDotNet/LLDP/ManagementAddress.cs b/PacketDotNet/LLDP/ManagementAddress.cs new file mode 100644 index 0000000..657431b --- /dev/null +++ b/PacketDotNet/LLDP/ManagementAddress.cs @@ -0,0 +1,329 @@ +using System; +using System.Text; +using MiscUtil.Conversion; +using PacketDotNet.Utils; + +namespace PacketDotNet.LLDP +{ + /// + /// A Time to Live TLV + /// + /// [TLV Type Length : 2][Mgmt Addr length : 1][Mgmt Addr Subtype : 1][Mgmt Addr : 1-31] + /// [Interface Subtype : 1][Interface number : 4][OID length : 1][OID : 0-128] + /// + /// + public class ManagementAddress : TLV + { +#if DEBUG + private static readonly log4net.ILog log = log4net.LogManager.GetLogger(System.Reflection.MethodBase.GetCurrentMethod().DeclaringType); +#else + // 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 + private static readonly ILogInactive log; +#pragma warning restore 0169 +#endif + + /// + /// Number of bytes in the AddressLength field + /// + private const int MgmtAddressLengthLength = 1; + + /// + /// Number of bytes in the interface number subtype field + /// + private const int InterfaceNumberSubTypeLength = 1; + + /// + /// Number of bytes in the interface number field + /// + private const int InterfaceNumberLength = 4; + + /// + /// Number of bytes in the object identifier length field + /// + private const int ObjectIdentifierLengthLength = 1; + + /// + /// Maximum number of bytes in the object identifier field + /// + private const int maxObjectIdentifierLength = 128; + + #region Constructors + + /// + /// Creates a Management Address TLV + /// + /// + /// The LLDP Data unit being modified + /// + /// + /// The Management Address TLV's offset from the + /// origin of the LLDP + /// + public ManagementAddress(byte[] bytes, int offset) : + base(bytes, offset) + { + log.Debug(""); + } + + /// + /// Creates a Management Address TLV and sets it value + /// + /// + /// The Management Address + /// + /// + /// The Interface Numbering Sub Type + /// + /// + /// The Interface Number + /// + /// + /// The Object Identifier + /// + public ManagementAddress(NetworkAddress managementAddress, + InterfaceNumbering interfaceSubType, uint ifNumber, + string oid) + { + log.Debug(""); + + // NOTE: We presume that the mgmt address length and the + // object identifier length are zero + var length = TLVTypeLength.TypeLengthLength + MgmtAddressLengthLength + + InterfaceNumberSubTypeLength + InterfaceNumberLength + + ObjectIdentifierLengthLength; + var bytes = new byte[length]; + var offset = 0; + tlvData = new ByteArraySegment(bytes, offset, length); + + // The lengths are both zero until the values are set + AddressLength = 0; + ObjIdLength = 0; + + Type = TLVTypes.ManagementAddress; + + MgmtAddress = managementAddress; + InterfaceSubType = interfaceSubType; + InterfaceNumber = ifNumber; + ObjectIdentifier = oid; + } + + #endregion + + #region Properties + + /// + /// The Management Address Length + /// + public int AddressLength + { + get { return (int)tlvData.Bytes[ValueOffset]; } + internal set { tlvData.Bytes[ValueOffset] = (byte)value; } + } + + /// + /// The Management Address Subtype + /// + /// Forward to the MgmtAddress instance + /// + public AddressFamily AddressSubType + { + get { return MgmtAddress.AddressFamily; } + } + + /// + /// The Management Address + /// + public NetworkAddress MgmtAddress + { + get + { + int offset = ValueOffset + MgmtAddressLengthLength; + + return new NetworkAddress(tlvData.Bytes, offset, AddressLength); + } + + set + { + var valueLength = value.Length; + var valueBytes = value.Bytes; + + // is the new address the same size as the old address? + if(AddressLength != valueLength) + { + // need to resize the tlv and shift data fields down + var newLength = TLVTypeLength.TypeLengthLength + MgmtAddressLengthLength + + valueLength + + InterfaceNumberSubTypeLength + + InterfaceNumberLength + + ObjectIdentifierLengthLength + + ObjIdLength; + + var newBytes = new byte[newLength]; + + int headerLength = TLVTypeLength.TypeLengthLength + MgmtAddressLengthLength; + int oldStartOfAfterData = ValueOffset + MgmtAddressLengthLength + AddressLength; + int newStartOfAfterData = TLVTypeLength.TypeLengthLength + MgmtAddressLengthLength + value.Length; + int afterDataLength = InterfaceNumberSubTypeLength + InterfaceNumberLength + ObjectIdentifierLengthLength + ObjIdLength; + + // copy the data before the mgmt address + Array.Copy(tlvData.Bytes, tlvData.Offset, + newBytes, 0, + headerLength); + + // copy the data over after the mgmt address over + Array.Copy(tlvData.Bytes, oldStartOfAfterData, + newBytes, newStartOfAfterData, + afterDataLength); + + var offset = 0; + tlvData = new ByteArraySegment(newBytes, offset, newLength); + + // update the address length field + AddressLength = valueLength; + } + + // copy the new address into the appropriate position in the byte[] + Array.Copy(valueBytes, 0, + tlvData.Bytes, ValueOffset + MgmtAddressLengthLength, + valueLength); + } + } + + /// + /// Interface Number Sub Type + /// + public InterfaceNumbering InterfaceSubType + { + get + { + return (InterfaceNumbering)tlvData.Bytes[ValueOffset + MgmtAddressLengthLength + MgmtAddress.Length]; + } + + set + { + tlvData.Bytes[ValueOffset + MgmtAddressLengthLength + MgmtAddress.Length] = (byte)value; + } + } + + private int InterfaceNumberOffset + { + get + { + return ValueOffset + MgmtAddressLengthLength + AddressLength + InterfaceNumberSubTypeLength; + } + } + + /// + /// Interface Number + /// + public uint InterfaceNumber + { + get + { + return EndianBitConverter.Big.ToUInt32(tlvData.Bytes, + InterfaceNumberOffset); + } + + set + { + EndianBitConverter.Big.CopyBytes(value, + tlvData.Bytes, + InterfaceNumberOffset); + } + } + + private int ObjIdLengthOffset + { + get + { + return InterfaceNumberOffset + InterfaceNumberLength; + } + } + + /// + /// Object ID Length + /// + public byte ObjIdLength + { + get + { + return tlvData.Bytes[ObjIdLengthOffset]; + } + + internal set + { + tlvData.Bytes[ObjIdLengthOffset] = value; + } + } + + private int ObjectIdentifierOffset + { + get { return ObjIdLengthOffset + ObjectIdentifierLengthLength; } + } + + /// + /// Object ID + /// + public string ObjectIdentifier + { + get + { + return UTF8Encoding.UTF8.GetString(tlvData.Bytes, ObjectIdentifierOffset, + ObjIdLength); + } + + set + { + byte[] oid = UTF8Encoding.UTF8.GetBytes(value); + + // check for out-of-range sizes + if(oid.Length > maxObjectIdentifierLength) + { + throw new System.ArgumentOutOfRangeException("ObjectIdentifier", "length > maxObjectIdentifierLength of " + maxObjectIdentifierLength); + } + + // does the object identifier length match the existing one? + if(ObjIdLength != oid.Length) + { + var oldLength = TLVTypeLength.TypeLengthLength + MgmtAddressLengthLength + + AddressLength + + InterfaceNumberSubTypeLength + InterfaceNumberLength + + ObjectIdentifierLengthLength; + var newLength = oldLength + oid.Length; + + var newBytes = new byte[newLength]; + + // copy the original bytes over + Array.Copy(tlvData.Bytes, tlvData.Offset, + newBytes, 0, + oldLength); + + var offset = 0; + tlvData = new ByteArraySegment(newBytes, offset, newLength); + + // update the length + ObjIdLength = (byte)value.Length; + } + + Array.Copy(oid, 0, + tlvData.Bytes, ObjectIdentifierOffset, + oid.Length); + } + } + + /// + /// Convert this Management Address TLV to a string. + /// + /// + /// A human readable string + /// + public override string ToString () + { + return string.Format("[ManagementAddress: AddressLength={0}, AddressSubType={1}, MgmtAddress={2}, InterfaceSubType={3}, InterfaceNumber={4}, ObjIdLength={5}, ObjectIdentifier={6}]", AddressLength, AddressSubType, MgmtAddress, InterfaceSubType, InterfaceNumber, ObjIdLength, ObjectIdentifier); + } + + #endregion + } +} \ No newline at end of file diff --git a/PacketDotNet/LLDP/NetworkAddress.cs b/PacketDotNet/LLDP/NetworkAddress.cs new file mode 100644 index 0000000..586508f --- /dev/null +++ b/PacketDotNet/LLDP/NetworkAddress.cs @@ -0,0 +1,198 @@ +using System; +using PacketDotNet.Utils; + +namespace PacketDotNet.LLDP +{ + /// + /// A Network Address + /// + public class NetworkAddress + { + /// + /// Length of AddressFamily field in bytes + /// + internal const int AddressFamilyLength = 1; + + internal ByteArraySegment data; + + #region Constructors + + /// + /// Creates a Network Address entity + /// + /// + /// The Network Address + /// + public NetworkAddress(System.Net.IPAddress address) + { + Address = address; + } + + /// + /// Create a network address from byte data + /// + /// + /// A + /// + /// + /// A + /// + /// + /// A + /// + public NetworkAddress(byte[] bytes, int offset, int length) + { + data = new ByteArraySegment(bytes, offset, length); + } + + #endregion + + /// + /// Number of bytes in the NetworkAddress + /// + internal int Length + { + get + { + return AddressFamilyLength + Address.GetAddressBytes().Length; + } + } + + internal byte[] Bytes + { + get + { + var addressBytes = Address.GetAddressBytes(); + var data = new byte[AddressFamilyLength + addressBytes.Length]; + data[0] = (byte)AddressFamily; + Array.Copy(addressBytes, 0, + data, AddressFamilyLength, + addressBytes.Length); + return data; + } + } + + #region Members + + /// The format of the Network Address + public LLDP.AddressFamily AddressFamily + { + get { return (LLDP.AddressFamily)data.Bytes[data.Offset]; } + set { data.Bytes[data.Offset] = (byte)value; } + } + + private static int LengthFromAddressFamily(LLDP.AddressFamily addressFamily) + { + int length; + + if(addressFamily == LLDP.AddressFamily.IPv4) + length = IPv4Fields.AddressLength; + else if(addressFamily == LLDP.AddressFamily.IPv6) + length = IPv6Fields.AddressLength; + else + throw new System.NotImplementedException("Unknown addressFamily of " + addressFamily); + + return length; + } + + private static LLDP.AddressFamily AddressFamilyFromSocketAddress(System.Net.IPAddress address) + { + if(address.AddressFamily == System.Net.Sockets.AddressFamily.InterNetwork) + { + return AddressFamily.IPv4; + } else + { + return AddressFamily.IPv6; + } + } + + /// The Network Address + public System.Net.IPAddress Address + { + get + { + var length = LengthFromAddressFamily(AddressFamily); + var bytes = new byte[length]; + Array.Copy(data.Bytes, data.Offset + AddressFamilyLength, + bytes, 0, + bytes.Length); + + return new System.Net.IPAddress(bytes); + } + + set + { + // do we have enough bytes for the address? + var length = LengthFromAddressFamily(AddressFamilyFromSocketAddress(value)); + length += AddressFamilyLength; + + if((data == null) || data.Length != length) + { + var bytes = new byte[length]; + var offset = 0; + + // allocate enough memory for the new Address + data = new ByteArraySegment(bytes, offset, length); + } + + AddressFamily = AddressFamilyFromSocketAddress(value); + + var addressBytes = value.GetAddressBytes(); + Array.Copy(addressBytes, 0, + data.Bytes, data.Offset + AddressFamilyLength, + addressBytes.Length); + } + } + + /// + /// Equals override + /// + /// + /// A + /// + /// + /// A + /// + public override bool Equals (object obj) + { + // Check for null values and compare run-time types. + if (obj == null || GetType() != obj.GetType()) + return false; + + var na = (NetworkAddress)obj; + + if(this.AddressFamily.Equals(na.AddressFamily) && + this.Address.Equals(na.Address)) + { + return true; + } + + return false; + } + + /// + /// GetHashCode() override + /// + /// + /// A + /// + public override int GetHashCode () + { + return AddressFamily.GetHashCode() + Address.GetHashCode(); + } + + /// + /// ToString() override + /// + /// + /// A + /// + public override string ToString () + { + return string.Format("[NetworkAddress: AddressFamily={0}, Address={1}]", + AddressFamily, Address); + } + + #endregion + } +} \ No newline at end of file diff --git a/PacketDotNet/LLDP/OrganizationSpecific.cs b/PacketDotNet/LLDP/OrganizationSpecific.cs new file mode 100644 index 0000000..0022fa9 --- /dev/null +++ b/PacketDotNet/LLDP/OrganizationSpecific.cs @@ -0,0 +1,173 @@ +using System; +using PacketDotNet.Utils; + +namespace PacketDotNet.LLDP +{ + /// + /// An Organization Specific TLV + /// + /// [TLV Type Length : 2][Organizationally Unique Identifier OUI : 3] + /// [Organizationally Defined Subtype : 1][Organizationally Defined Information String : 0 - 507] + /// + public class OrganizationSpecific : TLV + { +#if DEBUG + private static readonly log4net.ILog log = log4net.LogManager.GetLogger(System.Reflection.MethodBase.GetCurrentMethod().DeclaringType); +#else + // 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 + private static readonly ILogInactive log; +#pragma warning restore 0169 +#endif + + private const int OUILength = 3; + private const int OUISubTypeLength = 1; + + #region Constructors + + /// + /// Creates an Organization Specific TLV + /// + /// + /// The LLDP Data unit being modified + /// + /// + /// The Organization Specific TLV's offset from the + /// origin of the LLDP + /// + public OrganizationSpecific(byte[] bytes, int offset) : + base(bytes, offset) + { + log.Debug(""); + } + + /// + /// Creates an Organization Specific TLV and sets it value + /// + /// + /// An Organizationally Unique Identifier + /// + /// + /// An Organizationally Defined SubType + /// + /// + /// An Organizationally Defined Information String + /// + public OrganizationSpecific(byte[] oui, int subType, byte[] infoString) + { + log.Debug(""); + + var length = TLVTypeLength.TypeLengthLength + OUILength + OUISubTypeLength; + var bytes = new byte[length]; + var offset = 0; + tlvData = new ByteArraySegment(bytes, offset, length); + + Type = TLVTypes.OrganizationSpecific; + + OrganizationUniqueID = oui; + OrganizationDefinedSubType = subType; + OrganizationDefinedInfoString = infoString; + } + + #endregion + + #region Properties + + /// + /// An Organizationally Unique Identifier + /// + public byte[] OrganizationUniqueID + { + get + { + byte[] oui = new byte[OUILength]; + Array.Copy(tlvData.Bytes, ValueOffset, + oui, 0, + OUILength); + return oui; + } + + set + { + Array.Copy(value, 0, + tlvData.Bytes, ValueOffset, OUILength); + } + } + + /// + /// An Organizationally Defined SubType + /// + public int OrganizationDefinedSubType + { + get + { + return tlvData.Bytes[ValueOffset + OUILength]; + } + set + { + tlvData.Bytes[ValueOffset + OUILength] = (byte)value; + } + } + + /// + /// An Organizationally Defined Information String + /// + public byte[] OrganizationDefinedInfoString + { + get + { + var length = Length - (OUILength + OUISubTypeLength); + + var bytes = new byte[length]; + Array.Copy(tlvData.Bytes, ValueOffset + OUILength + OUISubTypeLength, + bytes, 0, + length); + + return bytes; + } + + set + { + var length = Length - (OUILength + OUISubTypeLength); + + // do we have the right sized tlv? + if(value.Length != length) + { + var headerLength = TLVTypeLength.TypeLengthLength + OUILength + OUISubTypeLength; + + // resize the tlv + var newLength = headerLength + value.Length; + var bytes = new byte[newLength]; + + // copy the header bytes over + Array.Copy(tlvData.Bytes, tlvData.Offset, + bytes, 0, + headerLength); + + // assign a new ByteArrayAndOffset to tlvData + var offset = 0; + tlvData = new ByteArraySegment(bytes, offset, newLength); + } + + // copy the byte array in + Array.Copy(value, 0, + tlvData.Bytes, ValueOffset + OUILength + OUISubTypeLength, + value.Length); + } + } + + /// + /// Convert this Organization Specific TLV to a string. + /// + /// + /// A human readable string + /// + public override string ToString () + { + return string.Format("[OrganizationSpecific: OrganizationUniqueID={0}, OrganizationDefinedSubType={1}, OrganizationDefinedInfoString={2}]", OrganizationUniqueID, OrganizationDefinedSubType, OrganizationDefinedInfoString); + } + + #endregion + } +} \ No newline at end of file diff --git a/PacketDotNet/LLDP/PortDescription.cs b/PacketDotNet/LLDP/PortDescription.cs new file mode 100644 index 0000000..435923e --- /dev/null +++ b/PacketDotNet/LLDP/PortDescription.cs @@ -0,0 +1,65 @@ +using System; +using System.Text; +using PacketDotNet.Utils; + +namespace PacketDotNet.LLDP +{ + /// + /// A Port Description TLV + /// + public class PortDescription : StringTLV + { +#if DEBUG + private static readonly log4net.ILog log = log4net.LogManager.GetLogger(System.Reflection.MethodBase.GetCurrentMethod().DeclaringType); +#else + // 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 + private static readonly ILogInactive log; +#pragma warning restore 0169 +#endif + + #region Constructors + + /// + /// Creates a Port Description TLV + /// + /// + /// + /// + /// The Port Description TLV's offset from the + /// origin of the LLDP + /// + public PortDescription(byte[] bytes, int offset) : + base(bytes, offset) + { + log.Debug(""); + } + + /// + /// Creates a Port Description TLV and sets it value + /// + /// + /// A textual description of the port + /// + public PortDescription(string description) : base(TLVTypes.PortDescription, description) + { + log.Debug(""); + } + + #endregion + + #region Properties + + /// + /// A textual Description of the port + /// + public string Description + { + get { return StringValue; } + set { StringValue = value; } + } + + #endregion + } +} \ No newline at end of file diff --git a/PacketDotNet/LLDP/PortID.cs b/PacketDotNet/LLDP/PortID.cs new file mode 100644 index 0000000..87ebf07 --- /dev/null +++ b/PacketDotNet/LLDP/PortID.cs @@ -0,0 +1,241 @@ +using System; +using System.Net; +using System.Net.NetworkInformation; +using PacketDotNet.Utils; + +namespace PacketDotNet.LLDP +{ + /// + /// A Port ID TLV + /// + public class PortID : TLV + { +#if DEBUG + private static readonly log4net.ILog log = log4net.LogManager.GetLogger(System.Reflection.MethodBase.GetCurrentMethod().DeclaringType); +#else + // 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 + private static readonly ILogInactive log; +#pragma warning restore 0169 +#endif + + private const int SubTypeLength = 1; + + #region Constructors + + /// + /// Creates a Port ID TLV + /// + /// + /// + /// + /// The Port ID TLV's offset from the + /// origin of the LLDP + /// + public PortID(byte[] bytes, int offset) : + base(bytes, offset) + { + log.Debug(""); + } + + /// + /// Creates a Port ID TLV and sets it value + /// + /// + /// The Port ID SubType + /// + /// + /// The subtype's value + /// + public PortID(PortSubTypes subType, object subTypeValue) + { + log.Debug(""); + + EmptyTLVDataInit(); + + Type = TLVTypes.PortID; + SubType = subType; + + // method will resize the tlv + SubTypeValue = subTypeValue; + } + + /// + /// Construct a PortID from a NetworkAddress + /// + /// + /// A + /// + public PortID(LLDP.NetworkAddress networkAddress) + { + log.DebugFormat("NetworkAddress {0}", networkAddress.ToString()); + + var length = TLVTypeLength.TypeLengthLength + SubTypeLength; + var bytes = new byte[length]; + var offset = 0; + tlvData = new ByteArraySegment(bytes, offset, length); + + Type = TLVTypes.PortID; + SubType = PortSubTypes.NetworkAddress; + SubTypeValue = networkAddress; + } + + #endregion + + #region Properties + + /// + /// The type of the TLV subtype + /// + public PortSubTypes SubType + { + get { return (PortSubTypes)tlvData.Bytes[tlvData.Offset + TLVTypeLength.TypeLengthLength]; } + set + { + tlvData.Bytes[tlvData.Offset + TLVTypeLength.TypeLengthLength] = (byte)value; + } + } + + /// + /// The TLV subtype value + /// + public object SubTypeValue + { + get { return GetSubTypeValue(); } + set { SetSubTypeValue(value); } + } + + /// + /// Offset to the value field + /// + private int DataOffset + { + get { return ValueOffset + SubTypeLength; } + } + + /// + /// Size of the value field + /// + private int DataLength + { + get { return Length - SubTypeLength; } + } + + #endregion + + #region Methods + + /// + /// Helper method to reduce duplication in type specific constructors + /// + private void EmptyTLVDataInit() + { + var length = TLVTypeLength.TypeLengthLength + SubTypeLength; + var bytes = new byte[length]; + int offset = 0; + tlvData = new ByteArraySegment(bytes, offset, length); + } + + private object GetSubTypeValue() + { + byte[] arrAddress; + + switch (SubType) + { + case PortSubTypes.InterfaceAlias: + case PortSubTypes.InterfaceName: + case PortSubTypes.LocallyAssigned: + case PortSubTypes.PortComponent: + case PortSubTypes.AgentCircuitID: + // get the address + arrAddress = new byte[DataLength]; + Array.Copy(tlvData.Bytes, DataOffset, arrAddress, 0, DataLength); + return arrAddress; + case PortSubTypes.MACAddress: + // get the address + arrAddress = new byte[DataLength]; + Array.Copy(tlvData.Bytes, DataOffset, arrAddress, 0, DataLength); + PhysicalAddress address = new PhysicalAddress(arrAddress); + return address; + case PortSubTypes.NetworkAddress: + // get the address + AddressFamily addressFamily = (AddressFamily)tlvData.Bytes[DataLength]; + return GetNetworkAddress(addressFamily); + default: + throw new ArgumentOutOfRangeException(); + } + } + + private void SetSubTypeValue(object subTypeValue) + { + switch (SubType) + { + case PortSubTypes.InterfaceAlias: + case PortSubTypes.InterfaceName: + case PortSubTypes.LocallyAssigned: + case PortSubTypes.PortComponent: + case PortSubTypes.AgentCircuitID: + SetSubTypeValue((byte[])subTypeValue); + break; + case PortSubTypes.MACAddress: + SetSubTypeValue(((PhysicalAddress)subTypeValue).GetAddressBytes()); + break; + case PortSubTypes.NetworkAddress: + SetSubTypeValue(((NetworkAddress)subTypeValue).Bytes); + break; + default: + throw new ArgumentOutOfRangeException(); + } + } + + private void SetSubTypeValue(byte[] val) + { + // does our current length match? + int dataLength = Length - SubTypeLength; + if(dataLength != val.Length) + { + var headerLength = TLVTypeLength.TypeLengthLength + SubTypeLength; + var newLength = headerLength + val.Length; + var newBytes = new byte[newLength]; + + // copy the header data over + Array.Copy(tlvData.Bytes, tlvData.Offset, + newBytes, 0, + headerLength); + + var offset = 0; + tlvData = new ByteArraySegment(newBytes, offset, newLength); + } + + Array.Copy(val, 0, + tlvData.Bytes, ValueOffset + SubTypeLength, + val.Length); + } + + private NetworkAddress GetNetworkAddress(AddressFamily addressFamily) + { + if(SubType != PortSubTypes.NetworkAddress) + { + throw new ArgumentOutOfRangeException("SubType != PortSubTypes.NetworkAddress"); + } + + var networkAddress = new NetworkAddress(tlvData.Bytes, DataOffset, DataLength); + + return networkAddress; + } + + /// + /// Convert this Port ID TLV to a string. + /// + /// + /// A human readable string + /// + public override string ToString () + { + return string.Format("[PortID: SubType={0}, SubTypeValue={1}]", SubType, SubTypeValue); + } + + #endregion + } +} \ No newline at end of file diff --git a/PacketDotNet/LLDP/PortSubTypes.cs b/PacketDotNet/LLDP/PortSubTypes.cs new file mode 100644 index 0000000..68f1cff --- /dev/null +++ b/PacketDotNet/LLDP/PortSubTypes.cs @@ -0,0 +1,30 @@ +namespace PacketDotNet.LLDP +{ + /// + /// The Port ID TLV subtypes + /// + public enum PortSubTypes + { + /// An Interface Alias identifier + /// See IETF RFC 2863 + InterfaceAlias = 1, + /// A Port Component identifier + /// See IETF RFC 2737 + PortComponent = 2, + /// A MAC (Media Access Control) Address identifier + /// See IEEE Std 802 + MACAddress = 3, + /// A Network Address (IP Address) Identifier + /// See IEEE Std 802 + NetworkAddress = 4, + /// An Interface Name identifier + /// See IEEE Std 802 + InterfaceName = 5, + /// An Agent Circiut ID identifier + /// See IETF RFC 3046 + AgentCircuitID = 6, + /// A Locally Assigned identifier + /// See IETF RFC 3046 + LocallyAssigned = 7 + }; +} \ No newline at end of file diff --git a/PacketDotNet/LLDP/StringTLV.cs b/PacketDotNet/LLDP/StringTLV.cs new file mode 100644 index 0000000..2991c38 --- /dev/null +++ b/PacketDotNet/LLDP/StringTLV.cs @@ -0,0 +1,101 @@ +using System; +using PacketDotNet.Utils; + +namespace PacketDotNet.LLDP +{ + /// + /// Base class for several TLV types that all contain strings + /// + public class StringTLV : TLV + { + #region Constructors + + /// + /// Creates a String TLV + /// + /// + /// + /// + /// The Port Description TLV's offset from the + /// origin of the LLDP + /// + public StringTLV(byte[] bytes, int offset) : + base(bytes, offset) + {} + + /// + /// Create from a type and string value + /// + /// + /// A + /// + /// + /// A + /// + public StringTLV(TLVTypes tlvType, string StringValue) + { + var bytes = new byte[TLVTypeLength.TypeLengthLength]; + var offset = 0; + tlvData = new ByteArraySegment(bytes, offset, bytes.Length); + + Type = tlvType; + this.StringValue = StringValue; + } + + #endregion + + #region Properties + + /// + /// A textual Description of the port + /// + public string StringValue + { + get + { + return System.Text.ASCIIEncoding.ASCII.GetString(tlvData.Bytes, + ValueOffset, + Length); + } + + set + { + var bytes = System.Text.ASCIIEncoding.ASCII.GetBytes(value); + var length = TLVTypeLength.TypeLengthLength + bytes.Length; + + // is the tlv the correct size? + if(tlvData.Length != length) + { + // allocate new memory for this tlv + var newTLVBytes = new byte[length]; + var offset = 0; + + // copy header over + Array.Copy(tlvData.Bytes, tlvData.Offset, + newTLVBytes, 0, + TLVTypeLength.TypeLengthLength); + + tlvData = new ByteArraySegment(newTLVBytes, offset, length); + } + + // set the description + Array.Copy(bytes, 0, + tlvData.Bytes, ValueOffset, + bytes.Length); + } + } + + /// + /// Convert this Port Description TLV to a string. + /// + /// + /// A human readable string + /// + public override string ToString () + { + return string.Format("[{0}: Description={0}]", Type, StringValue); + } + + #endregion + } +} diff --git a/PacketDotNet/LLDP/SystemCapabilities.cs b/PacketDotNet/LLDP/SystemCapabilities.cs new file mode 100644 index 0000000..99f2784 --- /dev/null +++ b/PacketDotNet/LLDP/SystemCapabilities.cs @@ -0,0 +1,158 @@ +using System; +using MiscUtil.Conversion; +using PacketDotNet.Utils; + +namespace PacketDotNet.LLDP +{ + /// + /// A System Capabilities TLV + /// + /// [TLVTypeLength - 2 bytes][System Capabilities - 2 bytes][Enabled Capabilities - 2 bytes] + /// + public class SystemCapabilities : TLV + { + private const int SystemCapabilitiesLength = 2; + private const int EnabledCapabilitiesLength = 2; + + #region Constructors + + /// + /// Creates a System Capabilities TLV + /// + /// + /// + /// + /// The System Capabilities TLV's offset from the + /// origin of the LLDP + /// + public SystemCapabilities(byte[] bytes, int offset) : + base(bytes, offset) + {} + + /// + /// Creates a System Capabilities TLV and sets the value + /// + /// + /// A bitmap containing the available System Capabilities + /// + /// + /// A bitmap containing the enabled System Capabilities + /// + public SystemCapabilities(ushort capabilities, ushort enabled) + { + var length = TLVTypeLength.TypeLengthLength + SystemCapabilitiesLength + EnabledCapabilitiesLength; + var bytes = new byte[length]; + var offset = 0; + tlvData = new ByteArraySegment(bytes, offset, length); + + Type = TLVTypes.SystemCapabilities; + Capabilities = capabilities; + Enabled = enabled; + } + + #endregion + + #region Properties + + /// + /// A bitmap containing the available System Capabilities + /// + public ushort Capabilities + { + get + { + // get the capabilities + return BigEndianBitConverter.Big.ToUInt16(tlvData.Bytes, + tlvData.Offset + TLVTypeLength.TypeLengthLength); + } + set + { + // set the capabilities + EndianBitConverter.Big.CopyBytes(value, + tlvData.Bytes, + tlvData.Offset + TLVTypeLength.TypeLengthLength); + } + } + + /// + /// A bitmap containing the Enabled System Capabilities + /// + public ushort Enabled + { + get + { + return EndianBitConverter.Big.ToUInt16(tlvData.Bytes, + tlvData.Offset + TLVTypeLength.TypeLengthLength + SystemCapabilitiesLength); + } + + set + { + // Add the length of the previous field, the SystemCapabilities field, to get + // to the location of the EnabledCapabilities + EndianBitConverter.Big.CopyBytes(value, tlvData.Bytes, + ValueOffset + SystemCapabilitiesLength); + } + } + + #endregion + + #region Methods + + /// + /// Checks whether the system is capable of a certain function + /// + /// + /// The capability being checked + /// + /// + /// Whether or not the system is capable of the function being tested + /// + public bool IsCapable(CapabilityOptions capability) + { + ushort mask = (ushort)capability; + if ((Capabilities & mask) != 0) + { + return true; + } + else + { + return false; + } + } + + /// + /// Checks whether the specified function has been enabled on the system + /// + /// + /// The capability being checked + /// + /// + /// Whether or not the specified function is enabled + /// + public bool IsEnabled(CapabilityOptions capability) + { + ushort mask = (ushort)capability; + if ((Enabled & mask) != 0) + { + return true; + } + else + { + return false; + } + } + + /// + /// Convert this System Capabilities TLV to a string. + /// + /// + /// A human readable string + /// + public override string ToString () + { + return string.Format("[SystemCapabilities: Capabilities={0}, Enabled={1}]", Capabilities, Enabled); + } + + #endregion + } +} \ No newline at end of file diff --git a/PacketDotNet/LLDP/SystemDescription.cs b/PacketDotNet/LLDP/SystemDescription.cs new file mode 100644 index 0000000..129614d --- /dev/null +++ b/PacketDotNet/LLDP/SystemDescription.cs @@ -0,0 +1,65 @@ +using System; +using System.Text; + +namespace PacketDotNet.LLDP +{ + /// + /// A System Description TLV + /// + public class SystemDescription : StringTLV + { +#if DEBUG + private static readonly log4net.ILog log = log4net.LogManager.GetLogger(System.Reflection.MethodBase.GetCurrentMethod().DeclaringType); +#else + // 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 + private static readonly ILogInactive log; +#pragma warning restore 0169 +#endif + + #region Constructors + + /// + /// Creates a System Description TLV + /// + /// + /// + /// + /// The System Description TLV's offset from the + /// origin of the LLDP + /// + public SystemDescription(byte[] bytes, int offset) : + base(bytes, offset) + { + log.Debug(""); + } + + /// + /// Creates a System Description TLV and sets it value + /// + /// + /// A textual Description of the system + /// + public SystemDescription(string description) : base(TLVTypes.SystemDescription, + description) + { + log.Debug(""); + } + + #endregion + + #region Properties + + /// + /// A textual Description of the system + /// + public string Description + { + get { return StringValue; } + set { StringValue = value; } + } + + #endregion + } +} \ No newline at end of file diff --git a/PacketDotNet/LLDP/SystemName.cs b/PacketDotNet/LLDP/SystemName.cs new file mode 100644 index 0000000..c90d4a7 --- /dev/null +++ b/PacketDotNet/LLDP/SystemName.cs @@ -0,0 +1,51 @@ +using System; +using System.Text; + +namespace PacketDotNet.LLDP +{ + /// + /// A System Name TLV + /// + public class SystemName : StringTLV + { + #region Constructors + + /// + /// Creates a System Name TLV + /// + /// + /// + /// + /// The System Name TLV's offset from the + /// origin of the LLDP + /// + public SystemName(byte[] bytes, int offset) : + base(bytes, offset) + {} + + /// + /// Creates a System Name TLV and sets it value + /// + /// + /// A textual Name of the system + /// + public SystemName(string name) : base(TLVTypes.SystemName, name) + { + } + + #endregion + + #region Properties + + /// + /// A textual Name of the system + /// + public string Name + { + get { return StringValue; } + set { StringValue = value; } + } + + #endregion + } +} \ No newline at end of file diff --git a/PacketDotNet/LLDP/TLV.cs b/PacketDotNet/LLDP/TLV.cs new file mode 100644 index 0000000..5ce7e31 --- /dev/null +++ b/PacketDotNet/LLDP/TLV.cs @@ -0,0 +1,151 @@ +using System; +using MiscUtil.Conversion; +using PacketDotNet.Utils; + +namespace PacketDotNet.LLDP +{ + /// + /// A Type-Length-Value object + /// + public class TLV + { +#if DEBUG + private static readonly log4net.ILog log = log4net.LogManager.GetLogger(System.Reflection.MethodBase.GetCurrentMethod().DeclaringType); +#else + // 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 + private static readonly ILogInactive log; +#pragma warning restore 0169 +#endif + + #region Constructors + + /// + /// Create a tlv + /// + public TLV() + { + } + + /// + /// Creates a TLV + /// + /// + /// Bytes that comprise the TLV + /// + /// + /// The TLVs offset from the start of byte[] bytes + /// + public TLV(byte[] bytes, int offset) + { + // setup a local ByteArrayAndOffset in order to retrieve the value length + // NOTE: we cannot set tlvData to retrieve the value length as + // setting tlvData results in the TypeLength.Length being updated with + // the length of the ByteArrayAndOffset which would overwrite the value + // we are trying to retrieve + var byteArraySegment = new ByteArraySegment(bytes, offset, TLVTypeLength.TypeLengthLength); + TypeLength = new TLVTypeLength(byteArraySegment); + + // set the tlvData assuming we have at least the bytes required for the + // type/length fields + tlvData = new ByteArraySegment(bytes, offset, TypeLength.Length + TLVTypeLength.TypeLengthLength); + + // retrieve the actual length + tlvData.Length = TypeLength.Length + TLVTypeLength.TypeLengthLength; + } + + #endregion + + #region Properties + + /// + /// Length of value portion of the TLV + /// NOTE: Does not include the length of the Type and Length fields + /// + public int Length + { + get { return TypeLength.Length; } + + // Length set property is internal because the tlv length is + // automatically set based on the length of the tlv value + internal set { TypeLength.Length = value; } + } + + /// + /// Total length of the TLV, including the length of the Type and Length fields + /// + public int TotalLength + { + get { return tlvData.Length; } + } + + /// + /// Tlv type + /// + public TLVTypes Type + { + get { return TypeLength.Type; } + + set + { + log.DebugFormat("value {0}", value); + TypeLength.Type = value; + } + } + + /// + /// Offset to the value bytes of the TLV + /// + internal int ValueOffset + { + get { return tlvData.Offset + TLVTypeLength.TypeLengthLength; } + } + + /// + /// Return a byte[] that contains the tlv + /// + public virtual byte[] Bytes + { + get + { + return tlvData.ActualBytes(); + } + } + + #endregion + + #region Members + + /// + /// Points to the TLV data + /// + private ByteArraySegment _tlvData; + + /// + /// Points to the TLV data + /// + internal ByteArraySegment tlvData + { + get { return _tlvData; } + + set + { + _tlvData = value; + + // create a new TypeLength that points at the new ByteArrayAndOffset + TypeLength = new TLVTypeLength(value); + + // update the length based upon the length of the ByteArrayAndOffset + TypeLength.Length = value.Length - TLVTypeLength.TypeLengthLength; + } + } + + /// + /// Interface to this TLVs type and length + /// + protected TLVTypeLength TypeLength; + + #endregion + } +} \ No newline at end of file diff --git a/PacketDotNet/LLDP/TLVCollection.cs b/PacketDotNet/LLDP/TLVCollection.cs new file mode 100644 index 0000000..8e51c1a --- /dev/null +++ b/PacketDotNet/LLDP/TLVCollection.cs @@ -0,0 +1,74 @@ +using System; +using System.Collections.ObjectModel; +using PacketDotNet.LLDP; + +namespace PacketDotNet +{ + /// + /// Custom collection for TLV types + /// + /// Special behavior includes: + /// - Preventing an EndOfLLDPDU tlv from being added out of place + /// - Checking and throwing exceptions if one-per-LLDP packet TLVs are added multiple times + /// + public class TLVCollection : Collection + { +#if DEBUG + private static readonly log4net.ILog log = log4net.LogManager.GetLogger(System.Reflection.MethodBase.GetCurrentMethod().DeclaringType); +#else + // 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 + private static readonly ILogInactive log; +#pragma warning restore 0169 +#endif + + /// + /// Override to: + /// - Prevent duplicate end tlvs from being added + /// - Ensure that an end tlv is present + /// - Replace any automatically added end tlvs with the user provided tlv + /// + /// + /// + /// A + /// + /// + /// A + /// + protected override void InsertItem (int index, TLV item) + { + log.DebugFormat("index {0}, TLV.GetType {1}, TLV.Type {2}", + index, item.GetType(), item.Type); + + // if this is the first item and it isn't an End TLV we should add the end tlv + if((Count == 0) && (item.Type != TLVTypes.EndOfLLDPU)) + { + log.Debug("Inserting EndOfLLDPDU"); + base.InsertItem(0, new EndOfLLDPDU()); + } else if(Count != 0) + { + // if the user is adding their own End tlv we should replace ours + // with theirs + if(item.Type == TLVTypes.EndOfLLDPU) + { + log.DebugFormat("Replacing {0} with user provided {1}, Type {2}", + this[Count - 1].GetType(), + item.GetType(), + item.Type); + SetItem(Count - 1, item); + return; + } + } + + // if we have no items insert the first item wherever + // if we have items insert the item befor the last item as the last item is a EndOfLLDPDU + int insertPosition = (Count == 0) ? 0 : Count - 1; + + log.DebugFormat("Inserting item at position {0}", insertPosition); + + base.InsertItem(insertPosition, item); + } + } +} + diff --git a/PacketDotNet/LLDP/TLVTypeLength.cs b/PacketDotNet/LLDP/TLVTypeLength.cs new file mode 100644 index 0000000..da1efbe --- /dev/null +++ b/PacketDotNet/LLDP/TLVTypeLength.cs @@ -0,0 +1,121 @@ +using System; +using MiscUtil.Conversion; +using PacketDotNet.Utils; + +namespace PacketDotNet.LLDP +{ + /// + /// Tlv type and length are 2 bytes + /// See http://en.wikipedia.org/wiki/Link_Layer_Discovery_Protocol#Frame_structure + /// + public class TLVTypeLength + { +#if DEBUG + private static readonly log4net.ILog log = log4net.LogManager.GetLogger(System.Reflection.MethodBase.GetCurrentMethod().DeclaringType); +#else + // 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 + private static readonly ILogInactive log; +#pragma warning restore 0169 +#endif + + /// + /// Length in bytes of the tlv type and length fields + /// + public const int TypeLengthLength = 2; + + private const int TypeBits = 7; + private const int TypeMask = 0xFE00; + private const int LengthBits = 9; + private const int LengthMask = 0x1FF; + + private const int MaximumTLVLength = 511; + + private ByteArraySegment byteArraySegment; + + /// + /// Construct a TLVTypeLength for a TLV + /// + /// + /// A + /// + public TLVTypeLength (ByteArraySegment byteArraySegment) + { + this.byteArraySegment = byteArraySegment; + } + + /// + /// The TLV Value's Type + /// + public TLVTypes Type + { + get + { + // get the type + ushort typeAndLength = TypeAndLength; + // remove the length info + return (TLVTypes)(typeAndLength >> LengthBits); + } + + set + { + log.DebugFormat("value of {0}", value); + + // shift type into the type position + var type = (ushort)((ushort)value << LengthBits); + // save the old length + ushort length = (ushort)(LengthMask & TypeAndLength); + // set the type + TypeAndLength = (ushort)(type | length); + } + } + + /// + /// The TLV Value's Length + /// NOTE: Value is the length of the TLV Value only, does not include the length + /// of the type and length fields + /// + public int Length + { + get + { + // get the length + ushort typeAndLength = TypeAndLength; + // remove the type info + return LengthMask & typeAndLength; + } + + // Length set is internal as the length of a tlv is automatically set based on + // the tlvs content + internal set + { + log.DebugFormat("value {0}", value); + + if(value < 0) { throw new System.ArgumentOutOfRangeException("Length", "Length must be a positive value"); } + if(value > MaximumTLVLength) { throw new ArgumentOutOfRangeException("Length", "The maximum value for a TLV length is 511"); } + + // save the old type + ushort type = (ushort)(TypeMask & TypeAndLength); + // set the length + TypeAndLength = (ushort)(type | value); + } + } + + /// + /// A unsigned short representing the concatenated Type and Length + /// + private ushort TypeAndLength + { + get + { + return EndianBitConverter.Big.ToUInt16(byteArraySegment.Bytes, byteArraySegment.Offset); + } + + set + { + EndianBitConverter.Big.CopyBytes(value, byteArraySegment.Bytes, byteArraySegment.Offset); + } + } + } +} diff --git a/PacketDotNet/LLDP/TLVTypes.cs b/PacketDotNet/LLDP/TLVTypes.cs new file mode 100644 index 0000000..9803dac --- /dev/null +++ b/PacketDotNet/LLDP/TLVTypes.cs @@ -0,0 +1,98 @@ +namespace PacketDotNet.LLDP +{ + /// + /// The TLV Types + /// + /// + /// See IETF RFC 802.1AB for more info + /// + public enum TLVTypes + { + /// Signifies the end of a LLDPU + /// + /// The End Of LLDPDU TLV is a 2-octet, all-zero + /// TLV that is used to mark the end of the TLV + /// sequence in LLDPDUs + /// + /// Source: IETF RFC 802.1AB + EndOfLLDPU = 0, + /// A Chassis Identifier + /// + /// A mandatory TLV that identifies the chassis + /// containing the IEEE 802 LAN station + /// associated with the transmitting LLDP agent + /// + /// Source: IETF RFC 802.1AB + ChassisID = 1, + /// A Port Identifier + /// + /// A mandatory TLV that identifies the + /// port component of the MSAP identifier associated + /// with the transmitting LLDP agent. + /// + /// Source: IETF RFC 802.1AB + PortID = 2, + /// Specifies the Time to Live + /// + /// Indicates the number of seconds that the + /// recipient LLDP agent is to regard the information + /// associated with this MSAP identifier to be valid + /// + /// A value of 0 signals that this source is no longer + /// available and all information associated with it + /// should be deleted. + /// + /// Source: IETF RFC 802.1AB + TimeToLive = 3, + /// A Description of the Port + /// + /// The port description field shall contain an + /// alpha-numeric string that indicates the port’s + /// description. + /// + /// Source: IETF RFC 802.1AB + PortDescription = 4, + /// The System's Assigned Name + /// + /// The System Name TLV allows network management + /// to advertise the system’s assigned name. + /// + /// Source: IETF RFC 802.1AB + SystemName = 5, + /// A Description of the System + /// + /// The System Description TLV allows network + /// management to advertise the system’s description + /// + /// Source: IETF RFC 802.1AB + SystemDescription = 6, + /// A bitmap containing the System's capabilities + /// + /// The System Capabilities TLV is an optional TLV + /// that identifies the primary function(s) of the + /// system and whether or not these primary functions + /// are enabled. + /// + /// Source: IETF RFC 802.1AB + SystemCapabilities = 7, + /// The Management Address + /// + /// The Management Address TLV identifies an address + /// associated with the local LLDP agent that may be + /// used to reach higher layer entities to assist + /// discovery by network management. + /// + /// Source: IETF RFC 802.1AB + ManagementAddress = 8, + /// A vendor-specifid TLV + /// + /// This TLV category is provided to allow different + /// organizations, such as IEEE 802.1, IEEE 802.3, IETF, + /// as well as individual software and equipment vendors, + /// to define TLVs that advertise information to remote + /// entities attached to the same media. + /// + /// Source: IETF RFC 802.1AB + OrganizationSpecific = 127 + }; +} \ No newline at end of file diff --git a/PacketDotNet/LLDP/TimeToLive.cs b/PacketDotNet/LLDP/TimeToLive.cs new file mode 100644 index 0000000..74232e9 --- /dev/null +++ b/PacketDotNet/LLDP/TimeToLive.cs @@ -0,0 +1,104 @@ +using System; +using MiscUtil.Conversion; +using PacketDotNet.Utils; + +namespace PacketDotNet.LLDP +{ + /// + /// A Time to Live TLV + /// + public class TimeToLive : TLV + { +#if DEBUG + private static readonly log4net.ILog log = log4net.LogManager.GetLogger(System.Reflection.MethodBase.GetCurrentMethod().DeclaringType); +#else + // 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 + private static readonly ILogInactive log; +#pragma warning restore 0169 +#endif + + /// + /// Number of bytes in the value portion of this tlv + /// + private const int ValueLength = 2; + + #region Constructors + + /// + /// Creates a TTL TLV + /// + /// + /// + /// + /// The TTL TLV's offset from the + /// origin of the LLDP + /// + public TimeToLive(byte[] bytes, int offset) : + base(bytes, offset) + { + log.Debug(""); + } + + /// + /// Creates a TTL TLV and sets it value + /// + /// + /// The length in seconds until the LLDP + /// is refreshed + /// + public TimeToLive(ushort seconds) + { + log.Debug(""); + + var bytes = new byte[TLVTypeLength.TypeLengthLength + ValueLength]; + int offset = 0; + int length = bytes.Length; + tlvData = new ByteArraySegment(bytes, offset, length); + + Type = TLVTypes.TimeToLive; + Seconds = seconds; + } + + #endregion + + #region Properties + + /// + /// The number of seconds until the LLDP needs + /// to be refreshed + /// + /// A value of 0 means that the LLDP source is + /// closed and should no longer be refreshed + /// + public ushort Seconds + { + get + { + // get the seconds + return BigEndianBitConverter.Big.ToUInt16(tlvData.Bytes, + tlvData.Offset + TLVTypeLength.TypeLengthLength); + } + set + { + EndianBitConverter.Big.CopyBytes(value, + tlvData.Bytes, + tlvData.Offset + TLVTypeLength.TypeLengthLength); + } + } + + /// + /// Convert this TTL TLV to a string. + /// + /// + /// A human readable string + /// + public override string ToString () + { + return string.Format("[TimeToLive: Seconds={0}]", Seconds); + } + + #endregion + } +} \ No newline at end of file diff --git a/PacketDotNet/LLDPPacket.cs b/PacketDotNet/LLDPPacket.cs new file mode 100644 index 0000000..7291ee3 --- /dev/null +++ b/PacketDotNet/LLDPPacket.cs @@ -0,0 +1,382 @@ + +using System; +using System.Collections; +using System.Collections.Generic; +using System.Net; +using System.Net.NetworkInformation; +using System.Text.RegularExpressions; +using MiscUtil.Conversion; +using PacketDotNet.Utils; +using PacketDotNet.LLDP; + +namespace PacketDotNet +{ + /// + /// A LLDP packet. + /// As specified in IEEE Std 802.1AB + /// + /// + /// See http://en.wikipedia.org/wiki/Link_Layer_Discovery_Protocol for general info + /// See IETF 802.1AB for the full specification + /// + public class LLDPPacket : InternetLinkLayerPacket, IEnumerable + { + + #region Preprocessor Directives + +#if DEBUG + private static readonly log4net.ILog log = log4net.LogManager.GetLogger(System.Reflection.MethodBase.GetCurrentMethod().DeclaringType); +#else + // 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 + private static readonly ILogInactive log; +#pragma warning restore 0169 +#endif + + #endregion + + #region Constructors + + /// + /// Create an empty LLDPPacket + /// + public LLDPPacket() : base(new PosixTimeval()) + { + log.Debug(""); + + // all lldp packets end with an EndOfLLDPDU tlv so add one + // by default + TlvCollection.Add(new EndOfLLDPDU()); + } + + /// + /// Creates a LLDP packet from a byte[] + /// + /// + /// A + /// + /// + /// A + /// + public LLDPPacket(byte[] bytes, int offset) : + this(bytes, offset, new PosixTimeval()) + { + log.Debug(""); + } + + /// + /// Creates a LLDP packet from a byte[] + /// + /// + /// A + /// + /// + /// A + /// + /// + /// A + /// + public LLDPPacket(byte[] bytes, int offset, PosixTimeval timeval) : + base(timeval) + { + log.Debug(""); + + header = new ByteArraySegment(bytes, offset, bytes.Length - offset); + + // Initiate the TLV list from the existing data + ParseByteArrayIntoTlvs(header.Bytes, header.Offset); + } + + #endregion + + #region Properties + + /// + /// The current length of the LLDPDU + /// + public int Length + { + get { return _Length; } + set { _Length = value; } + } + + /// + /// LLDPPacket specific implementation of BytesHighPerformance + /// Necessary because each TLV in the collection may have a + /// byte[] that is not shared by other TLVs + /// + /// NOTE: There is potential for the same performance improvement that + /// the Packet class uses where we check to see if each TLVs uses the + /// same byte[] and that there are no gaps. + /// + public override ByteArraySegment BytesHighPerformance + { + get + { + var ms = new System.IO.MemoryStream(); + foreach(var tlv in TlvCollection) + { + var tlvBytes = tlv.Bytes; + ms.Write(tlvBytes, 0, tlvBytes.Length); + } + + var offset = 0; + var msArray = ms.ToArray(); + return new ByteArraySegment(msArray, offset, msArray.Length); + } + } + + /// + /// Allows access of the TlvCollection by index + /// + /// The index of the item being set/retrieved in the collection + /// The requested TLV + public TLV this[int index] + { + get { return TlvCollection[index]; } + set { TlvCollection[index] = value; } + } + + /// + /// Enables foreach functionality for this class + /// + /// The next item in the list + public IEnumerator GetEnumerator() + { + return TlvCollection.GetEnumerator(); + } + + #endregion + + #region Methods + + /// + /// Parse byte[] into TLVs + /// + public void ParseByteArrayIntoTlvs(byte[] bytes, int offset) + { + log.DebugFormat("bytes.Length {0}, offset {1}", bytes.Length, offset); + + int position = 0; + + TlvCollection.Clear(); + + while(position < bytes.Length) + { + // The payload type + var byteArraySegment = new ByteArraySegment(bytes, offset + position, TLVTypeLength.TypeLengthLength); + var typeLength = new TLVTypeLength(byteArraySegment); + + // create a TLV based on the type and + // add it to the collection + TLV currentTlv = TLVFactory(bytes, offset + position, typeLength.Type); + if (currentTlv == null) + { + log.Debug("currentTlv == null"); + break; + } + + log.DebugFormat("Adding tlv {0}, Type {1}", + currentTlv.GetType(), currentTlv.Type); + TlvCollection.Add(currentTlv); + + // stop at the first end tlv we run into + if(currentTlv is EndOfLLDPDU) + { + break; + } + + // Increment the position to seek the next TLV + position += (currentTlv.TotalLength); + } + + log.DebugFormat("Done, position {0}", position); + } + + /// + /// + /// + /// + /// A + /// + /// + /// A + /// + /// + /// A + /// + /// + /// A + /// + private static TLV TLVFactory(byte[] Bytes, int offset, TLVTypes type) + { + switch(type) + { + case TLVTypes.ChassisID: + return new ChassisID(Bytes, offset); + case TLVTypes.PortID: + return new PortID(Bytes, offset); + case TLVTypes.TimeToLive: + return new TimeToLive(Bytes, offset); + case TLVTypes.PortDescription: + return new PortDescription(Bytes, offset); + case TLVTypes.SystemName: + return new SystemName(Bytes, offset); + case TLVTypes.SystemDescription: + return new SystemDescription(Bytes, offset); + case TLVTypes.SystemCapabilities: + return new SystemCapabilities(Bytes, offset); + case TLVTypes.ManagementAddress: + return new ManagementAddress(Bytes, offset); + case TLVTypes.OrganizationSpecific: + return new OrganizationSpecific(Bytes, offset); + case TLVTypes.EndOfLLDPU: + return new EndOfLLDPDU(Bytes, offset); + default: + throw new ArgumentOutOfRangeException(); + } + } + + /// + /// Returns the LLDP inside of the Packet p or null if + /// there is no encapsulated packet + /// + /// + /// A + /// + /// + /// A + /// + public static LLDPPacket GetType(Packet p) + { + log.Debug(""); + + if(p is InternetLinkLayerPacket) + { + var payload = InternetLinkLayerPacket.GetInnerPayload((InternetLinkLayerPacket)p); + if(payload is LLDPPacket) + { + return (LLDPPacket)payload; + } + } + + return null; + } + + /// + /// Create a randomized LLDP packet with some basic TLVs + /// + /// + /// A + /// + public static LLDPPacket RandomPacket() + { + var rnd = new Random(); + + var lldpPacket = new LLDPPacket(); + + byte[] physicalAddressBytes = new byte[EthernetFields.MacAddressLength]; + rnd.NextBytes(physicalAddressBytes); + var physicalAddress = new PhysicalAddress(physicalAddressBytes); + lldpPacket.TlvCollection.Add(new ChassisID(physicalAddress)); + + byte[] networkAddress = new byte[IPv4Fields.AddressLength]; + rnd.NextBytes(networkAddress); + lldpPacket.TlvCollection.Add(new PortID(new NetworkAddress(new IPAddress(networkAddress)))); + + ushort seconds = (ushort)rnd.Next(0,120); + lldpPacket.TlvCollection.Add(new TimeToLive(seconds)); + + lldpPacket.TlvCollection.Add(new EndOfLLDPDU()); + + return lldpPacket; + } + + /// + /// Convert this LLDP packet to a readable string. + /// + /// + /// A human readable string. + /// + public override string ToString () + { + return ToColoredString(false); + } + + /// + /// Convert this LLDP packet to a readable string. + /// + /// + /// Sets whether the output includes coloring. + /// + /// + /// A human readable string. + /// + public override string ToColoredString(bool colored) + { + System.Text.StringBuilder buffer = new System.Text.StringBuilder(); + buffer.Append('['); + if (colored) + buffer.Append(AnsiEscapeSequences.Blue); + buffer.Append("LLDPPacket"); + if (colored) + buffer.Append(AnsiEscapeSequences.Reset); + buffer.Append(":"); + + foreach(TLV tlv in TlvCollection) + { + // the regex trims the parent namespaces off of the class type + // ex. "PacketDotNet.LLDP.TimeToLive" returns "TimeToLive" + var r = new Regex(@"[^(\.)]([^\.]*)$"); + var m = r.Match(tlv.GetType().ToString()); + buffer.Append(" [" + m.Groups[0].Value + " length:" + tlv.Length + "]"); + } + buffer.Append(']'); + + return buffer.ToString(); + } + + /// + /// Convert this LLDP packet to a verbose readable string. + /// + /// + /// Sets whether the output includes coloring. + /// + /// + /// A verbose human readable string. + /// + public override string ToColoredVerboseString (bool colored) + { + System.Text.StringBuilder buffer = new System.Text.StringBuilder(); + buffer.Append('['); + if (colored) + buffer.Append(AnsiEscapeSequences.Blue); + buffer.Append("LLDPPacket"); + if (colored) + buffer.Append(AnsiEscapeSequences.Reset); + buffer.Append(":"); + + foreach(TLV tlv in TlvCollection) + { + buffer.Append(" " + tlv.ToString()); + } + buffer.Append(']'); + + return buffer.ToString(); + } + + #endregion + + #region Members + + /// + /// Contains the TLV's in the LLDPDU + /// + public TLVCollection TlvCollection = new TLVCollection(); + + int _Length; + + #endregion + } +} \ No newline at end of file diff --git a/PacketDotNet/Libraries/log4net.dll b/PacketDotNet/Libraries/log4net.dll new file mode 100644 index 0000000000000000000000000000000000000000..1bac5d9a06373a3722dd84407608bc1982d8a3f7 GIT binary patch literal 243712 zcmce<31A$>mH*%F9`%e`5+F;Su_cTI!J-jh%qyG8hT7f7)Ei( zwIM+6%UO~@?)!q=*@WD8oMbmA%C(!#xtsgg>@N9#zOSn1kYvcQ|KHfHu6kAV>eZ`P zuU?&f+Lq@8Q4jg@6>)(#9L`+C%8o)845E{lRk ze(L=v7rA{e*lq8=WpNO^70P(oZ=An+z9(vDojY`N12Fpa4#p`7aQzfB2==i5?YH+@ zSP-ln4}vq~2JcN`!lGX(=wAeTaKIkpK~U+4b~*lsb@ z1AOt?yq;eMz-Uw1#gDhUk`?i0VaVG2imNgrI$Avs-n}T^aYdTq{pU4%jFZ0gn;mz~ zE&eUv>9|#%rGFP4pXCp|>Z;Q&x$>Ie>iT6_e#I4QFTd)lpuptH9R@wY&!4?4xaycN zSmqJ^7{6G)=?hV?eQD(Xr_jB^l&q7

^e86wG)A)BWQh4ztZs5GV1BDqwgr&Uh!2 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%0DY!_)? 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 TIgrO`2ZOi_+b=)=?>O*Z3@A$+ literal 0 HcmV?d00001 diff --git a/PacketDotNet/LinkLayers.cs b/PacketDotNet/LinkLayers.cs new file mode 100644 index 0000000..a3ebb3d --- /dev/null +++ b/PacketDotNet/LinkLayers.cs @@ -0,0 +1,90 @@ +/* +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; + +namespace PacketDotNet +{ + ///

Link-layer type codes. + ///

+ /// Taken from libpcap/bpf/net/bpf.h and pcap/net/bpf.h. + ///

+ ///

+ /// The link-layer type is used to determine what data-structure the + /// IP protocol bits will be encapsulated inside of. + ///

+ ///

+ /// On a 10/100mbps network, packets are encapsulated inside of ethernet. + /// 14-byte ethernet headers which contain MAC addresses and an ethernet type + /// field. + ///

+ ///

+ /// On ethernet over ppp, the link-layer type is raw, and packets + /// are not encapsulated in any ethernet header. + ///

+ ///
+ public enum LinkLayers + { + /// no link-layer encapsulation + Null = 0, + /// Ethernet (10Mb) + Ethernet = 1, + /// Experimental Ethernet (3Mb) + ExperimentalEthernet3MB = 2, + /// Amateur Radio AX.25 + AmateurRadioAX25 = 3, + /// Proteon ProNET Token Ring + ProteonProNetTokenRing = 4, + /// Chaos + Chaos = 5, + /// IEEE 802 Networks + Ieee802 = 6, + /// ARCNET + ArcNet = 7, + /// Serial Line IP + Slip = 8, + /// Point-to-point Protocol + Ppp = 9, + /// FDDI + Fddi = 10, + /// LLC/SNAP encapsulated atm + AtmRfc1483 = 11, + /// raw IP + Raw = 12, + /// BSD Slip. + SlipBSD = 15, + /// BSD PPP. + PppBSD = 16, + /// IP over ATM. + AtmClip = 19, + /// PPP over HDLC. + PppSerial = 50, + /// Cisco HDLC. + CiscoHDLC = 104, + /// IEEE 802.11 wireless. + Ieee80211 = 105, + /// OpenBSD loopback. + Loop = 108, + /// Linux cooked sockets. + LinuxSLL = 113, + /// unknown link-layer type + Unknown = - 1, + } +} diff --git a/PacketDotNet/LinuxSLLFields.cs b/PacketDotNet/LinuxSLLFields.cs new file mode 100644 index 0000000..899ec96 --- /dev/null +++ b/PacketDotNet/LinuxSLLFields.cs @@ -0,0 +1,94 @@ +/* +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; + +namespace PacketDotNet +{ + /// + /// Lengths and offsets to the fields in the LinuxSLL packet + /// See http://github.com/mcr/libpcap/blob/master/pcap/sll.h + /// + public class LinuxSLLFields + { + /// + /// Length of the packet type field + /// + public readonly static int PacketTypeLength = 2; + + /// + /// Link layer address type + /// + public readonly static int LinkLayerAddressTypeLength = 2; + + /// + /// Link layer address length + /// + public readonly static int LinkLayerAddressLengthLength = 2; + + /// + /// The link layer address field length + /// NOTE: the actual link layer address MAY be shorter than this + /// + public readonly static int LinkLayerAddressMaximumLength = 8; + + /// + /// Number of bytes in a SLL header + /// + public readonly static int SLLHeaderLength = 16; + + /// + /// Length of the ethernet protocol field + /// + public readonly static int EthernetProtocolTypeLength = 2; + + /// + /// Position of the packet type field + /// + public readonly static int PacketTypePosition = 0; + + /// + /// Position of the link layer address type field + /// + public readonly static int LinkLayerAddressTypePosition; + + /// + /// Positino of the link layer address length field + /// + public readonly static int LinkLayerAddressLengthPosition; + + /// + /// Position of the link layer address field + /// + public readonly static int LinkLayerAddressPosition; + + /// + /// Position of the ethernet protocol type field + /// + public readonly static int EthernetProtocolTypePosition; + + static LinuxSLLFields() + { + LinkLayerAddressTypePosition = PacketTypePosition + PacketTypeLength; + LinkLayerAddressLengthPosition = LinkLayerAddressTypePosition + LinkLayerAddressTypeLength; + LinkLayerAddressPosition = LinkLayerAddressLengthPosition + LinkLayerAddressLengthLength; + EthernetProtocolTypePosition = LinkLayerAddressPosition + LinkLayerAddressMaximumLength; + } + } +} diff --git a/PacketDotNet/LinuxSLLPacket.cs b/PacketDotNet/LinuxSLLPacket.cs new file mode 100644 index 0000000..c942b9f --- /dev/null +++ b/PacketDotNet/LinuxSLLPacket.cs @@ -0,0 +1,227 @@ +/* +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 MiscUtil.Conversion; +using PacketDotNet.Utils; + +namespace PacketDotNet +{ + /// + /// Represents a Linux cooked capture packet, the kinds of packets + /// received when capturing on an 'any' device + /// + /// See http://github.com/mcr/libpcap/blob/master/pcap/sll.h + /// + public class LinuxSLLPacket : InternetLinkLayerPacket + { + /// + /// Information about the packet direction + /// + public LinuxSLLType Type + { + get + { + return (LinuxSLLType)EndianBitConverter.Big.ToInt16(header.Bytes, + header.Offset + LinuxSLLFields.PacketTypePosition); + } + + set + { + var theValue = (Int16)value; + EndianBitConverter.Big.CopyBytes(theValue, + header.Bytes, + header.Offset + LinuxSLLFields.PacketTypePosition); + } + } + + /// + /// The + /// + public int LinkLayerAddressType + { + get + { + return EndianBitConverter.Big.ToInt16(header.Bytes, + header.Offset + LinuxSLLFields.LinkLayerAddressTypePosition); + } + + set + { + var theValue = (Int16)value; + EndianBitConverter.Big.CopyBytes(theValue, + header.Bytes, + header.Offset + LinuxSLLFields.LinkLayerAddressTypePosition); + } + } + + /// + /// Number of bytes in the link layer address of the sender of the packet + /// + public int LinkLayerAddressLength + { + get + { + return EndianBitConverter.Big.ToInt16(header.Bytes, + header.Offset + LinuxSLLFields.LinkLayerAddressLengthPosition); + } + + set + { + // range check + if((value < 0) || (value > 8)) + { + throw new System.InvalidOperationException("value of " + value + " out of range of 0 to 8"); + } + + var theValue = (Int16)value; + EndianBitConverter.Big.CopyBytes(theValue, + header.Bytes, + header.Offset + LinuxSLLFields.LinkLayerAddressLengthPosition); + } + } + + /// + /// Link layer header bytes, maximum of 8 bytes + /// + public byte[] LinkLayerAddress + { + get + { + var headerLength = LinkLayerAddressLength; + var theHeader = new Byte[headerLength]; + Array.Copy(header.Bytes, header.Offset + LinuxSLLFields.LinkLayerAddressPosition, + theHeader, 0, + headerLength); + return theHeader; + } + + set + { + // update the link layer length + LinkLayerAddressLength = value.Length; + + // copy in the new link layer header bytes + Array.Copy(value, 0, + header.Bytes, header.Offset + LinuxSLLFields.LinkLayerAddressPosition, + value.Length); + } + } + + /// + /// The encapsulated protocol type + /// + public EthernetPacketType EthernetProtocolType + { + get + { + return (EthernetPacketType)EndianBitConverter.Big.ToInt16(header.Bytes, + header.Offset + LinuxSLLFields.EthernetProtocolTypePosition); + } + + set + { + var theValue = (Int16)value; + EndianBitConverter.Big.CopyBytes(theValue, + header.Bytes, + header.Offset + LinuxSLLFields.EthernetProtocolTypePosition); + } + } + + /// + /// Create an LinuxSLLPacket from a byte array + /// + /// + /// A + /// + /// + /// A + /// + public LinuxSLLPacket(byte[] bytes, int offset) : + this(bytes, offset, new PosixTimeval()) + { } + + /// + /// Create an LinuxSLLPacket from a byte array and a Timeval + /// + /// + /// A + /// + /// + /// A + /// + /// + /// A + /// + public LinuxSLLPacket(byte[] Bytes, int Offset, PosixTimeval Timeval) : + base(Timeval) + { + header = new ByteArraySegment(Bytes, Offset, LinuxSLLFields.SLLHeaderLength); + + // parse the payload via an EthernetPacket method + payloadPacketOrData = EthernetPacket.ParseEncapsulatedBytes(header, + EthernetProtocolType, + Timeval); + } + + /// + /// ToString implementation + /// + /// + /// A + /// + public override string ToString () + { + return ToColoredString(false); + } + + /// + /// Colored string that represents the values in this class instance + /// + /// + /// A + /// + /// + /// A + /// + public override string ToColoredString (bool colored) + { + var sb = new System.Text.StringBuilder(); + + sb.AppendFormat("[LinuxSLLPacket: Type={0}, LinkLayerAddressType={1}, LinkLayerAddressLength={2}, LinkLayerHeader={3}, EthernetProtocolType={4}]", + Type, + LinkLayerAddressType, + LinkLayerAddressLength, + LinkLayerAddress, + EthernetProtocolType); + + // append the base output + sb.Append(base.ToColoredString(colored)); + + return sb.ToString(); + } + + /// Convert a more verbose string. + public override System.String ToColoredVerboseString(bool colored) + { + //TODO: just output the colored output for now + return ToColoredString(colored); + } + } +} diff --git a/PacketDotNet/LinuxSLLType.cs b/PacketDotNet/LinuxSLLType.cs new file mode 100644 index 0000000..5119753 --- /dev/null +++ b/PacketDotNet/LinuxSLLType.cs @@ -0,0 +1,56 @@ +/* +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; + +namespace PacketDotNet +{ + /// + /// The types of cooked packets + /// See http://github.com/mcr/libpcap/blob/master/pcap/sll.h + /// + public enum LinuxSLLType + { + /// + /// Packet was sent to us by somebody else + /// + PacketSentToUs = 0x0, + + /// + /// Packet was broadcast by somebody else + /// + PacketBroadCast = 0x1, + + /// + /// Packet was multicast, but not broadcast + /// + PacketMulticast = 0x2, + + /// + /// Packet was sent by somebody else to somebody else + /// + PacketSentToSomeoneElse = 0x3, + + /// + /// Packet was sent by us + /// + PacketSentByUs = 0x4 + } +} diff --git a/PacketDotNet/MiscUtil/Conversion/BigEndianBitConverter.cs b/PacketDotNet/MiscUtil/Conversion/BigEndianBitConverter.cs new file mode 100644 index 0000000..7c20811 --- /dev/null +++ b/PacketDotNet/MiscUtil/Conversion/BigEndianBitConverter.cs @@ -0,0 +1,67 @@ + +namespace MiscUtil.Conversion +{ + /// + /// Implementation of EndianBitConverter which converts to/from big-endian + /// byte arrays. + /// + public sealed class BigEndianBitConverter : EndianBitConverter + { + /// + /// Indicates the byte order ("endianess") in which data is converted using this class. + /// + /// + /// Different computer architectures store data using different byte orders. "Big-endian" + /// means the most significant byte is on the left end of a word. "Little-endian" means the + /// most significant byte is on the right end of a word. + /// + /// true if this converter is little-endian, false otherwise. + public sealed override bool IsLittleEndian() + { + return false; + } + + /// + /// Indicates the byte order ("endianess") in which data is converted using this class. + /// + public sealed override Endianness Endianness + { + get { return Endianness.BigEndian; } + } + + /// + /// Copies the specified number of bytes from value to buffer, starting at index. + /// + /// The value to copy + /// The number of bytes to copy + /// The buffer to copy the bytes into + /// The index to start at + protected override void CopyBytesImpl(long value, int bytes, byte[] buffer, int index) + { + int endOffset = index+bytes-1; + for (int i=0; i < bytes; i++) + { + buffer[endOffset-i] = unchecked((byte)(value&0xff)); + value = value >> 8; + } + } + + /// + /// Returns a value built from the specified number of bytes from the given buffer, + /// starting at index. + /// + /// The data in byte array format + /// The first index to use + /// The number of bytes to use + /// The value built from the given bytes + protected override long FromBytes(byte[] buffer, int startIndex, int bytesToConvert) + { + long ret = 0; + for (int i=0; i < bytesToConvert; i++) + { + ret = unchecked((ret << 8) | buffer[startIndex+i]); + } + return ret; + } + } +} diff --git a/PacketDotNet/MiscUtil/Conversion/DoubleConverter.cs b/PacketDotNet/MiscUtil/Conversion/DoubleConverter.cs new file mode 100644 index 0000000..f6bf3d1 --- /dev/null +++ b/PacketDotNet/MiscUtil/Conversion/DoubleConverter.cs @@ -0,0 +1,219 @@ +using System; +using System.Globalization; + +namespace MiscUtil.Conversion +{ + /// + /// A class to allow the conversion of doubles to string representations of + /// their exact decimal values. The implementation aims for readability over + /// efficiency. + /// + public class DoubleConverter + { + /// + /// Converts the given double to a string representation of its + /// exact decimal value. + /// + /// The double to convert. + /// A string representation of the double's exact decimal value. + public static string ToExactString (double d) + { + if (double.IsPositiveInfinity(d)) + return "+Infinity"; + if (double.IsNegativeInfinity(d)) + return "-Infinity"; + if (double.IsNaN(d)) + return "NaN"; + + // Translate the double into sign, exponent and mantissa. + long bits = BitConverter.DoubleToInt64Bits(d); + bool negative = (bits < 0); + int exponent = (int) ((bits >> 52) & 0x7ffL); + long mantissa = bits & 0xfffffffffffffL; + + // Subnormal numbers; exponent is effectively one higher, + // but there's no extra normalisation bit in the mantissa + if (exponent==0) + { + exponent++; + } + // Normal numbers; leave exponent as it is but add extra + // bit to the front of the mantissa + else + { + mantissa = mantissa | (1L<<52); + } + + // Bias the exponent. It's actually biased by 1023, but we're + // treating the mantissa as m.0 rather than 0.m, so we need + // to subtract another 52 from it. + exponent -= 1075; + + if (mantissa == 0) + { + return "0"; + } + + /* Normalize */ + while((mantissa & 1) == 0) + { /* i.e., Mantissa is even */ + mantissa >>= 1; + exponent++; + } + + // Construct a new decimal expansion with the mantissa + ArbitraryDecimal ad = new ArbitraryDecimal (mantissa); + + // If the exponent is less than 0, we need to repeatedly + // divide by 2 - which is the equivalent of multiplying + // by 5 and dividing by 10. + if (exponent < 0) + { + for (int i=0; i < -exponent; i++) + ad.MultiplyBy(5); + ad.Shift(-exponent); + } + // Otherwise, we need to repeatedly multiply by 2 + else + { + for (int i=0; i < exponent; i++) + ad.MultiplyBy(2); + } + + // Finally, return the string with an appropriate sign + if (negative) + return "-"+ad.ToString(); + else + return ad.ToString(); + } + + /// + /// Private class used for manipulating sequences of decimal digits. + /// + class ArbitraryDecimal + { + /// Digits in the decimal expansion, one byte per digit + byte[] digits; + /// + /// How many digits are *after* the decimal point + /// + int decimalPoint=0; + + /// + /// Constructs an arbitrary decimal expansion from the given long. + /// The long must not be negative. + /// + internal ArbitraryDecimal (long x) + { + string tmp = x.ToString(CultureInfo.InvariantCulture); + digits = new byte[tmp.Length]; + for (int i=0; i < tmp.Length; i++) + digits[i] = (byte) (tmp[i]-'0'); + Normalize(); + } + + /// + /// Multiplies the current expansion by the given amount, which should + /// only be 2 or 5. + /// + internal void MultiplyBy(int amount) + { + byte[] result = new byte[digits.Length+1]; + for (int i=digits.Length-1; i >= 0; i--) + { + int resultDigit = digits[i]*amount+result[i+1]; + result[i]=(byte)(resultDigit/10); + result[i+1]=(byte)(resultDigit%10); + } + if (result[0] != 0) + { + digits=result; + } + else + { + Array.Copy (result, 1, digits, 0, digits.Length); + } + Normalize(); + } + + /// + /// Shifts the decimal point; a negative value makes + /// the decimal expansion bigger (as fewer digits come after the + /// decimal place) and a positive value makes the decimal + /// expansion smaller. + /// + internal void Shift (int amount) + { + decimalPoint += amount; + } + + /// + /// Removes leading/trailing zeroes from the expansion. + /// + internal void Normalize() + { + int first; + for (first=0; first < digits.Length; first++) + if (digits[first]!=0) + break; + int last; + for (last=digits.Length-1; last >= 0; last--) + if (digits[last]!=0) + break; + + if (first==0 && last==digits.Length-1) + return; + + byte[] tmp = new byte[last-first+1]; + for (int i=0; i < tmp.Length; i++) + tmp[i]=digits[i+first]; + + decimalPoint -= digits.Length-(last+1); + digits=tmp; + } + + /// + /// Converts the value to a proper decimal string representation. + /// + public override String ToString() + { + char[] digitString = new char[digits.Length]; + for (int i=0; i < digits.Length; i++) + digitString[i] = (char)(digits[i]+'0'); + + // Simplest case - nothing after the decimal point, + // and last real digit is non-zero, eg value=35 + if (decimalPoint==0) + { + return new string (digitString); + } + + // Fairly simple case - nothing after the decimal + // point, but some 0s to add, eg value=350 + if (decimalPoint < 0) + { + return new string (digitString)+ + new string ('0', -decimalPoint); + } + + // Nothing before the decimal point, eg 0.035 + if (decimalPoint >= digitString.Length) + { + return "0."+ + new string ('0',(decimalPoint-digitString.Length))+ + new string (digitString); + } + + // Most complicated case - part of the string comes + // before the decimal point, part comes after it, + // eg 3.5 + return new string (digitString, 0, + digitString.Length-decimalPoint)+ + "."+ + new string (digitString, + digitString.Length-decimalPoint, + decimalPoint); + } + } + } +} \ No newline at end of file diff --git a/PacketDotNet/MiscUtil/Conversion/EndianBitConverter.cs b/PacketDotNet/MiscUtil/Conversion/EndianBitConverter.cs new file mode 100644 index 0000000..b783167 --- /dev/null +++ b/PacketDotNet/MiscUtil/Conversion/EndianBitConverter.cs @@ -0,0 +1,696 @@ +using System; +using System.Runtime.InteropServices; + +namespace MiscUtil.Conversion +{ + /// + /// Equivalent of System.BitConverter, but with either endianness. + /// + public abstract class EndianBitConverter + { + #region Endianness of this converter + /// + /// Indicates the byte order ("endianess") in which data is converted using this class. + /// + /// + /// Different computer architectures store data using different byte orders. "Big-endian" + /// means the most significant byte is on the left end of a word. "Little-endian" means the + /// most significant byte is on the right end of a word. + /// + /// true if this converter is little-endian, false otherwise. + public abstract bool IsLittleEndian(); + + /// + /// Indicates the byte order ("endianess") in which data is converted using this class. + /// + public abstract Endianness Endianness { get; } + #endregion + + #region Factory properties + static LittleEndianBitConverter little = new LittleEndianBitConverter(); + /// + /// Returns a little-endian bit converter instance. The same instance is + /// always returned. + /// + public static LittleEndianBitConverter Little + { + get { return little; } + } + + static BigEndianBitConverter big = new BigEndianBitConverter(); + /// + /// Returns a big-endian bit converter instance. The same instance is + /// always returned. + /// + public static BigEndianBitConverter Big + { + get { return big; } + } + #endregion + + #region Double/primitive conversions + /// + /// Converts the specified double-precision floating point number to a + /// 64-bit signed integer. Note: the endianness of this converter does not + /// affect the returned value. + /// + /// The number to convert. + /// A 64-bit signed integer whose value is equivalent to value. + public long DoubleToInt64Bits(double value) + { + return BitConverter.DoubleToInt64Bits(value); + } + + /// + /// Converts the specified 64-bit signed integer to a double-precision + /// floating point number. Note: the endianness of this converter does not + /// affect the returned value. + /// + /// The number to convert. + /// A double-precision floating point number whose value is equivalent to value. + public double Int64BitsToDouble (long value) + { + return BitConverter.Int64BitsToDouble(value); + } + + /// + /// Converts the specified single-precision floating point number to a + /// 32-bit signed integer. Note: the endianness of this converter does not + /// affect the returned value. + /// + /// The number to convert. + /// A 32-bit signed integer whose value is equivalent to value. + public int SingleToInt32Bits(float value) + { + return new Int32SingleUnion(value).AsInt32; + } + + /// + /// Converts the specified 32-bit signed integer to a single-precision floating point + /// number. Note: the endianness of this converter does not + /// affect the returned value. + /// + /// The number to convert. + /// A single-precision floating point number whose value is equivalent to value. + public float Int32BitsToSingle (int value) + { + return new Int32SingleUnion(value).AsSingle; + } + #endregion + + #region To(PrimitiveType) conversions + /// + /// Returns a Boolean value converted from one byte at a specified position in a byte array. + /// + /// An array of bytes. + /// The starting position within value. + /// true if the byte at startIndex in value is nonzero; otherwise, false. + public bool ToBoolean (byte[] value, int startIndex) + { + CheckByteArgument(value, startIndex, 1); + return BitConverter.ToBoolean(value, startIndex); + } + + /// + /// Returns a Unicode character converted from two bytes at a specified position in a byte array. + /// + /// An array of bytes. + /// The starting position within value. + /// A character formed by two bytes beginning at startIndex. + public char ToChar (byte[] value, int startIndex) + { + return unchecked((char) (CheckedFromBytes(value, startIndex, 2))); + } + + /// + /// Returns a double-precision floating point number converted from eight bytes + /// at a specified position in a byte array. + /// + /// An array of bytes. + /// The starting position within value. + /// A double precision floating point number formed by eight bytes beginning at startIndex. + public double ToDouble (byte[] value, int startIndex) + { + return Int64BitsToDouble(ToInt64(value, startIndex)); + } + + /// + /// Returns a single-precision floating point number converted from four bytes + /// at a specified position in a byte array. + /// + /// An array of bytes. + /// The starting position within value. + /// A single precision floating point number formed by four bytes beginning at startIndex. + public float ToSingle (byte[] value, int startIndex) + { + return Int32BitsToSingle(ToInt32(value, startIndex)); + } + + /// + /// Returns a 16-bit signed integer converted from two bytes at a specified position in a byte array. + /// + /// An array of bytes. + /// The starting position within value. + /// A 16-bit signed integer formed by two bytes beginning at startIndex. + public short ToInt16 (byte[] value, int startIndex) + { + return unchecked((short) (CheckedFromBytes(value, startIndex, 2))); + } + + /// + /// Returns a 32-bit signed integer converted from four bytes at a specified position in a byte array. + /// + /// An array of bytes. + /// The starting position within value. + /// A 32-bit signed integer formed by four bytes beginning at startIndex. + public int ToInt32 (byte[] value, int startIndex) + { + return unchecked((int) (CheckedFromBytes(value, startIndex, 4))); + } + + /// + /// Returns a 64-bit signed integer converted from eight bytes at a specified position in a byte array. + /// + /// An array of bytes. + /// The starting position within value. + /// A 64-bit signed integer formed by eight bytes beginning at startIndex. + public long ToInt64 (byte[] value, int startIndex) + { + return CheckedFromBytes(value, startIndex, 8); + } + + /// + /// Returns a 16-bit unsigned integer converted from two bytes at a specified position in a byte array. + /// + /// An array of bytes. + /// The starting position within value. + /// A 16-bit unsigned integer formed by two bytes beginning at startIndex. + public ushort ToUInt16 (byte[] value, int startIndex) + { + return unchecked((ushort) (CheckedFromBytes(value, startIndex, 2))); + } + + /// + /// Returns a 32-bit unsigned integer converted from four bytes at a specified position in a byte array. + /// + /// An array of bytes. + /// The starting position within value. + /// A 32-bit unsigned integer formed by four bytes beginning at startIndex. + public uint ToUInt32 (byte[] value, int startIndex) + { + return unchecked((uint) (CheckedFromBytes(value, startIndex, 4))); + } + + /// + /// Returns a 64-bit unsigned integer converted from eight bytes at a specified position in a byte array. + /// + /// An array of bytes. + /// The starting position within value. + /// A 64-bit unsigned integer formed by eight bytes beginning at startIndex. + public ulong ToUInt64 (byte[] value, int startIndex) + { + return unchecked((ulong) (CheckedFromBytes(value, startIndex, 8))); + } + + /// + /// Checks the given argument for validity. + /// + /// The byte array passed in + /// The start index passed in + /// The number of bytes required + /// value is a null reference + /// + /// startIndex is less than zero or greater than the length of value minus bytesRequired. + /// + static void CheckByteArgument(byte[] value, int startIndex, int bytesRequired) + { + if (value==null) + { + throw new ArgumentNullException("value"); + } + if (startIndex < 0 || startIndex > value.Length-bytesRequired) + { + throw new ArgumentOutOfRangeException("startIndex"); + } + } + + /// + /// Checks the arguments for validity before calling FromBytes + /// (which can therefore assume the arguments are valid). + /// + /// The bytes to convert after checking + /// The index of the first byte to convert + /// The number of bytes to convert + /// + long CheckedFromBytes(byte[] value, int startIndex, int bytesToConvert) + { + CheckByteArgument(value, startIndex, bytesToConvert); + return FromBytes(value, startIndex, bytesToConvert); + } + + /// + /// Convert the given number of bytes from the given array, from the given start + /// position, into a long, using the bytes as the least significant part of the long. + /// By the time this is called, the arguments have been checked for validity. + /// + /// The bytes to convert + /// The index of the first byte to convert + /// The number of bytes to use in the conversion + /// The converted number + protected abstract long FromBytes(byte[] value, int startIndex, int bytesToConvert); + #endregion + + #region ToString conversions + /// + /// Returns a String converted from the elements of a byte array. + /// + /// An array of bytes. + /// All the elements of value are converted. + /// + /// A String of hexadecimal pairs separated by hyphens, where each pair + /// represents the corresponding element in value; for example, "7F-2C-4A". + /// + public static string ToString(byte[] value) + { + return BitConverter.ToString(value); + } + + /// + /// Returns a String converted from the elements of a byte array starting at a specified array position. + /// + /// An array of bytes. + /// The starting position within value. + /// The elements from array position startIndex to the end of the array are converted. + /// + /// A String of hexadecimal pairs separated by hyphens, where each pair + /// represents the corresponding element in value; for example, "7F-2C-4A". + /// + public static string ToString(byte[] value, int startIndex) + { + return BitConverter.ToString(value, startIndex); + } + + /// + /// Returns a String converted from a specified number of bytes at a specified position in a byte array. + /// + /// An array of bytes. + /// The starting position within value. + /// The number of bytes to convert. + /// The length elements from array position startIndex are converted. + /// + /// A String of hexadecimal pairs separated by hyphens, where each pair + /// represents the corresponding element in value; for example, "7F-2C-4A". + /// + public static string ToString(byte[] value, int startIndex, int length) + { + return BitConverter.ToString(value, startIndex, length); + } + #endregion + + #region Decimal conversions + /// + /// Returns a decimal value converted from sixteen bytes + /// at a specified position in a byte array. + /// + /// An array of bytes. + /// The starting position within value. + /// A decimal formed by sixteen bytes beginning at startIndex. + public decimal ToDecimal (byte[] value, int startIndex) + { + // HACK: This always assumes four parts, each in their own endianness, + // starting with the first part at the start of the byte array. + // On the other hand, there's no real format specified... + int[] parts = new int[4]; + for (int i=0; i < 4; i++) + { + parts[i] = ToInt32(value, startIndex+i*4); + } + return new Decimal(parts); + } + + /// + /// Returns the specified decimal value as an array of bytes. + /// + /// The number to convert. + /// An array of bytes with length 16. + public byte[] GetBytes(decimal value) + { + byte[] bytes = new byte[16]; + int[] parts = decimal.GetBits(value); + for (int i=0; i < 4; i++) + { + CopyBytesImpl(parts[i], 4, bytes, i*4); + } + return bytes; + } + + /// + /// Copies the specified decimal value into the specified byte array, + /// beginning at the specified index. + /// + /// A character to convert. + /// The byte array to copy the bytes into + /// The first index into the array to copy the bytes into + public void CopyBytes(decimal value, byte[] buffer, int index) + { + int[] parts = decimal.GetBits(value); + for (int i=0; i < 4; i++) + { + CopyBytesImpl(parts[i], 4, buffer, i*4+index); + } + } + #endregion + + #region GetBytes conversions + /// + /// Returns an array with the given number of bytes formed + /// from the least significant bytes of the specified value. + /// This is used to implement the other GetBytes methods. + /// + /// The value to get bytes for + /// The number of significant bytes to return + byte[] GetBytes(long value, int bytes) + { + byte[] buffer = new byte[bytes]; + CopyBytes(value, bytes, buffer, 0); + return buffer; + } + + /// + /// Returns the specified Boolean value as an array of bytes. + /// + /// A Boolean value. + /// An array of bytes with length 1. + public byte[] GetBytes(bool value) + { + return BitConverter.GetBytes(value); + } + + /// + /// Returns the specified Unicode character value as an array of bytes. + /// + /// A character to convert. + /// An array of bytes with length 2. + public byte[] GetBytes(char value) + { + return GetBytes(value, 2); + } + + /// + /// Returns the specified double-precision floating point value as an array of bytes. + /// + /// The number to convert. + /// An array of bytes with length 8. + public byte[] GetBytes(double value) + { + return GetBytes(DoubleToInt64Bits(value), 8); + } + + /// + /// Returns the specified 16-bit signed integer value as an array of bytes. + /// + /// The number to convert. + /// An array of bytes with length 2. + public byte[] GetBytes(short value) + { + return GetBytes(value, 2); + } + + /// + /// Returns the specified 32-bit signed integer value as an array of bytes. + /// + /// The number to convert. + /// An array of bytes with length 4. + public byte[] GetBytes(int value) + { + return GetBytes(value, 4); + } + + /// + /// Returns the specified 64-bit signed integer value as an array of bytes. + /// + /// The number to convert. + /// An array of bytes with length 8. + public byte[] GetBytes(long value) + { + return GetBytes(value, 8); + } + + /// + /// Returns the specified single-precision floating point value as an array of bytes. + /// + /// The number to convert. + /// An array of bytes with length 4. + public byte[] GetBytes(float value) + { + return GetBytes(SingleToInt32Bits(value), 4); + } + + /// + /// Returns the specified 16-bit unsigned integer value as an array of bytes. + /// + /// The number to convert. + /// An array of bytes with length 2. + public byte[] GetBytes(ushort value) + { + return GetBytes(value, 2); + } + + /// + /// Returns the specified 32-bit unsigned integer value as an array of bytes. + /// + /// The number to convert. + /// An array of bytes with length 4. + public byte[] GetBytes(uint value) + { + return GetBytes(value, 4); + } + + /// + /// Returns the specified 64-bit unsigned integer value as an array of bytes. + /// + /// The number to convert. + /// An array of bytes with length 8. + public byte[] GetBytes(ulong value) + { + return GetBytes(unchecked((long)value), 8); + } + + #endregion + + #region CopyBytes conversions + /// + /// Copies the given number of bytes from the least-specific + /// end of the specified value into the specified byte array, beginning + /// at the specified index. + /// This is used to implement the other CopyBytes methods. + /// + /// The value to copy bytes for + /// The number of significant bytes to copy + /// The byte array to copy the bytes into + /// The first index into the array to copy the bytes into + void CopyBytes(long value, int bytes, byte[] buffer, int index) + { + if (buffer==null) + { + throw new ArgumentNullException("buffer", "Byte array must not be null"); + } + if (buffer.Length < index+bytes) + { + throw new ArgumentOutOfRangeException("Buffer not big enough for value"); + } + CopyBytesImpl(value, bytes, buffer, index); + } + + /// + /// Copies the given number of bytes from the least-specific + /// end of the specified value into the specified byte array, beginning + /// at the specified index. + /// This must be implemented in concrete derived classes, but the implementation + /// may assume that the value will fit into the buffer. + /// + /// The value to copy bytes for + /// The number of significant bytes to copy + /// The byte array to copy the bytes into + /// The first index into the array to copy the bytes into + protected abstract void CopyBytesImpl(long value, int bytes, byte[] buffer, int index); + + /// + /// Copies the specified Boolean value into the specified byte array, + /// beginning at the specified index. + /// + /// A Boolean value. + /// The byte array to copy the bytes into + /// The first index into the array to copy the bytes into + public void CopyBytes(bool value, byte[] buffer, int index) + { + CopyBytes(value ? 1 : 0, 1, buffer, index); + } + + /// + /// Copies the specified Unicode character value into the specified byte array, + /// beginning at the specified index. + /// + /// A character to convert. + /// The byte array to copy the bytes into + /// The first index into the array to copy the bytes into + public void CopyBytes(char value, byte[] buffer, int index) + { + CopyBytes(value, 2, buffer, index); + } + + /// + /// Copies the specified double-precision floating point value into the specified byte array, + /// beginning at the specified index. + /// + /// The number to convert. + /// The byte array to copy the bytes into + /// The first index into the array to copy the bytes into + public void CopyBytes(double value, byte[] buffer, int index) + { + CopyBytes(DoubleToInt64Bits(value), 8, buffer, index); + } + + /// + /// Copies the specified 16-bit signed integer value into the specified byte array, + /// beginning at the specified index. + /// + /// The number to convert. + /// The byte array to copy the bytes into + /// The first index into the array to copy the bytes into + public void CopyBytes(short value, byte[] buffer, int index) + { + CopyBytes(value, 2, buffer, index); + } + + /// + /// Copies the specified 32-bit signed integer value into the specified byte array, + /// beginning at the specified index. + /// + /// The number to convert. + /// The byte array to copy the bytes into + /// The first index into the array to copy the bytes into + public void CopyBytes(int value, byte[] buffer, int index) + { + CopyBytes(value, 4, buffer, index); + } + + /// + /// Copies the specified 64-bit signed integer value into the specified byte array, + /// beginning at the specified index. + /// + /// The number to convert. + /// The byte array to copy the bytes into + /// The first index into the array to copy the bytes into + public void CopyBytes(long value, byte[] buffer, int index) + { + CopyBytes(value, 8, buffer, index); + } + + /// + /// Copies the specified single-precision floating point value into the specified byte array, + /// beginning at the specified index. + /// + /// The number to convert. + /// The byte array to copy the bytes into + /// The first index into the array to copy the bytes into + public void CopyBytes(float value, byte[] buffer, int index) + { + CopyBytes(SingleToInt32Bits(value), 4, buffer, index); + } + + /// + /// Copies the specified 16-bit unsigned integer value into the specified byte array, + /// beginning at the specified index. + /// + /// The number to convert. + /// The byte array to copy the bytes into + /// The first index into the array to copy the bytes into + public void CopyBytes(ushort value, byte[] buffer, int index) + { + CopyBytes(value, 2, buffer, index); + } + + /// + /// Copies the specified 32-bit unsigned integer value into the specified byte array, + /// beginning at the specified index. + /// + /// The number to convert. + /// The byte array to copy the bytes into + /// The first index into the array to copy the bytes into + public void CopyBytes(uint value, byte[] buffer, int index) + { + CopyBytes(value, 4, buffer, index); + } + + /// + /// Copies the specified 64-bit unsigned integer value into the specified byte array, + /// beginning at the specified index. + /// + /// The number to convert. + /// The byte array to copy the bytes into + /// The first index into the array to copy the bytes into + public void CopyBytes(ulong value, byte[] buffer, int index) + { + CopyBytes(unchecked((long)value), 8, buffer, index); + } + + #endregion + + #region Private struct used for Single/Int32 conversions + /// + /// Union used solely for the equivalent of DoubleToInt64Bits and vice versa. + /// + [StructLayout(LayoutKind.Explicit)] + struct Int32SingleUnion + { + /// + /// Int32 version of the value. + /// + [FieldOffset(0)] + int i; + /// + /// Single version of the value. + /// + [FieldOffset(0)] + float f; + + /// + /// Creates an instance representing the given integer. + /// + /// The integer value of the new instance. + internal Int32SingleUnion(int i) + { + this.f = 0; // Just to keep the compiler happy + this.i = i; + } + + /// + /// Creates an instance representing the given floating point number. + /// + /// The floating point value of the new instance. + internal Int32SingleUnion(float f) + { + this.i = 0; // Just to keep the compiler happy + this.f = f; + } + + /// + /// Returns the value of the instance as an integer. + /// + internal int AsInt32 + { + get { return i; } + } + + /// + /// Returns the value of the instance as a floating point number. + /// + internal float AsSingle + { + get { return f; } + } + } + #endregion + } +} diff --git a/PacketDotNet/MiscUtil/Conversion/Endianness.cs b/PacketDotNet/MiscUtil/Conversion/Endianness.cs new file mode 100644 index 0000000..ab7b99f --- /dev/null +++ b/PacketDotNet/MiscUtil/Conversion/Endianness.cs @@ -0,0 +1,18 @@ + +namespace MiscUtil.Conversion +{ + /// + /// Endianness of a converter + /// + public enum Endianness + { + /// + /// Little endian - least significant byte first + /// + LittleEndian, + /// + /// Big endian - most significant byte first + /// + BigEndian + } +} diff --git a/PacketDotNet/MiscUtil/Conversion/LittleEndianBitConverter.cs b/PacketDotNet/MiscUtil/Conversion/LittleEndianBitConverter.cs new file mode 100644 index 0000000..43bc204 --- /dev/null +++ b/PacketDotNet/MiscUtil/Conversion/LittleEndianBitConverter.cs @@ -0,0 +1,66 @@ + +namespace MiscUtil.Conversion +{ + /// + /// Implementation of EndianBitConverter which converts to/from little-endian + /// byte arrays. + /// + public sealed class LittleEndianBitConverter : EndianBitConverter + { + /// + /// Indicates the byte order ("endianess") in which data is converted using this class. + /// + /// + /// Different computer architectures store data using different byte orders. "Big-endian" + /// means the most significant byte is on the left end of a word. "Little-endian" means the + /// most significant byte is on the right end of a word. + /// + /// true if this converter is little-endian, false otherwise. + public sealed override bool IsLittleEndian() + { + return true; + } + + /// + /// Indicates the byte order ("endianess") in which data is converted using this class. + /// + public sealed override Endianness Endianness + { + get { return Endianness.LittleEndian; } + } + + /// + /// Copies the specified number of bytes from value to buffer, starting at index. + /// + /// The value to copy + /// The number of bytes to copy + /// The buffer to copy the bytes into + /// The index to start at + protected override void CopyBytesImpl(long value, int bytes, byte[] buffer, int index) + { + for (int i=0; i < bytes; i++) + { + buffer[i+index] = unchecked((byte)(value&0xff)); + value = value >> 8; + } + } + + /// + /// Returns a value built from the specified number of bytes from the given buffer, + /// starting at index. + /// + /// The data in byte array format + /// The first index to use + /// The number of bytes to use + /// The value built from the given bytes + protected override long FromBytes(byte[] buffer, int startIndex, int bytesToConvert) + { + long ret = 0; + for (int i=0; i < bytesToConvert; i++) + { + ret = unchecked((ret << 8) | buffer[startIndex+bytesToConvert-1-i]); + } + return ret; + } + } +} diff --git a/PacketDotNet/MiscUtil/IO/EndianBinaryReader.cs b/PacketDotNet/MiscUtil/IO/EndianBinaryReader.cs new file mode 100644 index 0000000..947718b --- /dev/null +++ b/PacketDotNet/MiscUtil/IO/EndianBinaryReader.cs @@ -0,0 +1,599 @@ +using System; +using System.IO; +using System.Text; +using MiscUtil.Conversion; + +namespace MiscUtil.IO +{ + /// + /// Equivalent of System.IO.BinaryReader, but with either endianness, depending on + /// the EndianBitConverter it is constructed with. No data is buffered in the + /// reader; the client may seek within the stream at will. + /// + public class EndianBinaryReader : IDisposable + { + #region Fields not directly related to properties + /// + /// Whether or not this reader has been disposed yet. + /// + bool disposed=false; + /// + /// Decoder to use for string conversions. + /// + Decoder decoder; + /// + /// Buffer used for temporary storage before conversion into primitives + /// + byte[] buffer = new byte[16]; + /// + /// Buffer used for temporary storage when reading a single character + /// + char[] charBuffer = new char[1]; + /// + /// Minimum number of bytes used to encode a character + /// + int minBytesPerChar; + #endregion + + #region Constructors + /// + /// Equivalent of System.IO.BinaryWriter, but with either endianness, depending on + /// the EndianBitConverter it is constructed with. + /// + /// Converter to use when reading data + /// Stream to read data from + public EndianBinaryReader (EndianBitConverter bitConverter, + Stream stream) : this (bitConverter, stream, Encoding.UTF8) + { + } + + /// + /// Constructs a new binary reader with the given bit converter, reading + /// to the given stream, using the given encoding. + /// + /// Converter to use when reading data + /// Stream to read data from + /// Encoding to use when reading character data + public EndianBinaryReader (EndianBitConverter bitConverter, Stream stream, Encoding encoding) + { + if (bitConverter==null) + { + throw new ArgumentNullException("bitConverter"); + } + if (stream==null) + { + throw new ArgumentNullException("stream"); + } + if (encoding==null) + { + throw new ArgumentNullException("encoding"); + } + if (!stream.CanRead) + { + throw new ArgumentException("Stream isn't writable", "stream"); + } + this.stream = stream; + this.bitConverter = bitConverter; + this.encoding = encoding; + this.decoder = encoding.GetDecoder(); + this.minBytesPerChar = 1; + + if (encoding is UnicodeEncoding) + { + minBytesPerChar = 2; + } + } + #endregion + + #region Properties + EndianBitConverter bitConverter; + /// + /// The bit converter used to read values from the stream + /// + public EndianBitConverter BitConverter + { + get { return bitConverter; } + } + + Encoding encoding; + /// + /// The encoding used to read strings + /// + public Encoding Encoding + { + get { return encoding; } + } + + Stream stream; + /// + /// Gets the underlying stream of the EndianBinaryReader. + /// + public Stream BaseStream + { + get { return stream; } + } + #endregion + + #region Public methods + /// + /// Closes the reader, including the underlying stream.. + /// + public void Close() + { + Dispose(); + } + + /// + /// Seeks within the stream. + /// + /// Offset to seek to. + /// Origin of seek operation. + public void Seek (int offset, SeekOrigin origin) + { + CheckDisposed(); + stream.Seek (offset, origin); + } + + /// + /// Reads a single byte from the stream. + /// + /// The byte read + public byte ReadByte() + { + ReadInternal(buffer, 1); + return buffer[0]; + } + + /// + /// Reads a single signed byte from the stream. + /// + /// The byte read + public sbyte ReadSByte() + { + ReadInternal(buffer, 1); + return unchecked((sbyte)buffer[0]); + } + + /// + /// Reads a boolean from the stream. 1 byte is read. + /// + /// The boolean read + public bool ReadBoolean() + { + ReadInternal(buffer, 1); + return bitConverter.ToBoolean(buffer, 0); + } + + /// + /// Reads a 16-bit signed integer from the stream, using the bit converter + /// for this reader. 2 bytes are read. + /// + /// The 16-bit integer read + public short ReadInt16() + { + ReadInternal(buffer, 2); + return bitConverter.ToInt16(buffer, 0); + } + + /// + /// Reads a 32-bit signed integer from the stream, using the bit converter + /// for this reader. 4 bytes are read. + /// + /// The 32-bit integer read + public int ReadInt32() + { + ReadInternal(buffer, 4); + return bitConverter.ToInt32(buffer, 0); + } + + /// + /// Reads a 64-bit signed integer from the stream, using the bit converter + /// for this reader. 8 bytes are read. + /// + /// The 64-bit integer read + public long ReadInt64() + { + ReadInternal(buffer, 8); + return bitConverter.ToInt64(buffer, 0); + } + + /// + /// Reads a 16-bit unsigned integer from the stream, using the bit converter + /// for this reader. 2 bytes are read. + /// + /// The 16-bit unsigned integer read + public ushort ReadUInt16() + { + ReadInternal(buffer, 2); + return bitConverter.ToUInt16(buffer, 0); + } + + /// + /// Reads a 32-bit unsigned integer from the stream, using the bit converter + /// for this reader. 4 bytes are read. + /// + /// The 32-bit unsigned integer read + public uint ReadUInt32() + { + ReadInternal(buffer, 4); + return bitConverter.ToUInt32(buffer, 0); + } + + /// + /// Reads a 64-bit unsigned integer from the stream, using the bit converter + /// for this reader. 8 bytes are read. + /// + /// The 64-bit unsigned integer read + public ulong ReadUInt64() + { + ReadInternal(buffer, 8); + return bitConverter.ToUInt64(buffer, 0); + } + + /// + /// Reads a single-precision floating-point value from the stream, using the bit converter + /// for this reader. 4 bytes are read. + /// + /// The floating point value read + public float ReadSingle() + { + ReadInternal(buffer, 4); + return bitConverter.ToSingle(buffer, 0); + } + + /// + /// Reads a double-precision floating-point value from the stream, using the bit converter + /// for this reader. 8 bytes are read. + /// + /// The floating point value read + public double ReadDouble() + { + ReadInternal(buffer, 8); + return bitConverter.ToDouble(buffer, 0); + } + + /// + /// Reads a decimal value from the stream, using the bit converter + /// for this reader. 16 bytes are read. + /// + /// The decimal value read + public decimal ReadDecimal() + { + ReadInternal(buffer, 16); + return bitConverter.ToDecimal(buffer, 0); + } + + /// + /// Reads a single character from the stream, using the character encoding for + /// this reader. If no characters have been fully read by the time the stream ends, + /// -1 is returned. + /// + /// The character read, or -1 for end of stream. + public int Read() + { + int charsRead = Read(charBuffer, 0, 1); + if (charsRead==0) + { + return -1; + } + else + { + return charBuffer[0]; + } + } + + /// + /// Reads the specified number of characters into the given buffer, starting at + /// the given index. + /// + /// The buffer to copy data into + /// The first index to copy data into + /// The number of characters to read + /// The number of characters actually read. This will only be less than + /// the requested number of characters if the end of the stream is reached. + /// + public int Read(char[] data, int index, int count) + { + CheckDisposed(); + if (buffer==null) + { + throw new ArgumentNullException("buffer"); + } + if (index < 0) + { + throw new ArgumentOutOfRangeException("index"); + } + if (count < 0) + { + throw new ArgumentOutOfRangeException("index"); + } + if (count+index > data.Length) + { + throw new ArgumentException + ("Not enough space in buffer for specified number of characters starting at specified index"); + } + + int read=0; + bool firstTime=true; + + // Use the normal buffer if we're only reading a small amount, otherwise + // use at most 4K at a time. + byte[] byteBuffer = buffer; + + if (byteBuffer.Length < count*minBytesPerChar) + { + byteBuffer = new byte[4096]; + } + + while (read < count) + { + int amountToRead; + // First time through we know we haven't previously read any data + if (firstTime) + { + amountToRead = count*minBytesPerChar; + firstTime=false; + } + // After that we can only assume we need to fully read "chars left -1" characters + // and a single byte of the character we may be in the middle of + else + { + amountToRead = ((count-read-1)*minBytesPerChar)+1; + } + if (amountToRead > byteBuffer.Length) + { + amountToRead = byteBuffer.Length; + } + int bytesRead = TryReadInternal(byteBuffer, amountToRead); + if (bytesRead==0) + { + return read; + } + int decoded = decoder.GetChars(byteBuffer, 0, bytesRead, data, index); + read += decoded; + index += decoded; + } + return read; + } + + /// + /// Reads the specified number of bytes into the given buffer, starting at + /// the given index. + /// + /// The buffer to copy data into + /// The first index to copy data into + /// The number of bytes to read + /// The number of bytes actually read. This will only be less than + /// the requested number of bytes if the end of the stream is reached. + /// + public int Read(byte[] buffer, int index, int count) + { + CheckDisposed(); + if (buffer==null) + { + throw new ArgumentNullException("buffer"); + } + if (index < 0) + { + throw new ArgumentOutOfRangeException("index"); + } + if (count < 0) + { + throw new ArgumentOutOfRangeException("index"); + } + if (count+index > buffer.Length) + { + throw new ArgumentException + ("Not enough space in buffer for specified number of bytes starting at specified index"); + } + int read=0; + while (count > 0) + { + int block = stream.Read(buffer, index, count); + if (block==0) + { + return read; + } + index += block; + read += block; + count -= block; + } + return read; + } + + /// + /// Reads the specified number of bytes, returning them in a new byte array. + /// If not enough bytes are available before the end of the stream, this + /// method will return what is available. + /// + /// The number of bytes to read + /// The bytes read + public byte[] ReadBytes(int count) + { + CheckDisposed(); + if (count < 0) + { + throw new ArgumentOutOfRangeException("count"); + } + byte[] ret = new byte[count]; + int index=0; + while (index < count) + { + int read = stream.Read(ret, index, count-index); + // Stream has finished half way through. That's fine, return what we've got. + if (read==0) + { + byte[] copy = new byte[index]; + Buffer.BlockCopy(ret, 0, copy, 0, index); + return copy; + } + index += read; + } + return ret; + } + + /// + /// Reads the specified number of bytes, returning them in a new byte array. + /// If not enough bytes are available before the end of the stream, this + /// method will throw an IOException. + /// + /// The number of bytes to read + /// The bytes read + public byte[] ReadBytesOrThrow(int count) + { + byte[] ret = new byte[count]; + ReadInternal(ret, count); + return ret; + } + + /// + /// Reads a 7-bit encoded integer from the stream. This is stored with the least significant + /// information first, with 7 bits of information per byte of value, and the top + /// bit as a continuation flag. This method is not affected by the endianness + /// of the bit converter. + /// + /// The 7-bit encoded integer read from the stream. + public int Read7BitEncodedInt() + { + CheckDisposed(); + + int ret=0; + for (int shift = 0; shift < 35; shift+=7) + { + int b = stream.ReadByte(); + if (b==-1) + { + throw new EndOfStreamException(); + } + ret = ret | ((b&0x7f) << shift); + if ((b & 0x80) == 0) + { + return ret; + } + } + // Still haven't seen a byte with the high bit unset? Dodgy data. + throw new IOException("Invalid 7-bit encoded integer in stream."); + } + + /// + /// Reads a 7-bit encoded integer from the stream. This is stored with the most significant + /// information first, with 7 bits of information per byte of value, and the top + /// bit as a continuation flag. This method is not affected by the endianness + /// of the bit converter. + /// + /// The 7-bit encoded integer read from the stream. + public int ReadBigEndian7BitEncodedInt() + { + CheckDisposed(); + + int ret=0; + for (int i=0; i < 5; i++) + { + int b = stream.ReadByte(); + if (b==-1) + { + throw new EndOfStreamException(); + } + ret = (ret << 7) | (b&0x7f); + if ((b & 0x80) == 0) + { + return ret; + } + } + // Still haven't seen a byte with the high bit unset? Dodgy data. + throw new IOException("Invalid 7-bit encoded integer in stream."); + } + + /// + /// Reads a length-prefixed string from the stream, using the encoding for this reader. + /// A 7-bit encoded integer is first read, which specifies the number of bytes + /// to read from the stream. These bytes are then converted into a string with + /// the encoding for this reader. + /// + /// The string read from the stream. + public string ReadString() + { + int bytesToRead = Read7BitEncodedInt(); + + byte[] data = new byte[bytesToRead]; + ReadInternal(data, bytesToRead); + return encoding.GetString(data, 0, data.Length); + } + + #endregion + + #region Private methods + /// + /// Checks whether or not the reader has been disposed, throwing an exception if so. + /// + void CheckDisposed() + { + if (disposed) + { + throw new ObjectDisposedException("EndianBinaryReader"); + } + } + + /// + /// Reads the given number of bytes from the stream, throwing an exception + /// if they can't all be read. + /// + /// Buffer to read into + /// Number of bytes to read + void ReadInternal (byte[] data, int size) + { + CheckDisposed(); + int index=0; + while (index < size) + { + int read = stream.Read(data, index, size-index); + if (read==0) + { + throw new EndOfStreamException + (String.Format("End of stream reached with {0} byte{1} left to read.", size-index, + size-index==1 ? "s" : "")); + } + index += read; + } + } + + /// + /// Reads the given number of bytes from the stream if possible, returning + /// the number of bytes actually read, which may be less than requested if + /// (and only if) the end of the stream is reached. + /// + /// Buffer to read into + /// Number of bytes to read + /// Number of bytes actually read + int TryReadInternal (byte[] data, int size) + { + CheckDisposed(); + int index=0; + while (index < size) + { + int read = stream.Read(data, index, size-index); + if (read==0) + { + return index; + } + index += read; + } + return index; + } + #endregion + + #region IDisposable Members + /// + /// Disposes of the underlying stream. + /// + public void Dispose() + { + if (!disposed) + { + disposed = true; + ((IDisposable)stream).Dispose(); + } + } + #endregion + } +} diff --git a/PacketDotNet/MiscUtil/IO/EndianBinaryWriter.cs b/PacketDotNet/MiscUtil/IO/EndianBinaryWriter.cs new file mode 100644 index 0000000..cd33e1f --- /dev/null +++ b/PacketDotNet/MiscUtil/IO/EndianBinaryWriter.cs @@ -0,0 +1,392 @@ +using System; +using System.IO; +using System.Text; +using MiscUtil.Conversion; + +namespace MiscUtil.IO +{ + /// + /// Equivalent of System.IO.BinaryWriter, but with either endianness, depending on + /// the EndianBitConverter it is constructed with. + /// + public class EndianBinaryWriter : IDisposable + { + #region Fields not directly related to properties + /// + /// Whether or not this writer has been disposed yet. + /// + bool disposed=false; + /// + /// Buffer used for temporary storage during conversion from primitives + /// + byte[] buffer = new byte[16]; + /// + /// Buffer used for Write(char) + /// + char[] charBuffer = new char[1]; + #endregion + + #region Constructors + /// + /// Constructs a new binary writer with the given bit converter, writing + /// to the given stream, using UTF-8 encoding. + /// + /// Converter to use when writing data + /// Stream to write data to + public EndianBinaryWriter (EndianBitConverter bitConverter, + Stream stream) : this (bitConverter, stream, Encoding.UTF8) + { + } + + /// + /// Constructs a new binary writer with the given bit converter, writing + /// to the given stream, using the given encoding. + /// + /// Converter to use when writing data + /// Stream to write data to + /// Encoding to use when writing character data + public EndianBinaryWriter (EndianBitConverter bitConverter, Stream stream, Encoding encoding) + { + if (bitConverter==null) + { + throw new ArgumentNullException("bitConverter"); + } + if (stream==null) + { + throw new ArgumentNullException("stream"); + } + if (encoding==null) + { + throw new ArgumentNullException("encoding"); + } + if (!stream.CanWrite) + { + throw new ArgumentException("Stream isn't writable", "stream"); + } + this.stream = stream; + this.bitConverter = bitConverter; + this.encoding = encoding; + } + #endregion + + #region Properties + EndianBitConverter bitConverter; + /// + /// The bit converter used to write values to the stream + /// + public EndianBitConverter BitConverter + { + get { return bitConverter; } + } + + Encoding encoding; + /// + /// The encoding used to write strings + /// + public Encoding Encoding + { + get { return encoding; } + } + + Stream stream; + /// + /// Gets the underlying stream of the EndianBinaryWriter. + /// + public Stream BaseStream + { + get { return stream; } + } + #endregion + + #region Public methods + /// + /// Closes the writer, including the underlying stream. + /// + public void Close() + { + Dispose(); + } + + /// + /// Flushes the underlying stream. + /// + public void Flush() + { + CheckDisposed(); + stream.Flush(); + } + + /// + /// Seeks within the stream. + /// + /// Offset to seek to. + /// Origin of seek operation. + public void Seek (int offset, SeekOrigin origin) + { + CheckDisposed(); + stream.Seek (offset, origin); + } + + /// + /// Writes a boolean value to the stream. 1 byte is written. + /// + /// The value to write + public void Write (bool value) + { + bitConverter.CopyBytes(value, buffer, 0); + WriteInternal(buffer, 1); + } + + /// + /// Writes a 16-bit signed integer to the stream, using the bit converter + /// for this writer. 2 bytes are written. + /// + /// The value to write + public void Write (short value) + { + bitConverter.CopyBytes(value, buffer, 0); + WriteInternal(buffer, 2); + } + + /// + /// Writes a 32-bit signed integer to the stream, using the bit converter + /// for this writer. 4 bytes are written. + /// + /// The value to write + public void Write (int value) + { + bitConverter.CopyBytes(value, buffer, 0); + WriteInternal(buffer, 4); + } + + /// + /// Writes a 64-bit signed integer to the stream, using the bit converter + /// for this writer. 8 bytes are written. + /// + /// The value to write + public void Write (long value) + { + bitConverter.CopyBytes(value, buffer, 0); + WriteInternal(buffer, 8); + } + + /// + /// Writes a 16-bit unsigned integer to the stream, using the bit converter + /// for this writer. 2 bytes are written. + /// + /// The value to write + public void Write (ushort value) + { + bitConverter.CopyBytes(value, buffer, 0); + WriteInternal(buffer, 2); + } + + /// + /// Writes a 32-bit unsigned integer to the stream, using the bit converter + /// for this writer. 4 bytes are written. + /// + /// The value to write + public void Write (uint value) + { + bitConverter.CopyBytes(value, buffer, 0); + WriteInternal(buffer, 4); + } + + /// + /// Writes a 64-bit unsigned integer to the stream, using the bit converter + /// for this writer. 8 bytes are written. + /// + /// The value to write + public void Write (ulong value) + { + bitConverter.CopyBytes(value, buffer, 0); + WriteInternal(buffer, 8); + } + + /// + /// Writes a single-precision floating-point value to the stream, using the bit converter + /// for this writer. 4 bytes are written. + /// + /// The value to write + public void Write (float value) + { + bitConverter.CopyBytes(value, buffer, 0); + WriteInternal(buffer, 4); + } + + /// + /// Writes a double-precision floating-point value to the stream, using the bit converter + /// for this writer. 8 bytes are written. + /// + /// The value to write + public void Write (double value) + { + bitConverter.CopyBytes(value, buffer, 0); + WriteInternal(buffer, 8); + } + + /// + /// Writes a decimal value to the stream, using the bit converter for this writer. + /// 16 bytes are written. + /// + /// The value to write + public void Write (decimal value) + { + bitConverter.CopyBytes(value, buffer, 0); + WriteInternal(buffer, 16); + } + + /// + /// Writes a signed byte to the stream. + /// + /// The value to write + public void Write (byte value) + { + buffer[0] = value; + WriteInternal(buffer, 1); + } + + /// + /// Writes an unsigned byte to the stream. + /// + /// The value to write + public void Write (sbyte value) + { + buffer[0] = unchecked((byte)value); + WriteInternal(buffer, 1); + } + + /// + /// Writes an array of bytes to the stream. + /// + /// The values to write + public void Write (byte[] value) + { + if (value == null) + { + throw (new System.ArgumentNullException("value")); + } + WriteInternal(value, value.Length); + } + + /// + /// Writes a portion of an array of bytes to the stream. + /// + /// An array containing the bytes to write + /// The index of the first byte to write within the array + /// The number of bytes to write + public void Write (byte[] value, int offset, int count) + { + CheckDisposed(); + stream.Write(value, offset, count); + } + + /// + /// Writes a single character to the stream, using the encoding for this writer. + /// + /// The value to write + public void Write(char value) + { + charBuffer[0] = value; + Write(charBuffer); + } + + /// + /// Writes an array of characters to the stream, using the encoding for this writer. + /// + /// An array containing the characters to write + public void Write(char[] value) + { + if (value==null) + { + throw new ArgumentNullException("value"); + } + CheckDisposed(); + byte[] data = Encoding.GetBytes(value, 0, value.Length); + WriteInternal(data, data.Length); + } + + /// + /// Writes a string to the stream, using the encoding for this writer. + /// + /// The value to write. Must not be null. + /// value is null + public void Write(string value) + { + if (value==null) + { + throw new ArgumentNullException("value"); + } + CheckDisposed(); + byte[] data = Encoding.GetBytes(value); + Write7BitEncodedInt(data.Length); + WriteInternal(data, data.Length); + } + + /// + /// Writes a 7-bit encoded integer from the stream. This is stored with the least significant + /// information first, with 7 bits of information per byte of value, and the top + /// bit as a continuation flag. + /// + /// The 7-bit encoded integer to write to the stream + public void Write7BitEncodedInt(int value) + { + CheckDisposed(); + if (value < 0) + { + throw new ArgumentOutOfRangeException("value", "Value must be greater than or equal to 0."); + } + int index=0; + while (value >= 128) + { + buffer[index++]= (byte)((value&0x7f) | 0x80); + value = value >> 7; + index++; + } + buffer[index++]=(byte)value; + stream.Write(buffer, 0, index); + } + + #endregion + + #region Private methods + /// + /// Checks whether or not the writer has been disposed, throwing an exception if so. + /// + void CheckDisposed() + { + if (disposed) + { + throw new ObjectDisposedException("EndianBinaryWriter"); + } + } + + /// + /// Writes the specified number of bytes from the start of the given byte array, + /// after checking whether or not the writer has been disposed. + /// + /// The array of bytes to write from + /// The number of bytes to write + void WriteInternal (byte[] bytes, int length) + { + CheckDisposed(); + stream.Write(bytes, 0, length); + } + #endregion + + #region IDisposable Members + /// + /// Disposes of the underlying stream. + /// + public void Dispose() + { + if (!disposed) + { + Flush(); + disposed = true; + ((IDisposable)stream).Dispose(); + } + } + #endregion + } +} diff --git a/PacketDotNet/PPPFields.cs b/PacketDotNet/PPPFields.cs new file mode 100644 index 0000000..de7bb3b --- /dev/null +++ b/PacketDotNet/PPPFields.cs @@ -0,0 +1,45 @@ +/* +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 2010 Chris Morgan + */ +using System; +namespace PacketDotNet +{ + /// + /// The fields in a PPP packet + /// See http://en.wikipedia.org/wiki/Point-to-Point_Protocol + /// + public class PPPFields + { + /// + /// Length of the Protocol field in bytes, the field is of type + /// PPPProtocol + /// + public static readonly int ProtocolLength = 2; + + /// + /// Offset from the start of the PPP packet where the Protocol field is located + /// + public static readonly int ProtocolPosition = 0; + + /// + /// The length of the header + /// + public static readonly int HeaderLength = ProtocolLength; + } +} diff --git a/PacketDotNet/PPPPacket.cs b/PacketDotNet/PPPPacket.cs new file mode 100644 index 0000000..0b55de5 --- /dev/null +++ b/PacketDotNet/PPPPacket.cs @@ -0,0 +1,201 @@ +/* +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 2010 Chris Morgan + */ +using System; +using PacketDotNet.Utils; +using MiscUtil.Conversion; + +namespace PacketDotNet +{ + /// + /// PPP packet + /// See http://en.wikipedia.org/wiki/Point-to-Point_Protocol + /// + public class PPPPacket : Packet + { +#if DEBUG + private static readonly log4net.ILog log = log4net.LogManager.GetLogger(System.Reflection.MethodBase.GetCurrentMethod().DeclaringType); +#else + // 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 + private static readonly ILogInactive log; +#pragma warning restore 0169 +#endif + + /// + /// See http://www.iana.org/assignments/ppp-numbers + /// + public PPPProtocol Protocol + { + get + { + return (PPPProtocol)EndianBitConverter.Big.ToUInt16(header.Bytes, + header.Offset + PPPFields.ProtocolPosition); + } + + set + { + var val = (UInt16)value; + EndianBitConverter.Big.CopyBytes(val, + header.Bytes, + header.Offset + PPPFields.ProtocolPosition); + } + } + + /// + /// Construct a new PPPPacket from source and destination mac addresses + /// + public PPPPacket(PPPoECode Code, + UInt16 SessionId) + : base(new PosixTimeval()) + { + log.Debug(""); + + // allocate memory for this packet + int offset = 0; + int length = PPPFields.HeaderLength; + var headerBytes = new byte[length]; + header = new ByteArraySegment(headerBytes, offset, length); + + // setup some typical values and default values + this.Protocol = PPPProtocol.Padding; + } + + /// + /// Create an PPPPacket from a byte array + /// + /// + /// A + /// + /// + /// A + /// + public PPPPacket(byte[] Bytes, int Offset) : + this(Bytes, Offset, new PosixTimeval()) + { + log.Debug(""); + } + + /// + /// Create an PPPPacket from a byte array and a Timeval + /// + /// + /// A + /// + /// + /// A + /// + /// + /// A + /// + public PPPPacket(byte[] Bytes, int Offset, PosixTimeval Timeval) : + base(Timeval) + { + log.Debug(""); + + // slice off the header portion as our header + header = new ByteArraySegment(Bytes, Offset, PPPFields.HeaderLength); + + // parse the encapsulated bytes + payloadPacketOrData = ParseEncapsulatedBytes(header, Timeval, Protocol); + } + + internal static PacketOrByteArraySegment ParseEncapsulatedBytes(ByteArraySegment Header, + PosixTimeval Timeval, + PPPProtocol Protocol) + { + // slice off the payload + var payload = Header.EncapsulatedBytes(); + + log.DebugFormat("payload: {0}", payload); + + var payloadPacketOrData = new PacketOrByteArraySegment(); + + switch(Protocol) + { + case PPPProtocol.IPv4: + payloadPacketOrData.ThePacket = new IPv4Packet(payload.Bytes, + payload.Offset, + Timeval); + break; + case PPPProtocol.IPv6: + payloadPacketOrData.ThePacket = new IPv6Packet(payload.Bytes, + payload.Offset, + Timeval); + break; + default: + throw new System.NotImplementedException("Protocol of " + Protocol + " is not implemented"); + } + + return payloadPacketOrData; + } + + /// Fetch ascii escape sequence of the color associated with this packet type. + public override System.String Color + { + get + { + return AnsiEscapeSequences.DarkGray; + } + } + + /// Convert this packet to a readable string. + public override System.String ToString() + { + return ToColoredString(false); + } + + /// Generate string with contents describing this packet. + /// whether or not the string should contain ansi + /// color escape sequences. + /// + public override System.String ToColoredString(bool colored) + { + var buffer = new System.Text.StringBuilder(); + + buffer.AppendFormat("[PPPPacket] Protocol {0}", + Protocol); + + // append the base output + buffer.Append(base.ToColoredString(colored)); + + return buffer.ToString(); + } + + /// Convert a more verbose string. + public override System.String ToColoredVerboseString(bool colored) + { + //TODO: just output the colored output for now + return ToColoredString(colored); + } + + /// + /// Generate a random PPPoEPacket + /// + /// + /// A + /// + public static PPPoEPacket RandomPacket() + { + throw new System.NotImplementedException(); + } + } +} + diff --git a/PacketDotNet/PPPProtocol.cs b/PacketDotNet/PPPProtocol.cs new file mode 100644 index 0000000..10edd2c --- /dev/null +++ b/PacketDotNet/PPPProtocol.cs @@ -0,0 +1,39 @@ +/* +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 2010 Chris Morgan + */ +using System; +namespace PacketDotNet +{ + /// + /// Indicates the protocol encapsulated by the PPP packet + /// See http://www.iana.org/assignments/ppp-numbers + /// + public enum PPPProtocol : ushort + { + /// Padding + Padding = 0x1, + + /// IPv4 + IPv4 = 0x21, + + /// IPv6 + IPv6 = 0x57, + } +} + diff --git a/PacketDotNet/PPPoECode.cs b/PacketDotNet/PPPoECode.cs new file mode 100644 index 0000000..95701cb --- /dev/null +++ b/PacketDotNet/PPPoECode.cs @@ -0,0 +1,61 @@ +/* +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 2010 Chris Morgan + */ + +using System; +namespace PacketDotNet +{ + /// + /// Values for the Code field of a PPPoE packet + /// See http://tools.ietf.org/html/rfc2516 + /// + public enum PPPoECode : ushort + { + /// + /// The PPPoe payload must contain a PPP packet + /// + SessionStage = 0x0, + + /// + /// Active Discovery Offer (PADO) packet + /// + ActiveDiscoveryOffer = 0x07, + + /// + /// From RFC2516: + /// The Host sends the PADI packet with the DESTINATION_ADDR set to the + /// broadcast address. The CODE field is set to 0x09 and the SESSION_ID + /// MUST be set to 0x0000. + /// + /// The PADI packet MUST contain exactly one TAG of TAG_TYPE Service- + /// Name, indicating the service the Host is requesting, and any number + /// of other TAG types. An entire PADI packet (including the PPPoE + /// header) MUST NOT exceed 1484 octets so as to leave sufficient room + /// for a relay agent to add a Relay-Session-Id TAG. + /// + ActiveDiscoveryInitiation = 0x9, + + /// + /// Indicate that the PPPoe session specified by the SessionId field of + /// the PPPoe packet has been terminated + /// + ActiveDiscoveryTerminate = 0xa7, + } +} + diff --git a/PacketDotNet/PPPoEFields.cs b/PacketDotNet/PPPoEFields.cs new file mode 100644 index 0000000..b81af38 --- /dev/null +++ b/PacketDotNet/PPPoEFields.cs @@ -0,0 +1,69 @@ +/* +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 2010 Chris Morgan + */ +using System; + +namespace PacketDotNet +{ + /// + /// Point to Point Protocol + /// See http://tools.ietf.org/html/rfc2516 + /// + public class PPPoEFields + { + /// Size in bytes of the version/type field + public readonly static int VersionTypeLength = 1; + + /// Size in bytes of the code field + public readonly static int CodeLength = 1; + + /// Size in bytes of the SessionId field + public readonly static int SessionIdLength = 2; + + /// Size in bytes of the Length field + public readonly static int LengthLength = 2; + + /// Offset from the start of the header to the version/type field + public readonly static int VersionTypePosition = 0; + + /// Offset from the start of the header to the Code field + public readonly static int CodePosition; + + /// Offset from the start of the header to the SessionId field + public readonly static int SessionIdPosition; + + /// Offset from the start of the header to the Length field + public readonly static int LengthPosition; + + /// + /// Length of the overall PPPoe header + /// + public readonly static int HeaderLength; + + static PPPoEFields() + { + CodePosition = VersionTypePosition + VersionTypeLength; + SessionIdPosition = CodePosition + CodeLength; + LengthPosition = SessionIdPosition + SessionIdLength; + + HeaderLength = LengthPosition + LengthLength; + } + } +} + diff --git a/PacketDotNet/PPPoEPacket.cs b/PacketDotNet/PPPoEPacket.cs new file mode 100644 index 0000000..4f477a3 --- /dev/null +++ b/PacketDotNet/PPPoEPacket.cs @@ -0,0 +1,288 @@ +/* +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 2010 Chris Morgan + */ +using System; +using PacketDotNet.Utils; +using MiscUtil.Conversion; + +namespace PacketDotNet +{ + /// + /// Point to Point Protocol + /// See http://tools.ietf.org/html/rfc2516 + /// + public class PPPoEPacket : Packet + { +#if DEBUG + private static readonly log4net.ILog log = log4net.LogManager.GetLogger(System.Reflection.MethodBase.GetCurrentMethod().DeclaringType); +#else + // 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 + private static readonly ILogInactive log; +#pragma warning restore 0169 +#endif + + private byte VersionType + { + get + { + return header.Bytes[header.Offset + PPPoEFields.VersionTypePosition]; + } + + set + { + header.Bytes[header.Offset + PPPoEFields.VersionTypePosition] = value; + } + } + + /// + /// PPPoe version, must be 0x1 according to RFC + /// + public byte Version + { + get + { + return (byte)((VersionType >> 4) & 0xF0); + } + + set + { + var versionType = VersionType; + + // mask the new value in + versionType = (byte)((versionType & 0x0F) | ((value << 4) & 0xF0)); + + VersionType = versionType; + } + } + + /// + /// Type, must be 0x1 according to RFC + /// + public byte Type + { + get + { + return (byte)((VersionType) & 0x0F); + } + + set + { + var versionType = VersionType; + + // mask the new value in + versionType = (byte)((versionType & 0xF0) | (value & 0xF0)); + + VersionType = versionType; + } + } + + /// + /// + /// + public PPPoECode Code + { + get + { + return (PPPoECode)EndianBitConverter.Big.ToUInt16(header.Bytes, + header.Offset + PPPoEFields.CodePosition); + } + + set + { + var val = (UInt16)value; + EndianBitConverter.Big.CopyBytes(val, + header.Bytes, + header.Offset + PPPoEFields.CodePosition); + } + } + + /// + /// Session identifier for this PPPoe packet + /// + public UInt16 SessionId + { + get + { + return EndianBitConverter.Big.ToUInt16(header.Bytes, + header.Offset + PPPoEFields.SessionIdPosition); + } + + set + { + var val = (UInt16)value; + EndianBitConverter.Big.CopyBytes(val, + header.Bytes, + header.Offset + PPPoEFields.SessionIdPosition); + } + } + + /// + /// Length of the PPPoe payload, not including the PPPoe header + /// + public UInt16 Length + { + get + { + return EndianBitConverter.Big.ToUInt16(header.Bytes, + header.Offset + PPPoEFields.LengthPosition); + } + + set + { + var val = (UInt16)value; + EndianBitConverter.Big.CopyBytes(val, + header.Bytes, + header.Offset + PPPoEFields.LengthPosition); + } + } + + /// + /// Construct a new PPPoEPacket from source and destination mac addresses + /// + public PPPoEPacket(PPPoECode Code, + UInt16 SessionId) + : base(new PosixTimeval()) + { + log.Debug(""); + + // allocate memory for this packet + int offset = 0; + int length = PPPoEFields.HeaderLength; + var headerBytes = new byte[length]; + header = new ByteArraySegment(headerBytes, offset, length); + + // set the instance values + this.Code = Code; + this.SessionId = SessionId; + + // setup some typical values and default values + this.Version = 1; + this.Type = 1; + this.Length = 0; + } + + /// + /// Create an PPPoEPacket from a byte array + /// + /// + /// A + /// + /// + /// A + /// + public PPPoEPacket(byte[] Bytes, int Offset) : + this(Bytes, Offset, new PosixTimeval()) + { + log.Debug(""); + } + + /// + /// Create an PPPoEPacket from a byte array and a Timeval + /// + /// + /// A + /// + /// + /// A + /// + /// + /// A + /// + public PPPoEPacket(byte[] Bytes, int Offset, PosixTimeval Timeval) : + base(Timeval) + { + log.Debug(""); + + // slice off the header portion + header = new ByteArraySegment(Bytes, Offset, PPPoEFields.HeaderLength); + + // parse the encapsulated bytes + payloadPacketOrData = ParseEncapsulatedBytes(header, Timeval); + } + + internal static PacketOrByteArraySegment ParseEncapsulatedBytes(ByteArraySegment Header, + PosixTimeval Timeval) + { + // slice off the payload + var payload = Header.EncapsulatedBytes(); + log.DebugFormat("payload {0}", payload.ToString()); + + var payloadPacketOrData = new PacketOrByteArraySegment(); + + // we assume that we have a PPPPacket as the payload + payloadPacketOrData.ThePacket = new PPPPacket(payload.Bytes, + payload.Offset, + Timeval); + + return payloadPacketOrData; + } + + /// Fetch ascii escape sequence of the color associated with this packet type. + public override System.String Color + { + get + { + return AnsiEscapeSequences.DarkGray; + } + } + + /// Convert this packet to a readable string. + public override System.String ToString() + { + return ToColoredString(false); + } + + /// Generate string with contents describing this packet. + /// whether or not the string should contain ansi + /// color escape sequences. + /// + public override System.String ToColoredString(bool colored) + { + var buffer = new System.Text.StringBuilder(); + + buffer.AppendFormat("[PPPoEPacket] Version {0}, Type {1}, Code {2}, SessionId {3}, Length {4}", + Version, Type, Code, SessionId, Length); + + // append the base output + buffer.Append(base.ToColoredString(colored)); + + return buffer.ToString(); + } + + /// Convert a more verbose string. + public override System.String ToColoredVerboseString(bool colored) + { + //TODO: just output the colored output for now + return ToColoredString(colored); + } + + /// + /// Generate a random PPPoEPacket + /// + /// + /// A + /// + public static PPPoEPacket RandomPacket() + { + throw new System.NotImplementedException(); + } + } +} diff --git a/PacketDotNet/Packet.cs b/PacketDotNet/Packet.cs new file mode 100644 index 0000000..1986011 --- /dev/null +++ b/PacketDotNet/Packet.cs @@ -0,0 +1,419 @@ +/* +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 PacketDotNet.Utils; + +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 + { +#if DEBUG + private static readonly log4net.ILog log = log4net.LogManager.GetLogger(System.Reflection.MethodBase.GetCurrentMethod().DeclaringType); +#else + // 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 + private static readonly ILogInactive log; +#pragma warning restore 0169 +#endif + + internal ByteArraySegment header; + + internal PacketOrByteArraySegment payloadPacketOrData = new PacketOrByteArraySegment(); + + internal Packet parentPacket; + + internal PosixTimeval timeval; + + /// + /// PosixTimeval of this packet, can be the packet arrival time + /// or the packet creation time + /// + public virtual PosixTimeval Timeval + { + get { return timeval; } + } + + // recursively finds the length of this packet and all of the packets + // encapsulated by this packet + internal 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 + /// + internal 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 (payloadPacketOrData.ThePacket == 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.Bytes.Length - 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); + } + } + } + + /// + /// Basic Packet constructor + /// + /// + /// A + /// + public Packet(PosixTimeval timeval) + { + this.timeval = timeval; + } + + /// + /// Turns an array of bytes into an EthernetPacket + /// + /// The packets caught + /// An ethernet packet which has references to the higher protocols + public static Packet Parse(byte[] data) + { + return new EthernetPacket(data, 0); + } + + /// + /// Parse a raw packet into its specific packets and payloads + /// + /// + /// A + /// + /// + /// A + /// + public static Packet ParsePacket(RawPacket rawPacket) + { + return ParsePacket(rawPacket.LinkLayerType, + rawPacket.Timeval, + rawPacket.Data); + } + + /// + /// Parse bytes into a packet + /// + /// + /// A + /// + /// + /// A + /// + /// + /// A + /// + /// + /// A + /// + public static Packet ParsePacket(LinkLayers LinkLayer, + PosixTimeval Timeval, + byte[] PacketData) + { + switch(LinkLayer) + { + case LinkLayers.Ethernet: + return new EthernetPacket(PacketData, 0, Timeval); + case LinkLayers.LinuxSLL: + return new LinuxSLLPacket(PacketData, 0, Timeval); + default: + throw new System.NotImplementedException("LinkLayer of " + LinkLayer + " is not implemented"); + } + } + + /// + /// Used to ensure that values like checksums and lengths are + /// properly updated + /// + protected void RecursivelyUpdateCalculatedValues() + { + // call the possibly overridden method + UpdateCalculatedValues(); + + // if the packet contains another packet, call its + if(payloadPacketOrData.Type == PayloadType.Packet) + { + payloadPacketOrData.ThePacket.RecursivelyUpdateCalculatedValues(); + } + } + + /// + /// 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() + { } + + /// + /// Returns a ansi colored string. This routine calls + /// the ToColoredString() of the payload packet if one + /// is present. + /// + /// + /// A + /// + /// + /// A + /// + public virtual System.String ToColoredString(bool colored) + { + if(payloadPacketOrData.Type == PayloadType.Packet) + { + return payloadPacketOrData.ThePacket.ToColoredString(colored); + } else + { + return String.Empty; + } + } + + /// + /// Returns a verbose ansi colored string. This routine calls + /// the ToColoredVerboseString() of the payload packet if one + /// is present. + /// + /// + /// A + /// + /// + /// A + /// + public virtual System.String ToColoredVerboseString(bool colored) + { + if(payloadPacketOrData.Type == PayloadType.Packet) + { + return payloadPacketOrData.ThePacket.ToColoredVerboseString(colored); + } else + { + return String.Empty; + } + } + + /// + /// Color used when generating the text description of a packet + /// + public virtual System.String Color + { + get + { + return AnsiEscapeSequences.Black; + } + } + } +} diff --git a/PacketDotNet/PacketDotNet.csproj b/PacketDotNet/PacketDotNet.csproj new file mode 100644 index 0000000..f1c7227 --- /dev/null +++ b/PacketDotNet/PacketDotNet.csproj @@ -0,0 +1,182 @@ + + + + Debug + AnyCPU + 9.0.21022 + 2.0 + {55ABBA4C-AAF9-4726-A592-0C92436CEC92} + Library + PacketDotNet + v3.5 + PacketDotNet + + + 3.5 + + false + + + publish\ + true + Disk + false + Foreground + 7 + Days + false + false + true + 0 + 1.0.0.%2a + false + true + + + true + full + false + bin\Debug + TRACE;DEBUG + prompt + 4 + false + true + + + none + false + bin\Release + prompt + 4 + false + true + TRACE;DEBUG + + + + Libraries\log4net.dll + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + False + Microsoft .NET Framework 4 %28x86 und x64%29 + true + + + False + .NET Framework 3.5 SP1 Client Profile + false + + + False + .NET Framework 3.5 SP1 + false + + + False + Windows Installer 3.1 + true + + + + + + + + + + + + \ No newline at end of file diff --git a/PacketDotNet/PacketDotNet.csproj.user b/PacketDotNet/PacketDotNet.csproj.user new file mode 100644 index 0000000..7a8e80c --- /dev/null +++ b/PacketDotNet/PacketDotNet.csproj.user @@ -0,0 +1,13 @@ + + + + + + + + + + de-DE + false + + \ No newline at end of file diff --git a/PacketDotNet/PacketOrByteArraySegment.cs b/PacketDotNet/PacketOrByteArraySegment.cs new file mode 100644 index 0000000..edb9909 --- /dev/null +++ b/PacketDotNet/PacketOrByteArraySegment.cs @@ -0,0 +1,104 @@ +/* +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 2010 Chris Morgan + */ + +using System; +using System.IO; +using PacketDotNet.Utils; + +namespace PacketDotNet +{ + /// + /// Encapsulates and ensures that we have either a Packet OR + /// a ByteArraySegment but not both + /// + internal class PacketOrByteArraySegment + { + private ByteArraySegment theByteArraySegment; + public ByteArraySegment TheByteArraySegment + { + get + { + return theByteArraySegment; + } + + set + { + thePacket = null; + theByteArraySegment = value; + } + } + + private Packet thePacket; + public Packet ThePacket + { + get + { + return thePacket; + } + + set + { + theByteArraySegment = null; + thePacket = value; + } + } + + /// + /// Appends to the MemoryStream either the byte[] represented by TheByteArray, or + /// if ThePacket is non-null, the Packet.Bytes will be appended to the memory stream + /// which will append ThePacket's header and any encapsulated packets it contains + /// + /// + /// A + /// + public void AppendToMemoryStream(MemoryStream ms) + { + if(ThePacket != null) + { + var theBytes = ThePacket.Bytes; + ms.Write(theBytes, 0, theBytes.Length); + } else if(TheByteArraySegment != null) + { + var theBytes = TheByteArraySegment.ActualBytes(); + ms.Write(theBytes, 0, theBytes.Length); + } + } + + /// + /// Whether or not this container contains a packet, a byte[] or neither + /// + public PayloadType Type + { + get + { + if(ThePacket != null) + { + return PayloadType.Packet; + } else if(TheByteArraySegment != null) + { + return PayloadType.Bytes; + } else + { + return PayloadType.None; + } + } + } + } +} \ No newline at end of file diff --git a/PacketDotNet/PayloadType.cs b/PacketDotNet/PayloadType.cs new file mode 100644 index 0000000..0693ac8 --- /dev/null +++ b/PacketDotNet/PayloadType.cs @@ -0,0 +1,35 @@ +/* +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 2010 Chris Morgan + */ + +using System; + +namespace PacketDotNet +{ + /// + /// Differentiates between a packet class payload, a byte[] payload + /// or no payload + /// + internal enum PayloadType + { + Packet, + Bytes, + None + } +} diff --git a/PacketDotNet/PosixTimeval.cs b/PacketDotNet/PosixTimeval.cs new file mode 100644 index 0000000..17ad684 --- /dev/null +++ b/PacketDotNet/PosixTimeval.cs @@ -0,0 +1,279 @@ +/* +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; +namespace PacketDotNet +{ + /// POSIX.4 timeval + public class PosixTimeval + { + private static readonly System.DateTime epochDateTime = new System.DateTime(1970, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc); + + internal long microsecondsPerMillisecond = 1000; + + /// + /// Number of seconds in the timeval + /// + virtual public ulong Seconds + { + get; + set; + } + + /// + /// Number of microseconds in the timeval + /// + virtual public ulong MicroSeconds + { + get; + set; + } + + /// The timeval as a DateTime in Utc + virtual public System.DateTime Date + { + get + { + return UnixTimeValToDateTime(Seconds, MicroSeconds); + } + } + + /// + /// Operator < overload + /// + /// + /// A + /// + /// + /// A + /// + /// + /// A + /// + public static bool operator<(PosixTimeval a, PosixTimeval b) + { + if(a.Seconds < b.Seconds) return true; + if((a.Seconds == b.Seconds) && + (a.MicroSeconds < b.MicroSeconds)) + { + return true; + } + + return false; + } + + /// + /// Operator > overload + /// + /// + /// A + /// + /// + /// A + /// + /// + /// A + /// + public static bool operator>(PosixTimeval a, PosixTimeval b) + { + return (b < a); + } + + /// + /// Operator <= + /// + /// + /// A + /// + /// + /// A + /// + /// + /// A + /// + public static bool operator<=(PosixTimeval a, PosixTimeval b) + { + if(a < b) return true; + + if((a.Seconds == b.Seconds) && + (a.MicroSeconds <= b.MicroSeconds)) + { + return true; + } + + return false; + } + + /// + /// Operator >= + /// + /// + /// A + /// + /// + /// A + /// + /// + /// A + /// + public static bool operator>=(PosixTimeval a, PosixTimeval b) + { + return (b <= a); + } + + /// + /// Operator == + /// + /// + /// A + /// + /// + /// A + /// + /// + /// A + /// + public static bool operator==(PosixTimeval a, PosixTimeval b) + { + return ((a.Seconds == b.Seconds) && + (a.MicroSeconds == b.MicroSeconds)); + } + + /// + /// Operator != + /// + /// + /// A + /// + /// + /// A + /// + /// + /// A + /// + public static bool operator!=(PosixTimeval a, PosixTimeval b) + { + return !(a == b); + } + + /// + /// Equals override + /// + /// + /// A + /// + /// + /// A + /// + public override bool Equals (object obj) + { + // Check for null values and compare run-time types. + if (obj == null || GetType() != obj.GetType()) + return false; + + var pt = (PosixTimeval)obj; + + return ((Seconds == pt.Seconds) && (MicroSeconds == pt.MicroSeconds)); + } + + /// + /// GetHashCode override + /// + /// + /// A + /// + public override int GetHashCode () + { + return Seconds.GetHashCode() + MicroSeconds.GetHashCode(); + } + + private static void DateTimeToUnixTimeVal(DateTime dateTime, + out UInt64 tvSec, + out UInt64 tvUsec) + { + // diff this with the dateTime value + // NOTE: make sure the time is in universal time when performing + // the subtraction so we get the difference between epoch in utc + // which is the definition of the unix timeval + TimeSpan timeSpan = dateTime.ToUniversalTime().Subtract(epochDateTime); + + tvSec = (UInt64)(timeSpan.TotalMilliseconds / 1000.0); + // find the milliseconds remainder and convert to microseconds + tvUsec = (UInt64)((timeSpan.TotalMilliseconds - (tvSec * 1000)) * 1000); + } + + private static DateTime UnixTimeValToDateTime(UInt64 tvSec, UInt64 tvUsec) + { + // add the tvSec value + DateTime dt = epochDateTime.AddSeconds(tvSec); + dt = dt.AddMilliseconds(tvUsec / 1000); // convert microseconds to milliseconds + + return dt; + } + + /// + /// Constructor with Seconds and MicroSeconds fields + /// + /// + /// A + /// + /// + /// A + /// + public PosixTimeval(ulong Seconds, ulong MicroSeconds) + { + this.Seconds = Seconds; + this.MicroSeconds = MicroSeconds; + } + + /// + /// Construct a PosixTimeval using the current UTC time + /// + public PosixTimeval() + { + ulong seconds; + ulong microseconds; + + DateTimeToUnixTimeVal(DateTime.UtcNow, + out seconds, + out microseconds); + + this.Seconds = seconds; + this.MicroSeconds = microseconds; + } + + /// + /// Convert the timeval to a string like 'SECONDS.MICROSECONDSs' + /// + /// + /// A + /// + public override System.String ToString() + { + System.Text.StringBuilder sb = new System.Text.StringBuilder(); + sb.Append(Seconds); + sb.Append('.'); + sb.Append(MicroSeconds); + sb.Append('s'); + + return sb.ToString(); + } + } +} \ No newline at end of file diff --git a/PacketDotNet/RawPacket.cs b/PacketDotNet/RawPacket.cs new file mode 100644 index 0000000..f192c20 --- /dev/null +++ b/PacketDotNet/RawPacket.cs @@ -0,0 +1,67 @@ +using System; + +namespace PacketDotNet +{ + /// + /// Raw packet as loaded from a pcap device or file + /// + public class RawPacket + { + /// + /// Link layer from which this packet was captured + /// + public LinkLayers LinkLayerType + { + get; + set; + } + + /// + /// The unix timeval when the packet was created + /// + public PosixTimeval Timeval + { + get; + set; + } + + /// Fetch data portion of the packet. + public virtual byte[] Data + { + get; + set; + } + + /// + /// Constructor + /// + /// + /// A + /// + /// + /// A + /// + /// + /// A + /// + public RawPacket(LinkLayers LinkLayerType, + PosixTimeval Timeval, + byte[] Data) + { + this.LinkLayerType = LinkLayerType; + this.Timeval = Timeval; + this.Data = Data; + } + + /// + /// ToString() override + /// + /// + /// A + /// + public override string ToString () + { + return string.Format("[RawPacket: LinkLayerType={0}, Timeval={1}, Data={2}]", LinkLayerType, Timeval, Data); + } + } +} diff --git a/PacketDotNet/SessionPacket.cs b/PacketDotNet/SessionPacket.cs new file mode 100644 index 0000000..41ed531 --- /dev/null +++ b/PacketDotNet/SessionPacket.cs @@ -0,0 +1,39 @@ +/* +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 PacketDotNet.Utils; + +namespace PacketDotNet +{ + /// + /// Session layer packet + /// + public abstract class SessionPacket : Packet + { + /// + /// Constructor + /// + /// + /// A + /// + public SessionPacket(PosixTimeval Timeval) : base(Timeval) + {} + } +} diff --git a/PacketDotNet/TcpFields.cs b/PacketDotNet/TcpFields.cs new file mode 100644 index 0000000..735e27a --- /dev/null +++ b/PacketDotNet/TcpFields.cs @@ -0,0 +1,95 @@ +/* +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; + +namespace PacketDotNet +{ + /// IP protocol field encoding information. + /// + /// + public struct TcpFields + { +#pragma warning disable 1591 + // flag bitmasks + public readonly static int TCP_CWR_MASK = 0x0080; + public readonly static int TCP_ECN_MASK = 0x0040; + public readonly static int TCP_URG_MASK = 0x0020; + public readonly static int TCP_ACK_MASK = 0x0010; + public readonly static int TCP_PSH_MASK = 0x0008; + public readonly static int TCP_RST_MASK = 0x0004; + public readonly static int TCP_SYN_MASK = 0x0002; + public readonly static int TCP_FIN_MASK = 0x0001; +#pragma warning restore 1591 + + /// Length of a TCP port in bytes. + public readonly static int PortLength = 2; + + /// Length of the sequence number in bytes. + public readonly static int SequenceNumberLength = 4; + /// Length of the acknowledgment number in bytes. + public readonly static int AckNumberLength = 4; + /// Length of the data offset and flags field in bytes. + public readonly static int DataOffsetLength = 1; + /// The length of the flags field + public readonly static int FlagsLength = 1; + /// Length of the window size field in bytes. + public readonly static int WindowSizeLength = 2; + /// Length of the checksum field in bytes. + public readonly static int ChecksumLength = 2; + /// Length of the urgent field in bytes. + public readonly static int UrgentPointerLength = 2; + + /// Position of the source port field. + public readonly static int SourcePortPosition = 0; + /// Position of the destination port field. + public readonly static int DestinationPortPosition; + /// Position of the sequence number field. + public readonly static int SequenceNumberPosition; + /// Position of the acknowledgment number field. + public readonly static int AckNumberPosition; + /// Position of the data offset + public readonly static int DataOffsetPosition; + /// Position of the flags field + public readonly static int FlagsPosition; + /// Position of the window size field. + public readonly static int WindowSizePosition; + /// Position of the checksum field. + public readonly static int ChecksumPosition; + /// Position of the urgent pointer field. + public readonly static int UrgentPointerPosition; + + /// Length in bytes of a TCP header. + public readonly static int HeaderLength; // == 20 + + static TcpFields() + { + DestinationPortPosition = PortLength; + SequenceNumberPosition = DestinationPortPosition + PortLength; + AckNumberPosition = SequenceNumberPosition + SequenceNumberLength; + DataOffsetPosition = AckNumberPosition + AckNumberLength; + FlagsPosition = DataOffsetPosition + DataOffsetLength; + WindowSizePosition = FlagsPosition + FlagsLength; + ChecksumPosition = WindowSizePosition + WindowSizeLength; + UrgentPointerPosition = ChecksumPosition + ChecksumLength; + HeaderLength = UrgentPointerPosition + UrgentPointerLength; + } + } +} diff --git a/PacketDotNet/TcpPacket.cs b/PacketDotNet/TcpPacket.cs new file mode 100644 index 0000000..d5307c2 --- /dev/null +++ b/PacketDotNet/TcpPacket.cs @@ -0,0 +1,645 @@ +/* +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 2010 Chris Morgan + */ + +using System; +using System.Collections.Generic; +using MiscUtil.Conversion; +using PacketDotNet.Utils; + +namespace PacketDotNet +{ + /// + /// TcpPacket + /// See: http://en.wikipedia.org/wiki/Transmission_Control_Protocol + /// + public class TcpPacket : TransportPacket + { +#if DEBUG + private static readonly log4net.ILog log = log4net.LogManager.GetLogger(System.Reflection.MethodBase.GetCurrentMethod().DeclaringType); +#else + // 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 + private static readonly ILogInactive log; +#pragma warning restore 0169 +#endif + + /// + /// 20 bytes is the smallest tcp header + /// + public const int HeaderMinimumLength = 20; + + /// Fetch the port number on the source host. + virtual public ushort SourcePort + { + get + { + return EndianBitConverter.Big.ToUInt16(header.Bytes, + header.Offset + TcpFields.SourcePortPosition); + } + + set + { + var theValue = value; + EndianBitConverter.Big.CopyBytes(theValue, + header.Bytes, + header.Offset + TcpFields.SourcePortPosition); + } + } + + /// Fetches the port number on the destination host. + virtual public ushort DestinationPort + { + get + { + return EndianBitConverter.Big.ToUInt16(header.Bytes, + header.Offset + TcpFields.DestinationPortPosition); + } + + set + { + var theValue = value; + EndianBitConverter.Big.CopyBytes(theValue, + header.Bytes, + header.Offset + TcpFields.DestinationPortPosition); + } + } + + /// Fetch the packet sequence number. + public uint SequenceNumber + { + get + { + return EndianBitConverter.Big.ToUInt32(header.Bytes, + header.Offset + TcpFields.SequenceNumberPosition); + } + + set + { + EndianBitConverter.Big.CopyBytes(value, + header.Bytes, + header.Offset + TcpFields.SequenceNumberPosition); + } + } + + /// Fetch the packet acknowledgment number. + public uint AcknowledgmentNumber + { + get + { + return EndianBitConverter.Big.ToUInt32(header.Bytes, + header.Offset + TcpFields.AckNumberPosition); + } + + set + { + EndianBitConverter.Big.CopyBytes(value, + header.Bytes, + header.Offset + TcpFields.AckNumberPosition); + } + } + + /// The size of the tcp header in 32bit words + virtual public int DataOffset + { + get + { + var theByte = header.Bytes[header.Offset + TcpFields.DataOffsetPosition]; + return (theByte >> 4) & 0xF; + } + + set + { + // read the original value + var theByte = header.Bytes[header.Offset + TcpFields.DataOffsetPosition]; + + // mask in the data offset value + theByte = (byte)((theByte & 0x0F) | ((value << 4) & 0xF0)); + + // write the value back + header.Bytes[header.Offset + TcpFields.DataOffsetPosition] = theByte; + } + } + + /// + /// The size of the receive window, which specifies the number of + /// bytes (beyond the sequence number in the acknowledgment field) that + /// the receiver is currently willing to receive. + /// + virtual public UInt16 WindowSize + { + get + { + return EndianBitConverter.Big.ToUInt16(header.Bytes, + header.Offset + TcpFields.WindowSizePosition); + } + + set + { + EndianBitConverter.Big.CopyBytes(value, + header.Bytes, + header.Offset + TcpFields.WindowSizePosition); + } + } + + /// + /// Tcp checksum field value of type UInt16 + /// + override public ushort Checksum + { + get + { + return EndianBitConverter.Big.ToUInt16(header.Bytes, + header.Offset + TcpFields.ChecksumPosition); + } + + set + { + var theValue = value; + EndianBitConverter.Big.CopyBytes(theValue, + header.Bytes, + header.Offset + TcpFields.ChecksumPosition); + } + } + + /// Check if the TCP packet is valid, checksum-wise. + public bool ValidChecksum + { + get + { + // IPv6 has no checksum so only the TCP checksum needs evaluation + if (parentPacket.GetType() == typeof(IPv6Packet)) + return ValidTCPChecksum; + // For IPv4 both the IP layer and the TCP layer contain checksums + else + return ((IPv4Packet)ParentPacket).ValidIPChecksum && ValidTCPChecksum; + } + } + + /// + /// True if the tcp checksum is valid + /// + virtual public bool ValidTCPChecksum + { + get + { + log.Debug("ValidTCPChecksum"); + var retval = IsValidChecksum(TransportPacket.TransportChecksumOption.AttachPseudoIPHeader); + log.DebugFormat("ValidTCPChecksum {0}", retval); + return retval; + } + } + + private int AllFlags + { + get + { + return header.Bytes[header.Offset + TcpFields.FlagsPosition]; + } + + set + { + header.Bytes[header.Offset + TcpFields.FlagsPosition] = (byte)value; + } + } + + /// Check the URG flag, flag indicates if the urgent pointer is valid. + virtual public bool Urg + { + get { return (AllFlags & TcpFields.TCP_URG_MASK) != 0; } + set { setFlag(value, TcpFields.TCP_URG_MASK); } + } + + /// Check the ACK flag, flag indicates if the ack number is valid. + virtual public bool Ack + { + get { return (AllFlags & TcpFields.TCP_ACK_MASK) != 0; } + set { setFlag(value, TcpFields.TCP_ACK_MASK); } + } + + /// Check the PSH flag, flag indicates the receiver should pass the + /// data to the application as soon as possible. + /// + virtual public bool Psh + { + get { return (AllFlags & TcpFields.TCP_PSH_MASK) != 0; } + set { setFlag(value, TcpFields.TCP_PSH_MASK); } + } + + /// Check the RST flag, flag indicates the session should be reset between + /// the sender and the receiver. + /// + virtual public bool Rst + { + get { return (AllFlags & TcpFields.TCP_RST_MASK) != 0; } + set { setFlag(value, TcpFields.TCP_RST_MASK); } + } + + /// Check the SYN flag, flag indicates the sequence numbers should + /// be synchronized between the sender and receiver to initiate + /// a connection. + /// + virtual public bool Syn + { + get { return (AllFlags & TcpFields.TCP_SYN_MASK) != 0; } + set { setFlag(value, TcpFields.TCP_SYN_MASK); } + } + + /// Check the FIN flag, flag indicates the sender is finished sending. + virtual public bool Fin + { + get { return (AllFlags & TcpFields.TCP_FIN_MASK) != 0; } + set { setFlag(value, TcpFields.TCP_FIN_MASK); } + } + + /// + /// ECN flag + /// + virtual public bool ECN + { + get { return (AllFlags & TcpFields.TCP_ECN_MASK) != 0; } + set { setFlag(value, TcpFields.TCP_ECN_MASK); } + } + + /// + /// CWR flag + /// + virtual public bool CWR + { + get { return (AllFlags & TcpFields.TCP_CWR_MASK) != 0; } + set { setFlag(value, TcpFields.TCP_CWR_MASK); } + } + + private void setFlag(bool on, int MASK) + { + if (on) + AllFlags = AllFlags | MASK; + else + AllFlags = AllFlags & ~MASK; + } + + /// Fetch ascii escape sequence of the color associated with this packet type. + override public System.String Color + { + get + { + return AnsiEscapeSequences.Yellow; + } + } + + /// + /// Create a new TCP packet from values + /// + public TcpPacket(ushort SourcePort, + ushort DestinationPort) : base(new PosixTimeval()) + { + log.Debug(""); + + // allocate memory for this packet + int offset = 0; + int length = TcpFields.HeaderLength; + var headerBytes = new byte[length]; + header = new ByteArraySegment(headerBytes, offset, length); + + // make this packet valid + DataOffset = length / 4; + + // set instance values + this.SourcePort = SourcePort; + this.DestinationPort = DestinationPort; + } + + /// + /// byte[]/int offset constructor, timeval defaults to the current time + /// + /// + /// A + /// + /// + /// A + /// + public TcpPacket(byte[] Bytes, int Offset) : + this(Bytes, Offset, new PosixTimeval()) + { + log.Debug(""); + } + + /// + /// byte[]/int offset/PosixTimeval constructor + /// + /// + /// A + /// + /// + /// A + /// + /// + /// A + /// + public TcpPacket(byte[] Bytes, int Offset, PosixTimeval Timeval) : + base(Timeval) + { + log.Debug(""); + + // set the header field, header field values are retrieved from this byte array + header = new ByteArraySegment(Bytes, Offset, Bytes.Length - Offset); + + // NOTE: we update the Length field AFTER the header field because + // we need the header to be valid to retrieve the value of DataOffset + header.Length = DataOffset * 4; + + // store the payload bytes + payloadPacketOrData = new PacketOrByteArraySegment(); + payloadPacketOrData.TheByteArraySegment = header.EncapsulatedBytes(); + } + + /// + /// Constructor when this packet is encapsulated in another packet + /// + /// + /// A + /// + /// + /// A + /// + /// + /// A + /// + /// + /// A + /// + public TcpPacket(byte[] Bytes, int Offset, PosixTimeval Timeval, + Packet ParentPacket) : + this(Bytes, Offset, Timeval) + { + log.DebugFormat("ParentPacket.GetType() {0}", ParentPacket.GetType()); + + this.ParentPacket = ParentPacket; + + // if the parent packet is an IPv4Packet we need to adjust + // the payload length because it is possible for us to have + // X bytes of data but only (X - Y) bytes are actually valid + if(this.ParentPacket is IPv4Packet) + { + // actual total length (tcp header + tcp payload) + var ipv4Parent = (IPv4Packet)this.ParentPacket; + var ipPayloadTotalLength = ipv4Parent.TotalLength - (ipv4Parent.HeaderLength * 4); + + log.DebugFormat("ipv4Parent.TotalLength {0}, ipv4Parent.HeaderLength {1}", + ipv4Parent.TotalLength, + ipv4Parent.HeaderLength * 4); + + var newTcpPayloadLength = ipPayloadTotalLength - this.Header.Length; + + log.DebugFormat("Header.Length {0}, Current payload length: {1}, new payload length {2}", + this.header.Length, + payloadPacketOrData.TheByteArraySegment.Length, + newTcpPayloadLength); + + // the length of the payload is the total payload length + // above, minus the length of the tcp header + payloadPacketOrData.TheByteArraySegment.Length = newTcpPayloadLength; + } + } + + /// Computes the TCP checksum, optionally updating the TCP checksum header. + /// + /// + /// The calculated TCP checksum. + public int CalculateTCPChecksum() + { + var newChecksum = CalculateChecksum(TransportChecksumOption.AttachPseudoIPHeader); + return newChecksum; + } + + /// + /// Update the checksum value. + /// + public void UpdateTCPChecksum() + { + this.Checksum = (ushort)CalculateTCPChecksum(); + } + + /// Fetch the urgent pointer. + public int UrgentPointer + { + get + { + return EndianBitConverter.Big.ToInt16(header.Bytes, + header.Offset + TcpFields.UrgentPointerPosition); + } + + set + { + var theValue = (Int16)value; + EndianBitConverter.Big.CopyBytes(theValue, + header.Bytes, + header.Offset + TcpFields.UrgentPointerPosition); + } + } + +#pragma warning disable 1591 + public enum OptionTypes + { + EndOfList = 0x0, + Nop = 0x1, + MaximumSegmentSize = 0x2, + WindowScale = 0x3, + SelectiveAckSupported = 0x4, + Unknown5 = 0x5, + Unknown6 = 0x6, + Unknown7 = 0x7, + Timestamp = 0x8 // http://en.wikipedia.org/wiki/Transmission_Control_Protocol#TCP_Timestamps + } +#pragma warning restore 1591 + + /// + /// Bytes that represent the tcp options + /// + /// + /// A + /// + public byte[] Options + { + get + { + if(Urg) + { + throw new System.NotImplementedException("Urg == true not implemented yet"); + } + + int optionsOffset = TcpFields.UrgentPointerPosition + TcpFields.UrgentPointerLength; + int optionsLength = (DataOffset * 4) - optionsOffset; + + byte[] optionBytes = new byte[optionsLength]; + Array.Copy(header.Bytes, header.Offset + optionsOffset, + optionBytes, 0, + optionsLength); + + return optionBytes; + } + } + + /// Convert this TCP packet to a readable string. + public override System.String ToString() + { + return ToColoredString(false); + } + + /// Generate string with contents describing this TCP packet. + /// whether or not the string should contain ansi + /// color escape sequences. + /// + public override System.String ToColoredString(bool colored) + { + System.Text.StringBuilder buffer = new System.Text.StringBuilder(); + buffer.Append('['); + if (colored) + buffer.Append(Color); + buffer.Append("TCPPacket"); + if (colored) + buffer.Append(AnsiEscapeSequences.Reset); + buffer.Append(": "); + buffer.Append(" SourcePort: "); + if(Enum.IsDefined(typeof(IpPort), (ushort)SourcePort)) + { + buffer.Append((IpPort)SourcePort); + buffer.Append(" (" + SourcePort + ") "); + } else + { + buffer.Append(SourcePort); + } + buffer.Append(" -> "); + buffer.Append(" DestinationPort: "); + if(Enum.IsDefined(typeof(IpPort), (ushort)DestinationPort)) + { + buffer.Append((IpPort)DestinationPort); + buffer.Append(" (" + DestinationPort + ") "); + } else + { + buffer.Append(DestinationPort); + } + + if (Urg) + buffer.Append(" urg[0x" + System.Convert.ToString(UrgentPointer, 16) + "]"); + if (Ack) + buffer.Append(" ack[" + AcknowledgmentNumber + " (0x" + System.Convert.ToString(AcknowledgmentNumber, 16) + ")]"); + if (Psh) + buffer.Append(" psh"); + if (Rst) + buffer.Append(" rst"); + if (Syn) + buffer.Append(" syn[0x" + System.Convert.ToString(SequenceNumber, 16) + "," + + SequenceNumber + "]"); + if (Fin) + buffer.Append(" fin"); + + //FIXME: not sure what to put here +// buffer.Append(" l=" + TCPHeaderLength + "," + PayloadDataLength); + buffer.Append(']'); + + // append the base class output + buffer.Append(base.ToColoredString(colored)); + + return buffer.ToString(); + } + + /// Convert this TCP packet to a verbose. + public override System.String ToColoredVerboseString(bool colored) + { + System.Text.StringBuilder buffer = new System.Text.StringBuilder(); + buffer.Append('['); + if (colored) + buffer.Append(Color); + buffer.Append("TCPPacket"); + if (colored) + buffer.Append(AnsiEscapeSequences.Reset); + buffer.Append(": "); + buffer.Append("sport=" + SourcePort + ", "); + buffer.Append("dport=" + DestinationPort + ", "); + buffer.Append("seqn=0x" + System.Convert.ToString(SequenceNumber, 16) + ", "); + buffer.Append("ackn=0x" + System.Convert.ToString(AcknowledgmentNumber, 16) + ", "); + //FIXME: what is header length now? +// buffer.Append("hlen=" + HeaderLength + ", "); + buffer.Append("urg=" + Urg + ", "); + buffer.Append("ack=" + Ack + ", "); + buffer.Append("psh=" + Psh + ", "); + buffer.Append("rst=" + Rst + ", "); + buffer.Append("syn=" + Syn + ", "); + buffer.Append("fin=" + Fin + ", "); + buffer.Append("wsize=" + WindowSize + ", "); + //FIXME: probably want to fix this one +// buffer.Append("sum=0x" + System.Convert.ToString(Checksum, 16)); +#if false + if (this.ValidTCPChecksum) + buffer.Append(" (correct), "); + else + buffer.Append(" (incorrect, should be " + ComputeTCPChecksum(false) + "), "); +#endif + buffer.Append("uptr=0x" + System.Convert.ToString(UrgentPointer, 16)); + buffer.Append(']'); + + // append the base class output + buffer.Append(base.ToColoredVerboseString(colored)); + + return buffer.ToString(); + } + + /// + /// Returns the TcpPacket embedded in Packet p or null if + /// there is no embedded TcpPacket + /// + public static TcpPacket GetEncapsulated(Packet p) + { + if(p is InternetLinkLayerPacket) + { + var payload = InternetLinkLayerPacket.GetInnerPayload((InternetLinkLayerPacket)p); + if(payload is IpPacket) + { + var innerPayload = payload.PayloadPacket; + if(innerPayload is TcpPacket) + { + return (TcpPacket)innerPayload; + } + } + } + + return null; + } + + /// + /// Create a randomized tcp packet with the given ip version + /// + /// + /// A + /// + public static TcpPacket RandomPacket() + { + var rnd = new Random(); + + // create a randomized TcpPacket + var srcPort = (ushort)rnd.Next(ushort.MinValue, ushort.MaxValue); + var dstPort = (ushort)rnd.Next(ushort.MinValue, ushort.MaxValue); + var tcpPacket = new TcpPacket(srcPort, dstPort); + + return tcpPacket; + } + } +} diff --git a/PacketDotNet/TransportPacket.cs b/PacketDotNet/TransportPacket.cs new file mode 100644 index 0000000..bd1e6c7 --- /dev/null +++ b/PacketDotNet/TransportPacket.cs @@ -0,0 +1,124 @@ +/* +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 . +*/ +using System; +using PacketDotNet.Utils; +using MiscUtil.Conversion; + +namespace PacketDotNet +{ + /// + /// Transport layer packet + /// + public abstract class TransportPacket : Packet + { +#if DEBUG + private static readonly log4net.ILog log = log4net.LogManager.GetLogger(System.Reflection.MethodBase.GetCurrentMethod().DeclaringType); +#else + // 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 + private static readonly ILogInactive log; +#pragma warning restore 0169 +#endif + /// + /// Constructor + /// + /// + /// A + /// + public TransportPacket(PosixTimeval Timeval) : base(Timeval) + { + } + + /// + /// The Checksum version + /// + public abstract ushort Checksum + { + get; + set; + } + + /// + /// Calculates the transport layer checksum, either for the + /// tcp or udp packet + /// + /// + /// + /// A + /// + internal int CalculateChecksum(TransportChecksumOption option) + { + // reset the checksum field (checksum is calculated when this field is + // zeroed) + Checksum = 0; + + // copy the tcp section with data + byte[] dataToChecksum = ((IpPacket)ParentPacket).PayloadPacket.Bytes; + + if (option == TransportChecksumOption.AttachPseudoIPHeader) + dataToChecksum = ((IpPacket)ParentPacket).AttachPseudoIPHeader(dataToChecksum); + + // calculate the one's complement sum of the tcp header + int cs = ChecksumUtils.OnesComplementSum(dataToChecksum); + return cs; + } + + /// + /// Determine if the transport layer checksum is valid + /// + /// + /// A + /// + /// + /// A + /// + public virtual bool IsValidChecksum(TransportChecksumOption option) + { + log.DebugFormat("option: {0}", option); + + var upperLayer = ((IpPacket)ParentPacket).PayloadPacket.Bytes; + + if (option == TransportChecksumOption.AttachPseudoIPHeader) + upperLayer = ((IpPacket)ParentPacket).AttachPseudoIPHeader(upperLayer); + + var onesSum = ChecksumUtils.OnesSum(upperLayer); + const int expectedOnesSum = 0xffff; + log.DebugFormat("onesSum {0} expected {1}", + onesSum, + expectedOnesSum); + + return (onesSum == expectedOnesSum); + } + + /// + /// Options for use when creating a transport layer checksum + /// + public enum TransportChecksumOption + { + /// + /// No extra options + /// + None, + + /// + /// Attach a pseudo IP header to the transport data being checksummed + /// + AttachPseudoIPHeader, + } + } +} diff --git a/PacketDotNet/TransportProtocols.cs b/PacketDotNet/TransportProtocols.cs new file mode 100644 index 0000000..02d2891 --- /dev/null +++ b/PacketDotNet/TransportProtocols.cs @@ -0,0 +1,10 @@ +namespace PacketDotNet +{ +#if false + public enum TransportProtocols : ushort + { + Tcp = 6, + Udp = 17 + }; +#endif +} diff --git a/PacketDotNet/UdpFields.cs b/PacketDotNet/UdpFields.cs new file mode 100644 index 0000000..a9bdc07 --- /dev/null +++ b/PacketDotNet/UdpFields.cs @@ -0,0 +1,53 @@ +/* +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 + */ +namespace PacketDotNet +{ + /// + /// Defines the lengths and positions of the udp fields within + /// a udp packet + /// + public struct UdpFields + { + /// Length of a UDP port in bytes. + public readonly static int PortLength = 2; + /// Length of the header length field in bytes. + public readonly static int HeaderLengthLength = 2; + /// Length of the checksum field in bytes. + public readonly static int ChecksumLength = 2; + /// Position of the source port. + public readonly static int SourcePortPosition = 0; + /// Position of the destination port. + public readonly static int DestinationPortPosition; + /// Position of the header length. + public readonly static int HeaderLengthPosition; + /// Position of the header checksum length. + public readonly static int ChecksumPosition; + /// Length of a UDP header in bytes. + public readonly static int HeaderLength; // == 8 + + static UdpFields() + { + DestinationPortPosition = PortLength; + HeaderLengthPosition = DestinationPortPosition + PortLength; + ChecksumPosition = HeaderLengthPosition + HeaderLengthLength; + HeaderLength = ChecksumPosition + ChecksumLength; + } + } +} diff --git a/PacketDotNet/UdpPacket.cs b/PacketDotNet/UdpPacket.cs new file mode 100644 index 0000000..f5c5f91 --- /dev/null +++ b/PacketDotNet/UdpPacket.cs @@ -0,0 +1,349 @@ +/* +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 PacketDotNet.Utils; +using MiscUtil.Conversion; + +namespace PacketDotNet +{ + /// + /// User datagram protocol + /// See http://en.wikipedia.org/wiki/Udp + /// + public class UdpPacket : TransportPacket + { +#if DEBUG + private static readonly log4net.ILog log = log4net.LogManager.GetLogger(System.Reflection.MethodBase.GetCurrentMethod().DeclaringType); +#else + // 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 + private static readonly ILogInactive log; +#pragma warning restore 0169 +#endif + + /// Fetch the port number on the source host. + virtual public ushort SourcePort + { + get + { + return EndianBitConverter.Big.ToUInt16(header.Bytes, header.Offset + UdpFields.SourcePortPosition); + } + + set + { + var val = value; + EndianBitConverter.Big.CopyBytes(val, header.Bytes, header.Offset + UdpFields.SourcePortPosition); + } + } + + /// Fetch the port number on the target host. + virtual public ushort DestinationPort + { + get + { + return EndianBitConverter.Big.ToUInt16(header.Bytes, + header.Offset + UdpFields.DestinationPortPosition); + } + + set + { + var val = value; + EndianBitConverter.Big.CopyBytes(val, + header.Bytes, + header.Offset + UdpFields.DestinationPortPosition); + } + } + + /// + /// Length in bytes of the header and payload, minimum size of 8, + /// the size of the Udp header + /// + virtual public int Length + { + get + { + return EndianBitConverter.Big.ToInt16(header.Bytes, + header.Offset + UdpFields.HeaderLengthPosition); + } + + // Internal because it is updated based on the payload when + // its bytes are retrieved + internal set + { + var val = (Int16)value; + EndianBitConverter.Big.CopyBytes(val, + header.Bytes, + header.Offset + UdpFields.HeaderLengthPosition); + } + } + + /// Fetch the header checksum. + override public ushort Checksum + { + get + { + return EndianBitConverter.Big.ToUInt16(header.Bytes, + header.Offset + UdpFields.ChecksumPosition); + } + + set + { + var val = value; + EndianBitConverter.Big.CopyBytes(val, + header.Bytes, + header.Offset + UdpFields.ChecksumPosition); + } + } + + /// Check if the UDP packet is valid, checksum-wise. + public bool ValidChecksum + { + get + { + // IPv6 has no checksum so only the TCP checksum needs evaluation + if (parentPacket.GetType() == typeof(IPv6Packet)) + return ValidUDPChecksum; + // For IPv4 both the IP layer and the TCP layer contain checksums + else + return ((IPv4Packet)ParentPacket).ValidIPChecksum && ValidUDPChecksum; + } + } + + /// + /// True if the udp checksum is valid + /// + virtual public bool ValidUDPChecksum + { + get + { + log.Debug("ValidUDPChecksum"); + var retval = IsValidChecksum(TransportPacket.TransportChecksumOption.AttachPseudoIPHeader); + log.DebugFormat("ValidUDPChecksum {0}", retval); + return retval; + } + } + + + /// Fetch ascii escape sequence of the color associated with this packet type. + override public System.String Color + { + get + { + return AnsiEscapeSequences.LightGreen; + } + } + + /// + /// Update the Udp length + /// + public override void UpdateCalculatedValues () + { + // update the length field based on the length of this packet header + // plus the length of all of the packets it contains + Length = TotalPacketLength; + } + + /// + /// Create from values + /// + /// + /// A + /// + /// + /// A + /// + public UdpPacket(ushort SourcePort, ushort DestinationPort) + : base(new PosixTimeval()) + { + log.Debug(""); + + // allocate memory for this packet + int offset = 0; + int length = UdpFields.HeaderLength; + var headerBytes = new byte[length]; + header = new ByteArraySegment(headerBytes, offset, length); + + // set instance values + this.SourcePort = SourcePort; + this.DestinationPort = DestinationPort; + } + + /// + /// byte[]/int offset constructor, timeval defaults to the current time + /// + /// + /// A + /// + /// + /// A + /// + public UdpPacket(byte[] Bytes, int Offset) : + this(Bytes, Offset, new PosixTimeval()) + { } + + /// + /// byte[]/int offset/PosixTimeval constructor + /// + /// + /// A + /// + /// + /// A + /// + /// + /// A + /// + public UdpPacket(byte[] Bytes, int Offset, PosixTimeval Timeval) : + base(Timeval) + { + // set the header field, header field values are retrieved from this byte array + header = new ByteArraySegment(Bytes, Offset, UdpFields.HeaderLength); + + // store the payload bytes + payloadPacketOrData = new PacketOrByteArraySegment(); + payloadPacketOrData.TheByteArraySegment = header.EncapsulatedBytes(); + } + + /// + /// Constructor when this packet is encapsulated in another packet + /// + /// + /// A + /// + /// + /// A + /// + /// + /// A + /// + /// + /// A + /// + public UdpPacket(byte[] Bytes, int Offset, PosixTimeval Timeval, + Packet ParentPacket) : + this(Bytes, Offset, Timeval) + { + this.ParentPacket = ParentPacket; + } + + /// + /// Calculates the UDP checksum, optionally updating the UDP checksum header. + /// + /// The calculated UDP checksum. + public int CalculateUDPChecksum() + { + var newChecksum = CalculateChecksum(TransportChecksumOption.AttachPseudoIPHeader); + return newChecksum; + } + + /// + /// Update the checksum value. + /// + public void UpdateUDPChecksum() + { + this.Checksum = (ushort)CalculateUDPChecksum(); + } + + /// Convert this UDP packet to a readable string. + public override System.String ToString() + { + return ToColoredString(false); + } + + /// Generate string with contents describing this UDP packet. + /// whether or not the string should contain ansi + /// color escape sequences. + /// + public override System.String ToColoredString(bool colored) + { + System.Text.StringBuilder buffer = new System.Text.StringBuilder(); + buffer.Append('['); + if (colored) + buffer.Append(Color); + buffer.Append("UDPPacket"); + if (colored) + buffer.Append(AnsiEscapeSequences.Reset); + buffer.Append(": "); + if(Enum.IsDefined(typeof(IpPort), SourcePort)) + { + buffer.Append((IpPort)SourcePort); + } else + { + buffer.Append(SourcePort); + } + buffer.Append(" -> "); + if(Enum.IsDefined(typeof(IpPort), DestinationPort)) + { + buffer.Append((IpPort)DestinationPort); + } else + { + buffer.Append(DestinationPort); + } + buffer.Append(" l=" + UdpFields.HeaderLengthLength + "," + (Length - UdpFields.HeaderLengthLength)); + buffer.Append(']'); + + return buffer.ToString(); + } + + /// + /// Returns the UdpPacket inside of the Packet p or null if + /// there is no encapsulated packet + /// + /// + /// A + /// + /// + /// A + /// + public static UdpPacket GetEncapsulated(Packet p) + { + if(p is InternetLinkLayerPacket) + { + var payload = InternetLinkLayerPacket.GetInnerPayload((InternetLinkLayerPacket)p); + if(payload is IpPacket) + { + var innerPayload = payload.PayloadPacket; + if(innerPayload is UdpPacket) + { + return (UdpPacket)innerPayload; + } + } + } + + return null; + } + + /// + /// Generate a random packet + /// + /// + /// A + /// + public static UdpPacket RandomPacket() + { + var rnd = new Random(); + var SourcePort = (ushort)rnd.Next(ushort.MinValue, ushort.MaxValue); + var DestinationPort = (ushort)rnd.Next(ushort.MinValue, ushort.MaxValue); + + return new UdpPacket(SourcePort, DestinationPort); + } + } +} diff --git a/PacketDotNet/Utils/AnsiEscapeSequences.cs b/PacketDotNet/Utils/AnsiEscapeSequences.cs new file mode 100644 index 0000000..1878316 --- /dev/null +++ b/PacketDotNet/Utils/AnsiEscapeSequences.cs @@ -0,0 +1,104 @@ +// ************************************************************************ +// Copyright (C) 2001, Patrick Charles and Jonas Lehmann * +// Distributed under the Mozilla Public License * +// http://www.mozilla.org/NPL/MPL-1.1.txt * +// ************************************************************************* +/* + * Copyright 2009 Chris Morgan + */ + +using System; + +namespace PacketDotNet.Utils +{ + /// String constants for color console output. + ///

+ /// This file contains control sequences to print color text on a text + /// console capable of interpreting and displaying control sequences. + ///

+ ///

+ /// A capable console would be + /// unix bash, os/2 shell, or command.com w/ ansi.sys loaded + ///

+ ///
+ /// Chris Cheetham + /// + public class AnsiEscapeSequences + { + /// + /// Delimits the start of an ansi color sequence, the color code goes after this + /// + public readonly static String EscapeBegin; + /// + /// Delimits the stop of the ansi color sequence, the color code comes before this + /// + public readonly static String EscapeEnd = "m"; + +#pragma warning disable 1591 + public readonly static String Reset; + public readonly static String Bold; + public readonly static String Underline; + public readonly static String Inverse; + public readonly static String Black; + public readonly static String Blue; + public readonly static String Green; + public readonly static String Cyan; + public readonly static String Red; + public readonly static String Purple; + public readonly static String Brown; + public readonly static String LightGray; + public readonly static String DarkGray; + public readonly static String LightBlue; + public readonly static String LightGreen; + public readonly static String LightCyan; + public readonly static String LightRed; + public readonly static String LightPurple; + public readonly static String Yellow; + public readonly static String White; + public readonly static String RedBackground; + public readonly static String GreenBackground; + public readonly static String YellowBackground; + public readonly static String BlueBackground; + public readonly static String PurpleBackground; + public readonly static String CyanBackground; + public readonly static String LightGrayBackground; +#pragma warning restore 1591 + + private static string BuildValue(string ColorCode) + { + return EscapeBegin + ColorCode + EscapeEnd; + } + + static AnsiEscapeSequences() + { + EscapeBegin = "" + (char) 27 + "["; + Reset = BuildValue("0"); + Bold = BuildValue("0;1"); + Underline = BuildValue("0;4"); + Inverse = BuildValue("0;7"); + Black = BuildValue("0;30"); + Blue = BuildValue("0;34"); + Green = BuildValue("0;32"); + Cyan = BuildValue("0;36"); + Red = BuildValue("0;31"); + Purple = BuildValue("0;35"); + Brown = BuildValue("0;33"); + LightGray = BuildValue("0;37"); + DarkGray = BuildValue("1;30"); + LightBlue = BuildValue("1;34"); + LightGreen = BuildValue("1;32"); + LightCyan = BuildValue("1;36"); + LightRed = BuildValue("1;31"); + LightPurple = BuildValue("1;35"); + Yellow = BuildValue("1;33"); + White = BuildValue("1;37"); + RedBackground = BuildValue("0;41"); + GreenBackground = BuildValue("0;42"); + YellowBackground = BuildValue("0;43"); + BlueBackground = BuildValue("0;44"); + PurpleBackground = BuildValue("0;45"); + CyanBackground = BuildValue("0;46"); + LightGrayBackground = BuildValue("0;47"); + } + } +} \ No newline at end of file diff --git a/PacketDotNet/Utils/ByteArraySegment.cs b/PacketDotNet/Utils/ByteArraySegment.cs new file mode 100644 index 0000000..4be2572 --- /dev/null +++ b/PacketDotNet/Utils/ByteArraySegment.cs @@ -0,0 +1,167 @@ +/* +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 . +*/ +using System; + +namespace PacketDotNet.Utils +{ + /// + /// Container class that refers to a segment of bytes in a byte[] + /// + public class ByteArraySegment + { +#if DEBUG + private static readonly log4net.ILog log = log4net.LogManager.GetLogger(System.Reflection.MethodBase.GetCurrentMethod().DeclaringType); +#else + // 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 + private static readonly ILogInactive log; +#pragma warning restore 0169 +#endif + + private int length; + + /// + /// Number of bytes beyond the offset into Bytes + /// + public int Length + { + get { return length; } + internal set + { + // check for invalid values + if(value < 0) + throw new System.InvalidOperationException("attempting to set a negative length of " + value); + + length = value; + log.DebugFormat("Length: {0}", value); + } + } + + /// + /// The byte[] array + /// + public byte[] Bytes { get; private set; } + + /// + /// Offset into Bytes + /// + public int Offset { get; private set; } + + /// + /// Constructor from a byte array, offset into the byte array and + /// a length beyond that offset of the bytes this class is referencing + /// + /// + /// A + /// + /// + /// A + /// + /// + /// A + /// + public ByteArraySegment(byte[] Bytes, int Offset, int Length) + { + log.DebugFormat("Bytes.Length {0}, Offset {1}, Length {2}", + Bytes.Length, + Offset, + Length); + + this.Bytes = Bytes; + this.Offset = Offset; + this.Length = Length; + } + + /// + /// Returns a contiguous byte[] from this container, if necessary, by copying + /// the bytes from the current offset into a newly allocated byte[]. + /// NeedsCopyForActualBytes can be used to determine if the copy is necessary + /// + /// + /// + /// A + /// + public byte[] ActualBytes() + { + log.DebugFormat("{0}", ToString()); + + if(NeedsCopyForActualBytes) + { + log.Debug("needs copy"); + var newBytes = new byte[Length]; + Array.Copy(Bytes, Offset, newBytes, 0, Length); + return newBytes; + } else + { + log.Debug("does not need copy"); + return Bytes; + } + } + + /// + /// Return true if we need to perform a copy to get + /// the bytes represented by this class + /// + /// + /// A + /// + public bool NeedsCopyForActualBytes + { + get + { + // we need a copy unless we are at the start of the byte[] + // and the length is the total byte[] length + var okWithoutCopy = ((Offset == 0) && (Length == Bytes.Length)); + var retval = !okWithoutCopy; + + log.DebugFormat("retval {0}", retval); + + return retval; + } + } + + /// + /// Helper method that returns the segment immediately following + /// this instance, useful for processing where the parent + /// wants to pass the next segment to a sub class for processing + /// + /// + /// A + /// + public ByteArraySegment EncapsulatedBytes() + { + int startingOffset = Offset + Length; // start at the end of the current segment + var newLength = Bytes.Length - startingOffset; + log.DebugFormat("Offset {0}, Length {1}, startingOffset {2}, newLength {3}", + Offset, Length, startingOffset, newLength); + return new ByteArraySegment(Bytes, startingOffset, newLength); + } + + /// + /// Format the class information as a string + /// + /// + /// A + /// + public override string ToString () + { + return string.Format("[ByteArraySegment: Length={0}, Bytes.Length={1}, Offset={2}, NeedsCopyForActualBytes={3}]", + Length, Bytes.Length, Offset, NeedsCopyForActualBytes); + } + } +} diff --git a/PacketDotNet/Utils/ChecksumUtils.cs b/PacketDotNet/Utils/ChecksumUtils.cs new file mode 100644 index 0000000..dc97752 --- /dev/null +++ b/PacketDotNet/Utils/ChecksumUtils.cs @@ -0,0 +1,119 @@ +/* +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 2005 Tamir Gal + * Copyright 2008-2009 Chris Morgan + * Copyright 2008-2009 Phillip Lemon + */ + +using System; +using System.IO; + +namespace PacketDotNet.Utils +{ + /// + /// Computes the one's sum on a byte array. + /// Based TCP/IP Illustrated Vol. 2(1995) by Gary R. Wright and W. Richard + /// Stevens. Page 236. And on http://www.cs.utk.edu/~cs594np/unp/checksum.html + /// + + /* + * taken from TCP/IP Illustrated Vol. 2(1995) by Gary R. Wright and W. + * Richard Stevens. Page 236 + */ + public sealed class ChecksumUtils + { + private ChecksumUtils() { } + + /// + /// Computes the one's complement sum on a byte array + /// + public static int OnesComplementSum(byte[] bytes) + { + //just complement the one's sum + return OnesComplementSum(bytes, 0, bytes.Length); + } + + /// + /// Computes the one's complement sum on a byte array + /// + public static int OnesComplementSum(byte[] bytes, int start, int len) + { + //just complement the one's sum + return (~OnesSum(bytes, start, len)) & 0xFFFF; + } + + /// + /// Compute a ones sum of a byte array + /// + /// + /// A + /// + /// + /// A + /// + public static int OnesSum(byte[] bytes) + { + return OnesSum(bytes, 0, bytes.Length); + } + + /// + /// 16 bit sum of all values + /// http://en.wikipedia.org/wiki/Signed_number_representations#Ones.27_complement + /// + /// + /// A + /// + /// + /// A + /// + /// + /// A + /// + /// + /// A + /// + public static int OnesSum(byte[] bytes, int start, int len) + { + MemoryStream memStream = new MemoryStream(bytes, start, len); + BinaryReader br = new BinaryReader(memStream); + Int32 sum = 0; + + UInt16 val; + + while (memStream.Position < memStream.Length -1) + { + val = (UInt16)System.Net.IPAddress.NetworkToHostOrder(br.ReadInt16()); + sum += val; + } + + // if we have a remaining byte we should add it + if (memStream.Position < len) + { + sum += br.ReadByte(); + } + + // fold the sum into 16 bits + while((sum >> 16) != 0) + { + sum = (sum & 0xffff) + (sum >> 16); + } + + return sum; + } + } +} \ No newline at end of file diff --git a/PacketDotNet/Utils/HexPrinter.cs b/PacketDotNet/Utils/HexPrinter.cs new file mode 100644 index 0000000..e272b15 --- /dev/null +++ b/PacketDotNet/Utils/HexPrinter.cs @@ -0,0 +1,41 @@ +using System; +using System.Text; + +namespace PacketDotNet.Utils +{ + /// + /// Helper class that prints out an array of hex values + /// + public class HexPrinter + { + /// + /// Create a string that contains the hex values of byte[] Byte in + /// text form + /// + /// + /// A + /// + /// + /// A + /// + /// + /// A + /// + /// + /// A + /// + public static string GetString(byte[] Byte, + int Offset, + int Length) + { + StringBuilder sb = new StringBuilder(); + + for(int i = Offset; i < Offset + Length; i++) + { + sb.AppendFormat("[{0:x2}]", Byte[i]); + } + + return sb.ToString(); + } + } +} diff --git a/PacketDotNet/Utils/RandomUtils.cs b/PacketDotNet/Utils/RandomUtils.cs new file mode 100644 index 0000000..8936abc --- /dev/null +++ b/PacketDotNet/Utils/RandomUtils.cs @@ -0,0 +1,40 @@ +using System; + +namespace PacketDotNet.Utils +{ + /// + /// Random utility methods + /// + public class RandomUtils + { + /// + /// Generate a random ip address + /// + /// + /// A + /// + /// + /// A + /// + public static System.Net.IPAddress GetIPAddress(IpVersion version) + { + var rnd = new Random(); + byte[] randomAddressBytes; + + if(version == IpVersion.IPv4) + { + randomAddressBytes = new byte[IPv4Fields.AddressLength]; + rnd.NextBytes(randomAddressBytes); + } else if(version == IpVersion.IPv6) + { + randomAddressBytes = new byte[IPv6Fields.AddressLength]; + rnd.NextBytes(randomAddressBytes); + } else + { + throw new System.InvalidOperationException("Unknown version of " + version); + } + + return new System.Net.IPAddress(randomAddressBytes); + } + } +}