之前在VS2015 2017 2019一直编译不通过,不知道是一些什么奇奇怪怪的问题,但过了几天,编译又成功了,很奇怪,但至少目前是没问题的,先贴一下MSDN上面给的示例代码吧#include
#include
#include
#include
#pragma comment(lib, "iphlpapi.lib")
#pragma comment(lib, "ws2_32.lib")
int __cdecl main(int argc, char **argv) {
// Declare and initialize variables
HANDLE hIcmpFile;
unsigned long ipaddr = INADDR_NONE;
DWORD dwRetVal = 0;
char SendData[32] = "Data Buffer";
LPVOID ReplyBuffer = NULL;
DWORD ReplySize = 0;
// Validate the parameters
if (argc != 2) {
printf("usage: %s IP address\n", argv[0]);
return 1;
}
ipaddr = inet_addr(argv[1]);
if (ipaddr == INADDR_NONE) {
printf("usage: %s IP address\n", argv[0]);
return 1;
}
hIcmpFile = IcmpCreateFile();
if (hIcmpFile == INVALID_HANDLE_VALUE) {
printf("\tUnable to open handle.\n");
printf("IcmpCreatefile returned error: %ld\n", GetLastError() );
return 1;
}
ReplySize = sizeof(ICMP_ECHO_REPLY) + sizeof(SendData);
ReplyBuffer = (VOID*) malloc(ReplySize);
if (ReplyBuffer == NULL) {
printf("\tUnable to allocate memory\n");
return 1;
}
dwRetVal = IcmpSendEcho(hIcmpFile, ipaddr, SendData, sizeof(SendData),
NULL, ReplyBuffer, ReplySize, 1000);
if (dwRetVal != 0) {
PICMP_ECHO_REPLY pEchoReply = (PICMP_ECHO_REPLY)ReplyBuffer;
struct in_addr ReplyAddr;
ReplyAddr.S_un.S_addr = pEchoReply->Address;
printf("\tSent icmp message to %s\n", argv[1]);
if (dwRetVal > 1) {
printf("\tReceived %ld icmp message responses\n", dwRetVal);
printf("\tInformation from the first response:\n");
}
else {
printf("\tReceived %ld icmp message response\n", dwRetVal);
printf("\tInformation from this response:\n");
}
printf("\t Received from %s\n", inet_ntoa( ReplyAddr ) );
printf("\t Status = %ld\n",
pEchoReply->Status);
printf("\t Roundtrip time = %ld milliseconds\n",
pEchoReply->RoundTripTime);
}
else {
printf("\tCall to IcmpSendEcho failed.\n");
printf("\tIcmpSendEcho returned error: %ld\n", GetLastError() );
return 1;
}
return 0;
}
这个程序的核心就是一个IcmpSendEcho函数,IcmpSendEcho函数像目标主机发送一个IPv4的ICMP响应请求。这个函数在IcmpAPI.h头文件中定义如下IPHLPAPI_DLL_LINKAGE
DWORD
WINAPI
IcmpSendEcho(
_In_ HANDLE IcmpHandle,
_In_ IPAddr DestinationAddress,
_In_reads_bytes_(RequestSize) LPVOID RequestData,
_In_ WORD RequestSize,
_In_opt_ PIP_OPTION_INFORMATION RequestOptions,
_Out_writes_bytes_(ReplySize) LPVOID ReplyBuffer,
_In_range_(>=, sizeof(ICMP_ECHO_REPLY) + RequestSize + 8)
DWORD ReplySize,
_In_ DWORD Timeout
);
有点凌乱,MSDN官网上面的更简洁一点IPHLPAPI_DLL_LINKAGE DWORD IcmpSendEcho(
HANDLE IcmpHandle,
IPAddr DestinationAddress,
LPVOID RequestData,
WORD RequestSize,
PIP_OPTION_INFORMATION RequestOptions,
LPVOID ReplyBuffer,
DWORD ReplySize,
DWORD Timeout
);
参数介绍
1、IcmpHandle
是一个句柄,HANDLE声明,返回IcmpCreateFile()函数的打开的ICMP文件句柄
2、DestinationAddress
目标IPv4地址,采用IPAddr结构的形式,也就是inet_addr(ip),如inet_addr("127.0.0.1")的返回值,作用是将点分十六进制转换为小端序形式的十六进制数。
3、RequestData
请求数据的地址指针,也就是发送的字符串数组名,一般发啥过去,返回的就是啥
4、RequestSize
RequestData的大小,一般在strlen(RequestData)的基础上+1,不加也可以,如示例代码中直接使用sizeof(RequestData)的返回值
5、RequestOptions
指向请求的IP标头选项的指针,形式为IP_OPTION_INFORMATION结构。在64位平台上,此参数采用IP_OPTION_INFORMATION32结构的形式。如果不需要指定IP标头选项,则此参数可以为NULL。这个需要注意的是,64位平台IP_OPTION_INFORMATION结构是有后32的,而32位的没有
这个结构体定义如下typedef struct ip_option_information {
UCHAR Ttl;
UCHAR Tos;
UCHAR Flags;
UCHAR OptionsSize;
PUCHAR OptionsData;
} IP_OPTION_INFORMATION, *PIP_OPTION_INFORMATION;
描述了被包括在IP分组的报头选项,也就是ICMP的报头结构,其中OptionData对应发送的数据,size对应大小
6、ReplyBuffer
一个缓冲区,用于保存对请求的所有答复。返回时,缓冲区包含ICMP_ECHO_REPLY结构数组, 后跟答复的选项和数据。缓冲区应足够大,以容纳至少一个 ICMP_ECHO_REPLY结构以及数据的RequestSize字节。
也就是说,回复的数据中包含一个ICMP_ECHO_REPLY结构体和一个RequestData,其中ICMP_ECHO_REPLY结构定义如下typedef struct icmp_echo_reply {
IPAddr Address;
ULONG Status;
ULONG RoundTripTime;
USHORT DataSize;
USHORT Reserved;
PVOID Data; struct ip_option_information Options;} ICMP_ECHO_REPLY, *PICMP_ECHO_REPLY;
Data也就是返回的实际数据,这里可以用wireshark抓包验证,结构体中重点要说的是IPAddr,也就是地址,这里返回的是一个小端序数,如下示例代码输出char ip[] = "127.0.0.1";
unsigned long ipHex = inet_addr(ip);
struct in_addr hex;
hex.S_un.S_addr = ipHex;
char ipStr[20];
memset(ipStr, 0, 20);
memcpy(ipStr, inet_ntoa(hex), strlen(inet_ntoa(hex)));
printf("ipHex = %x\n", ipHex);
printf("ipStr = %s\n", ipStr);
输出如图
7、ReplySize
分配的答复缓冲区大小(以字节为单位)。缓冲区应足够大,以容纳至少一个 ICMP_ECHO_REPLY结构以及数据的RequestSize字节。此缓冲区还应该足够大,以容纳8个更多字节的数据(ICMP错误消息的大小)。
8、Timeout
等待答复的时间(以毫秒为单位)。
到这里,MSDN官网上面的代码介绍基本完毕,稍修改输出便可实现cmd终端的ping功能输出,那么如何修改至用于探测主机存活呢?
首先,在真实环境中,一般不需要考虑IcmpCreateFile创建文件失败或者ReplyBuffer申请内存空间失败,如果遇到这种情况,直接认为主机不存在,不存活,而且ReplyBuffer的大小是可以直接计算出来的,甚至都不需要malloc函数来申请内存空间,其次,只是探测主机是否存活,并非重复造轮子一个ping,所以发送的数据也可以尽可能的简短,输出也不需要,直接修改为一个函数即可,同时为批量探测主机存活提供接口,如下#include
#include
#include
#include
#pragma comment(lib, "iphlpapi.lib")
#pragma comment(lib, "ws2_32.lib")
BOOL icmpProbe(char* ip)
{
HANDLE hIcmpFile;
unsigned long ipaddr = INADDR_NONE;
DWORD dwRetVal = 0;
char SendData[32] = "icmpTest";
LPVOID ReplyBuffer = NULL;
DWORD ReplySize = 0;
ipaddr = inet_addr(ip);
hIcmpFile = IcmpCreateFile();
if (hIcmpFile == INVALID_HANDLE_VALUE) {
return FALSE;
}
ReplySize = sizeof(ICMP_ECHO_REPLY) + sizeof(SendData);
ReplyBuffer = (VOID*)malloc(ReplySize);
if (ReplyBuffer == NULL) {
return FALSE;
}
dwRetVal = IcmpSendEcho(hIcmpFile, ipaddr, SendData, sizeof(SendData), NULL, ReplyBuffer, ReplySize, 1000);
if (dwRetVal != 0) {
return TRUE;
}
else {
return FALSE;
}
}
int main(int argc, char** argv)
{
if (argc == 1)
{
printf("Usage: %s IPv4 address\n\n", argv[0]);
exit(0);
}
unsigned long ipaddr = inet_addr(argv[1]);
if (ipaddr == INADDR_NONE) {
printf("[-] %s is not a IPv4 address, please input a valid IPv4 address\n\n", argv[1]);
exit(0);
}
if (icmpProbe(argv[1]))
{
printf("[+] %s alive!\n\n", argv[1]);
}
else
{
printf("[-] %s not alive!\n\n", argv[1]);
}
return 0;
}
暂时先到这,vs2019编译是通过的,但还是希望下次编译二次开发不会出啥奇奇怪怪的问题,至于如何用这它来实现批量探测主机是否存活,批量解析IP地址,后续补充。