Search code examples
delphipingindy

Indy TIdIcmpClient - how to detect a timeout?


(Using Delphi 2009) I'm trying to write a simple network connection monitor using Indy TidIcmpClient. The idea is to ping an address, then inside an attached TidIcmpClient.OnReply handler test how much data was returned. If the reply contains > 0 bytes then I know the connection succeeded but if either the TidIcmpClient timed out or the reply contains 0 bytes I will assume the link is down

I'm having difficulty understanding the logic of TidIcmpClient as there is no 'OnTimeout' event.

Two sub questions...

Does TidIcmpClient.OnReply get called anyway, either (a) when data is received OR (b) when the timeout is reached, whichever comes first?

How can I distinguish a zero byte reply because of a timeout from a real reply within the timeout period that happens to contain zero bytes (or can't this happen)?

In other words is this sort of code OK or do I need to do something else to tell if it timed out or not

procedure TForm1.IdIcmpClient1Reply(ASender: TComponent; const AReplyStatus: TReplyStatus);
begin
if IdIcmpClient1.ReplyStatus.BytesReceived = 0 then
  //we must have timed out, link is down
else
  //got some data, connection is up
end;

procedure DoPing;
begin
IdIcmpClient1.ReceiveTimeout := 200;
IdIcmpClient1.Host := '8.8.8.8';
IdIcmpClient1.Ping;
end;

Solution

  • When Ping() exits, the ReplyStatus property contains the same information that is passed to the AReplyStatus parameter of the OnReply event (you are ignoring that parameter). Ping() simply calls the OnReply handler right before exiting, passing it the ReplyStatus property, so you don't actually need to use the OnReply event in your example. All that is doing is breaking up your code unnecessarily.

    procedure DoPing;
    begin
      IdIcmpClient1.ReceiveTimeout := 200;
      IdIcmpClient1.Host := '8.8.8.8';
      IdIcmpClient1.Ping;
      // process IdIcmpClient1.ReplyStatus here as needed...
    end;
    

    That being said, you are not processing the ReplyStatus data correctly. The BytesReceived field can be greater than 0 even if the ping fails. As its name implies, it simply reports how many bytes were actually received for the ICMP response. ICMP defines many different kinds of responses. The ReplyStatusType field will be set to the type of response actually received. There are 20 values defined:

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

    If the ping is successful, the ReplyStatusType will be rsEcho, and the ReplyData field will contain the (optional) data that was passed to the ABuffer parameter of Ping(). You might also want to pay attention to the FromIpAddress and ToIpAddress fields as well, to make sure the response is actually coming from the expected target machine.

    If a timeout occurs, the ReplyStatusType will be rsTimeOut instead.

    Try this:

    procedure DoPing;
    begin
      IdIcmpClient1.ReceiveTimeout := 200;
      IdIcmpClient1.Host := '8.8.8.8';
      IdIcmpClient1.Ping;
      if IdIcmpClient1.ReplyStatus.ReplyStatusType = rsEcho then
      begin
        // got some data, connection is up
      end
      else if IdIcmpClient1.ReplyStatus.ReplyStatusType = rsTimeout then
      begin
        // have a timeout, link is down
      end
      else
      begin
        // some other response, do something else...
      end;
    end;