2

We have a legacy Delphi application which uses IcmpSendEcho (from iphlpapi.dll) to perform echo requests. As I understand it, this performs the same function as "ping" from the command prompt.

On Windows XP, the code below works fine. When the IPv4 address is correct the response is quick and if not error code 11010 (IP_REQ_TIMED_OUT) is returned as expected.

However, on my 32-bit Windows 10 machine, the error code is 87 (ERROR_INVALID_PARAMETER). I've carefully reviewed the Microsoft documentation for IcmpSendEcho and cannot see anything obvious that is wrong.

"ping 200.1.2.121" (the example IPv4 address I use in the code sample) works as expected from the command prompt in both XP and 10.

type
  PIpAddress = ^TIpAddress;
  TIpAddress = record
    case Integer of
        0: (S_un_b: TSunB);
        1: (S_un_w: TSunW);
        2: (S_addr: LongWord);
  end;
  IpAddress = TIpAddress;

// Functions imported from external DLLs
function IcmpCreateFile() : THandle; stdcall; external 'iphlpapi.dll';
function IcmpCloseHandle(icmpHandle: THandle) : Boolean; stdcall; external 'iphlpapi.dll';
function IcmpSendEcho(IcmpHandle: THandle; ipDest: IpAddress;
    pRequestData: Pointer; nRequestSize: SmallInt; RequestOptions: Pointer;
    pReplyBuffer: Pointer; dwReplySize: DWORD; dwTimeout: DWORD) : DWORD; stdcall; external 'iphlpapi.dll';

procedure TranslateStringToIpAddress(strIP: String; var ipAddress);
var
    phe: PHostEnt;
    pac: PChar;
begin
    try
        phe := GetHostByName(PChar(strIP));
        if (Assigned(phe)) then
            begin
            pac := phe^.h_addr_list^;
            if (Assigned(pac)) then
                begin
                with TIpAddress(ipAddress).S_un_b do
                    begin
                    by1 := Byte(pac[0]);
                    by2 := Byte(pac[1]);
                    by3 := Byte(pac[2]);
                    by4 := Byte(pac[3]);
                    end;
                end
            else
                begin
                raise Exception.Create('Error getting IP from HostName');
                end;
            end
        else
            begin
            raise Exception.Create('Error getting HostName');
            end;
    except
        FillChar(ipAddress, SizeOf(ipAddress), #0);
    end;
end;

function Ping(strIpAddress : String) : Boolean;
const
    ICMP_ECHO_BUFFER = 128;     // Works as low as 28 on Windows XP (nothing works on Windows 10)
var
    address: IpAddress;
    dwReplies: DWORD;
    {$IFDEF DBG} dwErrorCode: DWORD; {$ENDIF}
    abyReplyBuffer: array[1..ICMP_ECHO_BUFFER] of BYTE;
begin
    // Use this function to determine if an IPv4 address can be reached
    Result := False;

    // "m_cache.hPingHandle" is generated earlier with a call to "IcmpCreateFile"
    if (m_cache.hPingHandle = INVALID_HANDLE_VALUE) then
        Exit;

    TranslateStringToIpAddress(strIpAddress, address);
    dwReplies := IcmpSendEcho(
        m_cache.hPingHandle, address, nil, 0, nil, @abyReplyBuffer, ICMP_ECHO_BUFFER, 0);

    {$IFDEF DBG}
    if (dwReplies = 0) then
        begin
        dwErrorCode := GetLastError();
        // dwErrorCode = 87 (ERROR_INVALID_PARAMETER, "The parameter is incorrect")
        Application.MessageBox(
            PAnsiChar(Format('WinError = %d', [dwErrorCode])), 'Ping failed', MB_ICONEXCLAMATION);
        end;
    {$ENDIF}

    // Success?
    Result := (dwReplies <> 0);
end;

// Usage elsewhere in the application...
Ping('200.1.2.121');    // Works on Windows XP, but fails on Windows 10
AlainD
  • 5,413
  • 6
  • 45
  • 99
  • 1
    Try a larger reply buffer (`ERROR_INVALID_PARAMETER` is an expected error code if the buffer is too small). From the [documentation](https://learn.microsoft.com/en-us/windows/desktop/api/icmpapi/nf-icmpapi-icmpsendecho), it should be at least the size of `ICMP_ECHO_REPLY` (or `ICMP_ECHO_REPLY32` on 64-bit Windows) plus 8 bytes for any ICMP error message. – Ondrej Kelle Aug 15 '18 at 15:58
  • FYI, you should be using `getaddrinfo()` instead of `gethostbyname()`. And what is the point of having `TranslateStringToIpAddress()` return `0.0.0.0` if the host/IP can't be resolved? You can't ping that IP. So either get rid of the `try..except` block and let the raised exceptions reach the caller, or else give `TranslateStringToIpAddress()` a return value that the caller can check for error before calling `IcmpSendEcho()`. – Remy Lebeau Aug 15 '18 at 16:25
  • 2
    Simply changing Timeout to Zero in working code gives me an error in Win64. – FredS Aug 15 '18 at 17:06
  • @OndrejKelle: As mentioned in the question, I have tried various sizes for the buffer size, but to be sure I've now tried a buffer size of 2560 bytes. This works fine on 32-bit Windows XP, but fails with error 87 on Windows 10. – AlainD Aug 28 '18 at 14:39
  • @FredS: Thats it! I simply change the timeout from `0` to `10` and it starts working on Windows 10. Thanks, if you knock up an answer to that effect I'll accept it. – AlainD Aug 28 '18 at 15:26

1 Answers1

1

Based on the comment from @FredS (thanks!), the answer is simply to make the last parameter for the IcmpSendEcho non-zero (eg. "200").

The MSDN documentation for IcmpSendEcho does not make this clear, so Microsoft have probably changed the internal implementation of this method from the version in Windows XP so that a non-zero Timeout is now required.

AlainD
  • 5,413
  • 6
  • 45
  • 99