Ping своими руками
Теория
Команда ping служит для принудительного вызова
ответа конкретной машины. Для этого используется дейтаграмма
ECHO_REQUEST протокола ICMP. Это протокол низкого уровня, который не
требует наличия серверных процессов на зондируемой машине; это
хороший способ убедится в том, что питание машины включено и IP
находится в поднятом состоянии. Успешный результат использования
команды ping вовсе не обязательно означает, что выполняются какие-то
сервисные программы высокого уровня.
Ping - хорошее средство проверки правильности
конфигурации сети, поскольку в выполнении этой команды участвуют
система маршрутизации, схемы разрешения адресов и сетевые шлюзы.
Если данная команда не работает - можете быть совершенно уверены,
что более сложные средства тем более не функционируют. Несмотря на
свою простоту, ping - одна из главных рабочих лошадок,
использующихся при отладке сетей.
Практика
Для практической реализации, как всегда, можно
использовать несколько подходов. Первый из них - использование
низкоуровневых функций API (к примеру, встроенных в библиотеку
ICMP.DLL). Второй - использование высокоуровневых компонентов (к
примеру, Indy IdICMPClient).
И у первого, и у второго способа есть свои
позитивные и негативные моменты. Так, при использовании функций API
откомпилированный код будет иметь гораздо меньшие размеры, нежели
при использовании высокоуровневых компонентов,- да и
производительность его будет выше (например, при одновременном пинге
одной подсети с использованием потоков).
С другой стороны, компоненты можно использовать,
имея только отдаленное представление о работе с протоколом ICMP, а
также об использовании Windows API. Но, в то же время, компоненты
порождают неоправданно большой исполняемый код, да и
производительность в этом случае ниже. К счастью, сегодня
разработчиками процессоров и памяти почти стерта грань между
производительностью кода, написанного с использованием Windows API,
и кода, написанного с использованием средств более высокого уровня
абстракции (в нашем случае - компонентов Delphi).
На всякий случай приведем примеры пинга,
написанного как с использованием WinAPI так и с использованием
компонентов Indy.
Windows API
При использовании Windows API для написания функции
пинга воспользуемся библиотекой ICMP (icmp.dll), которая
предоставляет интерфейс для работы с одноименным протоколом. В этой
библиотеке реализованы три функции, с которыми в дальнейшем мы будем
работать. В интерпретации Delphi их объявления выглядят следующим
образом:
function IcmpCreateFile:Thandle; StdCall;
function IcmpCloseHandle (H:Thandle):Bool;
StdCall;
function IcmpSendEcho
(IcmpHandle:Thandle; DestinationAddress:TipAddr; RequestData:pointer; RequestSize:word; RequestOptions:POption_Information; ReplyBuffer:pointer; ReplySize:integer; Timeout:integer):Integer;
stdcall;
Первая из них (IcmpCreateFile) создает соединение,
с которым мы собираемся работать. Вторая закрывает его, а третья
посылает через установленное соединение соответствующие данные.
Остановимся подробнее на функции IcmpSendEcho. Принимаемые ею
параметры "звучат" следующим образом:
- IcmpHandle - идентификатор соединения, установленного при
помощи IcmpCreateFile;
- DestinationAddress - адрес пингуемого хоста;
- RequestData - буфер с данными, которые посылаются при запросе;
- RequestSize - размер буфера запроса;
- RequestOptions - дополнительные свойства запроса;
- ReplyBuffer - адрес буфера для приема результата;
- ReplySize - размер буфера приема;
- Timeout - время, в течение которого мы ожидаем ответа от
хоста;
- результат функции - количество записей типа ICMP_ECHO_REPLY,
сохраненных в ReplyBuffer. Статус каждой записи хранится в
соответствующем поле этой записи. При неудачном вызове функция
возвращает значение NULL; дополнительная информация доступна при
вызове GetLastError.
Структура ICMP_ECHO_REPLY имеет следующий вид:
Ticmp_echo_reply=record Address: TipAddr; //
Ответивший адрес Status: integer; // Статус
ответа RoundTripTime: integer; // Время прохождения
пакета DataSize: word; // Размер данных ответа в
байтах Reserved: word; // Зарезервировани Data: pointer; //
Указатель на буфер с ответом Options: Toption_Information; //
Опции ответа. End;
Помимо нее, мы можем использовать расширенный вариант структуры
ICMP_ECHO_REPLY:
TsmICMP_Echo_Reply=record Address: TipAddr; //
Ответивший адрес Status: integer; // Статус
ответа RoundTripTime: integer; // Время прохождения
пакета DataSize: word; // Размер данных ответа в
байтах Reserved: word; // Зарезервировани Data: pointer; //
Указатель на буфер с ответом Options: Toption_Information; //
Опции ответа. Data: array [0..255] of Char; end;
Теперь для реализации пинга хоста мы:
- создаем соединение;
- вызываем ICMPSendEcho;
- обрабатываем результат;
- закрываем соединение.
Эти действия удобно оформить в виде процедуры:
procedure Ping (const Address, EchoString: PChar; var
PingReply: TsmICMP_Echo_Reply; const PingTimeout: Integer =
500); var IPAddress: TipAddr; ICMPPort:
THandle; begin // Конвертация IP в понятный для API
формат IPAddress:= inet_addr (Address); // Проверка
корректности конвертации if (IPAddress = INADDR_NONE)
then begin raise Exception.Create ('Function call inet_addr
failed. ' + 'The IP address is probably invalid.'); end; //
Открытие соединения ICMPPort:= IcmpCreateFile (); // Проверка
правильности открытия if (ICMPPort = INVALID_HANDLE_VALUE)
then begin raise Exception.Create ('Function call
IcmpCreateFile failed.'); end; // Отправка запроса
"пинг" IcmpSendEcho (ICMPPort, IPAddress, EchoString, Length
(EchoString), nil, @PingReply, SizeOf (PingReply),
PingTimeout); // Закрытие соединения IcmpCloseHandle
(ICMPPort); end;
Теперь при использовании в коде программы конструкции:
Ping ('127.0.0.1',nil,Reply,5000);
в переменной Reply мы получим результат пинга.
Использование Indy
Те, кто не знакомы с практикой программирования с
использованием Windows API, могут воспользоваться встроенными в
Delphi компонентами для работы с сетью. В частности, для реализации
пинга на уровне объектно-ориентированного программирования
воспользуемся компонентом IdICMPClient из состава Indy.
Для этого создадим сначала на пустой форме
экземпляр класса TIdICMPClient, перетащив его с палитры компонентов
Indy Clients. Затем поместим на форму стандартную кнопку (TButton) и
в ее реакции на нажатие мышкой запишем код:
Self.IdIcmpClient1.Host:='localhost';//вместо 127.0.0.1
здесь можно использовать имя "localhost"
Self.IdIcmpClient1.Ping;
И не забудем выставить соответствующий интервал
ожидания ответа, по завершении которого (или при получении данных от
пингуемого) вызывается обработчик:
TidIcmpClient.OnReply (Sender:TComponent; const
AReplyStatus:TReplyStatus);
- в котором мы реализуем вывод данных пинга на экран:
procedure TForm1.IdIcmpClient1Reply (ASender:
TComponent; const AReplyStatus:
TReplyStatus); begin ListBox1.Items.Add ('Reply:'+IntToStr
(AReplyStatus.MsRoundTripTime)); end;
Послесловие
Конечно, для пинга удаленной машины вы всегда
можете обратиться к соответствующей программе командной строки,
поскольку 100% операционных систем поддерживает эту команду. Но не
всегда дело ограничивается одним пингом - особенно если вы
реализуете собственный интерфейс поверх IP. В таком случае вызов
внешней программы при каждой проверке - слишком большая роскошь.
Кроме того, поняв, как работать с ICMP, вы сможете использовать
эти возможности и в других - в том числе и "военных" - целях.
Листинг 1. Определения функций ICMP.DLL
unit icmp; interface uses windows; Const //
IP_STATUS codes returned from IP APIs
IP_STATUS_BASE = 11000; IP_SUCCESS =
0; IP_BUF_TOO_SMALL = (IP_STATUS_BASE +
1); IP_DEST_NET_UNREACHABLE = (IP_STATUS_BASE +
2); IP_DEST_HOST_UNREACHABLE = (IP_STATUS_BASE +
3); IP_DEST_PROT_UNREACHABLE = (IP_STATUS_BASE +
4); IP_DEST_PORT_UNREACHABLE = (IP_STATUS_BASE +
5); IP_NO_RESOURCES = (IP_STATUS_BASE + 6); IP_BAD_OPTION =
(IP_STATUS_BASE + 7); IP_HW_ERROR = (IP_STATUS_BASE +
8); IP_PACKET_TOO_BIG = (IP_STATUS_BASE + 9); IP_REQ_TIMED_OUT
= (IP_STATUS_BASE + 10); IP_BAD_REQ = (IP_STATUS_BASE +
11); IP_BAD_ROUTE = (IP_STATUS_BASE +
12); IP_TTL_EXPIRED_TRANSIT = (IP_STATUS_BASE +
13); IP_TTL_EXPIRED_REASSEM = (IP_STATUS_BASE +
14); IP_PARAM_PROBLEM = (IP_STATUS_BASE +
15); IP_SOURCE_QUENCH = (IP_STATUS_BASE +
16); IP_OPTION_TOO_BIG = (IP_STATUS_BASE +
17); IP_BAD_DESTINATION = (IP_STATUS_BASE + 18);
// The next group are status codes passed up on status
indications to // transport layer protocols. IP_ADDR_DELETED =
(IP_STATUS_BASE + 19); IP_SPEC_MTU_CHANGE = (IP_STATUS_BASE +
20); IP_MTU_CHANGE = (IP_STATUS_BASE + 21); IP_UNLOAD =
(IP_STATUS_BASE + 22); IP_GENERAL_FAILURE = (IP_STATUS_BASE +
50); MAX_IP_STATUS = IP_GENERAL_FAILURE; IP_PENDING =
(IP_STATUS_BASE + 255);
// Values used in the IP header Flags
field. IP_FLAG_DF = $2; // Don't fragment this packet.
// Supported IP Option Types. // These types define
the options which may be used in the OptionsData field // of the
ip_option_information structure. See RFC 791 for a complete //
description of each. IP_OPT_EOL = 0; // End of list
option IP_OPT_NOP = 1; // No operation IP_OPT_SECURITY = $82;
// Security option IP_OPT_LSRR = $83; // Loose source
route IP_OPT_SSRR = $89; // Strict source route IP_OPT_RR =
$7; // Record route IP_OPT_TS = $44; // Timestamp IP_OPT_SID =
$88; // Stream ID (obsolete)
MAX_OPT_SIZE = 40; // Maximum length of IP options in
bytes
Type
TIPAddr=integer; // An IP address. TIPMask=integer; //
An IP subnet mask. TIP_STATUS=Integer; // Status code returned
from IP APIs.
POption_Information=^TOption_Information; TOption_Information=record Ttl:byte;
// Time To Live Tos:byte; // Type Of Service Flags:byte; // IP
header flags OptionsSize:byte; // Size in bytes of options
data OptionsData:pointer; // Pointer to options
data end; Picmp_echo_reply=^Ticmp_echo_reply; Ticmp_echo_reply=record Address:TipAddr;
// Replying address Status:integer; // Reply
IP_STATUS RoundTripTime:integer; // RTT in
milliseconds DataSize:word; // Reply data size in
bytes Reserved:word; // Reserved for system use Data:pointer;
// Pointer to the reply data Options:Toption_Information; //
Reply
options end; TsmICMP_Echo_Reply=record Address:TipAddr; //
Replying address Status:integer; // Reply
IP_STATUS RoundTripTime:integer; // RTT in
milliseconds DataSize:word; // Reply data size in
bytes Reserved:word; // Reserved for system
use DataPtr:pointer; // Pointer to the reply
data Options:Toption_Information; // Reply options Data: array
[0..255] of Char; end;
function IcmpCreateFile:Thandle; StdCall; function
IcmpCloseHandle (H:Thandle):Bool; StdCall; function IcmpSendEcho
(IcmpHandle:Thandle;DestinationAddress:TipAddr; RequestData:pointer;RequestSize:word; RequestOptions:POption_Information;ReplyBuffer:pointer; ReplySize:integer;Timeout:integer):Integer;
stdcall; Implementation function IcmpCreateFile; external
'Icmp.Dll'; function IcmpCloseHandle; external
'Icmp.Dll'; Function IcmpSendEcho; external
'Icmp.Dll'; end.
Листинг 2. Процедура "пинга"
unit pingModule; interface uses icmp,
Windows; const INADDR_NONE: integer = -1; procedure Ping
(const Address, EchoString: PChar;var PingReply:
TsmICMP_Echo_Reply; const PingTimeout: Integer =
500); function PingStatusToStr (StatusCode: integer):
string; function inet_addr (IPAddress: PChar): TipAddr;
StdCall; implementation uses Dialogs, SysUtils; procedure
Ping (const Address, EchoString: PChar; var PingReply:
TsmICMP_Echo_Reply; const PingTimeout: Integer =
500); var IPAddress: TipAddr; ICMPPort:
THandle; begin IPAddress:= inet_addr (Address); if
(IPAddress = INADDR_NONE) then begin raise Exception.Create
('Function call inet_addr failed. ' + 'The IP address is probably
invalid.'); end; ICMPPort:= IcmpCreateFile (); if (ICMPPort
= INVALID_HANDLE_VALUE) then begin raise Exception.Create
('Function call IcmpCreateFile failed.'); end; IcmpSendEcho
(ICMPPort, IPAddress, EchoString, Length (EchoString),
nil, @PingReply, SizeOf (PingReply),
PingTimeout); IcmpCloseHandle (ICMPPort); end;
function PingStatusToStr (StatusCode: integer):
string; begin case (StatusCode) of IP_SUCCESS: Result:=
'IP_SUCCESS'; IP_BUF_TOO_SMALL: Result:=
'IP_BUF_TOO_SMALL'; IP_DEST_NET_UNREACHABLE: Result:=
'IP_DEST_NET_UNREACHABLE'; IP_DEST_HOST_UNREACHABLE: Result:=
'IP_DEST_HOST_UNREACHABLE'; IP_DEST_PROT_UNREACHABLE: Result:=
'IP_DEST_PROT_UNREACHABLE'; IP_DEST_PORT_UNREACHABLE: Result:=
'IP_DEST_PORT_UNREACHABLE'; IP_NO_RESOURCES: Result:=
'IP_NO_RESOURCES'; IP_BAD_OPTION: Result:=
'IP_BAD_OPTION'; IP_HW_ERROR: Result:=
'IP_HW_ERROR'; IP_PACKET_TOO_BIG: Result:=
'IP_PACKET_TOO_BIG'; IP_REQ_TIMED_OUT: Result:=
'IP_REQ_TIMED_OUT'; IP_BAD_REQ: Result:=
'IP_BAD_REQ'; IP_BAD_ROUTE: Result:=
'IP_BAD_ROUTE'; IP_TTL_EXPIRED_TRANSIT: Result:=
'IP_TTL_EXPIRED_TRANSIT'; IP_TTL_EXPIRED_REASSEM: Result:=
'IP_TTL_EXPIRED_REASSEM'; IP_PARAM_PROBLEM: Result:=
'IP_PARAM_PROBLEM'; IP_SOURCE_QUENCH: Result:=
'IP_SOURCE_QUENCH'; IP_OPTION_TOO_BIG: Result:=
'IP_OPTION_TOO_BIG'; IP_BAD_DESTINATION: Result:=
'IP_BAD_DESTINATION'; IP_ADDR_DELETED: Result:=
'IP_ADDR_DELETED'; IP_SPEC_MTU_CHANGE: Result:=
'IP_SPEC_MTU_CHANGE'; IP_MTU_CHANGE: Result:=
'IP_MTU_CHANGE'; IP_UNLOAD: Result:=
'IP_UNLOAD'; IP_GENERAL_FAILURE: Result:=
'IP_GENERAL_FAILURE'; else Result:=
''; end; end;
function inet_addr; external 'WSock32.Dll';
end.
Листинг 3. Демонстрация пингов
unit Unit1;
interface
uses Windows, Messages, SysUtils, Variants, Classes,
Graphics, Controls, Forms, Dialogs, IdBaseComponent, IdComponent,
IdRawBase, IdRawClient, IdIcmpClient, StdCtrls, IdUDPBase,
IdUDPClient, IdSNTP;
type TForm1 = class (TForm) Button1:
TButton; ListBox1: TListBox; IdIcmpClient1:
TIdIcmpClient; IdSNTP1: TIdSNTP; procedure Button1Click
(Sender: TObject); procedure IdIcmpClient1Reply (ASender:
TComponent; const AReplyStatus:
TReplyStatus); private {Private
declarations} public {Public declarations} end;
var Form1: TForm1;
implementation uses pingModule,icmp; {$R
*.dfm}
procedure TForm1.Button1Click (Sender: TObject); var
Reply:TsmICMP_Echo_Reply; begin Self.IdIcmpClient1.Host:='localhost'; Self.IdIcmpClient1.TTL:=192; Self.IdIcmpClient1.Ping; Ping
('127.0.0.1',nil,Reply,5000); ListBox1.Items.Add
('RawReply:'+IntToStr (Reply.RoundTripTime)); end;
procedure TForm1.IdIcmpClient1Reply (ASender:
TComponent; const AReplyStatus:
TReplyStatus); begin ListBox1.Items.Add ('Reply:'+IntToStr
(AReplyStatus.MsRoundTripTime)); end;
end.
Автор: Михаил Продан
Источник: www.cpp.com.ua
|