【问题标题】:WinAPI IcmpSendEcho on 64-bit platform64 位平台上的 WinAPI IcmpSendEcho
【发布时间】:2018-06-28 16:30:30
【问题描述】:

我正在尝试使用 Windows 提供的 IcmpSendEcho 函数发送 ping 请求,如文档中的 here 所述。
在大多数情况下,这似乎很简单,但是,我无法理解如何设置和使用调用者提供的回显请求缓冲区(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 个字节(并且已经看到 speculation that 8 is incorrect for 64-bit platforms) - 我不确定这是否会影响问题.

如果我改用ICMP_ECHO_REPLY 进行ReplySize 计算,IcmpSendEcho 将不再因任何有效负载大小而失败,但似乎仍不清楚该函数在内部实际使用了哪个函数。即,ReplyBuffer 是写成ICMP_ECHO_REPLYICMP_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);
}

【问题讨论】:

标签: c++ winapi visual-c++ windows-10-desktop win64


【解决方案1】:

我认为文档有误。

如果您查看ICMP_ECHO_REPLY32 中的实际内容,您会发现一些使用__ptr32 属性声明的指针,还有一些关于这些人here 的信息。

然而,当您实际取消引用其中一个时,它毕竟是一个真正的 64 位指针,这不是 Hans 帖子中所说的。我想自从他写了之后事情已经发生了变化(我刚刚在 Visual Studio 2017 上进行了测试)。

不管怎样,去看看,但是破坏你的代码的是 sizeof 对于这样的指针返回 4,即使在 64 位构建中,这意味着报告的总大小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-&gt;Status。但它应该这样做。 0 = 成功,见documentation

【讨论】:

  • 是的,文档错误。在 x64 模式下使用的 win 8.1/win10 中 ICMP_ECHO_REPLY 的最小值是多少。
  • 谢谢,@RbMm。我会提交错误报告。
  • 感谢您的精彩回答!出于好奇,关于__ptr32 的帖子的哪一部分是错误的? __ptr32 实际上不是 32 位的,然后它被转换为 64 位以在 64 位平台上取消引用?只是__ptr32 的较小尺寸导致ICMP_ECHO_REPLY32 太小?
  • @MCΔT 谢谢你,我相信你会accept it。我不太确定与__ptr32 的交易是什么,但这是一个奇怪的交易,这是肯定的。编译器似乎有两个想法,它有多大。我会追求这个。
  • @PaulSanders 在我的系统上,__ptr32 似乎使它成为 32 位,而不是所有其他指针的正常 64 位。我想所有关于在取消引用时将其“强制”为 64 位的大惊小怪是因为 CPU 不能在 64 位模式下直接使用 32 位指针?很奇怪,我更希望他们只给我们一个ICMP_ECHO_REPLY,让编译器决定哪个指针大小是合适的......
猜你喜欢
  • 2016-11-30
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2013-05-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多