div.main {margin-left: 20pt; margin-right: 20pt} Реализация ping с помощью ICMP API
Владимир
Артемов
При работе в сети Интернет в некоторых случаях необходимо знать
текущее состояние как локального, так и удаленных хостов (имеет ли
локальный хост в данный момент возможность выхода в сеть Интеренет,
доступен ли некий удаленный хост и т.д.)
Общеизвестно, что для указанных целей используется утилита ping.
В данной статье описана реализация на Delphi возможностей этой
программы с помощью ICMP API.
Принцип работы ping-а основан на использовании протокола ICMP -
Internet Control Message Protocol (протокол управляющих, или
контрольных, сообщений). С помощью ICMP хосты в сети обмениваются
различной служебной информацией (информацией о смене маршрута,
уменьшении скорости передачи, недоступности какого-либо адреса и
т.д.) В основе протокола ICMP лежит понятие сообщений. Сообщения
ICMP протокола, как правило, оповещают об ошибках, возникающих при
обработке датаграмм. ICMP использует основные свойства протокола IP,
как если бы он являлся протоколом более высокого уровня. На самом же
деле ICMP является составной частью IP.
Одним из типов сообщений протокола является т.н. "эхо-запрос".
Получив "эхо-запрос" хост обязан ответить пославшему "эхо-ответом".
По сути, "эхо-запрос" и "эхо-ответ" отличаются лишь адресами
отправителя и получателя и кодом типа сообщения (тип 8 -
"эхо-запрос", тип 0 - "эхо-ответ"). Более подробно протокол ICMP
описан в RFC792.
Реализации утилиты ping на различных платформах существенно
отличаются. Так, в ОС UNIX используются RAW sockets (необработанные,
"сырые" сокеты), а в ОС Windows всех версий - т.н. ICMP API.
Использование ICMP API оправдано только в ОС Windows NT и 2000, т.к.
для использования RAW sockets в этих ОС необходимы права
Администратора.
Из документации Microsoft: "Icmp.dll предоставляет возможность
разрабатывать приложения, использующие ICMP (ping, traceroute) без
использования Winsock. Если разработчик пишет под платформу,
поддерживающую Winsock, предпочтительнее использовать механизм
Winsock RAW sockets. Однако, при написании программ под Windows
NT и Windows 2000 следует учитывать, что механизм RAW sockets
доступен для использования только членам группы Administrators. Все
функции в icmp.dll не рассматриваются как часть Win 32 API и не
будут поддерживаться в следующих реализациях".
На практике, во всех 32-разрядных версиях Windows есть и
используется библиотека icmp.dll. Так что, на данный момент,
можно безбоязненно ее использовать.
ICMP APIОпределения типов и прототипы функций для Microsoft
icmp.dll: type
ip_option_information = record // Информация заголовка IP (Наполнение
// этой структуры и формат полей описан в RFC791.
Ttl : u_char; // Время жизни (используется traceroute-ом)
Tos : u_char; // Тип обслуживания, обычно 0
Flags : u_char; // Флаги заголовка IP, обычно 0
OptionsSize : u_char; // Размер данных в заголовке, обычно 0, максимум 40
OptionsData : PChar; // Указатель на данные
end;
icmp_echo_reply = record
Address : DWORD; // Адрес отвечающего
Status : u_long; // IP_STATUS (см. ниже)
RTTime : u_long; // Время между эхо-запросом и эхо-ответом
// в миллисекундах
DataSize : u_short; // Размер возвращенных данных
Reserved : u_short; // Зарезервировано
Data : Pointer; // Указатель на возвращенные данные
Options : ip_option_information; // Информация из заголовка IP
end;
PIPINFO = ^ip_option_information;
PVOID = Pointer;
function IcmpCreateFile() : THandle; stdcall; external 'ICMP.DLL' name
'IcmpCreateFile';
function IcmpCloseHandle(IcmpHandle : THandle) : BOOL; stdcall;
external 'ICMP.DLL' name 'IcmpCloseHandle';
function IcmpSendEcho(
IcmpHandle : THandle; // handle, возвращенный IcmpCreateFile()
DestAddress : u_long; // Адрес получателя (в сетевом порядке)
RequestData : PVOID; // Указатель на посылаемые данные
RequestSize : Word; // Размер посылаемых данных
RequestOptns : PIPINFO; // Указатель на посылаемую структуру
// ip_option_information (может быть nil)
ReplyBuffer : PVOID; // Указатель на буфер, содержащий ответы.
ReplySize : DWORD; // Размер буфера ответов
Timeout : DWORD // Время ожидания ответа в миллисекундах
) : DWORD; stdcall; external 'ICMP.DLL' name 'IcmpSendEcho';
Из "WinSock.pas" type
u_char = Char;
u_short = Word;
u_long = Longint;
Из "Windows.pas" type
DWORD = LongWord;
BOOL = LongBool;
Функция IcmpSendEcho() посылает ICMP эхо-запрос по заданному IP
адресу и помещает все ответы, полученные за время заданного
таймаута, в буфер ответов (ReplyBuffer). Перед использованием
функций Icmp.dll необходимо вызвать функцию WSAStartup() из Winsock.
Если этого не сделать, то первый же вызов функции IcmpSendEcho()
приведет к ошибке 10091 (WSASYSNOTREADY).
Следует заметить, что ответы, помещенные в буфер, необязательно
будут сообщениями "эхо-ответ". Возможно, что в ответ на "эхо-запрос"
придут сообщения ICMP о возникших ошибках. Естественно, эти
сообщения так же будут помещены в ReplyBuffer. Размер буфера
ответов должен быть достаточным для приема хотя бы одного ответа,
будь то "эхо-ответ" или сообщение об ошибке. Отсюда, указанный
размер складывается из размера самой сруктуры icmp_echo_reply плюс
Max(RequestSize, 8), т.к. сообщение ICMP об ошибке составляет 8
байт. Функция IcmpSendEcho() возвращает количество ответов (или
структур icmp_echo_reply) в массиве ReplyBuffer. Если функция
возвратила 0, то для более детальной диагностики возникшей ошибки
используется функция GetLastError().
Из "Windows.pas" function GetLastError: DWORD;
Коды ошибок в поле Status структуры icmp_echo_reply, если ответ
не является "эхо-ответом" (IP_STATUS):
IP_STATUS_BASE 11000
IP_SUCCESS 0
IP_BUF_TOO_SMALL 11001
IP_DEST_NET_UNREACHABLE 11002
IP_DEST_HOST_UNREACHABLE 11003
IP_DEST_PROT_UNREACHABLE 11004
IP_DEST_PORT_UNREACHABLE 11005
IP_NO_RESOURCES 11006
IP_BAD_OPTION 11007
IP_HW_ERROR 11008
IP_PACKET_TOO_BIG 11009
IP_REQ_TIMED_OUT 11010
IP_BAD_REQ 11011
IP_BAD_ROUTE 11012
IP_TTL_EXPIRED_TRANSIT 11013
IP_TTL_EXPIRED_REASSEM 11014
IP_PARAM_PROBLEM 11015
IP_SOURCE_QUENCH 11016
IP_OPTION_TOO_BIG 11017
IP_BAD_DESTINATION 11018
IP_ADDR_DELETED 11019
IP_SPEC_MTU_CHANGE 11020
IP_MTU_CHANGE 11021
IP_UNLOAD 11022
IP_GENERAL_FAILURE 11050
MAX_IP_STATUS IP_GENERAL_FAILURE
IP_PENDING 11255
Ниже приведен базовый фрагмент кода, необходимый для однократного
посыла эхо-запроса: procedure TForm1.Button1Click(Sender: TObject);
var
hIP : THandle;
pingBuffer : array [0..31] of Char;
pIpe : ^icmp_echo_reply;
pHostEn : PHostEnt;
wVersionRequested : WORD;
lwsaData : WSAData;
error : DWORD;
destAddress : In_Addr;
begin
// Создаем handle
hIP := IcmpCreateFile();
GetMem( pIpe,
sizeof(icmp_echo_reply) + sizeof(pingBuffer));
pIpe.Data := @pingBuffer;
pIpe.DataSize := sizeof(pingBuffer);
wVersionRequested := MakeWord(1,1);
error := WSAStartup(wVersionRequested,lwsaData);
if (error <> 0) then
begin
Memo1.SetTextBuf('Error in call to '+
'WSAStartup().');
Memo1.Lines.Add('Error code: '+IntToStr(error));
Exit;
end;
pHostEn := gethostbyname('delphi.mastak.com');
error := GetLastError();
if (error <> 0) then
begin
Memo1.SetTextBuf('Error in call to'+
'gethostbyname().');
Memo1.Lines.Add('Error code: '+IntToStr(error));
Exit;
end;
destAddress := PInAddr(pHostEn^.h_addr_list^)^;
// Посылаем ping-пакет
Memo1.Lines.Add('Pinging ' +
pHostEn^.h_name+' ['+
inet_ntoa(destAddress)+'] '+
' with '+
IntToStr(sizeof(pingBuffer)) +
' bytes of data:');
IcmpSendEcho(hIP,
destAddress.S_addr,
@pingBuffer,
sizeof(pingBuffer),
Nil,
pIpe,
sizeof(icmp_echo_reply) + sizeof(pingBuffer),
5000);
error := GetLastError();
if (error <> 0) then
begin
Memo1.SetTextBuf('Error in call to'+
'IcmpSendEcho()');
Memo1.Lines.Add('Error code: '+IntToStr(error));
Exit;
end;
// Смотрим некоторые из вернувшихся данных
Memo1.Lines.Add('Reply from '+
IntToStr(LoByte(LoWord(pIpe^.Address)))+'.'+
IntToStr(HiByte(LoWord(pIpe^.Address)))+'.'+
IntToStr(LoByte(HiWord(pIpe^.Address)))+'.'+
IntToStr(HiByte(HiWord(pIpe^.Address))));
Memo1.Lines.Add('Reply time: '+IntToStr(pIpe.RTTime)+' ms');
IcmpCloseHandle(hIP);
WSACleanup();
FreeMem(pIpe);
end;
Разумеется, это только "скелет". Многие возможные ошибки
необрабатываются.
Для работы этого фрагмента надо:
создать форму TForm1;
поместить в нее компонент TMemo1:TMemo и кнопку
TButton1:TButton;
в разделе Implementation поместить описания типов и функций
Icmp.dll (см. выше);
сопоставить событию OnClick кнопки приведенную функцию.
|