Узнаём mac-адреса сетевых карт не используя netbios

Как узнать mac-адреса сетевых карт ? В рунете упорно кочует только способ на базе netbios. Его недостаток - он применим только для интерфейсов, на которых этот самый протокол NetBios задействован, что как раз не есть гуд. Сама по себе идея оставлять NetBios на интерфейсе, смотрящем в нет - идея плохая. Поэтому способ определения mac посредством netbios нельзя считать универсальным.

Более универсальный способ

Нормальные герои, как известно, идут в обход. Я подсмотрел, откуда берёт данные утилитка ipconfig.exe. Оказалось, что нужную информацию совершенно бесплатно предоставляет iphlpapi.dll.

Определимся с терминами

В терминах мелкомягких библиотек, мы определяем не mac-адреса сетевых карт, а физические адреса подключенных сетевых адаптеров.

В списке под видом адаптеров фигурируют не только сетевые, но и, например, PPP-подключения. Loopback в списке не фигурирует.

Собсно, код.

Из инклудов нам интересен только <iphlpapi.h>.

#include "stdafx.h"
#include "delme.h"
#include <windows.h>
#include <iphlpapi.h>
#include <stdio.h>

Нужно сказать, что одного инклуда нам недостаточно. Нам нужно подключить dll динамически.

int _tmain(int argc, TCHAR* argv[], TCHAR* envp[])
{

    // Тип указателя на функцию GetAdaptersInfo
    typedef DWORD(CALLBACK* PTR_GETADAPTERSINFO)(PIP_ADAPTER_INFO,PULONG);

    // Пытаемся подгрузить iphlpapi.dll. Под win95 скорее всего и не подгрузим.
    HINSTANCE iphlpapi;
    iphlpapi=LoadLibrary("iphlpapi.dll");
    if(!iphlpapi)
    {
        printf ("iphlpapi.dll не поддерживается\n");
        return (1);
    }

    PTR_GETADAPTERSINFO GetAdaptersInfo;
    GetAdaptersInfo = (PTR_GETADAPTERSINFO)GetProcAddress(iphlpapi,
"GetAdaptersInfo");

Здесь всё стандартно. Единственное, перенося этот код, не забудьте проверить GetAdaptersInfo на NULL. Такого случиться вроде и не должно, но... очень уж переход по адресу NULL паскудно смотрится :)
Теперь GetAdaptersInfo подключена к одноимённой функции из iphlpapi.dll и её можно вызывать. Прототип функции выглядит так:

DWORD GetAdaptersInfo(
  PIP_ADAPTER_INFO pAdapterInfo,  // буфер для принятых данных
  PULONG pOutBufLen               // размер буфера
);

А вот определение IP_ADAPTER_INFO :

typedef struct _IP_ADAPTER_INFO {
  struct _IP_ADAPTER_INFO* Next;
  DWORD ComboIndex;
  char AdapterName[MAX_ADAPTER_NAME_LENGTH + 4];
  char Description[MAX_ADAPTER_DESCRIPTION_LENGTH + 4];
  UINT AddressLength;
  BYTE Address[MAX_ADAPTER_ADDRESS_LENGTH];
  DWORD Index;
  UINT Type;
  UINT DhcpEnabled;
  PIP_ADDR_STRING CurrentIpAddress;
  IP_ADDR_STRING IpAddressList;
  IP_ADDR_STRING GatewayList;
  IP_ADDR_STRING DhcpServer;
  BOOL HaveWins;
  IP_ADDR_STRING PrimaryWinsServer;
  IP_ADDR_STRING SecondaryWinsServer;
  time_t LeaseObtained;
  time_t LeaseExpires; 
} IP_ADAPTER_INFO, *PIP_ADAPTER_INFO;

Как узнать, сколько отдать под буфер ? Очень просто. Вторым параметром мы передаём GetAdapterInfo указатель на размер буфера. Если этого размера будет недостаточно, то GetAdapterInfo возвращает код ошибки, а в эту же самую переменную запишет требуемый размер.

    ULONG adapter_info_size = 0;
    PIP_ADAPTER_INFO ptr_adapter_info = NULL;
    PIP_ADAPTER_INFO ptr_adapter_info_first = NULL;

    GetAdaptersInfo( ptr_adapter_info, &adapter_info_size );

    ptr_adapter_info_first = ptr_adapter_info = (PIP_ADAPTER_INFO) new(
char[adapter_info_size] );

    if ( GetAdaptersInfo( ptr_adapter_info, &adapter_info_size ) != ERROR_SUCCESS)
    {
        printf( "Error while GetAdaptersInfo\n" );
        delete( ptr_adapter_info );
        return( 1 );
    }

Так мы и сделали: первый вызов GetAdaptersInfo "вхолостую", с нулевым буфером. Получили требуемый размер буфера, создали его, второй раз идёт уже "правильный" вызов GetAdaptersInfo.

Для понимания дальнейшего куска, нужно знать, что ptr_adapter_info->Next содержит указатель на следующую структуру IP_ADAPTER_INFO. Последняя в последовательности структура совершенно "неожиданно" содержит в Next... NULL :)

    while( ptr_adapter_info )
    {
        printf ( "ID of adapter: %s\n", ptr_adapter_info->AdapterName );
        printf ( "Description: %s\n", ptr_adapter_info->Description );
        printf ( "MAC address: " );

        for( char i=0; i < (int)ptr_adapter_info->AddressLength; i++)
        {
            printf ("%02x ", (unsigned char)ptr_adapter_info->Address[i]);
        }
        printf ("\n");

        printf ("Adapter type: %u\n\n", ptr_adapter_info->Type );
        
        ptr_adapter_info = ptr_adapter_info->Next;

    }

Дальнейшее не объясняю:

    delete( ptr_adapter_info_first );
    char a = getchar();
    return( 0 );
}

Ура три раза.

Что ещё ?

  • Как видно из описания её структуры, IP_ADAPTER_INFO содержит не только физические адреса. Остальное, как это модно писать, "выходит за рамки". Так сказать, man msdn :)
  • Adress содержит не обязательно 6 байт адреса. В зависимости от Type, длинна (и значение тоже) могут быть другими.
  • CurrentIpAddress зарезервирован и ничего полезного не содержит, IP-адреса брать из IpAddressList
  • Обратите внимание на идетнификаторы из поля AdapterName и раздел HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows NT\CurrentVersion\NetworkCards реестра. Так, на всякий случай.

Засада

Под Win95 этот способ не поддерживается. Также имеется засада под Win98, если установлен Интернет Испортил 5.0. версии (5.00.2314.1003).

Напоследок

Данный материал не претендует на полноту... и всё такое.
Если что - пишите, высылайте...
Будете копировать - оставляйте копирайты. Спасибо.

Автор: Денис Сетевой
www.proline.biz.ua



Опубликовал admin
4 Июл, Воскресенье 2010г.



Программирование для чайников.