首先要了解下ICMP,先前用IcmpSendEcho实现了Ping功能,见 http://ckfan.is-programmer.com/posts/36168.html
ICMP:是Internet 控制信息协议(ICMP)是 IP 组的一个整合部分,通过 IP 包传送的 ICMP 信息主要用于涉及网络
操作或错误操作的不可达信息。
ICMP 的报文类型:


这里再说说下,我原先一些误区: 1.ICMP没有端口,它并不像TCP/UDP那样有发到特定的接收端口,它是控制协议,服务于IP层。当某个网关发现传输错误时,立即向信源主机发 送 ICMP报文,报告出错信息,让信源主机采取相应处理措施,它是一种差错和控制报文协议,不仅用于传输差错报文,还传输控制报文。 2.ICMP 包发送是不可靠的。
code:
.h
#pragma once
#include <winsock2.h>
#include <ws2tcpip.h>
#pragma comment(lib, "ws2_32.lib")
#pragma pack(1)
// ICMP 头部
typedef struct _ihdr
{
BYTE i_type; // 8位类型
BYTE i_code; // 8位代码
USHORT i_cksum; // 16位校验和
USHORT i_id; // 识别号
USHORT i_seq; // 报文序列号
ULONG timestamp; // 时间戳
}ICMP_HEADER;
typedef struct _iphdr{
unsigned char h_lenver; // 4 位IP版本号+4位首部长度
unsigned char tos; // 8位服务类型TOS
unsigned short total_len; // 16位IP包总长度(字节)
unsigned short ident; // 16位标识, 用于辅助IP包的拆装,本实验不用,置零
unsigned short frag_and_flags; // 3位标志位+13位偏移位, 也是用于IP包的拆装,本实验不用,置零
unsigned char ttl; // 8位IP包生存时间 TTL
unsigned char proto; // 8位协议 (TCP, UDP 或其他), 本实验置ICMP,置为1
unsigned short checksum; // 16位IP首部校验和,最初置零,等
// 所有包头都填写正确后,计算并替换.
unsigned int sourceIP; // 32位源IP地址
unsigned int destIP; // 32位目的IP地址
}IP_HEADER;
#pragma pack()
class CIPing
{
public:
CIPing();
~CIPing();
// PING X.X.X.X
BOOL Ping(LPCTSTR ip, int timeout);
private:
SOCKET s;
};
.cpp
#include "stdafx.h"
#include "IPing.h"
// 校验和
unsigned short CheckSum(unsigned short *addr, int count)
{
/* Compute Internet Checksum for "count" bytes
* beginning at location "addr".
*/
register long sum = 0;
while( count > 1 )
{
/* This is the inner loop */
sum += *addr++;
count -= sizeof(USHORT);
}
/* Add left-over byte, if any */
if( count > 0 )
sum += * (unsigned char *) addr;
/* Fold 32-bit sum to 16 bits */
while (sum>>16)
sum = (sum & 0xffff) + (sum >> 16);
return (USHORT)~sum;
}
CIPing::CIPing()
{
}
CIPing::~CIPing()
{
}
// PING X.X.X.X
BOOL CIPing::Ping(LPCTSTR ip, int timeout)
{
struct sockaddr_in addrSrc;
struct sockaddr_in addrDest;
ICMP_HEADER head;
UCHAR ucSendBuf[1024];
UCHAR ucRecBuf[1024];
int recFromLen;
IP_HEADER *pIpHdr = NULL;
unsigned short len;
// create
if((s = WSASocket(AF_INET,SOCK_RAW,IPPROTO_ICMP,NULL,0,WSA_FLAG_OVERLAPPED))==INVALID_SOCKET)
{
return FALSE;
}
// 发送时限
setsockopt( s, SOL_SOCKET, SO_SNDTIMEO, ( char * )&timeout, sizeof( int ) );
// 接收时限
setsockopt( s, SOL_SOCKET, SO_RCVTIMEO, ( char * )&timeout, sizeof( int ) );
memset(&addrSrc, 0, sizeof(sockaddr_in));
// addr
addrSrc.sin_family = AF_INET;
addrSrc.sin_addr.s_addr = inet_addr(ip);
// 填充ICMP首部
memset(&head, 0, sizeof(ICMP_HEADER));
// 回显应答
head.i_type = 0;
head.i_code = 0;
// 校验和置0
head.i_cksum = 0;
head.i_id = 2;
// 时间戳
head.timestamp = GetTickCount();
head.i_seq = 999;
memset(ucSendBuf, 0, 1024);
memcpy(ucSendBuf, &head, sizeof(ICMP_HEADER));
memset(ucSendBuf + sizeof(ICMP_HEADER), '1', 32);
head.i_cksum = CheckSum((USHORT*)ucSendBuf, sizeof(ICMP_HEADER) + 32);
memcpy(ucSendBuf, &head, sizeof(ICMP_HEADER));
// 发送
if(sendto(s, (char*)ucSendBuf, sizeof(ICMP_HEADER) + 32, 0, (struct sockaddr*)&addrSrc,sizeof(addrSrc)) == SOCKET_ERROR)
{
closesocket(s);
s = NULL;
return FALSE;
}
memset(ucRecBuf, 0, 1024);
memset(&addrDest, 0, sizeof(struct sockaddr));
recFromLen = sizeof(sockaddr);
addrDest.sin_family = AF_INET;
if (recvfrom(s, (char*)ucRecBuf, 1023, 0, (struct sockaddr*)&addrDest, &recFromLen) == SOCKET_ERROR)
{
if(WSAGetLastError() == WSAETIMEDOUT)
{
OutputDebugString("Time out...\n");
}
closesocket(s);
s = NULL;
return FALSE;
}
closesocket(s);
s = NULL;
// 判断包的正确性
pIpHdr = (IP_HEADER *)ucRecBuf;
// Number of 32-bit words * 4 = bytes
// 计算ip包头长度
len = sizeof(unsigned long) * (pIpHdr->h_lenver & 0xf);
memcpy(&head,& ucRecBuf[len], sizeof(ICMP_HEADER));
// 标识
if (head.i_id != 2)
{
return FALSE;
}
return TRUE;
}