I am trying to send a ping request with the IcmpSendEcho
function provided by Windows, as documented here.
For the most part, it seems straightforward, however, I am having trouble understanding how the caller-provided echo request buffer (LPVOID ReplyBuffer
and DWORD ReplySize
parameters) should be set up and used. The documentation states the following:
ReplyBuffer
[...]
A buffer to hold any replies to the echo request. Upon return, the buffer contains an array of ICMP_ECHO_REPLY structures followed by the options and data for the replies. The buffer should be large enough to hold at least one ICMP_ECHO_REPLY structure plus RequestSize bytes of data.
On a 64-bit platform, upon return the buffer contains an array of ICMP_ECHO_REPLY32 structures followed by the options and data for the replies.
and
ReplySize
[...]
The allocated size, in bytes, of the reply buffer. The buffer should be large enough to hold at least one ICMP_ECHO_REPLY structure plus RequestSize bytes of data. On a 64-bit platform, The buffer should be large enough to hold at least one ICMP_ECHO_REPLY32 structure plus RequestSize bytes of data.
This buffer should also be large enough to also hold 8 more bytes of data (the size of an ICMP error message).
I am developing on and targeting a 64-bit system and version of Windows, so my understanding from the documentation is that I need to use ICMP_ECHO_REPLY32
to calculate the buffer size, ie.:
ReplySize >= sizeof(ICMP_ECHO_REPLY32) + RequestSize + 8
However when testing this, the IcmpSendEcho
always fails instantly with return value 0.
For RequestSize < 4
, GetLastError()
returns ERROR_INVALID_PARAMETER
, which the documentation says indicates that "the ReplySize parameter specifies a value less than the size of an ICMP_ECHO_REPLY or ICMP_ECHO_REPLY32 structure".
For RequestSize >= 4
, GetLastError
returns an undocumented value that when parsed with GetIpErrorString()
indicates "General failure".
I also see that the example code in the documentation excludes the 8 bytes in the reply buffer that is specified as required for an "ICMP error message" (and have seen speculation that 8 is incorrect for 64-bit platforms) - I am unsure if this affects the issue.
If I instead use ICMP_ECHO_REPLY
for the ReplySize
calculation, IcmpSendEcho
no longer fails for any payload size, however it still seems unclear which the function actually uses internally. Ie., is ReplyBuffer
written as an array of ICMP_ECHO_REPLY
or ICMP_ECHO_REPLY32
? Since once must access the buffer via casting the pointer type, using the wrong type could silently misalign and mangle all operations on it.
So how should the reply buffer be set up correctly? Should ICMP_ECHO_REPLY
or ICMP_ECHO_REPLY32
be used?
This is the test code I have been working with:
#include <WS2tcpip.h>
#include <Windows.h>
#include <iphlpapi.h>
#include <IcmpAPI.h>
#pragma comment(lib, "Iphlpapi.lib")
#pragma comment(lib, "Ws2_32.lib")
int main()
{
// Create the ICMP context.
HANDLE icmp_handle = IcmpCreateFile();
if (icmp_handle == INVALID_HANDLE_VALUE) {
throw;
}
// Parse the destination IP address.
IN_ADDR dest_ip{};
if (1 != InetPtonA(AF_INET, "<IP here>", &dest_ip)) {
throw;
}
// Payload to send.
constexpr WORD payload_size = 1;
unsigned char payload[payload_size]{};
// Reply buffer for exactly 1 echo reply, payload data, and 8 bytes for ICMP error message.
constexpr DWORD reply_buf_size = sizeof(ICMP_ECHO_REPLY32) + payload_size + 8;
unsigned char reply_buf[reply_buf_size]{};
// Make the echo request.
DWORD reply_count = IcmpSendEcho(icmp_handle, dest_ip.S_un.S_addr,
payload, payload_size, NULL, reply_buf, reply_buf_size, 10000);
// Return value of 0 indicates failure, try to get error info.
if (reply_count == 0) {
auto e = GetLastError();
// Some documented error codes from IcmpSendEcho docs.
switch (e) {
case ERROR_INSUFFICIENT_BUFFER:
throw;
case ERROR_INVALID_PARAMETER:
throw;
case ERROR_NOT_ENOUGH_MEMORY:
throw;
case ERROR_NOT_SUPPORTED:
throw;
case IP_BUF_TOO_SMALL:
throw;
}
// Try to get an error message for all other error codes.
DWORD buf_size = 1000;
WCHAR buf[1000];
GetIpErrorString(e, buf, &buf_size);
throw;
}
// Close ICMP context.
IcmpCloseHandle(icmp_handle);
}