601 lines
26 KiB
C#
601 lines
26 KiB
C#
/*
|
|
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 <http://www.gnu.org/licenses/>.
|
|
*/
|
|
/*
|
|
* Copyright 2010 Chris Morgan <chmorgan@gmail.com>
|
|
*/
|
|
using System;
|
|
using System.Net.NetworkInformation;
|
|
using PacketDotNet.Utils;
|
|
using MiscUtil.Conversion;
|
|
using System.IO;
|
|
|
|
namespace PacketDotNet
|
|
{
|
|
namespace Ieee80211
|
|
{
|
|
/// <summary>
|
|
/// Base class of all 802.11 frame types
|
|
/// </summary>
|
|
public abstract class MacFrame : Packet
|
|
{
|
|
// NOTE: No need to warn about lack of use, the compiler won't
|
|
// put any calls to 'log' here but we need 'log' to exist to compile
|
|
#pragma warning disable 0169, 0649
|
|
private static readonly ILogInactive log;
|
|
#pragma warning restore 0169, 0649
|
|
|
|
private int GetOffsetForAddress(int addressIndex)
|
|
{
|
|
int offset = header.Offset;
|
|
|
|
offset += MacFields.Address1Position + MacFields.AddressLength * addressIndex;
|
|
|
|
// the 4th address is AFTER the sequence control field so we need to skip past that
|
|
// field
|
|
if (addressIndex == 4)
|
|
offset += MacFields.SequenceControlLength;
|
|
|
|
return offset;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Frame control bytes are the first two bytes of the frame
|
|
/// </summary>
|
|
protected UInt16 FrameControlBytes
|
|
{
|
|
get
|
|
{
|
|
if(header.Length >= (MacFields.FrameControlPosition + MacFields.FrameControlLength))
|
|
{
|
|
return EndianBitConverter.Big.ToUInt16 (header.Bytes,
|
|
header.Offset);
|
|
}
|
|
else
|
|
{
|
|
return 0;
|
|
}
|
|
}
|
|
|
|
set
|
|
{
|
|
EndianBitConverter.Big.CopyBytes(value,
|
|
header.Bytes,
|
|
header.Offset);
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Frame control field
|
|
/// </summary>
|
|
public FrameControlField FrameControl
|
|
{
|
|
get;
|
|
set;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Duration bytes are the third and fourth bytes of the frame
|
|
/// </summary>
|
|
protected UInt16 DurationBytes
|
|
{
|
|
get
|
|
{
|
|
if(header.Length >= (MacFields.DurationIDPosition + MacFields.DurationIDLength))
|
|
{
|
|
return EndianBitConverter.Little.ToUInt16(header.Bytes,
|
|
header.Offset + MacFields.DurationIDPosition);
|
|
}
|
|
else
|
|
{
|
|
return 0;
|
|
}
|
|
}
|
|
|
|
set
|
|
{
|
|
EndianBitConverter.Little.CopyBytes(value,
|
|
header.Bytes,
|
|
header.Offset + MacFields.DurationIDPosition);
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Gets or sets the duration value. The value represents the number of microseconds
|
|
/// the the wireless medium is expected to remain busy.
|
|
/// </summary>
|
|
/// <value>
|
|
/// The duration field value
|
|
/// </value>
|
|
public DurationField Duration {get; set;}
|
|
|
|
/// <summary>
|
|
/// Writes the address into the specified address position.
|
|
/// </summary>
|
|
/// <remarks>The number of valid address positions in a MAC frame is determined by
|
|
/// the type of frame. There are between 1 and 4 address fields in MAC frames</remarks>
|
|
/// <param name="addressIndex">Zero based address to look up</param>
|
|
/// <param name="address"></param>
|
|
protected void SetAddress(int addressIndex, PhysicalAddress address)
|
|
{
|
|
var offset = GetOffsetForAddress(addressIndex);
|
|
SetAddressByOffset(offset, address);
|
|
}
|
|
|
|
|
|
/// <summary>
|
|
/// Writes the provided address into the backing <see cref="PacketDotNet.Utils.ByteArraySegment"/>
|
|
/// starting at the provided offset.
|
|
/// </summary>
|
|
/// <param name='offset'>
|
|
/// The position where the address should start to be copied
|
|
/// </param>
|
|
/// <param name='address'>
|
|
/// Address.
|
|
/// </param>
|
|
protected void SetAddressByOffset(int offset, PhysicalAddress address)
|
|
{
|
|
byte[] hwAddress = null;
|
|
//We will replace no address with a MAC of all zer
|
|
if(address == PhysicalAddress.None)
|
|
{
|
|
hwAddress = new byte[]{0, 0, 0, 0, 0, 0};
|
|
}
|
|
else
|
|
{
|
|
hwAddress = address.GetAddressBytes();
|
|
}
|
|
|
|
// using the offset, set the address
|
|
if (hwAddress.Length != MacFields.AddressLength)
|
|
{
|
|
throw new System.InvalidOperationException("address length " + hwAddress.Length
|
|
+ " not equal to the expected length of "
|
|
+ MacFields.AddressLength);
|
|
}
|
|
|
|
Array.Copy(hwAddress, 0, header.Bytes, offset,
|
|
hwAddress.Length);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Gets the address. There can be up to four addresses in a MacFrame depending on its type.
|
|
/// </summary>
|
|
/// <returns>
|
|
/// The address.
|
|
/// </returns>
|
|
/// <param name='addressIndex'>
|
|
/// Address index.
|
|
/// </param>
|
|
protected PhysicalAddress GetAddress(int addressIndex)
|
|
{
|
|
var offset = GetOffsetForAddress(addressIndex);
|
|
return GetAddressByOffset(offset);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Gets an address by offset.
|
|
/// </summary>
|
|
/// <returns>
|
|
/// The address as the specified index.
|
|
/// </returns>
|
|
/// <param name='offset'>
|
|
/// The offset into the packet buffer at which to start parsing the address.
|
|
/// </param>
|
|
protected PhysicalAddress GetAddressByOffset(int offset)
|
|
{
|
|
if((header.Offset + header.Length) >= (offset + MacFields.AddressLength))
|
|
{
|
|
byte[] hwAddress = new byte[MacFields.AddressLength];
|
|
Array.Copy(header.Bytes, offset,
|
|
hwAddress, 0, hwAddress.Length);
|
|
return new PhysicalAddress(hwAddress);
|
|
}
|
|
else
|
|
{
|
|
return PhysicalAddress.None;
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Frame check sequence, the last thing in the 802.11 mac packet
|
|
/// </summary>
|
|
public UInt32 FrameCheckSequence { get; set; }
|
|
|
|
/// <summary>
|
|
/// Recalculates and updates the frame check sequence.
|
|
/// </summary>
|
|
/// <remarks>After calling this method the FCS will be valud regardless of what the packet contains.</remarks>
|
|
public void UpdateFrameCheckSequence ()
|
|
{
|
|
var bytes = Bytes;
|
|
var length = (AppendFcs) ? bytes.Length - 4 : bytes.Length;
|
|
FrameCheckSequence = (uint) Crc32.Compute(Bytes, 0, length);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Length of the frame header.
|
|
///
|
|
/// This does not include the FCS, it represents only the header bytes that would
|
|
/// would preceed any payload.
|
|
/// </summary>
|
|
public abstract int FrameSize { get; }
|
|
|
|
/// <summary>
|
|
/// Returns the number of bytes of payload data currently available in
|
|
/// the buffer.
|
|
/// </summary>
|
|
/// <remarks>This method is used to work out how much space there is for the payload in the
|
|
/// underlying ByteArraySegment. To find out the length of
|
|
/// actual payload assigned to the packet use PayloadData.Length.</remarks>
|
|
/// <value>
|
|
/// The number of bytes of space available after the header for payload data.
|
|
/// </value>
|
|
protected int GetAvailablePayloadLength()
|
|
{
|
|
int payloadLength = header.BytesLength - (header.Offset + FrameSize);
|
|
return (payloadLength > 0) ? payloadLength : 0;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Parses the <see cref="PacketDotNet.Utils.ByteArraySegment"/> into a MacFrame.
|
|
/// </summary>
|
|
/// <returns>
|
|
/// The parsed MacFrame or null if it could not be parsed.
|
|
/// </returns>
|
|
/// <param name='bas'>
|
|
/// The bytes of the packet. bas.Offset should point to the first byte in the mac frame.
|
|
/// </param>
|
|
/// <remarks>If the provided bytes dont contain the FCS then call <see cref="MacFrame.ParsePacket"/> instead. The presence of the
|
|
/// FCS is usually determined by configuration of the device used to capture the packets.</remarks>
|
|
public static MacFrame ParsePacketWithFcs (ByteArraySegment bas)
|
|
{
|
|
if (bas.Length < (MacFields.FrameControlLength + MacFields.FrameCheckSequenceLength))
|
|
{
|
|
//There isn't enough data for there to be an FCS and a packet
|
|
return null;
|
|
}
|
|
|
|
//remove the FCS from the buffer that we will pass to the packet parsers
|
|
ByteArraySegment basWithoutFcs = new ByteArraySegment (bas.Bytes,
|
|
bas.Offset,
|
|
bas.Length - MacFields.FrameCheckSequenceLength,
|
|
bas.BytesLength - MacFields.FrameCheckSequenceLength);
|
|
|
|
UInt32 fcs = EndianBitConverter.Big.ToUInt32 (bas.Bytes,
|
|
(bas.Offset + bas.Length) - MacFields.FrameCheckSequenceLength);
|
|
|
|
MacFrame frame = ParsePacket (basWithoutFcs);
|
|
if (frame != null)
|
|
{
|
|
frame.AppendFcs = true;
|
|
frame.FrameCheckSequence = fcs;
|
|
}
|
|
|
|
return frame;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Parses the <see cref="PacketDotNet.Utils.ByteArraySegment"/> into a MacFrame.
|
|
/// </summary>
|
|
/// <returns>
|
|
/// The parsed MacFrame or null if it could not be parsed.
|
|
/// </returns>
|
|
/// <param name='bas'>
|
|
/// The bytes of the packet. bas.Offset should point to the first byte in the mac frame.
|
|
/// </param>
|
|
/// <remarks>If the provided bytes contain the FCS then call <see cref="MacFrame.ParsePacketWithFcs"/> instead. The presence of the
|
|
/// FCS is usually determined by configuration of the device used to capture the packets.</remarks>
|
|
public static MacFrame ParsePacket (ByteArraySegment bas)
|
|
{
|
|
if (bas.Length < MacFields.FrameControlLength)
|
|
{
|
|
//there isn't enough data to even try and work out what type of packet it is
|
|
return null;
|
|
}
|
|
//this is a bit ugly as we will end up parsing the framecontrol field twice, once here and once
|
|
//inside the packet constructor. Could create the framecontrol and pass it to the packet but I think that is equally ugly
|
|
FrameControlField frameControl = new FrameControlField (
|
|
EndianBitConverter.Big.ToUInt16 (bas.Bytes, bas.Offset));
|
|
|
|
MacFrame macFrame = null;
|
|
|
|
switch (frameControl.SubType)
|
|
{
|
|
case FrameControlField.FrameSubTypes.ManagementAssociationRequest:
|
|
{
|
|
macFrame = new AssociationRequestFrame (bas);
|
|
break;
|
|
}
|
|
case FrameControlField.FrameSubTypes.ManagementAssociationResponse:
|
|
{
|
|
macFrame = new AssociationResponseFrame (bas);
|
|
break;
|
|
}
|
|
case FrameControlField.FrameSubTypes.ManagementReassociationRequest:
|
|
{
|
|
macFrame = new ReassociationRequestFrame (bas);
|
|
break;
|
|
}
|
|
case FrameControlField.FrameSubTypes.ManagementReassociationResponse:
|
|
{
|
|
macFrame = new AssociationResponseFrame (bas);
|
|
break;
|
|
}
|
|
case FrameControlField.FrameSubTypes.ManagementProbeRequest:
|
|
{
|
|
macFrame = new ProbeRequestFrame (bas);
|
|
break;
|
|
}
|
|
case FrameControlField.FrameSubTypes.ManagementProbeResponse:
|
|
{
|
|
macFrame = new ProbeResponseFrame (bas);
|
|
break;
|
|
}
|
|
case FrameControlField.FrameSubTypes.ManagementReserved0:
|
|
break; //TODO
|
|
case FrameControlField.FrameSubTypes.ManagementReserved1:
|
|
break; //TODO
|
|
case FrameControlField.FrameSubTypes.ManagementBeacon:
|
|
{
|
|
macFrame = new BeaconFrame (bas);
|
|
break;
|
|
}
|
|
case FrameControlField.FrameSubTypes.ManagementATIM:
|
|
break; //TODO
|
|
case FrameControlField.FrameSubTypes.ManagementDisassociation:
|
|
{
|
|
macFrame = new DisassociationFrame (bas);
|
|
break;
|
|
}
|
|
case FrameControlField.FrameSubTypes.ManagementAuthentication:
|
|
{
|
|
macFrame = new AuthenticationFrame (bas);
|
|
break;
|
|
}
|
|
case FrameControlField.FrameSubTypes.ManagementDeauthentication:
|
|
{
|
|
macFrame = new DeauthenticationFrame (bas);
|
|
break;
|
|
}
|
|
case FrameControlField.FrameSubTypes.ManagementAction:
|
|
{
|
|
macFrame = new ActionFrame (bas);
|
|
break;
|
|
}
|
|
case FrameControlField.FrameSubTypes.ManagementReserved3:
|
|
break; //TODO
|
|
case FrameControlField.FrameSubTypes.ControlBlockAcknowledgmentRequest:
|
|
{
|
|
macFrame = new BlockAcknowledgmentRequestFrame (bas);
|
|
break;
|
|
}
|
|
case FrameControlField.FrameSubTypes.ControlBlockAcknowledgment:
|
|
{
|
|
macFrame = new BlockAcknowledgmentFrame (bas);
|
|
break;
|
|
}
|
|
case FrameControlField.FrameSubTypes.ControlPSPoll:
|
|
break; //TODO
|
|
case FrameControlField.FrameSubTypes.ControlRTS:
|
|
{
|
|
macFrame = new RtsFrame (bas);
|
|
break;
|
|
}
|
|
case FrameControlField.FrameSubTypes.ControlCTS:
|
|
{
|
|
macFrame = new CtsFrame (bas);
|
|
break;
|
|
}
|
|
case FrameControlField.FrameSubTypes.ControlACK:
|
|
{
|
|
macFrame = new AckFrame (bas);
|
|
break;
|
|
}
|
|
case FrameControlField.FrameSubTypes.ControlCFEnd:
|
|
{
|
|
macFrame = new ContentionFreeEndFrame (bas);
|
|
break;
|
|
}
|
|
case FrameControlField.FrameSubTypes.ControlCFEndCFACK:
|
|
break; //TODO
|
|
case FrameControlField.FrameSubTypes.Data:
|
|
case FrameControlField.FrameSubTypes.DataCFACK:
|
|
case FrameControlField.FrameSubTypes.DataCFPoll:
|
|
case FrameControlField.FrameSubTypes.DataCFAckCFPoll:
|
|
{
|
|
macFrame = new DataDataFrame (bas);
|
|
break;
|
|
}
|
|
case FrameControlField.FrameSubTypes.DataNullFunctionNoData:
|
|
case FrameControlField.FrameSubTypes.DataCFAckNoData:
|
|
case FrameControlField.FrameSubTypes.DataCFPollNoData:
|
|
case FrameControlField.FrameSubTypes.DataCFAckCFPollNoData:
|
|
{
|
|
macFrame = new NullDataFrame (bas);
|
|
break;
|
|
}
|
|
case FrameControlField.FrameSubTypes.QosData:
|
|
case FrameControlField.FrameSubTypes.QosDataAndCFAck:
|
|
case FrameControlField.FrameSubTypes.QosDataAndCFPoll:
|
|
case FrameControlField.FrameSubTypes.QosDataAndCFAckAndCFPoll:
|
|
{
|
|
macFrame = new QosDataFrame (bas);
|
|
break;
|
|
}
|
|
case FrameControlField.FrameSubTypes.QosNullData:
|
|
case FrameControlField.FrameSubTypes.QosCFAck:
|
|
case FrameControlField.FrameSubTypes.QosCFPoll:
|
|
case FrameControlField.FrameSubTypes.QosCFAckAndCFPoll:
|
|
{
|
|
macFrame = new QosNullDataFrame (bas);
|
|
break;
|
|
}
|
|
default:
|
|
//this is an unsupported (and unknown) packet type
|
|
break;
|
|
}
|
|
|
|
return macFrame;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Calculates the FCS value for the provided bytes and compates it to the FCS value passed to the method.
|
|
/// </summary>
|
|
/// <returns>
|
|
/// true if the FCS for the provided bytes matches the FCS passed in, false if not.
|
|
/// </returns>
|
|
/// <param name='data'>
|
|
/// The byte array for which the FCS will be calculated.
|
|
/// </param>
|
|
/// <param name='offset'>
|
|
/// The offset into data of the first byte to be covered by the FCS.
|
|
/// </param>
|
|
/// <param name='length'>
|
|
/// The number of bytes to calculate the FCS for.
|
|
/// </param>
|
|
/// <param name='fcs'>
|
|
/// The FCS to compare to the one calculated for the provided data.
|
|
/// </param>
|
|
/// <remarks>This method can be used to check the validity of a packet before attempting to parse it with either
|
|
/// <see cref="MacFrame.ParsePacket"/> or <see cref="MacFrame.ParsePacketWithFcs"/>. Attempting to parse a corrupted buffer
|
|
/// using these methods could cause unexpected exceptions.</remarks>
|
|
public static bool PerformFcsCheck (Byte[] data, int offset, int length, UInt32 fcs)
|
|
{
|
|
// Cast to uint for proper comparison to FrameCheckSequence
|
|
var check = (uint)Crc32.Compute(data, offset, length);
|
|
return check == fcs;
|
|
}
|
|
|
|
/// <summary>
|
|
/// FCSs the valid.
|
|
/// </summary>
|
|
/// <returns>
|
|
/// The valid.
|
|
/// </returns>
|
|
public bool FCSValid
|
|
{
|
|
get
|
|
{
|
|
var packetBytes = Bytes;
|
|
var packetLength = (AppendFcs) ? packetBytes.Length - MacFields.FrameCheckSequenceLength : packetBytes.Length;
|
|
return PerformFcsCheck(packetBytes, 0, packetLength, FrameCheckSequence);
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Gets or sets a value indicating whether this <see cref="PacketDotNet.Ieee80211.MacFrame"/> should include an FCS at the end
|
|
/// of the array returned by Bytes.
|
|
/// </summary>
|
|
/// <value>
|
|
/// <c>true</c> if append FCS should be appended; otherwise, <c>false</c>.
|
|
/// </value>
|
|
public bool AppendFcs { get; set; }
|
|
|
|
|
|
/// <value>
|
|
/// 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.
|
|
/// </value>
|
|
public override 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
|
|
var totalPacketLength = TotalPacketLength;
|
|
if(SharesMemoryWithSubPackets &&
|
|
((!AppendFcs) || (header.Bytes.Length >= (header.Offset + totalPacketLength + MacFields.FrameCheckSequenceLength))))
|
|
{
|
|
var packetLength = totalPacketLength;
|
|
if(AppendFcs)
|
|
{
|
|
packetLength += MacFields.FrameCheckSequenceLength;
|
|
//We need to update the FCS field because this couldn't be done during
|
|
//RecursivelyUpdateCalculatedValues because we didn't know where it would be
|
|
EndianBitConverter.Big.CopyBytes(FrameCheckSequence,
|
|
header.Bytes,
|
|
header.Offset + totalPacketLength);
|
|
}
|
|
|
|
// 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,
|
|
packetLength);
|
|
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);
|
|
|
|
if(AppendFcs)
|
|
{
|
|
var fcsBuffer = EndianBitConverter.Big.GetBytes(FrameCheckSequence);
|
|
ms.Write(fcsBuffer, 0, fcsBuffer.Length);
|
|
}
|
|
|
|
var newBytes = ms.ToArray();
|
|
|
|
return new ByteArraySegment(newBytes, 0, newBytes.Length);
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
/// <summary>
|
|
/// ToString() override
|
|
/// </summary>
|
|
/// <returns>
|
|
/// A <see cref="System.String"/>
|
|
/// </returns>
|
|
public override String ToString()
|
|
{
|
|
return string.Format ("802.11 MacFrame: [{0}], {1} FCS {2}",
|
|
FrameControl.ToString(),
|
|
GetAddressString(),
|
|
FrameCheckSequence);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Returns a string with a description of the addresses used in the packet.
|
|
/// This is used as a compoent of the string returned by ToString().
|
|
/// </summary>
|
|
/// <returns>
|
|
/// The address string.
|
|
/// </returns>
|
|
protected abstract String GetAddressString();
|
|
}
|
|
}
|
|
}
|