📄 idicmpclient.pas
字号:
{ $HDR$}
{**********************************************************************}
{ Unit archived using Team Coherence }
{ Team Coherence is Copyright 2002 by Quality Software Components }
{ }
{ For further information / comments, visit our WEB site at }
{ http://www.TeamCoherence.com }
{**********************************************************************}
{}
{ $Log: 10199: IdIcmpClient.pas
{
{ Rev 1.0 2002.11.12 10:41:36 PM czhower
}
unit IdIcmpClient;
// SG 25/1/02: Modified the component to support multithreaded PING and traceroute
// SG 25/1/02: NOTE!!!
// SG 25/1/02: The component no longer use the timing informations contained
// SG 25/1/02: in the packet to compute the roundtrip time. This is because
// SG 25/1/02: that information is only correctly set in case of ECHOREPLY
// SG 25/1/02: In case of TTL, it is incorrect.
interface
uses
Classes,
IdGlobal,
IdRawBase,
IdRawClient,
IdStack,
IdStackConsts,
SysUtils;
const
DEF_PACKET_SIZE = 32;
MAX_PACKET_SIZE = 1024;
// TODO: move ICMP_MIN to IdRawHeaders
ICMP_MIN = 8;
const
iDEFAULTPACKETSIZE = 128;
iDEFAULTREPLYBUFSIZE = 1024;
const
Id_TIDICMP_ReceiveTimeout = 5000;
type
TReplyStatusTypes = (rsEcho, rsError, rsTimeOut, rsErrorUnreachable, rsErrorTTLExceeded);
TReplyStatus = record
BytesReceived: integer; // number of bytes in reply from host
FromIpAddress: string; // IP address of replying host
MsgType: byte;
SequenceId: word; // sequence id of ping reply
// TODO: roundtrip time in ping reply should be float, not byte
MsRoundTripTime: longword; // ping round trip time in milliseconds
TimeToLive: byte; // time to live
ReplyStatusType: TReplyStatusTypes;
end;
TCharBuf = array [1..MAX_PACKET_SIZE] of char;
TICMPDataBuffer = array [1..iDEFAULTPACKETSIZE] of byte;
TOnReplyEvent = procedure(ASender: TComponent; const AReplyStatus: TReplyStatus) of object;
TIdIcmpClient = class(TIdRawClient)
protected
bufReceive: TCharBuf;
bufIcmp: TCharBuf;
wSeqNo: word;
iDataSize: integer;
FReplyStatus: TReplyStatus;
FOnReply: TOnReplyEvent;
FReplydata: String;
//
function CalcCheckSum: word;
function DecodeResponse(BytesRead: Cardinal; var AReplyStatus: TReplyStatus): boolean;
procedure DoReply(const AReplyStatus: TReplyStatus);
procedure GetEchoReply;
procedure PrepareEchoRequest(Buffer: string = ''); {Do not Localize}
procedure SendEchoRequest;
public
constructor Create(AOwner: TComponent); override;
procedure Ping(ABuffer: String = ''; SequenceID: word = 0); {Do not Localize}
function Receive(ATimeOut: Integer): TReplyStatus;
//
property ReplyStatus: TReplyStatus read FReplyStatus;
property ReplyData: string read FReplydata;
published
property ReceiveTimeout default Id_TIDICMP_ReceiveTimeout;
property Host;
property Port;
property Protocol Default Id_IPPROTO_ICMP;
property OnReply: TOnReplyEvent read FOnReply write FOnReply;
end;
implementation
uses
IdException
, IdResourceStrings, IdRawHeaders;
{ TIdIcmpClient }
constructor TIdIcmpClient.Create(AOwner: TComponent);
begin
inherited;
FProtocol := Id_IPPROTO_ICMP;
wSeqNo := 3489; // SG 25/1/02: Arbitrary Constant <> 0
FReceiveTimeOut := Id_TIDICMP_ReceiveTimeout;
end;
function TIdIcmpClient.CalcCheckSum: word;
type
PWordArray = ^TWordArray;
TWordArray = array [1..512] of word;
var
pwa: PWordarray;
dwChecksum: longword;
i, icWords, iRemainder: integer;
begin
icWords := iDataSize div 2;
iRemainder := iDatasize mod 2;
pwa := PWordArray(@bufIcmp);
dwChecksum := 0;
for i := 1 to icWords do begin
dwChecksum := dwChecksum + pwa^[i];
end;
if (iRemainder <> 0) then begin
dwChecksum := dwChecksum + byte(bufIcmp[iDataSize + 1]);
end;
dwCheckSum := (dwCheckSum shr 16) + (dwCheckSum and $FFFF);
dwCheckSum := dwCheckSum + (dwCheckSum shr 16);
Result := word(not dwChecksum);
end;
procedure TIdIcmpClient.PrepareEchoRequest(Buffer: string = ''); {Do not Localize}
var
pih: PIdIcmpHdr;
i: integer;
BufferPos: Integer;
begin
iDataSize := DEF_PACKET_SIZE + sizeof(TIdIcmpHdr);
FillChar(bufIcmp, iDataSize, 0);
pih := PIdIcmpHdr(@bufIcmp);
with pih^ do
begin
icmp_type := Id_ICMP_ECHO;
icmp_code := 0;
icmp_hun.echo.id := word(CurrentProcessId);
icmp_hun.echo.seq := wSeqNo;
icmp_dun.ts.otime := GetTickcount;
i := Succ(sizeof(TIdIcmpHdr));
// SG 19/12/01: Changed the fill algoritm
BufferPos := 1;
while (i <= iDataSize) do
begin
// SG 19/12/01: Build the reply buffer
if BufferPos <= Length(Buffer) then
begin
bufIcmp[i] := Buffer[BufferPos];
inc(BufferPos);
end
else
bufIcmp[i] := 'E'; {Do not Localize}
Inc(i);
end;
icmp_sum := CalcCheckSum;
end;
// SG 25/1/02: Retarded wSeqNo increment to be able to check it against the response
end;
procedure TIdIcmpClient.SendEchoRequest;
begin
Send(Host, Port, bufIcmp, iDataSize);
end;
function TIdIcmpClient.DecodeResponse(BytesRead: Cardinal; var AReplyStatus: TReplyStatus): Boolean;
var
// RTTime: longword;
pip, pOriginalIP: PIdIPHdr;
picmp, pOriginalICMP: PIdICMPHdr;
iIpHeaderLen: Cardinal;
ActualSeqID: word;
begin
if BytesRead = 0 then begin
// Timed out
AReplyStatus.BytesReceived := 0;
AReplyStatus.FromIpAddress := '0.0.0.0'; {Do not Localize}
AReplyStatus.MsgType := 0;
AReplyStatus.SequenceId := wSeqNo;
AReplyStatus.TimeToLive := 0;
AReplyStatus.ReplyStatusType := rsTimeOut;
result := true;
end else begin
AReplyStatus.ReplyStatusType := rsError;
pip := PIdIPHdr(@bufReceive);
iIpHeaderLen := (pip^.ip_verlen and $0F) * 4;
if (BytesRead < iIpHeaderLen + ICMP_MIN) then begin
// RSICMPNotEnoughtBytes 'Not enough bytes received' {Do not Localize}
raise EIdIcmpException.Create(RSICMPNotEnoughtBytes);
end;
picmp := PIdICMPHdr(@bufReceive[iIpHeaderLen + 1]);
{$IFDEF LINUX}
// TODO: baffled as to why linux kernel sends back echo from localhost
{$ENDIF}
// Check if we are reading the packet we are waiting for. if not, don't use it in treatement and discard it {Do not Localize}
case picmp^.icmp_type of
Id_ICMP_ECHOREPLY, Id_ICMP_ECHO:
begin
AReplyStatus.ReplyStatusType := rsEcho;
FReplydata := Copy(bufReceive, iIpHeaderLen + SizeOf(picmp^) + 1, Length(bufReceive));
// result is only valid if the seq. number is correct
end;
Id_ICMP_UNREACH:
AReplyStatus.ReplyStatusType := rsErrorUnreachable;
Id_ICMP_TIMXCEED:
AReplyStatus.ReplyStatusType := rsErrorTTLExceeded;
else
raise EIdICMPException.Create(RSICMPNonEchoResponse);// RSICMPNonEchoResponse = 'Non-echo type response received' {Do not Localize}
end; // case
// check if we got a reply to the packet that was actually sent
case AReplyStatus.ReplyStatusType of //
rsEcho:
begin
result := picmp^.icmp_hun.echo.seq = wSeqNo;
ActualSeqID := picmp^.icmp_hun.echo.seq;
// RTTime := GetTickCount - picmp^.icmp_dun.ts.otime;
end
else
begin
// not an echo reply: the original IP frame is contained withing the DATA section of the packet
pOriginalIP := PIdIPHdr(@picmp^.icmp_dun.data);
// move to offset
pOriginalICMP := Pointer(Cardinal(pOriginalIP) + (iIpHeaderLen));
// extract information from original ICMP frame
ActualSeqID := pOriginalICMP^.icmp_hun.echo.seq;
// RTTime := GetTickCount - pOriginalICMP^.icmp_dun.ts.otime;
result := pOriginalICMP^.icmp_hun.echo.seq = wSeqNo;
end;
end; // case
if result then
begin
with AReplyStatus do begin
BytesReceived := BytesRead;
FromIpAddress := GStack.TInAddrToString(pip^.ip_src);
MsgType := picmp^.icmp_type;
SequenceId := ActualSeqID;
// MsRoundTripTime := RTTime;
TimeToLive := pip^.ip_ttl;
end;
end;
end;
end;
procedure TIdIcmpClient.GetEchoReply;
begin
FReplyStatus := Receive(FReceiveTimeout);
end;
procedure TIdIcmpClient.Ping(ABuffer: String = ''; SequenceID: word = 0); {Do not Localize}
var
RTTime: Cardinal;
begin
if SequenceID <> 0 then
wSeqNo := SequenceID;
PrepareEchoRequest(ABuffer);
RTTime := getTickCount;
SendEchoRequest;
GetEchoReply;
RTTime := GetTickDiff(RTTime,GetTickCount);
Binding.CloseSocket;
FReplyStatus.MsRoundTripTime := RTTime;
DoReply(FReplyStatus);
Inc(wSeqNo); // SG 25/1/02: Only incread sequence number when finished.
end;
function TIdIcmpClient.Receive(ATimeOut: Integer): TReplyStatus;
var
BytesRead : Integer;
Size : Integer;
StartTime: Cardinal;
begin
FillChar(bufReceive, sizeOf(bufReceive),0);
Size := sizeof(bufReceive);
StartTime := GetTickCount;
repeat
BytesRead := ReceiveBuffer(bufReceive, Size, ATimeOut);
GStack.CheckForSocketError(BytesRead);
if DecodeResponse(BytesRead, Result) then
begin
break
end
else
begin
ATimeOut := Cardinal(ATimeOut) - GetTickDiff(StartTime,getTickCount); // compute new timeout value
end;
until ATimeOut <= 0;
end;
procedure TIdIcmpClient.DoReply(const AReplyStatus: TReplyStatus);
begin
if Assigned(FOnReply) then begin
FOnReply(Self, AReplyStatus);
end;
end;
end.
⌨️ 快捷键说明
复制代码
Ctrl + C
搜索代码
Ctrl + F
全屏模式
F11
切换主题
Ctrl + Shift + D
显示快捷键
?
增大字号
Ctrl + =
减小字号
Ctrl + -