{
  $Project$
  $Workfile$
  $Revision$
  $DateUTC$
  $Id$

  This file is part of the Indy (Internet Direct) project, and is offered
  under the dual-licensing agreement described on the Indy website.
  (http://www.indyproject.org/)

  Copyright:
   (c) 1993-2005, Chad Z. Hower and the Indy Pit Crew. All rights reserved.
}
{
  $Log$
}
{
  Rev 1.8    2004-04-25 12:08:24  Mattias
  Fixed multithreading issue

  Rev 1.7    2004.02.03 4:16:42 PM  czhower
  For unit name changes.

  Rev 1.6    2/1/2004 4:53:30 PM  JPMugaas
  Removed Todo;

  Rev 1.5    2004.01.20 10:03:24 PM  czhower
  InitComponent

  Rev 1.4    2003.12.31 10:37:54 PM  czhower
  GetTickcount --> Ticks

  Rev 1.3    10/16/2003 11:06:14 PM  SPerry
  Moved ICMP_MIN to IdRawHeaders

  Rev 1.2    2003.10.11 5:48:04 PM  czhower
  -VCL fixes for servers
  -Chain suport for servers (Super core)
  -Scheduler upgrades
  -Full yarn support

  Rev 1.1    2003.09.30 1:22:56 PM  czhower
  Stack split for DotNet

  Rev 1.0    11/13/2002 08:44:30 AM  JPMugaas

  25/1/02: SGrobety:
  Modified the component to support multithreaded PING and traceroute
  NOTE!!!
  The component no longer use the timing informations contained
  in the packet to compute the roundtrip time. This is because
  that information is only correctly set in case of ECHOREPLY
  In case of TTL, it is incorrect.
}

unit IdIcmpClient;

{
  Note that we can NOT remove the DotNET IFDEFS from this unit.   The reason is
  that Microsoft NET Framework 1.1 does not support ICMPv6 and that's required
  for IPv6.  In Win32 and Linux, we definately can and want to support IPv6.

  If we support a later version of the NET framework that has a better API, I may
  consider revisiting this.
}

// SG 25/1/02: Modified the component to support multithreaded PING and traceroute

interface

{$I IdCompilerDefines.inc}
//Put FPC into Delphi mode

uses
  Classes,
  IdGlobal,
  IdRawBase,
  IdRawClient,
  IdStackConsts,
  IdBaseComponent;

const
  DEF_PACKET_SIZE = 32;
  MAX_PACKET_SIZE = 1024;
  Id_TIDICMP_ReceiveTimeout = 5000;

type
  TReplyStatusTypes = (rsEcho,
    rsError, rsTimeOut, rsErrorUnreachable,
    rsErrorTTLExceeded,rsErrorPacketTooBig,
    rsErrorParameter,
    rsErrorDatagramConversion,
    rsErrorSecurityFailure,
    rsSourceQuench,
    rsRedirect,
    rsTimeStamp,
    rsInfoRequest,
    rsAddressMaskRequest,
    rsTraceRoute,
    rsMobileHostReg,
    rsMobileHostRedir,
    rsIPv6WhereAreYou,
    rsIPv6IAmHere,
    rsSKIP);

  TReplyStatus = class(TObject)
  protected
    FBytesReceived: integer; // number of bytes in reply from host
    FFromIpAddress: string;  // IP address of replying host
    FToIpAddress : string;   //who receives it (i.e., us.  This is for multihorned machines
    FMsgType: byte;
    FMsgCode : Byte;
    FSequenceId: word;       // sequence id of ping reply
    // TODO: roundtrip time in ping reply should be float, not byte
    FMsRoundTripTime: UInt32; // ping round trip time in milliseconds
    FTimeToLive: byte;       // time to live
    FReplyStatusType: TReplyStatusTypes;
    FPacketNumber : Integer;//number in packet for TraceRoute
    FHostName : String; //Hostname of computer that replied, used with TraceRoute
    FMsg : String;
    FRedirectTo : String; // valid only for rsRedirect
  public
    property RedirectTo : String read FRedirectTo write FRedirectTo;
    property Msg : String read FMsg write FMsg;
    property BytesReceived: integer read FBytesReceived write FBytesReceived; // number of bytes in reply from host
    property FromIpAddress: string read FFromIpAddress write FFromIpAddress;  // IP address of replying host
    property ToIpAddress : string read FToIpAddress write FToIpAddress;   //who receives it (i.e., us.  This is for multihorned machines
    property MsgType: byte read FMsgType write FMsgType;
    property MsgCode : Byte read FMsgCode write FMsgCode;
    property SequenceId: word read FSequenceId write FSequenceId;       // sequence id of ping reply
    // TODO: roundtrip time in ping reply should be float, not byte
    property MsRoundTripTime: UInt32 read FMsRoundTripTime write FMsRoundTripTime; // ping round trip time in milliseconds
    property TimeToLive: byte read FTimeToLive write FTimeToLive;       // time to live
    property ReplyStatusType: TReplyStatusTypes read FReplyStatusType write FReplyStatusType;
    property HostName : String read FHostName write FHostName;
    property PacketNumber : Integer read FPacketNumber write FPacketNumber;
  end;

  TOnReplyEvent = procedure(ASender: TComponent; const AReplyStatus: TReplyStatus) of object;

  // TODO: on MacOSX (and maybe iOS?), can use a UDP socket instead of a RAW
  // socket so that non-privilege processes do not require root access...

  TIdCustomIcmpClient = class(TIdRawClient)
  protected
    FStartTime : TIdTicks; //this is a fallback if no packet is returned
    FPacketSize : Integer;
    FBufReceive: TIdBytes;
    FBufIcmp: TIdBytes;
    wSeqNo: word;
    iDataSize: integer;
    FReplyStatus: TReplyStatus;
    FOnReply: TOnReplyEvent;
    FReplydata: String;
    //
    {$IFNDEF DOTNET_1_1}
    function DecodeIPv6Packet(BytesRead: UInt32): Boolean;
    {$ENDIF}
    function DecodeIPv4Packet(BytesRead: UInt32): Boolean;
    function DecodeResponse(BytesRead: UInt32): Boolean;
    procedure DoReply; virtual;
    procedure GetEchoReply;
    procedure InitComponent; override;
    {$IFNDEF DOTNET_1_1}
    procedure PrepareEchoRequestIPv6(const ABuffer: String);
    {$ENDIF}
    procedure PrepareEchoRequestIPv4(const ABuffer: String);
    procedure PrepareEchoRequest(const ABuffer: String);
    procedure SendEchoRequest; overload;
    procedure SendEchoRequest(const AIP : String); overload;
    function GetPacketSize: Integer;
    procedure SetPacketSize(const AValue: Integer);

    //these are made public in the client
    procedure InternalPing(const AIP : String; const ABuffer: String = ''; SequenceID: Word = 0); overload; {Do not Localize}
    //
    property PacketSize : Integer read GetPacketSize write SetPacketSize default DEF_PACKET_SIZE;
    property ReplyData: string read FReplydata;
    property ReplyStatus: TReplyStatus read FReplyStatus;

    property OnReply: TOnReplyEvent read FOnReply write FOnReply;

  public
    destructor Destroy; override;
    procedure Send(const AHost: string; const APort: TIdPort; const ABuffer : TIdBytes); override;
    procedure Send(const ABuffer : TIdBytes); override;
    function Receive(ATimeOut: Integer): TReplyStatus;
  end;

  TIdIcmpClient = class(TIdCustomIcmpClient)
  public
    procedure Ping(const ABuffer: String = ''; SequenceID: Word = 0);    {Do not Localize}
    property ReplyData;
    property ReplyStatus;
  published
    property Host;
    {$IFNDEF DOTNET_1_1}
    property IPVersion;
    {$ENDIF}
    property PacketSize;
    property ReceiveTimeout default Id_TIDICMP_ReceiveTimeout;
    property OnReply;
  end;

implementation

uses
  //facilitate inlining only.
  {$IFDEF WINDOWS}
  Windows,
  {$ENDIF}
  {$IFDEF USE_VCL_POSIX}
    {$IFDEF DARWIN}
  Macapi.CoreServices,
    {$ENDIF}
  {$ENDIF}
  IdExceptionCore, IdRawHeaders, IdResourceStringsCore,
  IdStack, IdStruct, SysUtils;

{ TIdCustomIcmpClient }

procedure TIdCustomIcmpClient.PrepareEchoRequest(const ABuffer: String);
begin
  {$IFNDEF DOTNET_1_1}
  if IPVersion = Id_IPv6 then begin
    PrepareEchoRequestIPv6(ABuffer);
    Exit;
  end;
  {$ENDIF}
  PrepareEchoRequestIPv4(ABuffer);
end;

{ TIdIPv4_ICMP }

type
  TIdIPv4_ICMP = class(TIdStruct)
  protected
    Fip_hdr: TIdIPHdr;
    Ficmp_hdr: TIdICMPHdr;
    function GetBytesLen: UInt32; override;
  public
    constructor Create; override;
    destructor Destroy; override;
    procedure ReadStruct(const ABytes : TIdBytes; var VIndex : UInt32); override;
    procedure WriteStruct(var VBytes : TIdBytes; var VIndex : UInt32); override;
    property ip_hdr: TIdIPHdr read Fip_hdr;
    property icmp_hdr: TIdICMPHdr read Ficmp_hdr;
  end;

constructor TIdIPv4_ICMP.Create;
begin
  inherited Create;
  Fip_hdr := TIdIPHdr.Create;
  Ficmp_hdr := TIdICMPHdr.Create;
end;
  
destructor TIdIPv4_ICMP.Destroy;
begin
  FreeAndNil(Fip_hdr);
  FreeAndNil(Ficmp_hdr);
  inherited Destroy;
end;

function TIdIPv4_ICMP.GetBytesLen: UInt32;
begin
  Result := inherited GetBytesLen + Fip_hdr.BytesLen + Ficmp_hdr.BytesLen;
end;

procedure TIdIPv4_ICMP.ReadStruct(const ABytes : TIdBytes; var VIndex : UInt32);
begin
  inherited ReadStruct(ABytes, VIndex);
  Fip_hdr.ReadStruct(ABytes, VIndex);
  Ficmp_hdr.ReadStruct(ABytes, VIndex);
end;

procedure TIdIPv4_ICMP.WriteStruct(var VBytes : TIdBytes; var VIndex : UInt32);
begin
  inherited WriteStruct(VBytes, VIndex);
  Fip_hdr.WriteStruct(VBytes, VIndex);
  Ficmp_hdr.WriteStruct(VBytes, VIndex);
end;

{ TIdCustomIcmpClient }

procedure TIdCustomIcmpClient.SendEchoRequest;
begin
  Send(FBufIcmp);
end;

function TIdCustomIcmpClient.DecodeResponse(BytesRead: UInt32): Boolean;
begin
  if BytesRead = 0 then begin
    // Timed out
    FReplyStatus.MsRoundTripTime := GetElapsedTicks(FStartTime);
    FReplyStatus.BytesReceived   := 0;
    if IPVersion = Id_IPv4 then
    begin
      FReplyStatus.FromIpAddress   := '0.0.0.0';
      FReplyStatus.ToIpAddress     := '0.0.0.0';
    end else
    begin
      FReplyStatus.FromIpAddress   := '::0';
      FReplyStatus.ToIpAddress     := '::0';
    end;
    FReplyStatus.MsgType         := 0;
    FReplyStatus.SequenceId      := wSeqNo;
    FReplyStatus.TimeToLive      := 0;
    FReplyStatus.ReplyStatusType := rsTimeOut;
    Result := True;
  end else
  begin
    FReplyStatus.ReplyStatusType := rsError;
    {$IFNDEF DOTNET_1_1}
    if IPVersion = Id_IPv6 then begin
      Result := DecodeIPv6Packet(BytesRead);
      Exit;
    end;
    {$ENDIF}
    Result := DecodeIPv4Packet(BytesRead);
  end;
end;

procedure TIdCustomIcmpClient.GetEchoReply;
begin
  Receive(FReceiveTimeout);
end;

function TIdCustomIcmpClient.Receive(ATimeOut: Integer): TReplyStatus;
var
  BytesRead : Integer;
  TripTime: UInt32;
begin
  Result := FReplyStatus;
  FillBytes(FBufReceive, Length(FBufReceive), 0);
  FStartTime := Ticks64;
  repeat
    BytesRead := ReceiveBuffer(FBufReceive, ATimeOut);
    if DecodeResponse(BytesRead) then begin
      Break;
    end;
    TripTime := GetElapsedTicks(FStartTime);
    ATimeOut := ATimeOut - Integer(TripTime); // compute new timeout value
    FReplyStatus.MsRoundTripTime := TripTime;
    FReplyStatus.Msg := RSICMPTimeout;
    // We caught a response that wasn't meant for this thread - so we must
    // make sure we don't report it as such in case we time out after this
    FReplyStatus.BytesReceived   := 0;
    if IPVersion = Id_IPv4 then
    begin
      FReplyStatus.FromIpAddress   := '0.0.0.0';
      FReplyStatus.ToIpAddress     := '0.0.0.0';
    end else
    begin
      FReplyStatus.FromIpAddress   := '::0';
      FReplyStatus.ToIpAddress     := '::0';
    end;
    FReplyStatus.MsgType         := 0;
    FReplyStatus.SequenceId      := wSeqNo;
    FReplyStatus.TimeToLive      := 0;
    FReplyStatus.ReplyStatusType := rsTimeOut;
  until ATimeOut <= 0;
end;

procedure TIdCustomIcmpClient.DoReply;
begin
  if Assigned(FOnReply) then begin
    FOnReply(Self, FReplyStatus);
  end;
end;

procedure TIdCustomIcmpClient.InitComponent;
begin
  inherited InitComponent;
  FReplyStatus:= TReplyStatus.Create;
  FProtocol := Id_IPPROTO_ICMP;
  {$IFNDEF DOTNET_1_1}
  ProtocolIPv6 := Id_IPPROTO_ICMPv6;
  {$ENDIF}
  wSeqNo := 3489; // SG 25/1/02: Arbitrary Constant <> 0
  FReceiveTimeOut := Id_TIDICMP_ReceiveTimeout;
  FPacketSize := DEF_PACKET_SIZE;
end;

destructor TIdCustomIcmpClient.Destroy;
begin
  FreeAndNil(FReplyStatus);
  inherited Destroy;
end;

function TIdCustomIcmpClient.DecodeIPv4Packet(BytesRead: UInt32): Boolean;
var
  LIPHeaderLen: UInt32;
  LIdx: UInt32;
  RTTime: UInt32;
  LActualSeqID: UInt16;
  LIcmp: TIdIPv4_ICMP;
  LIcmpts: TIdICMPTs;
begin
  Result := False;

  LIpHeaderLen := (FBufReceive[0] and $0F) * 4;
  if BytesRead < (LIpHeaderLen + ICMP_MIN) then begin
    raise EIdIcmpException.Create(RSICMPNotEnoughtBytes);
  end;
  LIdx := 0;

  LIcmp := TIdIPv4_ICMP.Create;
  try
    LIcmp.ReadStruct(FBufReceive, LIdx);

    {$IFDEF LINUX}
    // TODO: baffled as to why linux kernel sends back echo from localhost
    {$ENDIF}

    case LIcmp.icmp_hdr.icmp_type of
      Id_ICMP_ECHOREPLY, Id_ICMP_ECHO:
      begin
        FReplyStatus.ReplyStatusType := rsEcho;
        FReplyData := BytesToStringRaw(FBufReceive, LIdx, -1);
        // result is only valid if the seq. number is correct
      end;
      Id_ICMP_UNREACH:
        FReplyStatus.ReplyStatusType := rsErrorUnreachable;
      Id_ICMP_TIMXCEED:
        FReplyStatus.ReplyStatusType := rsErrorTTLExceeded;
      Id_ICMP_PARAMPROB :
        FReplyStatus.ReplyStatusType := rsErrorParameter;
      Id_ICMP_REDIRECT :
        FReplyStatus.ReplyStatusType := rsRedirect;
      Id_ICMP_TSTAMP, Id_ICMP_TSTAMPREPLY :
        FReplyStatus.ReplyStatusType := rsTimeStamp;
      Id_ICMP_IREQ, Id_ICMP_IREQREPLY :
        FReplyStatus.ReplyStatusType := rsInfoRequest;
      Id_ICMP_MASKREQ, Id_ICMP_MASKREPLY :
        FReplyStatus.ReplyStatusType := rsAddressMaskRequest;
      Id_ICMP_TRACEROUTE :
        FReplyStatus.ReplyStatusType := rsTraceRoute;
      Id_ICMP_DATAGRAM_CONV :
        FReplyStatus.ReplyStatusType := rsErrorDatagramConversion;
      Id_ICMP_MOB_HOST_REDIR :
        FReplyStatus.ReplyStatusType := rsMobileHostRedir;
      Id_ICMP_IPv6_WHERE_ARE_YOU :
        FReplyStatus.ReplyStatusType := rsIPv6WhereAreYou;
      Id_ICMP_IPv6_I_AM_HERE :
        FReplyStatus.ReplyStatusType := rsIPv6IAmHere;
      Id_ICMP_MOB_REG_REQ, Id_ICMP_MOB_REG_REPLY :
        FReplyStatus.ReplyStatusType := rsMobileHostReg;
      Id_ICMP_PHOTURIS :
        FReplyStatus.ReplyStatusType := rsErrorSecurityFailure;
      else
        raise EIdICMPException.Create(RSICMPNonEchoResponse);// RSICMPNonEchoResponse = 'Non-echo type response received'
    end;    // case

    // check if we got a reply to the packet that was actually sent
    case FReplyStatus.ReplyStatusType of
      rsEcho:
      begin
        LActualSeqID := LIcmp.icmp_hdr.icmp_hun.echo_seq;
        RTTime := GetElapsedTicks(BytesToTicks(FBufReceive, LIdx));
      end;
      rsTimeStamp:
      begin
        LActualSeqID := LIcmp.icmp_hdr.icmp_hun.echo_seq;
        LIcmpts := TIdICMPTs.Create;
        try
          LIcmpts.ReadStruct(FBufReceive, LIpHeaderLen);
          RTTime := (LIcmpts.ttime and $80000000) - (LIcmpts.otime and $80000000);
        finally
          LIcmpts.Free;
        end;
      end;
    else
      begin
        // not an echo or timestamp reply: the original IP frame is
        // contained withing the DATA section of the packet...
        // pOriginalIP := PIdIPHdr(@picmp^.icmp_dun.data);

        // TODO: verify this!  I don't think it is indexing far enough into the data
        LActualSeqID := BytesToUInt16(FBufReceive, LIpHeaderLen+8+6);//pOriginalICMP^.icmp_hun.echo.seq;
        RTTime := GetElapsedTicks(BytesToTicks(FBufReceive, LIpHeaderLen+8+8)); //pOriginalICMP^.icmp_dun.ts.otime;

        // move to offset
        // pOriginalICMP := Pointer(PtrUInt(pOriginalIP) + (iIpHeaderLen));
        // extract information from original ICMP frame
        // ActualSeqID := pOriginalICMP^.icmp_hun.echo.seq;
        // RTTime := Ticks64 - pOriginalICMP^.icmp_dun.ts.otime;
        // Result := pOriginalICMP^.icmp_hun.echo.seq = wSeqNo;
      end;
    end;

    Result := LActualSeqID = wSeqNo;//;picmp^.icmp_hun.echo.seq  = wSeqNo;
    if Result then
    begin
      if FReplyStatus.ReplyStatusType = rsEcho then begin
        FReplyStatus.BytesReceived := BytesRead - (Id_IP_HSIZE + ICMP_MIN + SizeOf(UInt32));
      end else begin
        FReplyStatus.BytesReceived := BytesRead - (Id_IP_HSIZE + ICMP_MIN);
      end;

      FReplyStatus.FromIpAddress := MakeUInt32IntoIPv4Address(GStack.NetworkToHost(Licmp.ip_hdr.ip_src.s_l));
      FReplyStatus.ToIpAddress   := MakeUInt32IntoIPv4Address(GStack.NetworkToHost(Licmp.ip_hdr.ip_dst.s_l));
      FReplyStatus.MsgType := LIcmp.icmp_hdr.icmp_type; //picmp^.icmp_type;
      FReplyStatus.MsgCode := LIcmp.icmp_hdr.icmp_code; //picmp^.icmp_code;
      FReplyStatus.SequenceId := LActualSeqID;
      FReplyStatus.MsRoundTripTime := RTTime;
      FReplyStatus.TimeToLive := LIcmp.ip_hdr.ip_ttl;
      // now process our message stuff

      case FReplyStatus.MsgType of
        Id_ICMP_UNREACH:
        begin
          case FReplyStatus.MsgCode of
            Id_ICMP_UNREACH_NET                : FReplyStatus.Msg := RSICMPNetUnreachable;
            Id_ICMP_UNREACH_HOST               : FReplyStatus.Msg := RSICMPHostUnreachable;
            Id_ICMP_UNREACH_PROTOCOL           : FReplyStatus.Msg := RSICMPProtUnreachable;
            Id_ICMP_UNREACH_NEEDFRAG           : FReplyStatus.Msg := RSICMPFragmentNeeded;
            Id_ICMP_UNREACH_SRCFAIL            : FReplyStatus.Msg := RSICMPSourceRouteFailed;
            Id_ICMP_UNREACH_NET_UNKNOWN        : FReplyStatus.Msg := RSICMPDestNetUnknown;
            Id_ICMP_UNREACH_HOST_UNKNOWN       : FReplyStatus.Msg := RSICMPDestHostUnknown;
            Id_ICMP_UNREACH_ISOLATED           : FReplyStatus.Msg := RSICMPSourceIsolated;
            Id_ICMP_UNREACH_NET_PROHIB         : FReplyStatus.Msg := RSICMPDestNetProhibitted;
            Id_ICMP_UNREACH_HOST_PROHIB        : FReplyStatus.Msg := RSICMPDestHostProhibitted;
            Id_ICMP_UNREACH_TOSNET             : FReplyStatus.Msg := RSICMPTOSNetUnreach;
            Id_ICMP_UNREACH_TOSHOST            : FReplyStatus.Msg := RSICMPTOSHostUnreach;
            Id_ICMP_UNREACH_FILTER_PROHIB      : FReplyStatus.Msg := RSICMPAdminProhibitted;
            Id_ICMP_UNREACH_HOST_PRECEDENCE    : FReplyStatus.Msg := RSICMPHostPrecViolation;
            Id_ICMP_UNREACH_PRECEDENCE_CUTOFF  : FReplyStatus.Msg := RSICMPPrecedenceCutoffInEffect;
          end;
        end;
        Id_ICMP_TIMXCEED:
        begin
          case FReplyStatus.MsgCode of
            0 : FReplyStatus.Msg := RSICMPTTLExceeded;
            1 : FReplyStatus.Msg := RSICMPFragAsmExceeded;
          end;
        end;
        Id_ICMP_PARAMPROB                   : FReplyStatus.Msg := IndyFormat(RSICMPParamError, [FReplyStatus.MsgCode]);
        Id_ICMP_REDIRECT:
        begin
          FReplyStatus.RedirectTo := MakeUInt32IntoIPv4Address(GStack.NetworkToHOst(LIcmp.icmp_hdr.icmp_hun.gateway_s_l));
          case FReplyStatus.MsgCode of
            0 : FReplyStatus.Msg := RSICMPRedirNet;
            1 : FReplyStatus.Msg := RSICMPRedirHost;
            2 : FReplyStatus.Msg := RSICMPRedirTOSNet;
            3 : FReplyStatus.Msg := RSICMPRedirTOSHost;
          end;
        end;
        Id_ICMP_SOURCEQUENCH                : FReplyStatus.Msg := RSICMPSourceQuenchMsg;
        Id_ICMP_ECHOREPLY, Id_ICMP_ECHO     : FReplyStatus.Msg := RSICMPEcho;
        Id_ICMP_TSTAMP, Id_ICMP_TSTAMPREPLY : FReplyStatus.Msg := RSICMPTimeStamp;
        Id_ICMP_IREQ, Id_ICMP_IREQREPLY     : FReplyStatus.Msg := RSICMPTimeStamp;
        Id_ICMP_MASKREQ, Id_ICMP_MASKREPLY  : FReplyStatus.Msg := RSICMPMaskRequest;
        Id_ICMP_TRACEROUTE :
        begin
          case FReplyStatus.MsgCode of
            Id_ICMP_TRACEROUTE_PACKET_FORWARDED : FReplyStatus.Msg := RSICMPTracePacketForwarded;
            Id_ICMP_TRACEROUTE_NO_ROUTE         : FReplyStatus.Msg := RSICMPTraceNoRoute;
          end;
        end;
        Id_ICMP_DATAGRAM_CONV:
        begin
          case FReplyStatus.MsgCode of
            Id_ICMP_CONV_UNSPEC                    : FReplyStatus.Msg := RSICMPTracePacketForwarded;
            Id_ICMP_CONV_DONTCONV_OPTION           : FReplyStatus.Msg := RSICMPTraceNoRoute;
            Id_ICMP_CONV_UNKNOWN_MAN_OPTION        : FReplyStatus.Msg := RSICMPConvUnknownMandOptPresent;
            Id_ICMP_CONV_UNKNWON_UNSEP_OPTION      : FReplyStatus.Msg := RSICMPConvKnownUnsupportedOptionPresent;
            Id_ICMP_CONV_UNSEP_TRANSPORT           : FReplyStatus.Msg := RSICMPConvUnsupportedTransportProtocol;
            Id_ICMP_CONV_OVERALL_LENGTH_EXCEEDED   : FReplyStatus.Msg := RSICMPConvOverallLengthExceeded;
            Id_ICMP_CONV_IP_HEADER_LEN_EXCEEDED    : FReplyStatus.Msg := RSICMPConvIPHeaderLengthExceeded;
            Id_ICMP_CONV_TRANS_PROT_255            : FReplyStatus.Msg := RSICMPConvTransportProtocol_255;
            Id_ICMP_CONV_PORT_OUT_OF_RANGE         : FReplyStatus.Msg := RSICMPConvPortConversionOutOfRange;
            Id_ICMP_CONV_TRANS_HEADER_LEN_EXCEEDED : FReplyStatus.Msg := RSICMPConvTransportHeaderLengthExceeded;
            Id_ICMP_CONV_32BIT_ROLLOVER_AND_ACK    : FReplyStatus.Msg := RSICMPConv32BitRolloverMissingAndACKSet;
            Id_ICMP_CONV_UNKNOWN_MAN_TRANS_OPTION  : FReplyStatus.Msg := RSICMPConvUnknownMandatoryTransportOptionPresent;
          end;
        end;
        Id_ICMP_MOB_HOST_REDIR                     : FReplyStatus.Msg := RSICMPMobileHostRedirect;
        Id_ICMP_IPv6_WHERE_ARE_YOU                 : FReplyStatus.Msg := RSICMPIPv6WhereAreYou;
        Id_ICMP_IPv6_I_AM_HERE                     : FReplyStatus.Msg := RSICMPIPv6IAmHere;
        Id_ICMP_MOB_REG_REQ, Id_ICMP_MOB_REG_REPLY : FReplyStatus.Msg := RSICMPIPv6IAmHere;
        Id_ICMP_SKIP                               : FReplyStatus.Msg := RSICMPSKIP;
        Id_ICMP_PHOTURIS :
        begin
          case FReplyStatus.MsgCode of
            Id_ICMP_BAD_SPI             : FReplyStatus.Msg := RSICMPSecBadSPI;
            Id_ICMP_AUTH_FAILED         : FReplyStatus.Msg := RSICMPSecAuthenticationFailed;
            Id_ICMP_DECOMPRESS_FAILED   : FReplyStatus.Msg := RSICMPSecDecompressionFailed;
            Id_ICMP_DECRYPTION_FAILED   : FReplyStatus.Msg := RSICMPSecDecryptionFailed;
            Id_ICMP_NEED_AUTHENTICATION : FReplyStatus.Msg := RSICMPSecNeedAuthentication;
            Id_ICMP_NEED_AUTHORIZATION  : FReplyStatus.Msg := RSICMPSecNeedAuthorization;
          end;
        end;
      end;
    end;
  finally
    FreeAndNil(LIcmp);
  end;
end;

procedure TIdCustomIcmpClient.PrepareEchoRequestIPv4(const ABuffer: String);
var
  LIcmp: TIdICMPHdr;
  LIdx: UInt32;
  LBuffer: TIdBytes;
  LBufferLen: Integer;
begin
  LBuffer := ToBytes(ABuffer, IndyTextEncoding_8Bit);
  LBufferLen := IndyMin(Length(LBuffer), FPacketSize);

  SetLength(FBufIcmp, ICMP_MIN + SizeOf(TIdTicks) + LBufferLen);
  FillBytes(FBufIcmp, Length(FBufIcmp), 0);
  SetLength(FBufReceive, Length(FBufIcmp) + Id_IP_HSIZE);

  LIdx := 0;
  LIcmp := TIdICMPHdr.Create;
  try
    LIcmp.icmp_type :=  Id_ICMP_ECHO;
    LIcmp.icmp_code := 0;
    LIcmp.icmp_sum := 0;
    LIcmp.icmp_hun.echo_id := Word(CurrentProcessId);
    LIcmp.icmp_hun.echo_seq := wSeqNo;
    LIcmp.WriteStruct(FBufIcmp, LIdx);
    CopyTIdTicks(Ticks64, FBufIcmp, LIdx);
    Inc(LIdx, SizeOf(TIdTicks));
    if LBufferLen > 0 then begin
      CopyTIdBytes(LBuffer, 0, FBufIcmp, LIdx, LBufferLen);
    end;
  finally
    FreeAndNil(LIcmp);
  end;
end;

{$IFNDEF DOTNET_1_1}
procedure TIdCustomIcmpClient.PrepareEchoRequestIPv6(const ABuffer: String);
var
  LIcmp : TIdicmp6_hdr;
  LIdx : UInt32;
  LBuffer: TIdBytes;
  LBufferLen: Integer;
begin
  LBuffer := ToBytes(ABuffer, IndyTextEncoding_8Bit);
  LBufferLen := IndyMin(Length(LBuffer), FPacketSize);

  SetLength(FBufIcmp, ICMP_MIN + SizeOf(TIdTicks) + LBufferLen);
  FillBytes(FBufIcmp, Length(FBufIcmp), 0);
  SetLength(FBufReceive, Length(FBufIcmp) + (Id_IPv6_HSIZE*2));

  LIdx := 0;
  LIcmp := TIdicmp6_hdr.Create;
  try
    LIcmp.icmp6_type := ICMP6_ECHO_REQUEST;
    LIcmp.icmp6_code := 0;
    LIcmp.data.icmp6_un_data16[0] := Word(CurrentProcessId);
    LIcmp.data.icmp6_un_data16[1] := wSeqNo;
    LIcmp.icmp6_cksum := 0;
    LIcmp.WriteStruct(FBufIcmp, LIdx);
    CopyTIdTicks(Ticks64, FBufIcmp, LIdx);
    Inc(LIdx, SizeOf(TIdTicks));
    if LBufferLen > 0 then begin
      CopyTIdBytes(LBuffer, 0, FBufIcmp, LIdx, LBufferLen);
    end;
  finally
    FreeAndNil(LIcmp);
  end;
end;

function TIdCustomIcmpClient.DecodeIPv6Packet(BytesRead: UInt32): Boolean;
var
  LIdx : UInt32;
  LIcmp : TIdicmp6_hdr;
  RTTime : UInt32;
  LActualSeqID : Word;
begin
  LIdx := 0;
  LIcmp := TIdicmp6_hdr.Create;
  try
    // Note that IPv6 raw headers are not being returned.
    LIcmp.ReadStruct(FBufReceive, LIdx);

    case LIcmp.icmp6_type of
      ICMP6_ECHO_REQUEST,
      ICMP6_ECHO_REPLY           : FReplyStatus.ReplyStatusType := rsEcho;
      //group membership messages
      ICMP6_MEMBERSHIP_QUERY     : ;
      ICMP6_MEMBERSHIP_REPORT    : ;
      ICMP6_MEMBERSHIP_REDUCTION : ;
      //errors
      ICMP6_DST_UNREACH          : FReplyStatus.ReplyStatusType := rsErrorUnreachable;
      ICMP6_PACKET_TOO_BIG       : FReplyStatus.ReplyStatusType := rsErrorPacketTooBig;
      ICMP6_TIME_EXCEEDED        : FReplyStatus.ReplyStatusType := rsErrorTTLExceeded;
      ICMP6_PARAM_PROB           : FReplyStatus.ReplyStatusType := rsErrorParameter;
      else                         FReplyStatus.ReplyStatusType  := rsError;
    end;
    FReplyStatus.MsgType := LIcmp.icmp6_type; //picmp^.icmp_type;
    FReplyStatus.MsgCode := LIcmp.icmp6_code;

    //errors are values less than ICMP6_INFOMSG_MASK
    if LIcmp.icmp6_type < ICMP6_INFOMSG_MASK then
    begin
      //read info from the original packet part
      LIcmp.ReadStruct(FBufReceive, LIdx);
    end;

    LActualSeqID := LIcmp.data.icmp6_seq;
    Result := LActualSeqID = wSeqNo;

    RTTime := GetElapsedTicks(BytesToTicks(FBufReceive, LIdx));
    Inc(LIdx, SizeOf(TIdTicks));

    if Result then
    begin
      FReplyStatus.BytesReceived := BytesRead - LIdx;
      FReplyStatus.SequenceId := LActualSeqID;
      FReplyStatus.MsRoundTripTime := RTTime;
      // TimeToLive := FBufReceive[8];
      // TimeToLive := pip^.ip_ttl;
      FReplyStatus.TimeToLive := FPkt.TTL;
      FReplyStatus.FromIpAddress := FPkt.SourceIP;
      FReplyStatus.ToIpAddress := FPkt.DestIP;

      case FReplyStatus.MsgType of
        ICMP6_ECHO_REQUEST, ICMP6_ECHO_REPLY : FReplyStatus.Msg := RSICMPEcho;
        ICMP6_TIME_EXCEEDED :
        begin
          case FReplyStatus.MsgCode of
            ICMP6_TIME_EXCEED_TRANSIT    : FReplyStatus.Msg := RSICMPHopLimitExceeded;
            ICMP6_TIME_EXCEED_REASSEMBLY : FReplyStatus.Msg := RSICMPFragAsmExceeded;
          end;
        end;
        ICMP6_DST_UNREACH :
        begin
          case FReplyStatus.MsgCode of
            ICMP6_DST_UNREACH_NOROUTE          : FReplyStatus.Msg := RSICMPNoRouteToDest;
            ICMP6_DST_UNREACH_ADMIN            : FReplyStatus.Msg := RSICMPAdminProhibitted;
            ICMP6_DST_UNREACH_ADDR             : FReplyStatus.Msg := RSICMPHostUnreachable;
            ICMP6_DST_UNREACH_NOPORT           : FReplyStatus.Msg := RSICMPProtUnreachable;
            ICMP6_DST_UNREACH_SOURCE_FILTERING : FReplyStatus.Msg := RSICMPSourceFilterFailed;
            ICMP6_DST_UNREACH_REJCT_DST        : FReplyStatus.Msg := RSICMPRejectRoutToDest;
          end;
        end;
        ICMP6_PACKET_TOO_BIG           : FReplyStatus.Msg := IndyFormat(RSICMPPacketTooBig, [LIcmp.data.icmp6_mtu]);
        ICMP6_PARAM_PROB :
        begin
          case FReplyStatus.MsgCode of
            ICMP6_PARAMPROB_HEADER     : FReplyStatus.Msg := IndyFormat(RSICMPParamHeader, [LIcmp.data.icmp6_pptr]);
            ICMP6_PARAMPROB_NEXTHEADER : FReplyStatus.Msg := IndyFormat(RSICMPParamNextHeader, [LIcmp.data.icmp6_pptr]);
            ICMP6_PARAMPROB_OPTION     : FReplyStatus.Msg := IndyFormat(RSICMPUnrecognizedOpt, [LIcmp.data.icmp6_pptr]);
          end;
        end;
        ICMP6_MEMBERSHIP_QUERY : ;
        ICMP6_MEMBERSHIP_REPORT : ;
        ICMP6_MEMBERSHIP_REDUCTION :;
      end;
    end;
  finally
    FreeAndNil(LIcmp);
  end;
end;
{$ENDIF}

procedure TIdCustomIcmpClient.Send(const AHost: string; const APort: TIdPort;
  const ABuffer: TIdBytes);
var
  LBuffer : TIdBytes;
  LIP : String;
begin
  LBuffer := ABuffer;
  LIP := GStack.ResolveHost(AHost, IPVersion);
  GStack.WriteChecksum(Binding.Handle, LBuffer, 2, LIP, APort, IPVersion);
  FBinding.SendTo(LIP, APort, LBuffer, IPVersion);
end;

procedure TIdCustomIcmpClient.Send(const ABuffer: TIdBytes);
var
  LBuffer : TIdBytes;
  LIP : String;
begin
  LBuffer := ABuffer;
  LIP := GStack.ResolveHost(Host, IPVersion);
  GStack.WriteChecksum(Binding.Handle, LBuffer, 2, LIP, Port, IPVersion);
  FBinding.SendTo(LIP, Port, LBuffer, IPVersion);
end;

function TIdCustomIcmpClient.GetPacketSize: Integer;
begin
  Result := FPacketSize;
end;

procedure TIdCustomIcmpClient.SetPacketSize(const AValue: Integer);
begin
  if AValue < 0 then begin
    FPacketSize := 0;
  end else begin
    FPacketSize := IndyMin(AValue, MAX_PACKET_SIZE);
  end;
end;

procedure TIdCustomIcmpClient.InternalPing(const AIP, ABuffer: String; SequenceID: Word);
begin
  if SequenceID <> 0 then begin
    wSeqNo := SequenceID;
  end;
  PrepareEchoRequest(ABuffer);
  SendEchoRequest(AIP);
  GetEchoReply;
  Binding.CloseSocket;
  DoReply;
  Inc(wSeqNo); // SG 25/1/02: Only increase sequence number when finished.
end;

procedure TIdCustomIcmpClient.SendEchoRequest(const AIP: String);
begin
  Send(AIP, 0, FBufIcmp);
end;

{ TIdIcmpClient }

procedure TIdIcmpClient.Ping(const ABuffer: String; SequenceID: Word);
begin
  InternalPing(GStack.ResolveHost(Host, IPVersion), ABuffer, SequenceID);
end;

end.
