ARP-протокол
Я уже говорили о том, что перед обращением к компьютеру по локальной сети необходимо узнать его МАС-адрес. Для этого существует ARP-протокол, который по IP-адресу ищет МАС-адрес. Происходит это автоматически и незаметно для рядового пользователя, но иногда появляется возможность ручного управления таблицей ARP. В ОС Windows для этих целей есть утилита с одноименным названием аrр, но она консольная, и работать с ней не очень удобно. Сейчас я покажу простую графическую утилиту, на примере которой и объясню функции работы с данным протоколом.
Создайте новое MFC-приложение на основе диалога. Внешний вид главного диалогового окна представлен на 6.9. Здесь используются две строки ввода для указания IP- и МАС-адреса. По нажатии кнопки Add будет добавляться новая ARP-запись, в которой по указанному IP-адресу будет определяться МАС-адрес. По нажатии кнопки Update в списке List Box будет отображаться таблица ARP. Кнопка Delete будет использоваться для удаления записи из таблицы.
В обработчик события кнопки Update нужно написать код из листинга 6.10.
Листинг 6.10. Отображение таблицы ARP |
DWORD dwActualSize = 0; GetIpNetTable(pIpArpTab, dwActualSize, true);
pIpArpTab = (PMIB_IPNETTABLE) malloc(dwActualSize); if (GetIpNetTable(pIpArpTab, dwActualSize, true) != NO_ERROR) { if (pIpArpTab) free (pIpArpTab); return; }
DWORD i, dwCurrIndex; char sPhysAddr[256], sType[256], sAddr[256]; PMIB_IPADDRTABLE pIpAddrTable = NULL; char Str[255];
dwActualSize = 0; GetIpAddrTable(pIpAddrTable, dwActualSize, true); pIpAddrTable = (PMIB_IPADDRTABLE) malloc(dwActualSize); GetIpAddrTable(pIpAddrTable, dwActualSize, true);
dwCurrIndex = -100;
for (i = 0; i pIpArpTab-dwNumEntries; ++i) { if (pIpArpTab-table[i].dwIndex != dwCurrIndex) { dwCurrIndex = pIpArpTab-table[i].dwIndex;
struct in_addr in_ad; sAddr[0] = '\n'; for (int i = 0; i pIpAddrTable-dwNumEntries; i++) { if (dwCurrIndex != pIpAddrTable-table[i].dwIndex) continue;
in_ad.s_addr = pIpAddrTable-table[i].dwAddr; strcpy(sAddr, inet_ntoa(in_ad)); }
sprintf(Str,"Interface: %s on Interface 0x%X", sAddr, dwCurrIndex); lbMessages.AddString(Str); lbMessages.AddString(" Internet Address | Physical Address | Type"); }
AddrToStr(pIpArpTab-table[i].bPhysAddr, pIpArpTab-table[i].dwPhysAddrLen, sPhysAddr);
switch (pIpArpTab-table[i].dwType) { case 1: strcpy(sType,"Other"); break; case 2: strcpy(sType,"Invalidated"); break; case 3: strcpy(sType,"Dynamic"); break; case 4: strcpy(sType,"Static"); break; default: strcpy(sType,""); }
struct in_addr in_ad; in_ad.s_addr = pIpArpTab-table[i].dwAddr; sprintf(Str, " %-16s | %-17s | %-11s", inet_ntoa(in_ad), sPhysAddr, sType); lbMessages.AddString(Str); }
free(pIpArpTab); }
Посмотрите внимательно на код. Обратите внимание, что в данном случае не загружается библиотека WinSock. К проекту подключается заголовочный файл winsock.h, потому что используются типы данных, которые объявлены в нем. Но обращение к ним происходит только при компиляции. В данном случае не применяются библиотечные функции, необходимые на этапе выполнения.
Если вы добавите в программу код, который будет содержать хотя бы одну функцию из библиотеки WinSock (например, определение имени хоста по IP-адресу), то прежде чем ее использовать, необходимо будет загрузить библиотеку.
Первым делом из системы получена таблица соответствия IP-адресов их физическим МАС-адресам. Это и есть ничто иное, как ARP-таблица. Функция для получения этой таблицы имеет следующий вид:
DWORD GetIpNetTable( PMIB_IPNETTABLE pIpNetTable, PULONG pdwSize, BOOL border );
ARP-таблица описывается следующими параметрами:
указатель на структуру типа MIB_IPNETTABLE, в которой будет размещена таблица;
размер структуры. Если он нулевой, то функция вернет объем памяти, требуемый для таблицы;
сортировка — если параметр равен true, то таблица будет упорядоченной.
Структура MIB_IPNETTABLE, которая задается в качестве первого параметра, имеет следующий вид:
typedef struct _MIB_IPNETTABLE { DWORD dwNumEntries; MIB_IPNETROW table[ANY_SIZE]; } MIB_IPNETTABLE, *PMIB_IPNETTABLE;
Первый параметр указывает на количество записей в таблице, а второй — это структура, содержащая данные таблицы:
typedef struct _MIB_IPNETROW { DWORD dwIndex; DWORD dwPhysAddrLen; BYTE bPhysAddr[MAXLEN_PHYSADDR]; DWORD dwAddr; DWORD dwType; } MIB_IPNETROW, *PMIB_IPNETROW;
Структура с данными таблицы описывается набором параметров:
dwIndex — индекс адаптера;
dwPhysAddrLen — длина физического адреса;
bPhysAddr — физический адрес;
dwAddr — IP-адрес;
dwType — тип записи. В свою очередь может принимать такие значения:
4 — статический. Если запись добавлена вручную с помощью функций, которые будут рассмотрены позже;
3 — динамический. Записи с адресами, которые получены автоматически с помощью протокола ARP (действительны в течение определенного времени, а потом автоматически уничтожаются);
2 — неправильный. Записи с ошибками;
1 — другой.
В принципе, ARP-таблица уже создана. Если в компьютере установлена только одна сетевая карта, то этого достаточно, потому что все записи будут относиться к ней. Если установлена хотя бы две сетевые карты, то часть записей будет принадлежать одному интерфейсу, а остальные — второму.
Чтобы картина была полной, необходимо показать, какие записи к какому интерфейсу относятся. Для этого есть индекс интерфейса в структуре MIB_IPNETROW, но этот индекс абсолютно ничего не скажет конечному пользователю. Но в сочетании с IP-адресом адаптера это станет более информативно.
А вот IP-адреса адаптера у нас пока нет. Чтобы его узнать, нужно получить таблицу соответствия IP-адресов адаптерам. Это можно сделать с помощью функции GetIpAddrTable. Функция похожа на GetIpNetTable:
DWORD GetIpAddrTable ( PMIB_IPADDRTABLE pIpAddrTable, PULONG pdwSize, BOOL bOrder );
И так же имеет три параметра: указатель на структуру типа MIB_IPADDRTABLE (pIpAddrTable), размер структуры (pdwSize) и флаг сортировки (bOrder).
Первый параметр — это структура следующего вида:
typedef struct _MIB_IPADDRTABLE { DWORD dwNumEntries; MIB_IPADDRROW table[ANY_SIZE]; } MIB_IPADDRTABLE, *PMIB_IPADDRTABLE;
У этой структуры два параметра:
dwNumEntries — количество структур, указанных во втором параметре;
table — массив структур типа MIB_IPADDRROW.
Структура MIB_IPADDRROW описывается следующим образом:
typedef struct _MIB_IPADDRROW { DWORD dwAddr; DWORDIF_INDEX dwIndex; DWORD dwMask; DWORD dwBCastAddr; DWORD dwReasmSize; unsigned short unused1; unsigned short wType; } MIB_IPADDRROW, *PMIB_IPADDRROW;
В этой структуре доступны следующие параметры:
dwAddr — IP-адрес;
dwIndex — индекс адаптера, с которым связан IP-адрес;
dwMask — маска для IP-адреса;
dwBCastAddr — широковещательный адрес. Чаще всего это IP-адрес, в котором нулевое значение номера узла. Например, если у вас IP-адрес 192.168.4.7, то широковещательный адрес будет 192.168.4.0;
dwReasmSize — максимальный размер получаемых пакетов;
unused1 — зарезервировано;
wType — тип адреса, может принимать следующие значения:
MIB_IPADDR_PRIMARY — основной IP-адрес;
MIB_IPADDR_DYNAMIC — динамический адрес;
MIB_IPADDR_DISCONNECTED — адрес на отключенном интерфейсе, например, отсутствует сетевой кабель;
MIB_IPADDR_DELETED — адрес в процессе удаления;
MIB_IPADDR_TRANSIENT — временный адрес.
Когда получены необходимые данные, запускается цикл перебора всех строк в таблице ARP. Но прежде чем выводить на экран информацию о строке, надо проверить, к какому интерфейсу она относится.
При получении данных был выбран режим сортировки записей, поэтому можно надеяться, что вначале идут записи одного интерфейса, а потом другого. Поэтому перед циклом в переменную dwCurrIndex занесено значение —100. Интерфейса с таким номером точно не будет. На первом же шаге цикла будет видно, что запись из ARP-таблицы не относится к интерфейсу с номером —100, и необходимо вывести на экран IP-адрес сетевой карты, к которой относится эта запись. Для этого по параметру dwIndex ищется запись в таблице соответствия IP-адресов номерам интерфейса. Если запись найдена (а она должна быть найдена), то выводится заголовок таблицы, который будет выглядеть примерно так:
Interface: 192.168.1.100 on Interface 0x10000003
Internet Address | Physical Address | Type
Затем выводится информация из ARP-таблицы, пока не встретится запись, относящаяся к другому интерфейсу. Тогда снова выводится заголовок и т.д.
На 6.9 вы можете увидеть пример работы программы на моем компьютере. Обращаю ваше внимание, что у вас может и не быть ARP-записей, потому что они существуют только при работе в локальной сети. При выходе в Интернет по модему протокол ARP не используется.

