Иногда, нужен исходник простой DHCP-службы, чтобы добавить её функциональность к себе в проект, а не использовать для этого полноценный DHCP сервер. Например, в локальной сети, есть главный сервер DHCP, который выделяет компьютерам IP-адреса, и есть также небольшой сервер DHCP, который выделяет определённый IP-адрес только конкретному устройству. Так как DHCP-протокол работает через UPD, то данные можно посылать широковещательно (а не точка-точка), соответственно и подхватить их в сети можно легко. Для этого мы будем воспольльзуемся простым алгоритмом фильтрации, который будет фильтровать запросы и выдавать IP-адреса только разрешенным MAC-адресам.
Определение DHCP
DHCP расшифровывается как Dynamic Host Configuration Protocol и состоит из двух компонент: клиент DHCP (сетевое устройство запрашивающее IP-настройки), и DHCP-сервер (Интернет-узел, который возвращает параметры конфигурации запросившему клиенту).
Краткое описание того, как работает небольшой DHCP-сервер
DHCP-сервер обычно устанавливается в локальной сети, и используется для централизованного выделения конфигураций TCP-IP сетевым устройствам или компьютерам с установкой автоматического получения айпишника. DHCP-сервер ожидает запросы на UDP-порте номер 67 и отправляет данные клиентам так же на UDP-порт 67. Служба UDP использует асинхронный метод с использованием функции обратного вызова:
//эта функция запускает слушающий UDP-сервис
private void IniListnerCallBack()
{
try
{
// start teh recieve call back method
s.u.BeginReceive(new AsyncCallback(OnDataRecieved), s);
}
catch (Exception ex)
{
if (IsListening == true)
Console.WriteLine(ex.Message);
}
}
// Это коллбэк функция, которая вызывает в момент приёма данных.
// переменная asyn должна содержать экземпляр UPD-структуры (UDPstate)
public void OnDataRecieved(IAsyncResult asyn)
{
Byte[] receiveBytes;
UdpClient u;
IPEndPoint e;
try
{
//получаем udp-пакет клиента
u = (UdpClient)((UdpState)(asyn.AsyncState)).u;
//get the endpoint (shall contain refernce about the client)
e = (IPEndPoint)((UdpState)(asyn.AsyncState)).e;
//останавливаем коллбэк и получаем количество принятых байт
receiveBytes = u.EndReceive(asyn, ref e);
//генерируем событие с полученными данными в DHCP-класс
DataRcvd(receiveBytes, e);
}
catch (Exception ex)
{
if (IsListening == true)
Console.WriteLine(ex.Message);
}
finally
{
u = null;
e = null;
receiveBytes = null;
// Заново запускаем слушающий сервис
IniListnerCallBack();
}
}
UDP-клиент должен быть запущен и слушать в сети входящие запросы. Каждое сообщение должно идентифицироваться уникальным МАС-адресом и номером транзакции (Transaction ID (D_xid) - это случайное число, сгенерированное клиентом). При обмене данные передаются как поток байтов и формат должен соответствовать следующей RFC-структуре:
public struct DHCPstruct
{
public byte D_op; //Op код: 1 = bootRequest, 2 = BootReply
public byte D_htype; //Тип физического адреса: 1 = 10MB эзернет
public byte D_hlen; //длина физического адреса: длина MACID
public byte D_hops; //физические опции
public byte[] D_xid; //transaction id (5),
public byte[] D_secs; //elapsed time from trying to boot (3)
public byte[] D_flags; //флаги (3)
public byte[] D_ciaddr; // IP клиента (5)
public byte[] D_yiaddr; // IP вашего клиента (5)
public byte[] D_siaddr; // IP сервера (5)
public byte[] D_giaddr; // relay agent IP (5)
public byte[] D_chaddr; // физический адрес клиента (16)
public byte[] D_sname; // Необязательное имя сервера (64)
public byte[] D_file; // имя бут-файла (128)
public byte[] M_Cookie; // Магические кукисы (4)
public byte[] D_options; //опции (rest)
}
Таким образом, данные передаются в DHCP-класс через событие как поток байтов. Для этого воспользуемся .NET-классом BinaryReader, чтобы помещать байты в соответствующем порядке. OPTION_OFFSET - константа, определяющая место, с которого начинаются данные DHCP-структуры:
//pass over a byte as convert it
//using the predefined stream reader function
//Data is an array containing the udp data sent.
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);
}
}
Клиент, запрашивающий адрес IP также должен передать серверу список опций, которые сервер должен заполнить и передать обратно клиенту. Опции, которые можно передать в списке определены в RFC и могут содержать следующие значения:
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
}
В массиве байтов список опций будет выглядеть следующим образом:
-------------------------------------------------
|a|len|Message|a|len|Message|........|END_OPTION|
-------------------------------------------------
где:
•"а" означает начало кода опции, как показано выше •len - длина сообщения в байтах •Message - передаваемое сообщение, длина которого определена в len •END_OPTION - означает конец сообщения с настройками
Тип сообщения (message type)
Тип сообщения находится в списке опций под номером 53 и определяет текущее соостояние переговоров клиента и сервера:public enum DHCPMsgType //Типы сообщений, описанные в RFC
{
DHCPDISCOVER = 1, //клиент пытается найти dhcp-сервера
DHCPOFFER = 2, //сервер предлагает IP-адреса устройству
DHCPREQUEST = 3, //клиент согласен принять айпишник от DHCP-сервера
DHCPDECLINE = 4, //клиент отверг предложенный адрес
DHCPACK = 5, //server to client + committed IP address
DHCPNAK = 6, //server to client to state net address incorrect
DHCPRELEASE = 7, //graceful shutdown from client to Server
DHCPINFORM = 8 //клиент запрашивает локальную информацию
}
В коде, это должно быть реализовано как сообщение из DHCP-класса в главную форму:
//an event has to call a delegate (function pointer)
#region "event Delegates"
public delegate void AnnouncedEventHandler(cDHCPStruct d_DHCP,string MacId);
public delegate void ReleasedEventHandler();//(cDHCPStruct d_DHCP);
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;
Перед тем как назначить IP-адресс, необходимо воспользоваться фреймфорковский классом Ping, чтобы проверить, не используется ли уже это айпишник.
public static bool CheckAlive(string IpAdd)
{
Ping pingSender = new Ping();
IPAddress address;
PingReply reply;
try
{
address = IPAddress.Parse(IpAdd);//IPAddress.Loopback;
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;
}
}
Затем наше приложение должно конвертировать данные из структуры в поток байтов, используя для этого класс Array:
//функция для преобразования структуры данных в массив байт
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;
}
}
//function to grow an array, we shall pass
//the array back by using references
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);
}
}
Заключение
Приведённый в этой статье код делится на три основных класса: асинхронный UDP сервис, конвертор DHCP-структуры и главная форма. Общаются они между собой при помощи событий, однако, можно использовать и обратные вызовы (колбэки). Важно отметить, что при вызове управления из события, события должны быть мультикастовыми и необходимо использовать Invoke.
Скачать исходник - 167 кб /filesmou/smallDHCPServer.zip
Скачать приложение - 85 кб /filesmou/ApplicationDHCPserver.zip