Поиск на сайте
Главная Журнал Форум Wiki DRKB Страны мира

Небольшой DHCP сервер

Иногда, нужен исходник простой 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 кб

Скачать приложение - 85 кб




Основные разделы сайта


 

Реклама надежные часы марки 4asiki