Commit ca4b7ede authored by Boaz Brickner's avatar Boaz Brickner

Support IGMP version 0.

IgmpDatagram.Version now returns -1 if the version is unknown (instead of throwing an InvalidOperationException).
parent 92d02194
......@@ -21,10 +21,7 @@ namespace PcapDotNet.Core.Test
switch (field.Name())
{
case "igmp.version":
if (field.Show() == "0")
return false; // TODO: support IGMP version 0.
field.AssertShowDecimal(igmpDatagram.Version);
field.AssertShowDecimal(Math.Max(igmpDatagram.Version, 0));
break;
case "igmp.type":
......@@ -68,11 +65,21 @@ namespace PcapDotNet.Core.Test
break;
default:
if (typeof(IgmpMessageType).GetEnumValues<IgmpMessageType>().Contains(igmpDatagram.MessageType))
throw new InvalidOperationException("Invalid message type " + igmpDatagram.MessageType);
field.AssertValue(igmpDatagram.Skip(1));
// field.AssertShow(igmpDatagram.Skip(1));
switch (field.Show())
{
case "Data":
if (field.Value().Length > 0)
{
// TODO: Change following https://bugs.wireshark.org/bugzilla/show_bug.cgi?id=11582
field.AssertValue(field.Value().Length / 2 == igmpDatagram.Length - 21 ? igmpDatagram.Skip(21) : igmpDatagram.Skip(1));
}
break;
default:
if (typeof(IgmpMessageType).GetEnumValues<IgmpMessageType>().Contains(igmpDatagram.MessageType))
throw new InvalidOperationException("Invalid message type " + igmpDatagram.MessageType);
break;
}
break;
}
......@@ -99,7 +106,23 @@ namespace PcapDotNet.Core.Test
break;
case "igmp.identifier":
// todo support IGMP version 0 and IGMP identifier.
field.AssertShowDecimal(igmpDatagram.Identifier);
break;
case "igmp.access_key":
field.AssertValue(igmpDatagram.AccessKey);
break;
case "igmp.reply":
field.AssertShowDecimal((byte)igmpDatagram.ReplyCode);
break;
case "igmp.reply.pending":
field.AssertShowDecimal(igmpDatagram.RetryInThisManySeconds);
break;
case "igmp.group_type":
field.AssertShowDecimal(igmpDatagram.IsPrivate);
break;
case "igmp.mtrace.max_hops":
......
......@@ -28,7 +28,7 @@ namespace PcapDotNet.Core.Test
switch (field.Name())
{
case "tcp.len":
field.AssertShowDecimal(tcpDatagram.Payload.Length);
field.AssertShowDecimal(tcpDatagram.Length - tcpDatagram.HeaderLength);
field.AssertNoFields();
break;
......
using System;
using System.Collections.Generic;
using System.Linq;
using Microsoft.VisualStudio.TestTools.UnitTesting;
using PcapDotNet.Base;
using PcapDotNet.Packets.Ethernet;
......@@ -102,6 +103,38 @@ namespace PcapDotNet.Packets.Test
Assert.AreEqual(IgmpQueryVersion.None, packet.Ethernet.Ip.Igmp.QueryVersion);
switch (igmpLayer.MessageType)
{
case IgmpMessageType.CreateGroupRequestVersion0:
case IgmpMessageType.CreateGroupReplyVersion0:
case IgmpMessageType.JoinGroupRequestVersion0:
case IgmpMessageType.JoinGroupReplyVersion0:
case IgmpMessageType.LeaveGroupRequestVersion0:
case IgmpMessageType.LeaveGroupReplyVersion0:
case IgmpMessageType.ConfirmGroupRequestVersion0:
case IgmpMessageType.ConfirmGroupReplyVersion0:
Assert.AreEqual(0, packet.Ethernet.Ip.Igmp.Version);
IgmpVersion0Layer igmpVersion0Layer = (IgmpVersion0Layer)igmpLayer;
Assert.AreEqual(igmpVersion0Layer.IdentifierValue, packet.Ethernet.Ip.Igmp.Identifier);
Assert.AreEqual(igmpVersion0Layer.AccessKeyValue, packet.Ethernet.Ip.Igmp.AccessKey);
switch (igmpLayer.MessageType)
{
case IgmpMessageType.CreateGroupRequestVersion0:
Assert.AreEqual(((IgmpCreateGroupRequestVersion0Layer)igmpLayer).IsPrivate, packet.Ethernet.Ip.Igmp.IsPrivate);
break;
case IgmpMessageType.CreateGroupReplyVersion0:
case IgmpMessageType.JoinGroupReplyVersion0:
case IgmpMessageType.LeaveGroupReplyVersion0:
case IgmpMessageType.ConfirmGroupReplyVersion0:
IgmpReplyVersion0Layer igmpReplyVersion0Layer = (IgmpReplyVersion0Layer)igmpVersion0Layer;
Assert.AreEqual(igmpReplyVersion0Layer.Code, packet.Ethernet.Ip.Igmp.ReplyCode);
if (packet.Ethernet.Ip.Igmp.ReplyCode == IgmpVersion0ReplyCode.RequestPendingRetryInThisManySeconds)
Assert.AreEqual(igmpReplyVersion0Layer.RetryInThisManySeconds, packet.Ethernet.Ip.Igmp.RetryInThisManySeconds);
break;
}
break;
case IgmpMessageType.MembershipQuery:
switch (igmpLayer.QueryVersion)
{
......@@ -358,7 +391,6 @@ namespace PcapDotNet.Packets.Test
}
[TestMethod]
[ExpectedException(typeof(InvalidOperationException), AllowDerivedTypes = false)]
public void IgmpIllegalReportVersionTest()
{
Packet packet = PacketBuilder.Build(DateTime.Now, new EthernetLayer(), new IpV4Layer(), new IgmpReportVersion1Layer());
......@@ -371,8 +403,7 @@ namespace PcapDotNet.Packets.Test
buffer.Write(EthernetDatagram.HeaderLengthValue + IpV4Datagram.HeaderMinimumLength, 0);
Packet illegalPacket = new Packet(buffer, packet.Timestamp, packet.DataLink);
Assert.IsFalse(illegalPacket.IsValid);
Assert.IsNull(illegalPacket.Ethernet.IpV4.Igmp.Version);
Assert.Fail();
Assert.AreEqual(-1, illegalPacket.Ethernet.IpV4.Igmp.Version);
}
[TestMethod]
......@@ -445,11 +476,11 @@ namespace PcapDotNet.Packets.Test
[TestMethod]
public void DifferentIgmpSimpleLayersTest()
{
IgmpSimpleLayer layer1 = new IgmpQueryVersion1Layer
IgmpVersion1PlusSimpleLayer layer1 = new IgmpQueryVersion1Layer
{
GroupAddress = new IpV4Address("1.2.3.4")
};
IgmpSimpleLayer layer2 = new IgmpQueryVersion2Layer
IgmpVersion1PlusSimpleLayer layer2 = new IgmpQueryVersion2Layer
{
GroupAddress = new IpV4Address("1.2.3.4"),
MaxResponseTime = TimeSpan.FromMinutes(55)
......
......@@ -22,25 +22,56 @@ namespace PcapDotNet.Packets.TestUtils
public static IgmpLayer NextIgmpLayer(this Random random)
{
IgmpMessageType igmpMessageType = random.NextEnum(IgmpMessageType.None, IgmpMessageType.CreateGroupRequestVersion0,
IgmpMessageType.CreateGroupReplyVersion0, IgmpMessageType.JoinGroupRequestVersion0,
IgmpMessageType.JoinGroupReplyVersion0, IgmpMessageType.LeaveGroupRequestVersion0,
IgmpMessageType.LeaveGroupReplyVersion0, IgmpMessageType.ConfirmGroupRequestVersion0,
IgmpMessageType.ConfirmGroupReplyVersion0,
IgmpMessageType igmpMessageType = random.NextEnum(IgmpMessageType.None,
IgmpMessageType.MulticastTraceRouteResponse, IgmpMessageType.MulticastTraceRoute); // todo support IGMP traceroute http://www.ietf.org/proceedings/48/I-D/idmr-traceroute-ipm-07.txt.
IgmpQueryVersion igmpQueryVersion = IgmpQueryVersion.None;
TimeSpan igmpMaxResponseTime = random.NextTimeSpan(TimeSpan.FromSeconds(0.1), TimeSpan.FromSeconds(256 * 0.1) - TimeSpan.FromTicks(1));
IpV4Address igmpGroupAddress = random.NextIpV4Address();
bool? igmpIsSuppressRouterSideProcessing;
byte? igmpQueryRobustnessVariable;
TimeSpan? igmpQueryInterval;
IpV4Address[] igmpSourceAddresses;
IgmpGroupRecord[] igmpGroupRecords;
ulong accessKey = random.NextULong();
uint identifier = random.NextUInt();
switch (igmpMessageType)
{
case IgmpMessageType.CreateGroupRequestVersion0:
return new IgmpCreateGroupRequestVersion0Layer
{
IsPrivate = random.NextBool(),
Identifier = identifier,
};
case IgmpMessageType.CreateGroupReplyVersion0:
case IgmpMessageType.JoinGroupReplyVersion0:
case IgmpMessageType.LeaveGroupReplyVersion0:
case IgmpMessageType.ConfirmGroupReplyVersion0:
IgmpVersion0ReplyCode code = random.NextEnum<IgmpVersion0ReplyCode>();
return new IgmpReplyVersion0Layer
{
Type = igmpMessageType,
Code = code,
RetryInThisManySeconds = (byte)(code == IgmpVersion0ReplyCode.RequestPendingRetryInThisManySeconds? random.NextByte((byte)IgmpVersion0ReplyCode.RequestPendingRetryInThisManySeconds, byte.MaxValue) : 0),
Identifier = identifier,
GroupAddress = igmpGroupAddress,
AccessKey = accessKey,
};
case IgmpMessageType.JoinGroupRequestVersion0:
case IgmpMessageType.LeaveGroupRequestVersion0:
return new IgmpRequestVersion0Layer
{
Type = igmpMessageType,
Identifier = identifier,
GroupAddress = igmpGroupAddress,
AccessKey = accessKey,
};
case IgmpMessageType.ConfirmGroupRequestVersion0:
return new IgmpConfirmGroupRequestVersion0Layer
{
GroupAddress = igmpGroupAddress,
AccessKey = accessKey,
};
case IgmpMessageType.MembershipQuery:
igmpQueryVersion = random.NextEnum(IgmpQueryVersion.None, IgmpQueryVersion.Unknown);
IgmpQueryVersion igmpQueryVersion = random.NextEnum(IgmpQueryVersion.None, IgmpQueryVersion.Unknown);
switch (igmpQueryVersion)
{
case IgmpQueryVersion.Version1:
......@@ -57,20 +88,20 @@ namespace PcapDotNet.Packets.TestUtils
};
case IgmpQueryVersion.Version3:
igmpIsSuppressRouterSideProcessing = random.NextBool();
igmpQueryRobustnessVariable = random.NextByte(8);
bool igmpIsSuppressRouterSideProcessing = random.NextBool();
byte igmpQueryRobustnessVariable = random.NextByte(8);
igmpMaxResponseTime = random.NextTimeSpan(TimeSpan.FromSeconds(0.1),
IgmpDatagram.MaxVersion3MaxResponseTime - TimeSpan.FromTicks(1));
igmpQueryInterval = random.NextTimeSpan(TimeSpan.Zero, IgmpDatagram.MaxQueryInterval - TimeSpan.FromTicks(1));
igmpSourceAddresses = random.NextIpV4Addresses(random.Next(1000));
TimeSpan igmpQueryInterval = random.NextTimeSpan(TimeSpan.Zero, IgmpDatagram.MaxQueryInterval - TimeSpan.FromTicks(1));
IpV4Address[] igmpSourceAddresses = random.NextIpV4Addresses(random.Next(1000));
return new IgmpQueryVersion3Layer
{
SourceAddresses = igmpSourceAddresses.AsReadOnly(),
MaxResponseTime = igmpMaxResponseTime,
GroupAddress = igmpGroupAddress,
IsSuppressRouterSideProcessing = igmpIsSuppressRouterSideProcessing.Value,
QueryRobustnessVariable = igmpQueryRobustnessVariable.Value,
QueryInterval = igmpQueryInterval.Value,
IsSuppressRouterSideProcessing = igmpIsSuppressRouterSideProcessing,
QueryRobustnessVariable = igmpQueryRobustnessVariable,
QueryInterval = igmpQueryInterval,
};
default:
......@@ -98,8 +129,8 @@ namespace PcapDotNet.Packets.TestUtils
};
case IgmpMessageType.MembershipReportVersion3:
igmpGroupRecords = random.NextIgmpGroupRecords(random.Next(100));
if (igmpGroupRecords.Count() == 0 && random.NextBool())
IgmpGroupRecord[] igmpGroupRecords = random.NextIgmpGroupRecords(random.Next(100));
if (!igmpGroupRecords.Any() && random.NextBool())
return new IgmpReportVersion3Layer();
return new IgmpReportVersion3Layer
{
......
......@@ -7,6 +7,23 @@ using PcapDotNet.Packets.IpV4;
namespace PcapDotNet.Packets.Igmp
{
/// <summary>
/// RFC 988.
/// Version 0:
/// <pre>
/// +-----+------+------+----------+
/// | Bit | 0-7 | 8-15 | 16-31 |
/// +-----+------+------+----------+
/// | 0 | Type | Code | Checksum |
/// +-----+------+------+----------+
/// | 32 | Identifier |
/// +-----+------------------------+
/// | 64 | Group Address |
/// +-----+------------------------+
/// | 96 | Access Key |
/// | | |
/// +-----+------------------------+
/// </pre>
///
/// RFC 1112.
/// Version 1 (query or report):
/// <pre>
......@@ -93,7 +110,12 @@ namespace PcapDotNet.Packets.Igmp
public sealed class IgmpDatagram : Datagram
{
/// <summary>
/// The number of bytes the IGMP header takes for all messages but query version 3.
/// The number of bytes the version 0 IGMP message header takes.
/// </summary>
public const int Version0HeaderLength = Offset.AccessKey + sizeof(ulong);
/// <summary>
/// The number of bytes the IGMP header takes for all messages but version 0 and query version 3.
/// All the bytes but the records of the report version 3.
/// </summary>
public const int HeaderLength = 8;
......@@ -107,20 +129,32 @@ namespace PcapDotNet.Packets.Igmp
private static class Offset
{
public const int MessageType = 0;
public const int MaxResponseCode = 1;
public const int Checksum = 2;
public const int GroupAddress = 4;
public const int MaxResponseCode = MessageType + sizeof(byte);
public const int Checksum = MaxResponseCode + sizeof(byte);
public const int Version1PlusGroupAddress = Checksum + sizeof(ushort);
// Version 0
public const int Code = MaxResponseCode;
public const int Identifier = Checksum + sizeof(ushort);
public const int Version0GroupAddress = Identifier + sizeof(uint);
public const int AccessKey = Version0GroupAddress + IpV4Address.SizeOf;
// Version 3 query
public const int IsSuppressRouterSideProcessing = 8;
public const int QueryRobustnessVariable = 8;
public const int QueryIntervalCode = 9;
public const int NumberOfSources = 10;
public const int SourceAddresses = 12;
public const int IsSuppressRouterSideProcessing = Version1PlusGroupAddress + IpV4Address.SizeOf;
public const int QueryRobustnessVariable = IsSuppressRouterSideProcessing;
public const int QueryIntervalCode = QueryRobustnessVariable + sizeof(byte);
public const int NumberOfSources = QueryIntervalCode + sizeof(byte);
public const int SourceAddresses = NumberOfSources + sizeof(ushort);
// Version 3 report
public const int NumberOfGroupRecords = 6;
public const int GroupRecords = 8;
public const int NumberOfGroupRecords = Checksum + sizeof(ushort) + sizeof(ushort);
public const int GroupRecords = NumberOfGroupRecords + sizeof(ushort);
}
private static class Mask
{
// Version 0
public const byte Code = 0x01;
}
/// <summary>
......@@ -162,6 +196,7 @@ namespace PcapDotNet.Packets.Igmp
/// <summary>
/// The version of the IGMP protocol for this datagram.
/// Returns -1 if unknown.
/// </summary>
public int Version
{
......@@ -169,6 +204,16 @@ namespace PcapDotNet.Packets.Igmp
{
switch (MessageType)
{
case IgmpMessageType.CreateGroupRequestVersion0:
case IgmpMessageType.CreateGroupReplyVersion0:
case IgmpMessageType.JoinGroupRequestVersion0:
case IgmpMessageType.JoinGroupReplyVersion0:
case IgmpMessageType.LeaveGroupRequestVersion0:
case IgmpMessageType.LeaveGroupReplyVersion0:
case IgmpMessageType.ConfirmGroupRequestVersion0:
case IgmpMessageType.ConfirmGroupReplyVersion0:
return 0;
case IgmpMessageType.MembershipQuery:
switch (QueryVersion)
{
......@@ -198,7 +243,7 @@ namespace PcapDotNet.Packets.Igmp
return 2;
default:
throw new InvalidOperationException("Invalid IGMP Message Type " + MessageType);
return -1;
}
}
}
......@@ -233,6 +278,51 @@ namespace PcapDotNet.Packets.Igmp
}
}
/// <summary>
/// In IGMP version 0 Create Group Request message, indicates if the new host group is to be private or public.
/// </summary>
public bool IsPrivate
{
get
{
if (MessageType != IgmpMessageType.CreateGroupRequestVersion0)
throw new InvalidOperationException("IsPrivate can only be accessed for CreateGroupRequestVersion0.");
return ReadBool(Offset.Code, Mask.Code);
}
}
/// <summary>
/// In IGMP version 0 Reply message, specifies the outcome of the request.
/// </summary>
public IgmpVersion0ReplyCode ReplyCode
{
get
{
if (MessageType != IgmpMessageType.CreateGroupReplyVersion0 &&
MessageType != IgmpMessageType.JoinGroupReplyVersion0 &&
MessageType != IgmpMessageType.LeaveGroupReplyVersion0 &&
MessageType != IgmpMessageType.ConfirmGroupReplyVersion0)
{
throw new InvalidOperationException("ReplyCode can only be accessed for Version 0 Replies.");
}
return (IgmpVersion0ReplyCode)Math.Min(this[Offset.Code], (byte)IgmpVersion0ReplyCode.RequestPendingRetryInThisManySeconds);
}
}
/// <summary>
/// In IGMP version 0 Reply message with ReplyCode = RequestPendingRetryInThisManySeconds, specifies the amount of seconds to retry in.
/// </summary>
public byte RetryInThisManySeconds
{
get
{
if (ReplyCode != IgmpVersion0ReplyCode.RequestPendingRetryInThisManySeconds)
throw new InvalidOperationException("RetryInThisManySeconds can only be accessed when ReplyCode is RequestPendingRetryInThisManySeconds");
return this[Offset.Code];
}
}
/// <summary>
/// The Max Resp Code field specifies the maximum time allowed before sending a responding report.
/// The actual time allowed, called the Max Resp Time, is represented in units of 1/10 second and is derived from the Max Resp Code as follows:
......@@ -315,19 +405,62 @@ namespace PcapDotNet.Packets.Igmp
}
/// <summary>
/// IGMP version 0 specific.
/// In a Confirm Group Request message, the identifier field contains zero.
///
/// In all other Request messages, the identifier field contains a value to distinguish the request from other requests by the same host.
///
/// In a Reply message, the identifier field contains the same value as in the corresponding Request message.
/// </summary>
public uint Identifier
{
get
{
return ReadUInt(Offset.Identifier, Endianity.Big);
}
}
/// <summary>
/// Version 0:
/// In a Create Group Request message, the group address field contains zero.
/// In all other Request messages, the group address field contains a host group address.
/// In a Create Group Reply message, the group address field contains either a newly allocated host group address (if the request is granted)
/// or zero (if denied).
/// In all other Reply messages, the group address field contains the same host group address as in the corresponding Request message.
///
/// Version 1 or higher:
/// The Group Address field is set to zero when sending a General Query,
/// and set to the IP multicast address being queried when sending a Group-Specific Query or Group-and-Source-Specific Query.
/// In a Membership Report of version 1 or 2 or Leave Group message, the group address field holds the IP multicast group address of the group being reported or left.
/// In a Membership Report of version 1 or 2 or Leave Group message,
/// the group address field holds the IP multicast group address of the group being reported or left.
/// In a Membership Report of version 3 this field is meaningless.
/// </summary>
public IpV4Address GroupAddress
{
get { return ReadIpV4Address(Offset.GroupAddress, Endianity.Big); }
get { return ReadIpV4Address(Version <= 0 ? Offset.Version0GroupAddress : Offset.Version1PlusGroupAddress, Endianity.Big); }
}
/// <summary>
/// Version 0 specific.
/// In a Create Group Request message, the access key field contains zero.
/// In all other Request messages, the access key field contains the access key assigned to the host group identified in the Group Address field
/// (zero for public groups).
/// In a Create Group Reply message, the access key field contains either a non-zero 64-bit number (if the request for a private group is granted)
/// or zero.
/// In all other Reply messages, the access key field contains the same access key as in the corresponding Request.
/// </summary>
public ulong AccessKey
{
get
{
return ReadULong(Offset.AccessKey, Endianity.Big);
}
}
/// <summary>
/// When set to one, the S Flag indicates to any receiving multicast routers that they are to suppress the normal timer updates they perform upon hearing a Query.
/// It does not, however, suppress the querier election or the normal "host-side" processing of a Query
/// When set to one, the S Flag indicates to any receiving multicast routers
/// that they are to suppress the normal timer updates they perform upon hearing a Query.
/// It does not, however, suppress the querier election or the normal "host-side" processing of a Query
/// that a router may be required to perform as a consequence of itself being a group member.
/// </summary>
/// <remarks>
......@@ -492,6 +625,44 @@ namespace PcapDotNet.Packets.Igmp
{
switch (MessageType)
{
case IgmpMessageType.CreateGroupRequestVersion0:
return new IgmpCreateGroupRequestVersion0Layer
{
IsPrivate = IsPrivate,
Identifier = Identifier,
};
case IgmpMessageType.CreateGroupReplyVersion0:
case IgmpMessageType.JoinGroupReplyVersion0:
case IgmpMessageType.LeaveGroupReplyVersion0:
case IgmpMessageType.ConfirmGroupReplyVersion0:
return new IgmpReplyVersion0Layer
{
Type = MessageType,
Code = ReplyCode,
RetryInThisManySeconds = (byte)(ReplyCode == IgmpVersion0ReplyCode.RequestPendingRetryInThisManySeconds ? RetryInThisManySeconds : 0),
Identifier = Identifier,
GroupAddress = GroupAddress,
AccessKey = AccessKey,
};
case IgmpMessageType.JoinGroupRequestVersion0:
case IgmpMessageType.LeaveGroupRequestVersion0:
return new IgmpRequestVersion0Layer
{
Type = MessageType,
Identifier = Identifier,
GroupAddress = GroupAddress,
AccessKey = AccessKey,
};
case IgmpMessageType.ConfirmGroupRequestVersion0:
return new IgmpConfirmGroupRequestVersion0Layer
{
GroupAddress = GroupAddress,
AccessKey = AccessKey,
};
case IgmpMessageType.MembershipQuery:
switch (QueryVersion)
{
......@@ -571,8 +742,19 @@ namespace PcapDotNet.Packets.Igmp
igmpGroupRecords.Sum(record => IgmpGroupRecordDatagram.GetLength(record.SourceAddresses.Count, record.AuxiliaryData.Length));
}
internal static void WriteHeader(byte[] buffer, int offset,
IgmpMessageType igmpMessageType, TimeSpan maxResponseTime, IpV4Address groupAddress)
internal static void WriteVersion0Header(byte[] buffer, int offset,
IgmpMessageType igmpMessageType, byte code, uint identifier, IpV4Address groupAddress, ulong accessKey)
{
buffer.Write(offset + Offset.MessageType, (byte)igmpMessageType);
buffer.Write(offset + Offset.Code, code);
buffer.Write(offset + Offset.Version0GroupAddress, groupAddress, Endianity.Big);
buffer.Write(offset + Offset.Identifier, identifier, Endianity.Big);
buffer.Write(offset + Offset.AccessKey, accessKey, Endianity.Big);
WriteChecksum(buffer, offset, Version0HeaderLength);
}
internal static void WriteVersion1PlusSimpleHeader(byte[] buffer, int offset,
IgmpMessageType igmpMessageType, TimeSpan maxResponseTime, IpV4Address groupAddress)
{
buffer.Write(offset + Offset.MessageType, (byte)igmpMessageType);
......@@ -581,7 +763,7 @@ namespace PcapDotNet.Packets.Igmp
throw new ArgumentOutOfRangeException("maxResponseTime", maxResponseTime, "must be in the range [" + TimeSpan.Zero + ", " + TimeSpan.FromSeconds(255 * 0.1) + "]");
buffer.Write(offset + Offset.MaxResponseCode, (byte)numTenthOfASecond);
buffer.Write(offset + Offset.GroupAddress, groupAddress, Endianity.Big);
buffer.Write(offset + Offset.Version1PlusGroupAddress, groupAddress, Endianity.Big);
WriteChecksum(buffer, offset, HeaderLength);
}
......@@ -602,7 +784,7 @@ namespace PcapDotNet.Packets.Igmp
buffer.Write(offset + Offset.MaxResponseCode, maxResponseCode);
// GroupAddress
buffer.Write(offset + Offset.GroupAddress, groupAddress, Endianity.Big);
buffer.Write(offset + Offset.Version1PlusGroupAddress, groupAddress, Endianity.Big);
// IsSuppressRouterSideProcessing and QueryRobustnessVariable
if (queryRobustnessVariable > MaxQueryRobustnessVariable)
......@@ -660,6 +842,9 @@ namespace PcapDotNet.Packets.Igmp
if (Length < HeaderLength || !IsChecksumCorrect)
return false;
if (Version == 0)
return Length == Version0HeaderLength;
switch (MessageType)
{
case IgmpMessageType.MembershipQuery:
......@@ -703,7 +888,8 @@ namespace PcapDotNet.Packets.Igmp
private static void WriteChecksum(byte[] buffer, int offset, int length)
{
buffer.Write(offset + Offset.Checksum, Sum16BitsToChecksum(Sum16Bits(buffer, offset, length)), Endianity.Big);
uint sum = Sum16Bits(buffer, offset, length);
buffer.Write(offset + Offset.Checksum, Sum16BitsToChecksum(sum), Endianity.Big);
}
/// <summary>
......
using System;
using PcapDotNet.Base;
using PcapDotNet.Packets.IpV4;
......@@ -24,10 +23,6 @@ namespace PcapDotNet.Packets.Igmp
get { return IgmpQueryVersion.None; }
}
/// <summary>
/// The actual time allowed, called the Max Resp Time.
/// </summary>
public abstract TimeSpan MaxResponseTimeValue { get; }
/// <summary>
/// The protocol that should be written in the previous (IPv4) layer.
......@@ -45,8 +40,7 @@ namespace PcapDotNet.Packets.Igmp
return other != null &&
MessageType == other.MessageType &&
QueryVersion == other.QueryVersion &&
EqualMaxResponseTime(MaxResponseTimeValue, other.MaxResponseTimeValue) &&
EqualFields(other);
EqualsVersionSpecific(other);
}
/// <summary>
......@@ -69,11 +63,6 @@ namespace PcapDotNet.Packets.Igmp
/// <summary>
/// true iff the fields that are not mutual to all IGMP layers are equal.
/// </summary>
protected abstract bool EqualFields(IgmpLayer other);
private static bool EqualMaxResponseTime(TimeSpan value1, TimeSpan value2)
{
return value1.Divide(2) <= value2 && value1.Multiply(2) >= value2;
}
protected abstract bool EqualsVersionSpecific(IgmpLayer other);
}
}
\ No newline at end of file
using System;
using System.Runtime.InteropServices;
using PcapDotNet.Base;
using PcapDotNet.Packets.IpV4;
namespace PcapDotNet.Packets.Igmp
{
/// <summary>
......@@ -25,4 +30,259 @@ namespace PcapDotNet.Packets.Igmp
}
}
}
/// <summary>
/// RFC 988.
/// </summary>
public abstract class IgmpVersion0Layer : IgmpLayer
{
/// <summary>
/// The number of bytes this layer will take.
/// </summary>
public override sealed int Length
{
get { return IgmpDatagram.Version0HeaderLength; }
}
public abstract uint IdentifierValue { get; }
public abstract ulong AccessKeyValue { get; }
public override int GetHashCode()
{
return new[]
{
base.GetHashCode(),
CodeValue.GetHashCode(),
IdentifierValue.GetHashCode(),
GroupAddressValue.GetHashCode(),
AccessKeyValue.GetHashCode()
}.Xor();
}
protected override sealed bool EqualsVersionSpecific(IgmpLayer other)
{
return EqualsVersionSpecific(other as IgmpVersion0Layer);
}
protected abstract byte CodeValue { get; }
protected abstract IpV4Address GroupAddressValue { get; }
protected override void Write(byte[] buffer, int offset)
{
IgmpDatagram.WriteVersion0Header(buffer, offset, MessageType, CodeValue, IdentifierValue, GroupAddressValue, AccessKeyValue);
}
private bool EqualsVersionSpecific(IgmpVersion0Layer other)
{
return other != null &&
CodeValue == other.CodeValue &&
IdentifierValue == other.IdentifierValue &&
GroupAddressValue.Equals(other.GroupAddressValue) &&
AccessKeyValue == other.AccessKeyValue;
}
}
/// <summary>
/// RFC 988.
/// </summary>
public sealed class IgmpCreateGroupRequestVersion0Layer : IgmpVersion0Layer
{
/// <summary>
/// The type of the IGMP message of concern to the host-router interaction.
/// </summary>
public override IgmpMessageType MessageType
{
get { return IgmpMessageType.CreateGroupRequestVersion0; }
}
public bool IsPrivate { get; set; }
public uint Identifier { get; set; }
public override uint IdentifierValue
{
get { return Identifier; }
}
public override ulong AccessKeyValue
{
get { return 0; }
}
protected override byte CodeValue
{
get { return IsPrivate.ToByte(); }
}
protected override IpV4Address GroupAddressValue
{
get { return IpV4Address.Zero; }
}
}
/// <summary>
/// RFC 988.
/// </summary>
public sealed class IgmpReplyVersion0Layer : IgmpVersion0Layer
{
/// <summary>
/// The type of the IGMP message of concern to the host-router interaction.
/// </summary>
public override IgmpMessageType MessageType
{
get { return Type; }
}
public IgmpMessageType Type
{
get { return _type; }
set
{
switch (value)
{
case IgmpMessageType.CreateGroupReplyVersion0:
case IgmpMessageType.JoinGroupReplyVersion0:
case IgmpMessageType.LeaveGroupReplyVersion0:
case IgmpMessageType.ConfirmGroupReplyVersion0:
_type = value;
break;
default:
throw new ArgumentOutOfRangeException("value", value, string.Format("Do not use {0} for {1}", GetType(), value));
}
}
}
public IgmpVersion0ReplyCode Code { get; set; }
public byte RetryInThisManySeconds { get; set; }
public uint Identifier { get; set; }
public override uint IdentifierValue
{
get { return Identifier; }
}
public IpV4Address GroupAddress { get; set; }
public ulong AccessKey { get; set; }
public override ulong AccessKeyValue
{
get { return AccessKey; }
}
protected override byte CodeValue
{
get { return Code == IgmpVersion0ReplyCode.RequestPendingRetryInThisManySeconds ? RetryInThisManySeconds : (byte)Code; }
}
protected override IpV4Address GroupAddressValue
{
get { return GroupAddress; }
}
private IgmpMessageType _type = IgmpMessageType.CreateGroupReplyVersion0;
}
/// <summary>
/// RFC 988.
/// </summary>
public sealed class IgmpRequestVersion0Layer : IgmpVersion0Layer
{
/// <summary>
/// The type of the IGMP message of concern to the host-router interaction.
/// </summary>
public override IgmpMessageType MessageType
{
get { return Type; }
}
public IgmpMessageType Type
{
get { return _type; }
set
{
switch (value)
{
case IgmpMessageType.JoinGroupRequestVersion0:
case IgmpMessageType.LeaveGroupRequestVersion0:
_type = value;
break;
default:
throw new ArgumentOutOfRangeException("value", value, string.Format("Do not use {0} for {1}", GetType(), value));
}
}
}
public uint Identifier { get; set; }
public override uint IdentifierValue
{
get { return Identifier; }
}
public IpV4Address GroupAddress { get; set; }
public ulong AccessKey { get; set; }
public override ulong AccessKeyValue
{
get { return AccessKey; }
}
protected override byte CodeValue
{
get { return 0; }
}
protected override IpV4Address GroupAddressValue
{
get { return GroupAddress; }
}
private IgmpMessageType _type = IgmpMessageType.JoinGroupRequestVersion0;
}
/// <summary>
/// RFC 988.
/// </summary>
public sealed class IgmpConfirmGroupRequestVersion0Layer : IgmpVersion0Layer
{
/// <summary>
/// The type of the IGMP message of concern to the host-router interaction.
/// </summary>
public override IgmpMessageType MessageType
{
get { return IgmpMessageType.ConfirmGroupRequestVersion0; }
}
public override uint IdentifierValue
{
get { return 0; }
}
public IpV4Address GroupAddress { get; set; }
public ulong AccessKey { get; set; }
public override ulong AccessKeyValue
{
get { return AccessKey; }
}
protected override byte CodeValue
{
get { return 0; }
}
protected override IpV4Address GroupAddressValue
{
get { return GroupAddress; }
}
}
}
\ No newline at end of file
......@@ -11,7 +11,7 @@ namespace PcapDotNet.Packets.Igmp
/// Represents an IGMP Query version 3 layer.
/// <seealso cref="IgmpDatagram"/>
/// </summary>
public sealed class IgmpQueryVersion3Layer : IgmpLayer, IIgmpLayerWithGroupAddress
public sealed class IgmpQueryVersion3Layer : IgmpVersion1PlusLayer, IIgmpLayerWithGroupAddress
{
/// <summary>
/// Creates an instance of an IGMP Query Version 3 layer.
......@@ -122,7 +122,7 @@ namespace PcapDotNet.Packets.Igmp
/// true iff the GroupAddress, IsSuppressRouterSideProcessing, QueryRobustnessVariable and SourceAddresses fields are equal
/// and the QueryInterval is similar.
/// </summary>
protected override bool EqualFields(IgmpLayer other)
protected override bool EqualFields(IgmpVersion1PlusLayer other)
{
return EqualFields(other as IgmpQueryVersion3Layer);
}
......
......@@ -10,7 +10,7 @@ namespace PcapDotNet.Packets.Igmp
/// Represents an IGMP Report version 3 layer.
/// <seealso cref="IgmpDatagram"/>
/// </summary>
public sealed class IgmpReportVersion3Layer : IgmpLayer
public sealed class IgmpReportVersion3Layer : IgmpVersion1PlusLayer
{
/// <summary>
/// Creates an instance of an IGMP Report Version 3 Layer with a default of no Group Records.
......@@ -72,7 +72,7 @@ namespace PcapDotNet.Packets.Igmp
/// <summary>
/// true iff the group records are equal.
/// </summary>
protected override bool EqualFields(IgmpLayer other)
protected override bool EqualFields(IgmpVersion1PlusLayer other)
{
return EqualFields(other as IgmpReportVersion3Layer);
}
......
namespace PcapDotNet.Packets.Igmp
{
/// <summary>
/// RFC 988.
/// In an IGMP version 0 Reply message, specifies the outcome of the request:
/// </summary>
public enum IgmpVersion0ReplyCode : byte
{
/// <summary>
/// Request granted.
/// </summary>
RequestGranted = 0,
/// <summary>
/// Request denied, no resources.
/// </summary>
RequestDeniedNoResources = 1,
/// <summary>
/// Request denied, invalid code.
/// </summary>
RequestDeniedInvalidCode = 2,
/// <summary>
/// Request denied, invalid group address.
/// </summary>
RequestDeniedInvalidGroupAddress = 3,
/// <summary>
/// Request denied, invalid access key.
/// </summary>
RequestDeniedInvalidAccessKey = 4,
/// <summary>
/// Request pending, retry in this many seconds.
/// </summary>
RequestPendingRetryInThisManySeconds = 5,
}
}
\ No newline at end of file
......@@ -7,7 +7,7 @@ namespace PcapDotNet.Packets.Igmp
/// Represents an IGMP version 1 layer.
/// <seealso cref="IgmpDatagram"/>
/// </summary>
public abstract class IgmpVersion1Layer : IgmpSimpleLayer
public abstract class IgmpVersion1Layer : IgmpVersion1PlusSimpleLayer
{
/// <summary>
/// The actual time allowed, called the Max Resp Time.
......
using System;
using PcapDotNet.Base;
namespace PcapDotNet.Packets.Igmp
{
public abstract class IgmpVersion1PlusLayer : IgmpLayer
{
/// <summary>
/// The actual time allowed, called the Max Resp Time.
/// </summary>
public abstract TimeSpan MaxResponseTimeValue { get; }
/// <summary>
/// true iff the fields that are not mutual to all IGMP layers are equal.
/// </summary>
protected override sealed bool EqualsVersionSpecific(IgmpLayer other)
{
return EqualsVersionSpecific(other as IgmpVersion1PlusLayer);
}
/// <summary>
/// true iff the fields that are not mutual to all IGMP version 1+ layers are equal.
/// </summary>
protected abstract bool EqualFields(IgmpVersion1PlusLayer other);
private bool EqualsVersionSpecific(IgmpVersion1PlusLayer other)
{
return other != null &&
EqualMaxResponseTime(MaxResponseTimeValue, other.MaxResponseTimeValue) &&
EqualFields(other);
}
private static bool EqualMaxResponseTime(TimeSpan value1, TimeSpan value2)
{
return value1.Divide(2) <= value2 && value1.Multiply(2) >= value2;
}
}
}
\ No newline at end of file
using PcapDotNet.Packets.IpV4;
namespace PcapDotNet.Packets.Igmp
{
/// <summary>
/// Represents a Simple IGMP layer.
/// A simple layer only has the 8 bytes header without additional fields.
/// <seealso cref="IgmpDatagram"/>
/// </summary>
public abstract class IgmpSimpleLayer : IgmpLayer, IIgmpLayerWithGroupAddress
{
/// <summary>
/// The Group Address field is set to zero when sending a General Query,
/// and set to the IP multicast address being queried when sending a Group-Specific Query or Group-and-Source-Specific Query.
/// In a Membership Report of version 1 or 2 or Leave Group message, the group address field holds the IP multicast group address of the group being reported or left.
/// In a Membership Report of version 3 this field is meaningless.
/// </summary>
public IpV4Address GroupAddress { get; set; }
/// <summary>
/// The number of bytes this layer will take.
/// </summary>
public sealed override int Length
{
get { return IgmpDatagram.HeaderLength; }
}
/// <summary>
/// Writes the layer to the buffer.
/// This method ignores the payload length, and the previous and next layers.
/// </summary>
/// <param name="buffer">The buffer to write the layer to.</param>
/// <param name="offset">The offset in the buffer to start writing the layer at.</param>
protected sealed override void Write(byte[] buffer, int offset)
{
IgmpDatagram.WriteHeader(buffer, offset,
MessageType, MaxResponseTimeValue, GroupAddress);
}
/// <summary>
/// Xor of the hash codes of the layer length, datalink, message type, query version and group address.
/// </summary>
public sealed override int GetHashCode()
{
return base.GetHashCode() ^
GroupAddress.GetHashCode();
}
/// <summary>
/// true iff the the group address is equal.
/// </summary>
protected sealed override bool EqualFields(IgmpLayer other)
{
return EqualFields(other as IgmpSimpleLayer);
}
/// <summary>
/// true iff the the group address is equal.
/// </summary>
private bool EqualFields(IgmpSimpleLayer other)
{
return other != null &&
GroupAddress == other.GroupAddress;
}
}
using PcapDotNet.Packets.IpV4;
namespace PcapDotNet.Packets.Igmp
{
/// <summary>
/// Represents a Simple IGMP layer.
/// A simple layer only has the 8 bytes header without additional fields.
/// <seealso cref="IgmpDatagram"/>
/// </summary>
public abstract class IgmpVersion1PlusSimpleLayer : IgmpVersion1PlusLayer, IIgmpLayerWithGroupAddress
{
/// <summary>
/// The Group Address field is set to zero when sending a General Query,
/// and set to the IP multicast address being queried when sending a Group-Specific Query or Group-and-Source-Specific Query.
/// In a Membership Report of version 1 or 2 or Leave Group message, the group address field holds the IP multicast group address of the group being reported or left.
/// In a Membership Report of version 3 this field is meaningless.
/// </summary>
public IpV4Address GroupAddress { get; set; }
/// <summary>
/// The number of bytes this layer will take.
/// </summary>
public sealed override int Length
{
get { return IgmpDatagram.HeaderLength; }
}
/// <summary>
/// Writes the layer to the buffer.
/// This method ignores the payload length, and the previous and next layers.
/// </summary>
/// <param name="buffer">The buffer to write the layer to.</param>
/// <param name="offset">The offset in the buffer to start writing the layer at.</param>
protected sealed override void Write(byte[] buffer, int offset)
{
IgmpDatagram.WriteVersion1PlusSimpleHeader(buffer, offset,
MessageType, MaxResponseTimeValue, GroupAddress);
}
/// <summary>
/// Xor of the hash codes of the layer length, datalink, message type, query version and group address.
/// </summary>
public sealed override int GetHashCode()
{
return base.GetHashCode() ^
GroupAddress.GetHashCode();
}
/// <summary>
/// True iff the fields that are not mutual to all IGMP version 1+ layers are equal.
/// </summary>
protected override bool EqualFields(IgmpVersion1PlusLayer other)
{
return EqualFields(other as IgmpVersion1PlusSimpleLayer);
}
/// <summary>
/// True iff the the group address is equal.
/// </summary>
private bool EqualFields(IgmpVersion1PlusSimpleLayer other)
{
return other != null &&
GroupAddress == other.GroupAddress;
}
}
}
\ No newline at end of file
......@@ -7,7 +7,7 @@ namespace PcapDotNet.Packets.Igmp
/// Represents a generic IGMP version 2 datagram.
/// <seealso cref="IgmpDatagram"/>
/// </summary>
public abstract class IgmpVersion2Layer : IgmpSimpleLayer
public abstract class IgmpVersion2Layer : IgmpVersion1PlusSimpleLayer
{
/// <summary>
/// The actual time allowed, called the Max Resp Time.
......
......@@ -309,7 +309,9 @@
<Compile Include="Igmp\IgmpReportVersion1Layer.cs" />
<Compile Include="Igmp\IgmpReportVersion2Layer.cs" />
<Compile Include="Igmp\IgmpReportVersion3Layer.cs" />
<Compile Include="Igmp\IgmpSimpleLayer.cs" />
<Compile Include="Igmp\IgmpVersion1PlusLayer.cs" />
<Compile Include="Igmp\IgmpVersion1PlusSimpleLayer.cs" />
<Compile Include="Igmp\IgmpVersion0ReplyCode.cs" />
<Compile Include="Igmp\IgmpVersion1Layer.cs" />
<Compile Include="Igmp\IgmpVersion2Layer.cs" />
<Compile Include="Igmp\IgmpDatagram.cs" />
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment