关于icmp的概念不做过多赘述,直奔主题。因为涉及到ip报头以及icmp会送应答报文的报头,故而给出两者的表格形式:
IP报文报头
ICMP会送应答报文报头
应当注意:icmp的会送请求报文的数据区与它的应答报文的数据区是完全相同的。
#include<winsock2.h>
#include<stdio.h>
#include<string.h>
#pragma comment(lib,"ws2_32.lib")
#define ICMP_ECHO 8
#define ICMP_ECHOREPLY 0
#define ICMP_MIN 8
#define DEF_PACKET_SIZE 32
#define MAX_PACKET 1024
//构造IP报头
typedef struct iphdr
{
unsigned int h_len:4; //4
unsigned int version:4; //4
unsigned char tos; //8
unsigned short total_len; //16
unsigned short ident; //16
unsigned short frag_and_flags; //16
unsigned char ttl; //8
unsigned char proto; //8
unsigned short checksun; //16
unsigned int sourceIP; //32
unsigned int destIP; //32
}IpHeader;
//构造icmp报头
typedef struct _ihdr
{
BYTE i_type; //8
BYTE i_code; //8
USHORT i_cksum; //16
USHORT i_id; //16
USHORT i_seq; //16
ULONG timestamp; //32
}IcmpHeader;
void fill_icmp_date (char* icmp_data, int datasize); //填充icmp报文
int decode_resp (char* buf, int bytes, struct sockaddr_in* from, DWORD tid); //回送请求
int ping (const char* ip, DWORD timeout); //ping命令
USHORT checksum(USHORT *buffer, int size);
int max_time(int number[],int n);
int min_time(int number[],int n);
FILE *fp;
int main(int argc, char* argv[])
{
printf("--------------------------------------------\n");
printf("| 程序制作者: |\n");
printf("| |\n");
printf("| 某某 |\n");
printf("--------------------------------------------\n");
if (argc != 2)
{
printf ("参数数量不正确。请指定要ping的IP地址。\n");
return -1;
}
printf ("ping %s with 60 bytes....\n", argv[1]);
int ret[4];
printf("---------------------------------------------------------\n");
for(int i=4;i>0;i--)
{
Sleep(500);
ret[i-1] = ping (argv[1], 500);
if (ret[i-1] >= 0)
printf("| Reply from %s: bytes=60 time=%d ttl=64 |\n",argv[1],ret[i-1]);
}
printf("---------------------------------------------------------\n");
printf("--------------------------------------------\n");
printf("| the statics of the time: |\n");
printf("| |\n");
printf("| maxtime=%d mintime=%d |\n",max_time(ret,4),min_time(ret,4));
printf("--------------------------------------------\n");
//printf ("%d\n",ret);
//printf ("..............................\n");
system ("pause");
return 0;
}
USHORT checksum(USHORT *buffer, int size)
{
unsigned long cksum=0; //校验位16位,用32位容纳
while(size > 1)
{
cksum += *buffer++;
size -= sizeof(USHORT);
}
if(size)
{
cksum += *(UCHAR*)buffer;
}
//高十六位与低十六位相加
cksum = (cksum >> 16) + (cksum & 0xffff);
cksum += (cksum >>16);
//返回十六位的校验码
return (USHORT)(~cksum);
}
/*
填充icmp报文
*/
void fill_icmp_data(char* icmp_data, int datasize)
{
IcmpHeader* imcp_hdr; //icmp头部
char* datapart; //icmp数据部分
imcp_hdr = (IcmpHeader*)icmp_data; //强制转化位icmp_hdr
imcp_hdr->i_type = ICMP_ECHO; //icmp报文类型:回送请求
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)); //将数据部分用'E'填充
}
/*.................................................................................
解析应答报文
buf为返回数据,bytes是数据总长度,from会送icmp报文的客户机地址 ,tid为报文标识
......................................................................................*/
int decode_resp (char* buf, int bytes,DWORD tid)
{
//printf("here is decode_resp().....\n");
IpHeader* iphdr; //IP报文头
IcmpHeader* icmphdr; //icmp报文头
unsigned short iphdrlen; //IP头长度
iphdr = (IpHeader*)buf; //强制转化为IpHeader的结构体
iphdrlen = iphdr->h_len * 4; //单位是4字节
//printf("ip头部:%c\n",iphdr->proto);
fp=fopen("ip_header.txt","wb");
fwrite(buf,iphdrlen,1,fp);
fclose(fp);
if (bytes < iphdrlen + ICMP_MIN) //如果总长度小于IP头加上icmp头的长度,则返回-1
return -1; //
icmphdr = (IcmpHeader*)(buf + iphdrlen); //
if (icmphdr->i_type != ICMP_ECHOREPLY) //如果返回的报文类型不是ICMP_ECHOREPLY,则返回-2
return -2; //
if (icmphdr->i_id != (USHORT)tid) //匹配一对会送请求和应答报文
return -3;
int time = GetTickCount() - icmphdr->timestamp;
//printf("%d\n",time);
if (time >= 0) //得到从发送回送报文到接受应答报文所用的时间
return time;
else
return -4;
}
/*................................................................
ip 为目的主机ip
timeout 为超时时间
.................................................................*/
int ping(const char* ip,DWORD timeout)
{
int ret;
WSADATA wsaData;
if (WSAStartup (MAKEWORD(2, 1), &wsaData) != 0) //启动winsock
{
ret = -100;
return ret;
}
/*................................................................................
dest为目标主机地址,from为本地主机地址
.................................................................................*/
struct sockaddr_in dest, from;
/*.................................................................................
AF_INET指定tcpip协议,SOCK_RAW原生接口IPPROTO_ICMP使用的协议
WSA_FLAG_OVERLAPPED表示这个套接口具备重叠io功能
套接口建立失败 返回 -2
.......................................................................*/
SOCKET sockRaw=WSASocket (AF_INET, SOCK_RAW, IPPROTO_ICMP, NULL, 0, WSA_FLAG_OVERLAPPED);
if (sockRaw == INVALID_SOCKET)
{
ret = -2;return ret;
}
/*.....................................................................................
第二个参数有三个选项SOL_SOCKET(最适用传输层协议tcp,udp),IPPROTO_IP,IPPROTO_TCP.
SO_RCVTIMEO设置一个套接口口上的接收超时值
设置失败 返回 -3
........................................................................................*/
int bread = setsockopt (sockRaw, SOL_SOCKET, SO_RCVTIMEO, (char*)&timeout, sizeof(timeout));
if (bread == SOCKET_ERROR)
{
ret = -3;return ret;
}
/*........................................................
SO_RCVTIMEO设置一个套接口口上的发送超时值
设置失败 返回 -4
..........................................................*/
bread = setsockopt (sockRaw, SOL_SOCKET, SO_SNDTIMEO, (char*)&timeout, sizeof(timeout));
if (bread == SOCKET_ERROR)
{
ret = -4;return ret;
}
/*........................................................
建立目标主机地址
由于不存在通信端口的概念
故而只需添加0,无效端口。
..........................................................*/
dest.sin_family=AF_INET;
dest.sin_port=htons(0);
dest.sin_addr.s_addr=inet_addr(ip);
/*........................................................
准备发送的数据
ICMP的报文格式:
type code check 标识 seq 任选
————————————————————————————————————————————————
| 8 | 8 | 16 | 16 | 16 | 32 |
————————————————————————————————————————————————
..........................................................*/
int datasize=DEF_PACKET_SIZE; //任选数据大小32字节
char* icmp_data = NULL; //icmp数据
char* recvbuf = NULL; //接收缓冲区
USHORT seq_no = 0; //序列号
datasize += sizeof(IcmpHeader); //数据大小
char icmp_dataStack[MAX_PACKET]; //icmp数据大小
char recvbufStack[MAX_PACKET]; //接收栈大小
icmp_data = icmp_dataStack;
recvbuf = recvbufStack;
memset(icmp_data,0,MAX_PACKET);
fill_icmp_data(icmp_data,datasize); //填充icmp
/*............................................................
设置icmp数据包的一些参数
i_cksum 校验和
timestamp 时间戳
i_seq 标识
................................................................*/
((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);
/*.....................................................................
发送数据
如果发送超时则返回 -5
........................................................................*/
int bwrote = sendto (sockRaw, icmp_data, datasize, 0, (struct sockaddr*)&dest, sizeof(dest));
if (bwrote == SOCKET_ERROR) //
{
if (WSAGetLastError() == WSAETIMEDOUT) //
{
ret = -5;return ret;
}
}
/*....................................................................
接收数据
如果接收超时则返回 -1
如果其他错误则返回 -6
......................................................................*/
int fromlen=sizeof(from);
bread=recvfrom (sockRaw, recvbuf, MAX_PACKET, 0, (struct sockaddr*)&from, &fromlen);
if (bread == SOCKET_ERROR)
{
if (WSAGetLastError() == WSAETIMEDOUT)
{
ret = -1;return ret;
}
else
{
ret = -6;
return ret;
}
}
/*....................................................................
解析返回的icmp报文,获取传输时间time
如何获取传输时间这里很巧妙:首先在构造icmp报文时,
填入发送时的程序运行时间,然后在目标主机会将这部分
数据回显,在解析报文时用当前时间减去报文里的时间就
可以的到传输时间time。
......................................................................*/
int time=decode_resp(recvbuf, bread, GetCurrentThreadId());
if(time>=0)
{
ret=time;return ret;
}
else
{
ret=-7;
return ret;
}
closesocket (sockRaw);
WSACleanup ();
return ret;
}
/*....................................................................
求最大时间
......................................................................*/
int max_time(int number[],int n)
{
int temp=number[0];
for(int i=0;i<n;i++)
{
if(temp<number[i])
temp=number[i];
}
return temp;
}
/*....................................................................
求最小时间
......................................................................*/
int min_time(int number[],int n)
{
int temp=number[0];
for(int i=0;i<n;i++)
{
if(temp>number[i])
temp=number[i];
}
return temp;
}
下面是两个结构体,正是对应于ip报头和icmp报头。这种编程思想非常利于理解。对于如何传输这种结构体。博主之前也是茫然,因为编译器报错,说传输时不能强制将结构体强制转换为char *,原来有一个小技巧,传输时传送的char*,那好吧,我就先定义一个char* buff,然后将buff强制转换为结构体,按照结构体的方式赋值。然后发送时还是发送buff.接收端接收的是buff,也同样强制转换为结构体,把有用信息取出来。
//构造IP报头
typedef struct iphdr
{
unsigned int h_len:4; //4
unsigned int version:4; //4
unsigned char tos; //8
unsigned short total_len; //16
unsigned short ident; //16
unsigned short frag_and_flags; //16
unsigned char ttl; //8
unsigned char proto; //8
unsigned short checksun; //16
unsigned int sourceIP; //32
unsigned int destIP; //32
}IpHeader;
//构造icmp报头
typedef struct _ihdr
{
BYTE i_type; //8
BYTE i_code; //8
USHORT i_cksum; //16
USHORT i_id; //16
USHORT i_seq; //16
ULONG timestamp; //32
}IcmpHeader;