6.9. Результат работы программы ARPApplication
Если вы пока не знаете, как применить программу, то у меня уже были случаи, когда она оказалась незаменима. Допустим, что нужно узнать МАС-адрес компьютера в локальной сети, который находится от вас очень далеко. Можно пойти к этому компьютеру и посмотреть адрес с помощью утилиты ipconfig, а можно произвести следующие действия:
Выполнить программу Ping для проверки связи с удаленным компьютером. В этот момент отсылаются эхо-пакеты, которым также нужно знать МАС-адрес, и для этого задействуется ARP-протокол.
Запустить программу просмотра ARP-таблицы и там посмотреть МАС-адрес нужного компьютера.
Теперь посмотрите, как можно добавлять новые записи в таблицу ARP. Напоминаю, что все записи, добавленные программно, становятся статичными и не уничтожаются автоматически на протяжении всей работы ОС. По нажатии кнопки Add будет выполняться код из листинга 6.11.
Листинг 6.11. Добавление новой записи в таблицу ARP |
edIPAddress.GetWindowText(sInetAddr, 255); edMacAddress.GetWindowText(sMacAddr, 255); edInterface.GetWindowText(sInterface, 255);
if (sInetAddr == NULL || sMacAddr == NULL || sInterface == NULL) { AfxMessageBox("Fill IP address, MAC address and Interface"); return; }
DWORD dwInetAddr; dwInetAddr = inet_addr(sInetAddr); if (dwInetAddr == INADDR_NONE) { AfxMessageBox("Bad IP Address"); return; }
StrToMACAddr(sMacAddr, sPhysAddr);
MIB_IPNETROW arpRow; sscanf(sInterface, "%X",(arpRow.dwIndex));
arpRow.dwPhysAddrLen = 6; memcpy(arpRow.bPhysAddr, sPhysAddr, 6); arpRow.dwAddr = dwInetAddr; arpRow.dwType = MIB_IPNET_TYPE_STATIC;
if (SetIpNetEntry(arpRow) != NO_ERROR) AfxMessageBox("Couldn't add ARP record"); }
Самое главное здесь — это функция SetIpNetEntry , которая добавляет новую ARP-запись и выглядит следующим образом:
DWORD SetIpNetEntry ( PMIB_IPNETROW pArpEntry );
В качестве единственного параметра функции указывается структура типа MIB_IPNETROW, которую мы уже использовали при получении данных ARP-таблицы. В этой структуре необходимо указать четыре параметра: интерфейс (dwIndex), МАС-адрес (bPhysAddr) и IP-адрес (dwInetAddr), запись которого надо добавить, и тип записи (в поле dwType значение MIB_IPNET_TYPE_STATIC). Остальные поля в этой функции не используются, и их заполнять не надо.
Теперь посмотрите на функцию удаления. По нажатии кнопки Delete выполняется код из листинга 6.12.
Листинг 6.12. Удаление записи из ARP-таблицы |
edIPAddress.GetWindowText(sInetAddr, 255); edInterface.GetWindowText(sInterface, 255);
if (sInetAddr == NULL || sInterface == NULL) { AfxMessageBox("Fill IP address and Interface"); return; }
DWORD dwInetAddr; dwInetAddr = inet_addr(sInetAddr); if (dwInetAddr == INADDR_NONE) { printf("IpArp: Bad Argument %s\n", sInetAddr); return; }
MIB_IPNETROW arpEntry;
sscanf(sInterface, "%X",(arpEntry.dwIndex)); arpEntry.dwAddr = dwInetAddr;
if (DeleteIpNetEntry(arpEntry) != NO_ERROR) AfxMessageBox("Couldn't delete ARP record"); }
Для удаления записи используется функция DeleteIpNetEntry, которая выглядит следующим образом:
DWORD DeleteIpNetEntry( PMIB_IPNETROW pArpEntry );
У нее один параметр в виде структуры PMIB_IPNETROW, в которой нужно указывать только интерфейс и IP-адрес, запись которого надо удалить.
Примечание |
Исходный код примера, описанного в этом разделе, вы можете найти на компакт - диске в каталоге \Demo\Chapter6\ARPApplication. |
![]() |
![]() |