WinAPI IcmpSendEcho на 64-битной платформе - PullRequest
0 голосов
/ 28 июня 2018

Я пытаюсь отправить запрос ping с помощью функции IcmpSendEcho, предоставляемой Windows, как описано здесь .
По большей части это кажется простым, однако у меня возникают проблемы с пониманием того, как должен быть установлен и использован буфер эхо-запросов, предоставляемый вызывающим абонентом (параметры LPVOID ReplyBuffer и DWORD 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 < 4, GetLastError() возвращает ERROR_INVALID_PARAMETER, что, как говорится в документации, указывает, что «параметр ReplySize задает значение, которое меньше размера структуры ICMP_ECHO_REPLY или ICMP_ECHO_REPLY32».
Для RequestSize >= 4, GetLastError возвращает недокументированное значение, которое при анализе с GetIpErrorString() указывает на «Общий сбой».

Я также вижу, что пример кода в документации исключает 8 байтов в буфере ответа, который указан как требуется для «сообщения об ошибке ICMP» (и видел предположение, что 8 неверно для 64-битных платформ ) - Я не уверен, влияет ли это на проблему.

Если я вместо этого использую ICMP_ECHO_REPLY для вычисления ReplySize, IcmpSendEcho больше не завершается ошибкой для любого размера полезной нагрузки, однако все еще кажется неясным, какая функция на самом деле использует внутренне. То есть ReplyBuffer записывается как массив ICMP_ECHO_REPLY или ICMP_ECHO_REPLY32? Так как однажды должен получить доступ к буферу через приведение типа указателя, использование неправильного типа может молча сместить и исказить все операции над ним.

Так как правильно настроить буфер ответов? Нужно ли использовать 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);
}

1 Ответ

0 голосов
/ 28 июня 2018

Я считаю, что документация неверна.

Если вы посмотрите на то, что на самом деле в ICMP_ECHO_REPLY32, вы найдете несколько указателей, объявленных с атрибутом __ptr32, и есть некоторая информация об этих парнях здесь .

Однако , когда вы действительно разыменовываете один из них, он оказывается добросовестным 64-битным указателем в конце концов, а это не то, что говорится в посте Ганса. Я думаю, что все изменилось с тех пор, как он написал это (проверено мной на Visual Studio 2017 только сейчас).

В любом случае, поймите, но ваш код нарушает то, что sizeof для такого указателя возвращает 4 , даже в 64-битной сборке, и это означает, что общий размер сообщения * Структура 1017 * недостаточно велика (с такой небольшой полезной нагрузкой), чтобы вместить даже один ICMP-ответ, поэтому IcmpSendEcho завершается с ERROR_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-адрес моего сайта, вы видели, что я там делал?)

Примечание для будущих посетителей: Этот код не проверяет r->Status при возврате IcmpSendEcho. Но это должно сделать. 0 = успех, см. документацию .

...