Introduction
About a while ago, I was working on a project that needed to identify devices on a network that were being powered up, and allocate a separate known IP address to each.
To solve this problem, I decided to use the DHCP service, as each separate MAC address shall get a separate IP.
There are compiled programs on the net, or DLLs or objects that you can purchase to include with your project; however, none do present the code.
Sometimes, we need the code of the DHCP service to include in the project as well as to put in added functionality that is not offered by normal DHCP servers. For example, on a local network, there is a main DHCP server, which allocates IPs to computers, and there is also the small DHCP server that allocates a known IP address to a particular device only. Since the DHCP protocol communicates using UPD, the data is broadcasted (not point to point) and can be picked up very easily. In order to do that, we shall use a simple filtering algorithm that shall filter out requests and shall allow only permitted MAC addresses to be assigned IPs.
Definition of DHCP
DHCP means Dynamic Host Configuration Protocol, and is made up of two components which include a DHCP client (the network device requesting IP settings), and a DHCP Server (an Internet Host that shall return configuration parameters to the client requesting it).
A brief description of how the small DHCP server works
A DHCP server is generally installed on a local network, and is used to centrally allocate TCP-IP configurations to network devices or computers without automatically setting it manually. DHCP means Dynamic Host Configuration Protocol. It saves you plenty of time to set up and manage TCP/IP networks, especially if you have a big network.
The DHCP server listens on UDP port 67, and sends data to the clients using UDP on port 67 as well. The UDP service uses an asynchronous method by using callbacks:
private void IniListnerCallBack()
{
try
{
s.u.BeginReceive(new AsyncCallback(OnDataRecieved), s);
}
catch (Exception ex)
{
if (IsListening == true)
Console.WriteLine(ex.Message);
}
}
public void OnDataRecieved(IAsyncResult asyn)
{
Byte[] receiveBytes;
UdpClient u;
IPEndPoint e;
try
{
u = (UdpClient)((UdpState)(asyn.AsyncState)).u;
e = (IPEndPoint)((UdpState)(asyn.AsyncState)).e;
receiveBytes = u.EndReceive(asyn, ref e);
DataRcvd(receiveBytes, e);
}
catch (Exception ex)
{
if (IsListening == true)
Console.WriteLine(ex.Message);
}
finally
{
u = null;
e = null;
receiveBytes = null;
IniListnerCallBack();
}
}
The UDP client shall be set up and will listen on the network for incoming requests.
Each message shall be identified as being unique with the MAC address and a Transaction ID (D_xid
), which is a random number chosen by the client. On data exchange, the data is sent as a byte stream, and the format will follow the RFC defined structure as follows:
public struct DHCPstruct
{
public byte D_op;
public byte D_htype;
public byte D_hlen;
public byte D_hops;
public byte[] D_xid;
public byte[] D_secs;
public byte[] D_flags;
public byte[] D_ciaddr;
public byte[] D_yiaddr;
public byte[] D_siaddr;
public byte[] D_giaddr;
public byte[] D_chaddr;
public byte[] D_sname;
public byte[] D_file;
public byte[] M_Cookie;
public byte[] D_options;
}
Therefore, data is sent to the DHCP class via an event as a byte stream. And using the BinaryReader
class as defined by .NET, we shall put the bytes in their relevant place. OPTION_OFFSET
is a predefined constant which will state where the Option Structure starts:
public cDHCPStruct(byte[] Data)
{
System.IO.BinaryReader rdr;
System.IO.MemoryStream stm =
new System.IO.MemoryStream(Data, 0, Data.Length);
try
{
dStruct.D_op = rdr.ReadByte();
dStruct.D_htype = rdr.ReadByte();
dStruct.D_hlen = rdr.ReadByte();
dStruct.D_hops = rdr.ReadByte();
dStruct.D_xid = rdr.ReadBytes(4);
dStruct.D_secs = rdr.ReadBytes(2);
dStruct.D_flags = rdr.ReadBytes(2);
dStruct.D_ciaddr = rdr.ReadBytes(4);
dStruct.D_yiaddr = rdr.ReadBytes(4);
dStruct.D_siaddr = rdr.ReadBytes(4);
dStruct.D_giaddr = rdr.ReadBytes(4);
dStruct.D_chaddr = rdr.ReadBytes(16);
dStruct.D_sname = rdr.ReadBytes(64);
dStruct.D_file = rdr.ReadBytes(128);
dStruct.M_Cookie = rdr.ReadBytes(4);
dStruct.D_options = rdr.ReadBytes(Data.Length - OPTION_OFFSET);
}
catch(Exception ex)
{
Console.WriteLine (ex.Message);
}
}
A client requesting an IP address shall also pass over a list of options in the option list that need to filled out by the DHCP server, to pass back. The options that can be passed over as defined by the RFC, can include:
public enum DHCPOptionEnum
{
SubnetMask = 1,
TimeOffset = 2,
Router = 3,
TimeServer = 4,
NameServer = 5,
DomainNameServer = 6,
LogServer = 7,
CookieServer = 8,
LPRServer = 9,
ImpressServer = 10,
ResourceLocServer = 11,
HostName = 12,
BootFileSize = 13,
MeritDump = 14,
DomainName = 15,
SwapServer = 16,
RootPath = 17,
ExtensionsPath = 18,
IpForwarding = 19,
NonLocalSourceRouting = 20,
PolicyFilter = 21,
MaximumDatagramReAssemblySize = 22,
DefaultIPTimeToLive = 23,
PathMTUAgingTimeout = 24,
PathMTUPlateauTable = 25,
InterfaceMTU = 26,
AllSubnetsAreLocal = 27,
BroadcastAddress = 28,
PerformMaskDiscovery = 29,
MaskSupplier = 30,
PerformRouterDiscovery = 31,
RouterSolicitationAddress = 32,
StaticRoute = 33,
TrailerEncapsulation = 34,
ARPCacheTimeout = 35,
EthernetEncapsulation = 36,
TCPDefaultTTL = 37,
TCPKeepaliveInterval = 38,
TCPKeepaliveGarbage = 39,
NetworkInformationServiceDomain = 40,
NetworkInformationServers = 41,
NetworkTimeProtocolServers = 42,
VendorSpecificInformation = 43,
NetBIOSoverTCPIPNameServer = 44,
NetBIOSoverTCPIPDatagramDistributionServer = 45,
NetBIOSoverTCPIPNodeType = 46,
NetBIOSoverTCPIPScope = 47,
XWindowSystemFontServer = 48,
XWindowSystemDisplayManager = 49,
RequestedIPAddress = 50,
IPAddressLeaseTime = 51,
OptionOverload = 52,
DHCPMessageTYPE = 53,
ServerIdentifier = 54,
ParameterRequestList = 55,
Message = 56,
MaximumDHCPMessageSize = 57,
RenewalTimeValue_T1 = 58,
RebindingTimeValue_T2 = 59,
Vendorclassidentifier = 60,
ClientIdentifier = 61,
NetworkInformationServicePlusDomain = 64,
NetworkInformationServicePlusServers = 65,
TFTPServerName = 66,
BootfileName = 67,
MobileIPHomeAgent = 68,
SMTPServer = 69,
POP3Server = 70,
NNTPServer = 71,
DefaultWWWServer = 72,
DefaultFingerServer = 73,
DefaultIRCServer = 74,
StreetTalkServer = 75,
STDAServer = 76,
END_Option = 255
}
The code shall then have to breakdown the option byte array, which shall follow the format of:
-------------------------------------------------
|a|len|Message|a|len|Message|........|END_OPTION|
-------------------------------------------------
where:
a
signifies the option message code as defined abovelen
is the length of the message in bytesMessage
is the message that is passed over, whose length is determined by len
END_OPTION
shall signify the end of the option message
The message type
The message type is an identifier (53) in the OPTIONS
message. It shall determine the state of negotiation, and shall include:
public enum DHCPMsgType
{
DHCPDISCOVER = 1,
DHCPOFFER = 2,
DHCPREQUEST = 3,
DHCPDECLINE = 4,
DHCPACK = 5,
DHCPNAK = 6,
DHCPRELEASE = 7,
DHCPINFORM = 8
}
In the code, these shall be raised as events from the DHCP class to the main form:
#region "event Delegates"
public delegate void AnnouncedEventHandler(cDHCPStruct d_DHCP,string MacId);
public delegate void ReleasedEventHandler();
public delegate void RequestEventHandler(cDHCPStruct d_DHCP, string MacId);
public delegate void AssignedEventHandler(string IPAdd,string MacID );
#endregion
public event AnnouncedEventHandler Announced;
public event RequestEventHandler Request;
The main form, before assigning an IP address, will use the Ping
class of .NET and see if the IP is already in use.
public static bool CheckAlive(string IpAdd)
{
Ping pingSender = new Ping();
IPAddress address;
PingReply reply;
try
{
address = IPAddress.Parse(IpAdd);
reply = pingSender.Send(address,100);
if (reply.Status == IPStatus.Success)
{
Console.WriteLine("Address: {0}",
reply.Address.ToString());
Console.WriteLine("RoundTrip time: {0}",
reply.RoundtripTime);
Console.WriteLine("Time to live: {0}",
reply.Options.Ttl);
Console.WriteLine("Don't fragment: {0}",
reply.Options.DontFragment);
Console.WriteLine("Buffer size: {0}",
reply.Buffer.Length);
return true;
}
else
{
Console.WriteLine(reply.Status);
return false;
}
}
catch (Exception ex)
{
MessageBox.Show(ex.Message);
return false;
}
finally
{
if (pingSender != null) pingSender.Dispose();
pingSender = null;
address = null;
reply = null;
}
}
The main program shall, then, take in the user parameters, which include the leas time, subnet, the server host name etc.. and send the data over by converting from the structure to a byte
stream, using the Array
class as defined in .NET:
private byte[] BuildDataStructure(cDHCPStruct.DHCPstruct ddHcpS)
{
byte[] mArray;
try
{
mArray = new byte[0];
AddOptionElement(new byte[] { ddHcpS.D_op }, ref mArray);
AddOptionElement(new byte[] { ddHcpS.D_htype }, ref mArray);
AddOptionElement(new byte[] { ddHcpS.D_hlen }, ref mArray);
AddOptionElement(new byte[] { ddHcpS.D_hops }, ref mArray);
AddOptionElement(ddHcpS.D_xid, ref mArray);
AddOptionElement(ddHcpS.D_secs, ref mArray);
AddOptionElement(ddHcpS.D_flags, ref mArray);
AddOptionElement(ddHcpS.D_ciaddr, ref mArray);
AddOptionElement(ddHcpS.D_yiaddr, ref mArray);
AddOptionElement(ddHcpS.D_siaddr, ref mArray);
AddOptionElement(ddHcpS.D_giaddr, ref mArray);
AddOptionElement(ddHcpS.D_chaddr, ref mArray);
AddOptionElement(ddHcpS.D_sname, ref mArray);
AddOptionElement(ddHcpS.D_file, ref mArray);
AddOptionElement(ddHcpS.M_Cookie, ref mArray);
AddOptionElement(ddHcpS.D_options, ref mArray);
return mArray;
}
catch (Exception ex)
{
MessageBox.Show(ex.Message);
return false;
}
finally
{
marray = null;
}
}
private void AddOptionElement(byte[] FromValue, ref byte[] TargetArray)
{
try
{
if (TargetArray != null)
Array.Resize(ref TargetArray,
TargetArray.Length + FromValue.Length );
else
Array.Resize(ref TargetArray, FromValue.Length );
Array.Copy(FromValue, 0, TargetArray,
TargetArray.Length - FromValue.Length,
FromValue.Length);
}
catch (Exception ex)
{
Console.WriteLine(ex.Message);
}
}
The code provided
The code provided is split into three main classes, an asynchronous UDP service, a DHCP structure converter, and the main form. Each communicate to the other by using events; however, callbacks can be used as well. It is important to note that when calling controls from an event, the events shall be multi-cast, and Invoke
will need to be used.
Conclusion
This is a very basic implementation of a DHCP server class, and various improvements can be made by adding more options according to individual needs.