Я пытаюсь отправить запрос на пинг с 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);
}
Я считаю, что документация неверна.
Если вы посмотрите на то, что на самом деле в ICMP_ECHO_REPLY32, вы найдете несколько указателей, объявленных с __ptr32
атрибут, и есть некоторая информация об этих парнях Вот.
тем не мение, когда вы на самом деле разыменовываете один из них, он оказывается добросовестным 64-битным указателем в конце концов, а это не то, что говорится в посте Ганса. Я думаю, что все изменилось с тех пор, как он написал это (проверено мной на Visual Studio 2017 только сейчас).
Во всяком случае, пойди разберись, но что нарушает твой код, так это sizeof
за такой указатель возвращает 4, даже в 64-битной сборке, а это означает, что общий размер сообщения ICMP_ECHO_REPLY32
структура не достаточно велика (с такой небольшой полезной нагрузкой), чтобы вместить даже один 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 = успех, см. документация.
Других решений пока нет …