ping

来源:互联网 发布:手机正则表达式 js 编辑:程序博客网 时间:2024/04/30 15:47

ICMP Structure

在linux gnu c library 中函数icmp的定义

 1 struct icmp2         {3             u_int8_t icmp_type; /* type of message, see below */4             u_int8_t icmp_code; /* type sub code */5             u_int16_t icmp_cksum; /* ones complement checksum of struct */6             union /**/7             {8                 u_char ih_pptr; /* ICMP_PARAMPROB */9                 struct in_addr ih_gwaddr; /* gateway address */10                 struct ih_idseq /* echo datagram */11                 {12                     u_int16_t icd_id;13                     u_int16_t icd_seq;14                 } ih_idseq;15                 u_int32_t ih_void;16                 /* ICMP_UNREACH_NEEDFRAG -- Path MTU Discovery (RFC1191) */17                 struct ih_pmtu18                 {19                     u_int16_t ipm_void;20                     u_int16_t ipm_nextmtu;21                 } ih_pmtu;22                 struct ih_rtradv23                 {24                     u_int8_t irt_num_addrs;25                     u_int8_t irt_wpa;26                     u_int16_t irt_lifetime;27                 } ih_rtradv;28          } icmp_hun;29         #define icmp_pptr icmp_hun.ih_pptr30         #define icmp_gwaddr icmp_hun.ih_gwaddr31         #define icmp_id icmp_hun.ih_idseq.icd_id(标识一个ICMP报文,一般我们用PID标识)32         #define icmp_seq icmp_hun.ih_idseq.icd_seq(发送报文的序号)33         #define icmp_void icmp_hun.ih_void34         #define icmp_pmvoid icmp_hun.ih_pmtu.ipm_void35         #define icmp_nextmtu icmp_hun.ih_pmtu.ipm_nextmtu36         #define icmp_num_addrs icmp_hun.ih_rtradv.irt_num_addrs37         #define icmp_wpa icmp_hun.ih_rtradv.irt_wpa38         #define icmp_lifetime icmp_hun.ih_rtradv.irt_lifetime39         union40         {41              struct42             {43                 u_int32_t its_otime;44                 u_int32_t its_rtime;45                 u_int32_t its_ttime;46             } id_ts;47             struct48             {49                 struct ip idi_ip;50                 /* options and then 64 bits of data */51             } id_ip; /// ip header 20 字节52             struct icmp_ra_addr id_radv;53             u_int32_t id_mask; //4 个字节54             u_int8_t id_data[1]; / / 2 个字节 = sizeof (struct timeval )55             } icmp_dun;56         #define icmp_otime icmp_dun.id_ts.its_otime57         #define icmp_rtime icmp_dun.id_ts.its_rtime58         #define icmp_ttime icmp_dun.id_ts.its_ttime59         #define icmp_ip icmp_dun.id_ip.idi_ip60         #define icmp_radv icmp_dun.id_radv61         #define icmp_mask icmp_dun.id_mask62         #define icmp_data icmp_dun.id_data(可以看到id_data是含有一个元素的数组名,为什么这样干呀?思考...)63          };


wps_clip_image-9490

在linux 内核的ICMP Header  的定义

1 struct icmphdr {2   unsigned char type;3   unsigned char code;4   unsigned short checksum;5   union {6 struct {7 unsigned short id;8 unsigned short sequence;9 } echo;10 unsigned long gateway;11   } un;12 };


从中我们也可以看到icmp header 至少是8B ,其中icmp_id , icmp_seq 要根据具体情况会有所不同,具体情况可以参考《TCP / IP 协议详解 卷一》。

下面是ping的简单实现源码,不支持IPV6 。

 1 #include <stdio.h>2 #include <stdlib.h>3 #include <string.h>4 #include <errno.h>5 #include <sys/socket.h>6 #include <sys/types.h>7 #include <netinet/in.h>8 #include <arpa/inet.h>9 #include <netdb.h>10 #include <sys/time.h>11 #include <netinet/ip_icmp.h>12 #include <unistd.h>13 #include <signal.h>14 #define MAX_SIZE 102415 char send_buf[MAX_SIZE];16 char recv_buf[MAX_SIZE];17 int nsend = 0,nrecv = 0;18 int datalen = 56;19 //统计结果20 void statistics(int signum)21 {22     printf("\n----------------PING statistics---------------\n");23     printf("%d packets transmitted,%d recevid,%%%d lost\n",nsend,nrecv,(nsend - nrecv)/nsend * 100);24     exit(EXIT_SUCCESS);25 }26 //校验和算法27 int calc_chsum(unsigned short *addr,int len)28 {29     int sum = 0,n = len;30     unsigned short answer = 0;31     unsigned short *p = addr;32     //每两个字节相加33     while(n > 1)34     {35         sum += *p ++;36         n -= 2;37     }38     //处理数据大小是奇数,在最后一个字节后面补039     if(n == 1)40     {41         *((unsigned char *)&answer) = *(unsigned char *)p;42         sum += answer;43     }44 //将得到的sum值的高2字节和低2字节相加45     sum = (sum >> 16) + (sum & 0xffff);46 //处理溢出的情况47     sum += sum >> 16;48     answer = ~sum;49     return answer;50 }51 /*打第几个包*/52 int pack(int pack_num)53 {54     int packsize;55     /*icmp sizeof = 28 */56     struct icmp *icmp;57     struct timeval *tv;58     /*send_buf 为将要被发送的缓冲区,大小还没确定*/59     icmp = (struct icmp *)send_buf;60     /*请求报文*/61     icmp->icmp_type = ICMP_ECHO;62     icmp->icmp_code = 0;63     icmp->icmp_cksum = 0;64     icmp->icmp_id = htons(getpid());65     icmp->icmp_seq = htons(pack_num);66     /*绑定时间*/67     tv = (struct timeval *)icmp->icmp_data;68     //记录发送时间69     if(gettimeofday(tv,NULL) < 0)70     {71         perror("Fail to gettimeofday");72         return -1;73     }74     /*包的大小64  = icmp首部 + datalen  = 8 + 56*/75     /*神马意思,貌似为了好计算*/76     packsize = 8 + datalen;77     /*icmp 报文验证 只校验 64 个字节*/78     icmp->icmp_cksum = calc_chsum((unsigned short *)icmp,packsize);79     return packsize;80 }81 int send_packet(int sockfd,struct sockaddr *paddr)82 {83     int packsize;84     //将send_buf填上a 1024 个a85     memset(send_buf,'a',sizeof(send_buf));86     nsend ++;87     //打icmp包 ,最后,要效的send_buf = 64 ,其中的1024 - 56  = 968 是无效的88     packsize = pack(nsend);89     if(sendto(sockfd,send_buf,packsize,0,paddr,sizeof(struct sockaddr)) < 0)90     {91         perror("Fail to sendto");92         return -1;93     }94     return 0;95 }96 struct timeval time_sub(struct timeval *tv_send,struct timeval *tv_recv)97 {98     struct timeval ts;99     if(tv_recv->tv_usec - tv_send->tv_usec < 0)100     {101         tv_recv->tv_sec --;102         tv_recv->tv_usec += 1000000;103     }104     ts.tv_sec = tv_recv->tv_sec - tv_send->tv_sec;105     ts.tv_usec = tv_recv->tv_usec - tv_send->tv_usec;106     return ts;107 }108 int unpack(int len,struct timeval *tv_recv,struct sockaddr *paddr,char *ipname)109 {110     /*sizeof struct ip = 20  */111     struct ip *ip;112     struct icmp *icmp;113     struct timeval *tv_send,ts;114     int ip_head_len;115     float rtt;116     ip = (struct ip *)recv_buf;117     ip_head_len = ip->ip_hl << 2;118     /*过滤掉ip头*/119     icmp = (struct icmp *)(recv_buf + ip_head_len);120     len -= ip_head_len;121     if(len < 8)122     {123         printf("ICMP packets\'s is less than 8.\n");124         return -1;125     }126     if(ntohs(icmp->icmp_id) == getpid() && icmp->icmp_type == ICMP_ECHOREPLY)127     {128         nrecv ++;129         tv_send = (struct timeval *)icmp->icmp_data;130         ts = time_sub(tv_send,tv_recv);131         rtt = ts.tv_sec * 1000 + (float)ts.tv_usec/1000;//以毫秒为单位132         printf("%d bytes from %s (%s):icmp_req = %d ttl=%d time=%.3fms.\n",133                len,ipname,inet_ntoa(((struct sockaddr_in *)paddr)->sin_addr),ntohs(icmp->icmp_seq),ip->ip_ttl,rtt);134     }135     return 0;136 }137 /*接收包*/138 int recv_packet(int sockfd,char *ipname)139 {140     int addr_len ,n;141     struct timeval tv;142     struct sockaddr from_addr;143     /*原始套接字的长度 = 16 B*/144     addr_len = sizeof(struct sockaddr);145     /*接收包放到rev_buf缓冲区里面*/146     if((n = recvfrom(sockfd,recv_buf,sizeof(recv_buf),0,&from_addr,&addr_len)) < 0)147     {148         perror("Fail to recvfrom");149         return -1;150     }151     if(gettimeofday(&tv,NULL) < 0)152     {153         perror("Fail to gettimeofday");154         return -1;155     }156     /*拆包*/157     unpack(n,&tv,&from_addr,ipname);158     return 0;159 }160 int main(int argc,char *argv[])161 {162     int size = 50 * 1024;163     int sockfd,netaddr;164     /*主机协议结构信息*/165     struct protoent *protocol;166     /*主机信息结构*/167     struct hostent *host;168     /*IPv4 协议 sizeof = 16 B */169     struct sockaddr_in peer_addr;170     if(argc < 2)171     {172         fprintf(stderr,"usage : %s ip.\n",argv[0]);173         exit(EXIT_FAILURE);174     }175     //获取icmp的信息176     if((protocol = getprotobyname("icmp")) == NULL)177     {178         perror("Fail to getprotobyname");179         exit(EXIT_FAILURE);180     }181     //创建原始套接字182     if((sockfd = socket(AF_INET,SOCK_RAW,protocol->p_proto)) < 0)183     {184         perror("Fail to socket");185         exit(EXIT_FAILURE);186     }187     //回收root权限,设置当前用户权限188     setuid(getuid());189     /*190     扩大套接子接收缓冲区到50k,这样做主要为了减少接收缓冲区溢出的可能性191     若无影中ping一个广播地址或多播地址,将会引来大量应答192     */193     if(setsockopt(sockfd,SOL_SOCKET,SO_RCVBUF,&size,sizeof(size)) < 0)194     {195         perror("Fail to setsockopt");196         exit(EXIT_FAILURE);197     }198     //填充对方的地址199     bzero(&peer_addr,sizeof(peer_addr));200     peer_addr.sin_family = AF_INET;201     //判断是主机名(域名)还是ip202     if((netaddr = inet_addr(argv[1])) == INADDR_NONE)203     {204         //是主机名(域名)205         /**/206         if((host = gethostbyname(argv[1])) == NULL)207         {208             fprintf(stderr,"%s unknown host : %s.\n",argv[0],argv[1]);209             exit(EXIT_FAILURE);210         }211         memcpy((char *)&peer_addr.sin_addr,host->h_addr,host->h_length);212     }213     else  //ip地址214     {215         peer_addr.sin_addr.s_addr = netaddr;216     }217     //注册信号处理函数218     signal(SIGALRM,statistics);219     signal(SIGINT,statistics);220     alarm(5);221     //开始信息222     printf("PING %s(%s) %d bytes of data.\n",argv[1],inet_ntoa(peer_addr.sin_addr),datalen);223     //发送包文和接收报文224     int i = 0;225     while(1)226     {227         send_packet(sockfd,(struct sockaddr *)&peer_addr);228         printf("I:%d\n",++i);229         recv_packet(sockfd,argv[1]);230         alarm(5);231         usleep(1);232     }233     exit(EXIT_SUCCESS);234 }


注:

发送时:

我们要注意的是recvfrom这个函数

ssize_t recvfrom(int sockfd, void *buf, size_t len, int flags,struct sockaddr *src_addr, socklen_t *addrlen);

The recvfrom() and recvmsg() calls are used to receive messages from a socket, and may be used to receive data on a socket whether or not it is connection-oriented.

If src_addr is not NULL, and the underlying protocol provides the source address, this source address is filled in. When src_addr is NULL, nothing is filled in; in this case,addrlen is not used, and should also be NULL. The argument addrlen is a value-result argument, which the caller should initialize before the call to the size of the buffer associated with src_addr, and modified on return to indicate the actual size of the source address. The returned address is truncated if the buffer provided is too small; in this case, addrlen will return a value greater than was supplied to the call.

就是所revcfrom会在接收数据是把发送放的源ip 也会加到数据里面一起放到接收缓冲区里面,可以查看《UNIX网络编程 第一卷:套接口API (第三版)》里面的page204 ,并且也会存到src_addr 里,

原始数据区:其中45 对应于IP Header 下面的version :4 , 和Header Length : 5 (20 byte),其实四十五就是’E’对应的16进制的ascii 值,也就是接收缓冲区recv_buf 的第一个字符,也是就是上面的解释是正确的

wps_clip_image-28100

以上数据是用OmniPeek抓包软件抓的包。

0 0