// IcmpDemo.cpp : Defines the entry point for the console application.
//
#include "stdafx.h"
#include <WinSock2.h>
#include <WS2tcpip.h>//inet_ntop, inet_pton
#pragma comment(lib, "WS2_32.lib")
#define RECV_BUFF_SIZE 1024
struct icmp_header {
unsigned char type; //消息类型
unsigned char code; //代码
unsigned short checksum;//校验和
unsigned short id; //唯一标识此请求的id,一般设为进程ID
unsigned short sequence;//序列号
};
#define ICMP_HEADER_SIZE sizeof(icmp_header)
#define ICMP_PACKET_SIZE (ICMP_HEADER_SIZE + 32)
/*
ICMP协议的"类型码"与"代码"根据不同的情况,各自取不同的值
Ping命令的类型码用到了2个值,分别是0和8,而代码的取值都是0
发送方:
(1)当类型码取值为0,代码取值为0时,表示"请求回显"
(2)当类型码取值为8,代码取值为0时,表示"回送回答"
接收方正好相反
*/
#define ICMP_ECHO_REQUEST 0x08
#define ICMP_ECHO_REPLY 0x00
unsigned short calcChecksum(struct icmp_header *picmp, int len) {
picmp->checksum = 0;
//把IP数据包的校验和字段置为0
long sum = 0;
//把首部看成以16位为单位的数字组成,依次进行二进制求和(注意:求和时应将最高位的进位保存,所以加法应采用32位加法)
unsigned short *pusicmp = (unsigned short *)picmp;
while (len > 1) {
sum += *(pusicmp++);
//加法过程中产生的进位(最高位的进位)加到低16位(采用32位加法时,即为将高16位与低16位相加,之后还要把该次加法最高位产生的进位加到低16位)
if (sum & 0x80000000) {
sum = (sum & 0xffff) + (sum >> 16);
}
len -= 2;
}
//如果长度是奇数,还要加上最后1字节
if (len) {
sum += (unsigned short)*(unsigned char*)pusicmp;
}
while (sum >> 16) {
sum = (sum & 0xffff) + (sum >> 16);
}
//将上述的和取反,即得到校验和。
return (unsigned short)~sum;
}
void ping(const char *szDestIp) {
BOOL ret;
char szBuff[ICMP_PACKET_SIZE] = { 0 };
struct icmp_header *pIcmp = (struct icmp_header *)szBuff;
WSADATA wsaData;
WORD wVersionRequested = MAKEWORD(2, 2);
ret = WSAStartup(wVersionRequested, &wsaData);
if (ret != 0) {
printf("WSAStartup() called failed!\n");
return;
}
else {
printf("WSAStartup() called success!\n");
}
//主版本号在低位,副版本号在高位。这是由小端性质决定的
if (LOBYTE(wsaData.wVersion) != 2 || HIBYTE(wsaData.wVersion) != 2) {
//若不是所请求的版本号2.2,则终止WinSock库的使用
WSACleanup();
return;
}
/*
创建套接字
WINSOCK_API_LINKAGE _Must_inspect_result_ SOCKET WSAAPI socket(
_In_ int af,
_In_ int type,
_In_ int protocol
);
AF 表示ADDRESS FAMILY 地址族
PF 表示PROTOCL FAMILY 协议族
所以在windows中AF_INET与PF_INET完全一样
而在Unix/Linux系统中,在不同的版本中这两者有微小差别,对于BSD,是AF,对于POSIX是PF
#define PF_INET AF_INET
#define SOCK_STREAM 1 // stream socket
#define SOCK_DGRAM 2 // datagram socket
#define SOCK_RAW 3 // raw-protocol interface
#define SOCK_RDM 4 // reliably-delivered message
#define SOCK_SEQPACKET 5 // sequenced packet stream
typedef enum {
#if(_WIN32_WINNT >= 0x0501)
IPPROTO_HOPOPTS = 0, // IPv6 Hop-by-Hop options
#endif//(_WIN32_WINNT >= 0x0501)
IPPROTO_ICMP = 1,
IPPROTO_IGMP = 2,
IPPROTO_GGP = 3,
#if(_WIN32_WINNT >= 0x0501)
IPPROTO_IPV4 = 4,
#endif//(_WIN32_WINNT >= 0x0501)
#if(_WIN32_WINNT >= 0x0600)
IPPROTO_ST = 5,
#endif//(_WIN32_WINNT >= 0x0600)
IPPROTO_TCP = 6,
#if(_WIN32_WINNT >= 0x0600)
IPPROTO_CBT = 7,
IPPROTO_EGP = 8,
IPPROTO_IGP = 9,
#endif//(_WIN32_WINNT >= 0x0600)
IPPROTO_PUP = 12,
IPPROTO_UDP = 17,
IPPROTO_IDP = 22,
#if(_WIN32_WINNT >= 0x0600)
IPPROTO_RDP = 27,
#endif//(_WIN32_WINNT >= 0x0600)
#if(_WIN32_WINNT >= 0x0501)
IPPROTO_IPV6 = 41, // IPv6 header
IPPROTO_ROUTING = 43, // IPv6 Routing header
IPPROTO_FRAGMENT = 44, // IPv6 fragmentation header
IPPROTO_ESP = 50, // encapsulating security payload
IPPROTO_AH = 51, // authentication header
IPPROTO_ICMPV6 = 58, // ICMPv6
IPPROTO_NONE = 59, // IPv6 no next header
IPPROTO_DSTOPTS = 60, // IPv6 Destination options
#endif//(_WIN32_WINNT >= 0x0501)
IPPROTO_ND = 77,
#if(_WIN32_WINNT >= 0x0501)
IPPROTO_ICLFXBM = 78,
#endif//(_WIN32_WINNT >= 0x0501)
#if(_WIN32_WINNT >= 0x0600)
IPPROTO_PIM = 103,
IPPROTO_PGM = 113,
IPPROTO_L2TP = 115,
IPPROTO_SCTP = 132,
#endif//(_WIN32_WINNT >= 0x0600)
IPPROTO_RAW = 255,
IPPROTO_MAX = 256,
//
// These are reserved for internal use by Windows.
//
IPPROTO_RESERVED_RAW = 257,
IPPROTO_RESERVED_IPSEC = 258,
IPPROTO_RESERVED_IPSECOFFLOAD = 259,
IPPROTO_RESERVED_WNV = 260,
IPPROTO_RESERVED_MAX = 261
} IPPROTO, *PIPROTO;
*/
//SOCKET s = socket(PF_INET, SOCK_RAW, IPPROTO_ICMP);
SOCKET s = WSASocket(PF_INET, SOCK_RAW, IPPROTO_ICMP, NULL, 0, WSA_FLAG_OVERLAPPED);
//设置接收超时(仅对非阻塞有用)
struct timeval tv;
tv.tv_sec = 3;
tv.tv_usec = 0;
int setResult;
setResult = setsockopt(s, SOL_SOCKET, SO_RCVTIMEO, (const char*)&tv, sizeof(tv));
if (setResult == SOCKET_ERROR) {
printf("setsockopt SO_RCVTIMEO fail(%d)\n", WSAGetLastError());
}
else {
printf("setsockopt SO_RCVTIMEO succ\n");
}
int ttl = 64;
setResult = setsockopt(s, IPPROTO_IP, IP_TTL, (const char*)&ttl, sizeof(ttl));
if (setResult == SOCKET_ERROR) {
printf("setsockopt IP_TTL fail(%d)\n", WSAGetLastError());
}
else {
printf("setsockopt IP_TTL succ\n");
}
//设置目的地址
sockaddr_in dest_addr;
dest_addr.sin_family = PF_INET;
inet_pton(PF_INET, szDestIp, &dest_addr.sin_addr);
dest_addr.sin_port = htons(0);
//绑定端口
//bind(s, (SOCKADDR *)&dest_addr, sizeof(dest_addr));
//构造ICMP封包
unsigned short sequence = 0;
pIcmp->type = ICMP_ECHO_REQUEST;
pIcmp->code = 0;
//pIcmp->id = GetCurrentProcessId();
pIcmp->id = 0x0100;
pIcmp->checksum = 0;
while(sequence < 4) {
pIcmp->sequence = sequence++;
//拷贝数据,这里的数据可以是任意的
memcpy((szBuff + ICMP_HEADER_SIZE), "abcdefghijklmnopqrstuvwabcdefghi", 32);
//计算校验和
pIcmp->checksum = calcChecksum((struct icmp_header *)szBuff, ICMP_PACKET_SIZE);
//发送和接收
sockaddr_in recv_addr;
char szRecvBuff[RECV_BUFF_SIZE];
int addrLen = sizeof(recv_addr);
int sendLen = sendto(s, szBuff, ICMP_PACKET_SIZE, 0, (SOCKADDR *)&dest_addr, sizeof(dest_addr));
if (sendLen == SOCKET_ERROR) {
printf("sendto -> sendLen=%d -> fail(%d)\n", sendLen, WSAGetLastError());
}
else {
printf("sendto -> sendLen=%d -> succ\n", sendLen);
int recvLen = recvfrom(s, szRecvBuff, RECV_BUFF_SIZE, 0, (SOCKADDR *)&recv_addr, &addrLen);
if (recvLen == SOCKET_ERROR) {
printf("recvfrom -> recvLen=%d -> fail(%d)\n", recvLen, WSAGetLastError());
}
else {
printf("recvfrom -> recvLen=%d -> succ\n", recvLen);
//判断接收到的是否是自己请求的地址
char szRecvIp[20];
inet_ntop(PF_INET, &recv_addr.sin_addr, szRecvIp, 20);
printf("szRecvIp is %s\n", szRecvIp);
if (strcmp(szRecvIp, szDestIp)) {
ret = FALSE;
}
else {
ret = TRUE;
}
}
}
}
closesocket(s);
WSACleanup();
}
int main()
{
const char *dest_addr = "127.0.0.1";
ping(dest_addr);
system("pause");
return 0;
}
遇到的问题
在中途做的时候也有碰到 WSAError(10060)的错误
注意:该错误必须使用GetWSAError(),使用GetLastError()并不能获得真正的错误。
解决思路:
用WireShark抓icmp包。然后执行该程序,再与该程序的包进行对比。
以下2种原因都可能导致该错误。
- 长度错误 –> ICMP包没有“时间戳”字段
- 校验和计算错误