Определение пути пакета
Как можно определить путь пакета, по которому он идет от нас до адресата? Если принять во внимание предназначение ICMP-сообщений, то проблема решается просто. Каждый пакет имеет поле TTL (Time To Leave, время жизни). Каждый маршрутизатор уменьшает значение поля на единицу, и когда оно становится равным нулю, пакет считается заблудившимся, и маршрутизатор возвращает ICMP-сообщение об ошибке. Использование этого поля еще упрощает проблему.
Надо направить пакет на сервер с временем жизни, равным 1. Первый же маршрутизатор уменьшит значение на 1 и увидит 0. Это заставит его вернуть ICMP-сообщение об ошибке, по которому можно узнать первый узел, через который проходит пакет. Затем отсылается пакет с временем жизни, равным 2, и определяется второй маршрутизатор (первый пропустит пакет, а второй вернет ICMP-сообщение). Таким образом можно отсылать множество пакетов, пока не достигнем адресата.
Стало быть, есть возможность узнать не только маршрут, но и время отклика каждого маршрутизатора, что позволяет определить слабое звено на пути следования пакета. Связь с каждым из устройств на пути пакета может изменяться в зависимости от нагрузки, поэтому желательно сделать несколько попыток соединения, чтобы определить среднее время отклика.
Конечно же, первый пакет может пойти одним маршрутом, а второй — другим, но чаще всего все пакеты движутся по одному и тому же маршруту.
Сейчас я покажу простейший пример определения пути следования маршрута. Но только ICMP-пакеты я буду посылать не через RAW-сокеты, а через библиотеку icmp.dll. В этой библиотеке есть все необходимые функции для создания сокета и отправки пакета. Нужно только указать адрес, содержимое пакета и его параметры, а все остальное сделают за вас. Таким образом, вы научитесь пользоваться библиотекой icmp.dll и сможете выяснить путь прохождения пакета.
Создайте новое MFC-приложение TraceRote. На главном окне вам понадобится одна строка ввода для указания адреса компьютера, связь с которым необходимо проверить, один компонент типа List Box для отображения информации и кнопка (например, Trace), по которой будет пинговаться удаленный компьютер. По нажатии кнопки будет выполняться код из листинга 6.9.
Листинг 6.9. Определение пути следования пакета |
hIcmp = LoadLibrary("ICMP.DLL"); if (hIcmp == NULL) { AfxMessageBox("Can't load ICMP DLL"); return; }
pIcmpCreateFile = (lpIcmpCreateFile) GetProcAddress(hIcmp, "IcmpCreateFile"); pIcmpSendEcho = (lpIcmpSendEcho) GetProcAddress(hIcmp, "IcmpSendEcho"); pIcmpCloseHandle = (lpIcmpCloseHandle) GetProcAddress(hIcmp, "IcmpCloseHandle");
in_addr Address; if (pIcmpCreateFile == NULL) { AfxMessageBox("ICMP library error"); return; }
char chHostName[255]; edHostName.GetWindowText(chHostName, 255); LPHOSTENT hp = gethostbyname(chHostName); if (hp== NULL) { AfxMessageBox("Host not found"); return; } unsigned long addr; memcpy(addr, hp-h_addr, hp-h_length);
BOOL bReachedHost = FALSE; for (UCHAR i=1; i=50 !bReachedHost; i++) { Address.S_un.S_addr = 0;
int iPacketSize=32; int iRTT;
HANDLE hIP = pIcmpCreateFile(); if (hIP == INVALID_HANDLE_VALUE) { AfxMessageBox("Could not get a valid ICMP handle"); return; }
unsigned char* pBuf = new unsigned char[iPacketSize]; FillMemory(pBuf, iPacketSize, 80);
int iReplySize = sizeof(ICMP_ECHO_REPLY) + iPacketSize; unsigned char* pReplyBuf = new unsigned char[iReplySize]; ICMP_ECHO_REPLY* pEchoReply = (ICMP_ECHO_REPLY*) pReplyBuf;
IP_OPTION_INFORMATION ipOptionInfo; ZeroMemory(ipOptionInfo, sizeof(IP_OPTION_INFORMATION)); ipOptionInfo.Ttl = i;
DWORD nRecvPackets = pIcmpSendEcho(hIP, addr, pBuf, iPacketSize, ipOptionInfo, pReplyBuf, iReplySize, 30000);
if (nRecvPackets != 1) { AfxMessageBox("Can't ping host"); return; } Address.S_un.S_addr = pEchoReply-Address; iRTT = pEchoReply-RoundTripTime;
pIcmpCloseHandle(hIP);
delete [] pReplyBuf; delete [] pBuf;
char lpszText[255];
hostent* phostent = NULL; phostent = gethostbyaddr((char *)Address.S_un.S_addr, 4, PF_INET);
if (phostent) sprintf(lpszText, "%d: %d ms [%s] (%d.%d.%d.%d)", i, iRTT, phostent-h_name, Address.S_un.S_un_b.s_b1, Address.S_un.S_un_b.s_b2, Address.S_un.S_un_b.s_b3, Address.S_un.S_un_b.s_b4); else sprintf(lpszText, "%d - %d ms (%d.%d.%d.%d)", i, iRTT, Address.S_un.S_un_b.s_b1, Address.S_un.S_un_b.s_b2, Address.S_un.S_un_b.s_b3, Address.S_un.S_un_b.s_b4);
lbMessages.AddString(lpszText);
if (addr == Address.S_un.S_addr) bReachedHost = TRUE; }
if (hIcmp) { FreeLibrary(hIcmp); hIcmp = NULL; }
WSACleanup(); }
Несмотря на то, что используется дополнительная библиотека icmp.dll, библиотеку WinSock надо загрузить в любом случае. К тому же будет использоваться функция gethostbyname для определения IP-адреса, если пользователь укажет символьное имя компьютера. В данном случае будет достаточно первой версии библиотеки, т. к. не будут применяться RAW-сокеты. Таким образом, программа сможет работать и в Windows 98 (без WinSock 2.0).
После этого нужно загрузить динамическую библиотеку icmp.dll с помощью функции LoadLibrary. Она находится в папке windows/system (или windows/system32), поэтому не надо указывать полный путь. Программа без проблем найдет и загрузит библиотеку.
В библиотеке нас будут интересовать следующие процедуры:
IcmpCreateFile — инициализация;
IcmpSendEcho — отправка эхо-пакета;
IcmpCloseHandle — закрытие ICMP.
Прежде чем посылать пакет, следует его проинициализировать с помощью функции IcmpCreateFile. По завершении работы с ICMP нужно вызвать функцию IcmpCloseHandle, чтобы закрыть его.
Теперь в заранее подготовленные переменные запоминаются адреса необходимых процедур из библиотеки:
pIcmpCreateFile=(lpIcmpCreateFile)GetProcAddress(hIcmp,"IcmpCreateFile");
pIcmpSendEcho=(lpIcmpSendEcho)GetProcAddress(hIcmp,"IcmpSendEcho");
pIcmpCloseHandle=(lpIcmpCloseHandle)GetProcAddress(hIcmp,"IcmpCloseHandle");
Если писать программу по всем правилам, то необходимо было бы проверить полученные адреса на равенство нулю. Если хотя бы один адрес функции нулевой, то она не найдена, и дальнейшее ее использование невозможно. Чаще всего такое бывает из-за неправильного написания имени функции. Но может случиться, когда программа загрузит другую библиотеку с таким же именем, в которой вообще нет таких функций. Чтобы этого не произошло, переменная pIcmpCreateFile (она должна содержать адрес функции IcmpCreateFile) проверяется на равенство нулю. Если это так, то загрузилась ошибочная библиотека, и об этом выводится соответствующее сообщение. Остальные переменные не проверяются (в надежде на правильное написание).
Следующим этапом определяется адрес компьютера, путь к которому надо найти, и переводится в IP-адрес. Если в этот момент произошла ошибка, то адрес указан неверно, и дальнейшее выполнение кода невозможно.
Вот теперь можно переходить к пингованию удаленного компьютера. Так как может возникнуть необходимость послать несколько пакетов с разным временем жизни, запускается цикл от 1 до 50. Использование в данном случае цикла while, который выполнялся бы, пока пинг не дойдет до нужного компьютера, не рекомендуется, т.к. появляется вероятность возникновения бесконечного цикла.
Внутри цикла инициализируется ICMP-пакет с помощью функции IcmpCreateFile. Результатом будет указатель на созданный объект, который понадобится при посылке эхо-пакета, поэтому он сохраняется в переменной hIP типа HANDLE:
HANDLE hIP = pIcmpCreateFile(); if (hIP == INVALID_HANDLE_VALUE) { AfxMessageBox("Could not get a valid ICMP handle"); return; }
Если результат равен INVALID_HANDLE_VALUE, то во время инициализации произошла ошибка, и дальнейшее выполнение невозможно.
После этого выделяется буфер для данных, который, как и в случае с пин-гом, заполняется символом с кодом 80. Далее создается пакет типа ICMP_ECHO_REPLY, в котором возвращается информация, полученная от маршрутизатора или компьютера. Нужно также создать пакет типа IP_OPTION_INFORMATION, в котором указывается время жизни пакета (параметр Ttl).
Когда все подготовлено, можно отправлять ICMP-пакет с помощью функции IcmpSendEcho, у которой 8 параметров:
указатель ICMP (получен во время инициализации);
адрес компьютера;
буфер с данными;
размер пакета (с учетом объема посылаемых данных);
IP-пакет с указанием времени жизни (на первом шаге он будет равен единице, потом двум и т.д.);
буфер для хранения структуры типа ICMP_ECHO_REPLY, в которую будет записан результирующий пакет;
размер буфера;
время ожидания ответа.
В качестве результата функция возвращает количество принятых пакетов. В нашем случае он один. Если возвращаемое значение равно нулю, то маршрутизатор или компьютер не ответили ICMP-пакетом, и невозможно выяснить его параметры.
Время ответа можно получить из параметра RoundTripTime структуры ICMP_ECHO_REPLY, а адрес сетевого устройства, ответившего на запрос, — из параметра Address.
После работы не забывайте закрывать указатель на созданный ICMP с помощью IcmpCloseHandle.
Теперь можно выводить полученную информацию. Для удобства восприятия в программе реализован перевод IP-адреса в символьное имя с помощью функции gethostbyaddr. У этой функции три параметра:
IP-адрес компьютера, символьное имя которого надо определить;
длина адреса;
семейство протокола. От этого зависит формат предоставляемого адреса.
Далее проверяется, если поступил ответ от искомого компьютера, то цикл прерывается, иначе нужно увеличить на единицу время жизни пакета и повторить посылку ICMP-пакета:
if (addr == Address.S_un.S_addr) bReachedHost = TRUE;
По окончании работы нужно выгрузить из памяти библиотеку icmp.dll и освободить библиотеку WinSock:
if (hIcmp) { FreeLibrary(hIcmp); hIcmp = NULL; } WSACleanup();
Запустите программу TraceRoute. На 6.8 показано окно с результатами ее работы.

6.8. Окно с результатом работы программы TraceRoute
Я постарался сделать пример простым, а логику — прямолинейной, чтобы вам легче было разобраться. При этом, если в процессе работы происходит ошибка, то по выходе из программы библиотеки icmp и winsock остаются загруженными. Самый простой способ избавиться от этого недостатка — производить загрузку библиотек при старте, а выгрузку — при выходе из программы, и если библиотеки не загружены, то можно отключать кнопки отправки пакета, чтобы пользователь не смог ими воспользоваться.
В Интернете можно найти заголовочные файлы для библиотеки icmp.dll, которые могут еще больше упростить этот пример. Но я не стал их использовать, чтобы ничего не ускользнуло от вашего внимания.
Примечание |
Исходный код примера, описанного в этом разделе, вы можете найти на компакт - диске в каталоге \Demo\Chapter6\TraceRoute. |
![]() |
![]() |