这些天在学习windows网络编程,昨天看到了探测网络中的在线设备,其中刚好有一个实现ping命令的实例,就照着
拍了下代码,结果老是超时,后来到网上问才知道少了计算检验和,于是网上找了下关于检验和的知识。
校验和计算:
为了计算一份数据报的校验和码。首先把校验和字段置为0。然后,对首部中每一个16bit进行二进制反码求和,结果存在校验和字段中。当受到到一份ip数据报后,同样对首部中每个16bit进行二进制反码的求和。由于接收方在计算过程中包含了发送方存在首部中的检验和,因此,如果首部在传输过程中没有任何差错,那么接收方计算的结果应该为全1.
Icmp 校验和的计算:
TCP/ip协议对校验和计算方法:对16位的数据进行累加计算,并返回求反的计算结果。
需要注意的是对奇数个字节数据的计算。是将最后的有效数据作为最高位的字节,低字节填充为0.
实现代码如下
USHORT checksum(USHORT *buffer, int size)
{
unsigned long cksum=0;
while(size > 1)
{
cksum += *buffer++;
size -= sizeof(USHORT);
}
if(size)
{
cksum += *(UCHAR*)buffer;
}
cksum = (cksum >> 16) + (cksum & 0xffff);
cksum += (cksum >>16);
return (USHORT)(~cksum);
}
#include<winsock2.h>
#include<stdio.h>
#include<iostream>
using namespace std;
#define ICMP_ECHO 8
#define ICMP_ECHOREPLY 0
#define ICMP_MIN 8
#define DEF_PACKET_SIZE 32
#define MAX_PACKET 1024
typedef struct iphdr
{
unsigned int h_len:4;
unsigned int version:4;
unsigned char tos;
unsigned short total_len;
unsigned short ident;
unsigned short frag_and_flags;
unsigned char ttl;
unsigned char proto;
unsigned short checksun;
unsigned int sourceIP;
unsigned int destIP;
}IpHeader;
typedef struct _ihdr
{
BYTE i_type;
BYTE i_code;
USHORT i_cksum;
USHORT i_id;
USHORT i_seq;
ULONG timestamp;
}IcmpHeader;
void fill_icmp_date (char* icmp_data, int datasize);
int decode_resp (char* buf, int bytes, struct sockaddr_in* from, DWORD tid);
int ping (const char* ip, DWORD timeout);
USHORT checksum(USHORT *buffer, int size);
int main(int argc, char* argv[])
{
if (argc != 2)
{
printf ("参数数量不正确。请指定要ping的IP地址。\n");
return -1;
}
/*char argv[2][20];
argv[1] = "192.168.1.102";*/
printf ("ping %s\n", argv[1]);
int ret = ping (argv[1], 500);
if (ret >= 0)
printf ("%s在线,执行ping操作用时%dms。\n", argv[1], ret);
else
{
switch (ret)
{
case -1:
printf ("ping 超时...\n");
break;
case -2:
printf ("创建Socket 出错...\n");
break;
case -3:
printf ("设置Socket的接收超时选项出错...\n");
break;
case -4:
printf ("设置Socket的发送超时选项出错...\n");
break;
case -5:
printf ("获取域名出错,可能是IP地址不正确...\n");
break;
case -6:
printf ("未能为ICMP数据包分配到足够的空间...\n");
break;
case -7:
printf ("发送ICMP数据包出错...\n");
break;
case -8:
printf ("发送ICMP数据包的数量不正确...\n");
break;
case -9:
printf ("接收ICMP数据包出错...\n");
break;
case -1000:
printf ("初始化Windows Sockets环境出错...\n");
break;
default:
printf ("未知的错误");
break;
}
}
printf ("\n");
system ("pause");
return 0;
}
USHORT checksum(USHORT *buffer, int size)
{
unsigned long cksum=0;
while(size > 1)
{
cksum += *buffer++;
size -= sizeof(USHORT);
}
if(size)
{
cksum += *(UCHAR*)buffer;
}
cksum = (cksum >> 16) + (cksum & 0xffff);
cksum += (cksum >>16);
return (USHORT)(~cksum);
}
void fill_icmp_data (char* icmp_data, int datasize)
{
IcmpHeader* imcp_hdr;
char* datapart;
imcp_hdr = (IcmpHeader*)icmp_data;
imcp_hdr->i_type = ICMP_ECHO;
imcp_hdr->i_code = 0;
imcp_hdr->i_id = (USHORT)GetCurrentThreadId();
imcp_hdr->i_cksum = 0;
imcp_hdr->i_seq = 0;
datapart = icmp_data + sizeof(IcmpHeader);
memset (datapart, 'E', datasize - sizeof(IcmpHeader));
}
int decode_resp (char* buf, int bytes, struct sockaddr_in* from, DWORD tid)
{
IpHeader* iphdr;
IcmpHeader* icmphdr;
unsigned short iphdrlen;
iphdr = (IpHeader*)buf;
iphdrlen = iphdr->h_len * 4;
if (bytes < iphdrlen + ICMP_MIN)
return -1;
icmphdr = (IcmpHeader*)(buf + iphdrlen);
if (icmphdr->i_type != ICMP_ECHOREPLY)
return -2;
if (icmphdr->i_id != (USHORT)tid)
return -3;
int time = GetTickCount() - icmphdr->timestamp;
if (time >= 0)
return time;
else
return -4;
}
int ping (const char* ip, DWORD timeout)
{
WSADATA wsaData;
SOCKET sockRaw = NULL;
struct sockaddr_in dest, from;
struct hostent* hp;
int datasize;
char* dest_ip = NULL;
char* icmp_data = NULL;
char* recvbuf = NULL;
USHORT seq_no = 0;
int ret = -1;
if (WSAStartup (MAKEWORD(2, 1), &wsaData) != 0)
{
ret = -100;
goto FIN;
}
sockRaw = WSASocket (AF_INET, SOCK_RAW, IPPROTO_ICMP, NULL, 0, WSA_FLAG_OVERLAPPED);
if (sockRaw == INVALID_SOCKET)
{
ret = -2;
goto FIN;
}
int bread = setsockopt (sockRaw, SOL_SOCKET, SO_RCVTIMEO, (char*)&timeout, sizeof(timeout));
if (bread == SOCKET_ERROR)
{
ret = -3;
goto FIN;
}
bread = setsockopt (sockRaw, SOL_SOCKET, SO_SNDTIMEO, (char*)&timeout, sizeof(timeout));
if (bread == SOCKET_ERROR)
{
ret = -4;
goto FIN;
}
memset (&dest, 0, sizeof(dest));
unsigned int addr = 0;
hp = gethostbyname (ip);
if (!hp)
addr = inet_addr (ip);
if ((!hp) && (addr == INADDR_NONE))
{
ret = -5;
goto FIN;
}
/*配置远程通信地址*/
if (hp != NULL)
memcpy (&(dest.sin_addr), hp->h_addr_list[0], hp->h_length);
else
dest.sin_addr.S_un.S_addr = addr;
if (hp)
dest.sin_family = hp->h_addrtype;
else
dest.sin_family = AF_INET;
dest_ip = inet_ntoa (dest.sin_addr);
/*准备发送数据*/
datasize = DEF_PACKET_SIZE;
datasize += sizeof(IcmpHeader);
char icmp_dataStack[MAX_PACKET];
char recvbufStack[MAX_PACKET];
icmp_data = icmp_dataStack;
recvbuf = recvbufStack;
//memset (recvbuf, 0, MAX_PACKET);
if (!icmp_data)
{
ret = -6;
goto FIN;
}
memset (icmp_data, 0, MAX_PACKET);
fill_icmp_data (icmp_data, datasize);
((IcmpHeader*)icmp_data)->i_cksum = 0;
DWORD startTime = GetTickCount();
((IcmpHeader*)icmp_data)->timestamp = startTime;
((IcmpHeader*)icmp_data)->i_seq = seq_no++;
((IcmpHeader*)icmp_data)->i_cksum = checksum ((USHORT*)icmp_data, datasize);
/*发送数据*/
int bwrote;
bwrote = sendto (sockRaw, icmp_data, datasize, 0, (struct sockaddr*)&dest, sizeof(dest));
if (bwrote == SOCKET_ERROR)
{
if (WSAGetLastError() == WSAETIMEDOUT)
{
ret = -7;
goto FIN;
}
}
if (bwrote < datasize)
{
ret = -8;
goto FIN;
}
LARGE_INTEGER ticksPerSecond;
LARGE_INTEGER start_tick;
LARGE_INTEGER end_tick;
double elapsed;
QueryPerformanceFrequency (&ticksPerSecond);
QueryPerformanceCounter (&start_tick);
int fromlen = sizeof(from);
while (1)
{
bread = recvfrom (sockRaw, recvbuf, MAX_PACKET, 0, (struct sockaddr*)&from, &fromlen);
if (bread == SOCKET_ERROR)
{
if (WSAGetLastError() == WSAETIMEDOUT)
{
ret = -1;
goto FIN;
}
ret = -9;
goto FIN;
}
/*对回应的IP数据包进行解析,定位ICMP数据*/
int time = decode_resp (recvbuf, bread, &from, GetCurrentThreadId());
if (time >= 0)
{
QueryPerformanceCounter (&end_tick);
elapsed = ((double)(end_tick.QuadPart - start_tick.QuadPart) / ticksPerSecond.QuadPart);
ret = (int)(elapsed * 1000);
goto FIN;
}
else if (GetTickCount() - startTime >= timeout || GetTickCount() < startTime)
{
ret = -1;
goto FIN;
}
}
FIN:
closesocket (sockRaw);
WSACleanup ();
return ret;
}
这个程序的测试IP是通过命令行传递的 ,我的测试环境是VS2010,直接打开项目属性 --> 配置属性 --> 调试 --> 命令参数,在命令参数那一栏中输入要测试的Ip地址即可。