关于icmp协议的理解以及ping命令的实现

关于icmp的概念不做过多赘述,直奔主题。因为涉及到ip报头以及icmp会送应答报文的报头,故而给出两者的表格形式:

IP报文报头

135429_u2co_2371822.png

ICMP会送应答报文报头

140158_O1ZG_2371822.png

应当注意: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; 

转载于:https://my.oschina.net/hudongliang/blog/418898

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值