Logo Море(!) аналитической информации!
IT-консалтинг Software Engineering Программирование СУБД Безопасность Internet Сети Операционные системы Hardware
2004 г.

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.

Зарегистрируйте номер 007-101-303
на сайте VIP.GFI.RU и получите
наши продукты БЕСПЛАТНО

Новости мира IT:

Архив новостей

Последние комментарии:

Loading
Форумы Газета

IT-консалтинг Software Engineering Программирование СУБД Безопасность Internet Сети Операционные системы Hardware

Информация для рекламодателей PR-акции, размещение рекламы — adv@citforum.ru,
тел. +7 495 6608306, ICQ 232284597
Пресс-релизы — pr@citforum.ru
Послать комментарий
Информация для авторов
Rambler's Top100 TopList liveinternet.ru: показано число просмотров за 24 часа, посетителей за 24 часа и за сегодня This Web server launched on February 24, 1997
Copyright © 1997-2000 CIT, © 2001-2009 CIT Forum
Внимание! Любой из материалов, опубликованных на этом сервере, не может быть воспроизведен в какой бы то ни было форме и какими бы то ни было средствами без письменного разрешения владельцев авторских прав. Подробнее...