Skip to content

Add Dhcp-Support #84

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 19 commits into
base: master
Choose a base branch
from
Open

Add Dhcp-Support #84

wants to merge 19 commits into from

Conversation

vbBerni
Copy link

@vbBerni vbBerni commented Dec 17, 2016

Added support for DHCP (RFC 2131) as requested in #55.
Also includes tests.

@vbBerni vbBerni mentioned this pull request Jan 8, 2017
return;
}

switch (random.Next(0, 2))
Copy link
Contributor

@bricknerb bricknerb Jan 14, 2017

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Make this random.NextBool() instead?

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I would keep it because there may be more than two possible payloads in the future

}
else
{
udpLayer.DestinationPort = 5355;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why use port 5355 here?

Copy link
Author

@vbBerni vbBerni Jan 14, 2017

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This port was copied from the DnsLayer-Test above because I thought during tests the port is randomly wrong on purpose. I just checked port 5355 and it is used for Link-Local Multicast Name Resolution. So therefor my code with random wrong port doesn't make sense. Was removed by commit 0e40921

else
{
udpLayer.SourcePort = 5355;
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Lines 432-439 seem to repeat lines 424-431.

/// The DHCP message structure is shown below:
/// <pre>
/// 0 1 2 3
/// 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

For consistency, it might be better to format this similar to other layers.

private static class Offset
{
public const int Op = 0;
public const int Htype = 1;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Consider not using short names, here and elsewhere.
Op -> Operation
Htype -> HardwareAddressType
Hlen -> HardwareAddressLength
Xid -> TransactionId
Secs -> Seconds
CiAddr -> ClientIpAddress

etc.

public const int Hops = 3;
public const int Xid = 4;
public const int Secs = 8;
public const int Flags = 10;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Instead of having a field for "flags", split it to the different flags (RFC 2131 only has Broadcast).

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I added another property Broadcast in 8a31f33

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks, but I meant split it to different fields:
I think it would make the usage easier.

/// +-------------------------------+------------+------------------+
/// |           secs (2)            |  Broadcase | reserved         |
/// +-------------------------------+------------+------------------+

/// RFC 2131.
/// Transaction ID, a random number chosen by the client, used by the client and server to associate messages and responses between a client and a server.
/// </summary>
public int TransactionId
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should probably be uint unless there's a reason to have signed values.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

change in 7971a20

{
}

private void ParseServerHostName()
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe inline in the getter as it's only used once?


private void ParseServerHostName()
{
if (_serverHostName == null)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Prefer if (_serverHostName != null)
return;

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I did it like in DnsDatagram to keep it consistent

{
//at the moment we only interpret server host name as sname and ignore options
byte[] byteServerHostName = ReadBytes(Offset.Sname, 64);
_serverHostName = Encoding.ASCII.GetString(byteServerHostName).TrimEnd('\0');
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Are you sure this is limited to ASCII?

}
}

private void ParseBootFileName()
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ditto comments in ParseServerHostName()

{
//at the moment we only interpret server host name as sname and ignore options
byte[] byteServerHostName = ReadBytes(Offset.Sname, 64);
_serverHostName = Encoding.ASCII.GetString(byteServerHostName).TrimEnd('\0');
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Regarding TrimEnd(), what happens if after the null character there's a non null character?

}
}

private void ParseOptions()
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ditto some of the comments in the other Parse methods.

if (_options == null)
{
List<DhcpOption> options = new List<DhcpOption>();
int pos = IsDhcp ? Offset.OptionsWithMagicCookie : Offset.Options;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Use offset instead of pos.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

changed in df7dcd2

{
options.Add(DhcpOption.CreateInstance(this, ref pos));
}
this._options = new ReadOnlyCollection<DhcpOption>(options);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Avoid using "this." explicitly.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

changed in 2912854

/// <summary>
/// Creates a Layer that represents the datagram to be used with PacketBuilder.
/// </summary>
public override ILayer ExtractLayer()
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Keep methods sorted: public, internal, private.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

changed in 5170fe1


/// <summary>
/// RFC 2131.
/// Optional server host name
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

End comments with periods.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

changed in 7e1df41

public const int Op = 0;
public const int Htype = 1;
public const int Hlen = 2;
public const int Hops = 3;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Use relative offsets.
Hops = Hlen + sizeof(byte).


/// <summary>
/// RFC 2131.
/// true if the magic dhcp-cookie is set
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Replace "true if" with "Whether"

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

changed in ae3f79c

public const int OptionsWithMagicCookie = 240;
}

internal const int DHCP_MAGIC_COOKIE = 0x63825363;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why not uint here?

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Changed in c1550dd

{
get
{
if (Length >= Offset.Options + 4)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Replace "4" with "sizeof(int)" ?

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Changed in c1550dd

/// RFC 2131.
/// true if the magic dhcp-cookie is set
/// </summary>
public bool IsDhcp
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What is this Datagram if it's false?

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Documentation added in ae3f79c


if (options != null)
{
length += options.Sum(p => 1 + (!(p is DhcpPadOption || p is DhcpEndOption) ? 1 : 0) + p.Length); //Type + Len? + Option
Copy link
Contributor

@bricknerb bricknerb Jan 14, 2017

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Replace "p" with "option".
Replace "1" with "sizeof(byte)".
Can you rely on p.Op instead of its class type?

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Changed in c1550dd.
Yes, option.OptionCode makes much more sense.


if (options != null)
{
length += options.Sum(p => 1 + (!(p is DhcpPadOption || p is DhcpEndOption) ? 1 : 0) + p.Length); //Type + Len? + Option
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Replace "//Type" with "// Type".

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Changed in c1550dd


internal static void Write(byte[] buffer, int offset, DhcpMessageType messageType, ArpHardwareType hardwareType, byte hardwareAddressLength, byte hops, int transactionId, ushort secondsElapsed, DhcpFlags flags, IpV4Address clientIpAddress, IpV4Address yourClientIpAddress, IpV4Address nextServerIpAddress, IpV4Address relayAgentIpAddress, DataSegment clientHardwareAddress, string serverHostName, string bootFileName, bool isDhcp, IList<DhcpOption> options)
{
buffer.Write(ref offset, (byte)messageType);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Be consistent with the name - either "op" or "messageType".

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Missed that. Changed in 41bc492

internal static void Write(byte[] buffer, int offset, DhcpMessageType messageType, ArpHardwareType hardwareType, byte hardwareAddressLength, byte hops, int transactionId, ushort secondsElapsed, DhcpFlags flags, IpV4Address clientIpAddress, IpV4Address yourClientIpAddress, IpV4Address nextServerIpAddress, IpV4Address relayAgentIpAddress, DataSegment clientHardwareAddress, string serverHostName, string bootFileName, bool isDhcp, IList<DhcpOption> options)
{
buffer.Write(ref offset, (byte)messageType);
buffer.Write(ref offset, (byte)hardwareType);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Consider relying on the defined offsets instead of using ref.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Changed in 8b2f118

offset += Offset.Sname - Offset.ChAddr;
if (serverHostName != null)
{
buffer.Write(offset, new DataSegment(Encoding.ASCII.GetBytes(serverHostName)));
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Consider also writing the null termination.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Changed in 8b2f118

/// <summary>
/// Hardware address length
/// </summary>
public byte HardwareAddressLength { get; set; }
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Shouldn't the hard address length just be derived from the hardware address?

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I would separate it from the ClientHardwareAddress to allow setting all ClientHardwareAddress-Bytes.
ex. HardwareAddressLength = 6 but the user still wants to set byte 7 - 16

Maybe automatically set the HardwareAddressLength to 6 if the ClientMacAddress is set. What do you think about that?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I see, makes sense.
I didn't realize the Hardware Address field has constant size, regardless of the Hardware Address Length field.
It makes sense to set it to 6 (you can use MacAddress.SizeOf instead of 6) when the helping property ClientMacAddress is used, just make sure it's clear from the property comment.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Added in cda7273

/// <param name="nextLayer">The layer that comes after this layer. null if this is the last layer.</param>
public override void Finalize(byte[] buffer, int offset, int payloadLength, ILayer nextLayer)
{
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't think you need to override this if you don't do anything.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Changed in 3ccd7fc

namespace PcapDotNet.Packets.Dhcp
{
/// <summary>
/// RFCs 2131.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Replace "RFCs" with "RFC".

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Changed in f10975f

/// <summary>
/// RFCs 2131.
/// </summary>
[System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Design", "CA1008:EnumsShouldHaveZeroValue")]
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Consider adding a None value instead of suppressing this.

/// create new DhcpBootfileNameOption
/// </summary>
/// <param name="bootfileName">Bootfilename</param>
public DhcpBootfileNameOption(string bootfileName) : base(bootfileName, DhcpOptionCode.BootfileName)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe rename bootfilename to bootFilename ?

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I always tried to keep the names according the RFC specification. Only if it was not possible (spaces, conflict with existing names,...) I changed the name

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Well, in general in Pcap.Net, it's attempted to have the names readable so they can be used without being familiar with the RFC.
In this case, if "bootfile" is considered a word, this makes sense. Otherwise it makes more sense to have bootFilename.

}

[System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Maintainability", "CA1502:AvoidExcessiveComplexity")]
[System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Maintainability", "CA1506:AvoidExcessiveClassCoupling")]
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Consider solving these maintainability issues by using registration mechanism for the options, similar to what is being done in other protocols.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

refactored in 6d0b4dd

Copy link
Contributor

@bricknerb bricknerb left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Wow thanks!
Awesome work!

I didn't go over all the details.
Specifically, I didn't check code matches the RFC and I assume it's ok.
One missing part that can help make sure the code indeed matches the RFC is the Wireshark comparison code. Let me know if you need help with this.

I've written a bunch of comments, many of them should be applied on many places in the code.

Let me know if you can address these comments. Otherwise, I'll have to see if I can take care of them myself.

Copy link
Contributor

@bricknerb bricknerb left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for the fixes!
Let me know when you want me to make a full pass on the code.

@@ -26,6 +26,7 @@ public DhcpArpCacheTimeoutOption(uint time) : base(time, DhcpOptionCode.ArpCache
{
}

[DhcpOptionReadRegistration(DhcpOptionCode.ArpCacheTimeout)]
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Any reason not to have this attribute on the classes instead of the creation method?
As long as you don't have two creation methods for the same class, I think putting this on the class is easier to maintain.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

2 participants