WinAPI IcmpSendEcho on 64-bit platform

WinAPI IcmpSendEcho on 64-bit platform

本文关键字:platform 64-bit on IcmpSendEcho WinAPI      更新时间:2023-10-16

我正在尝试使用 Windows 提供的IcmpSendEcho函数发送 ping 请求,如此处所述。
在大多数情况下,这似乎很简单,但是,我无法理解调用方提供的回显请求缓冲区(LPVOID ReplyBufferDWORD ReplySize参数(应该如何设置和使用。文档声明如下:

ReplyBuffer
[...]
用于保存对回显请求的任何回复的缓冲区。返回时,缓冲区包含一组ICMP_ECHO_REPLY结构,后跟回复的选项和数据。缓冲区应足够大,以容纳至少一个ICMP_ECHO_REPLY结构以及 RequestSize 数据字节。
在 64 位平台上,返回时缓冲区包含一个ICMP_ECHO_REPLY32结构数组,后跟回复的选项和数据。

ReplySize
[...]
回复缓冲区的分配大小(以字节为单位(。缓冲区应足够大,以容纳至少一个ICMP_ECHO_REPLY结构以及 RequestSize 数据字节。在 64 位平台上,缓冲区应足够大,以容纳至少一个ICMP_ECHO_REPLY32结构以及 RequestSize 字节的数据。
此缓冲区还应足够大,以便再容纳 8 个字节的数据(ICMP 错误消息的大小(。

我正在开发并针对 64 位系统和版本的 Windows 进行开发,因此我从文档中的理解是我需要使用ICMP_ECHO_REPLY32来计算缓冲区大小,即:

ReplySize >= sizeof(ICMP_ECHO_REPLY32) + RequestSize + 8

但是,在测试此功能时,IcmpSendEcho总是立即失败,返回值为 0。
对于RequestSize < 4GetLastError()返回ERROR_INVALID_PARAMETER,文档说这表示">ReplySize参数指定一个小于ICMP_ECHO_REPLY或ICMP_ECHO_REPLY32结构大小的值"。
对于RequestSize >= 4GetLastError返回一个未记录的值,当用GetIpErrorString()解析时,该值表示"一般失败"。

我还看到文档中的示例代码排除了回复缓冲区中为"ICMP 错误消息"指定的 8 个字节(并且已经看到猜测 8 对于 64 位平台不正确( - 我不确定这是否会影响问题。

如果我改用ICMP_ECHO_REPLY进行ReplySize计算,IcmpSendEcho不再为任何有效负载大小失败,但是似乎仍然不清楚该函数在内部实际使用哪个函数。即,ReplyBuffer写成ICMP_ECHO_REPLY数组还是ICMP_ECHO_REPLY32数组?由于 Once 必须通过强制转换指针类型来访问缓冲区,因此使用错误的类型可能会静默地错位并破坏其上的所有操作。

那么应该如何正确设置回复缓冲区呢?应该使用ICMP_ECHO_REPLY还是ICMP_ECHO_REPLY32

这是我一直在使用的测试代码:

#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);
}

我认为文档是错误的。

如果你看一下ICMP_ECHO_REPLY32的实际内容,你会发现一些用__ptr32属性声明的指针,这里有一些关于这些人的信息。

然而,当你真正取消引用其中一个时,它毕竟是一个真正的 64 位指针,这不是 Hans 帖子中所说的。 我想自从他写了这篇文章以来,情况已经发生了变化(我刚刚在Visual Studio 2017上测试过(。

无论如何,去想一想,但是破坏您的代码的是,即使在 64 位构建中,此类指针的sizeof也会返回4,这意味着ICMP_ECHO_REPLY32结构的总报告大小不够大(有效负载如此之小(甚至无法容纳单个 ICMP 回复,因此IcmpSendEchoERROR_INVALID_PARAMETER而失败, 正如你所观察到的。

因此,要使代码正常工作,您所要做的就是在设置请求时使用sizeof (ICMP_ECHO_REPLY),然后将reply_buf转换为const ICMP_ECHO_REPLY *以解释结果。

这是我的(工作(测试程序的整个代码,添加了一些日志记录(为了简洁起见,我删除了一些错误处理(:

#define _WINSOCK_DEPRECATED_NO_WARNINGS
#include <WS2tcpip.h>
#include <Windows.h>
#include <iphlpapi.h>
#include <IcmpAPI.h>
#include <iostream>
#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, "89.238.162.170", &dest_ip)) {
throw;
}
// Payload to send.
constexpr WORD payload_size = 1;
unsigned char payload[payload_size] { 42 };
// Reply buffer for exactly 1 echo reply, payload data, and 8 bytes for ICMP error message.
constexpr DWORD reply_buf_size = sizeof(ICMP_ECHO_REPLY) + 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();
DWORD buf_size = 1000;
WCHAR buf[1000];
GetIpErrorString(e, buf, &buf_size);
std::cout << "IcmpSendEcho returned error " << e << " (" << buf << ")" << std::endl;
return 255;
}
const ICMP_ECHO_REPLY *r = (const ICMP_ECHO_REPLY *) reply_buf;
struct in_addr addr;
addr.s_addr = r->Address;
char *s_ip = inet_ntoa (addr);
std::cout << "Reply from: " << s_ip << ": bytes=" << r->DataSize << " time=" << r->RoundTripTime << "ms TTL=" << (int) r->Options.Ttl << std::endl;
// Close ICMP context.
IcmpCloseHandle(icmp_handle);
return 0;
}

在rextester上运行它。(这是我网站的IP地址,你看到我在那里做了什么吗?:)

未来访客注意:此代码不会检查IcmpSendEcho返回时r->Status。 但它应该这样做。 0 = 成功,请参阅文档。