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 };
在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 的第一个字符,也是就是上面的解释是正确的
以上数据是用OmniPeek抓包软件抓的包。
- ping .............
- ping
- ping
- ping
- ping
- ping
- Ping
- ping
- ping
- ping
- ping
- ping .
- ping
- ping
- ping
- ping ....
- ping
- PING
- 同一个ImageView显示不同的图片(LevelDrawable)
- 进程控制编程
- nyoj-56-阶乘因式分解(一)
- android项目生成javadoc
- Odoo8.0中使用文泉译中文字体
- ping
- HDU 3466 - Proud Merchants(01背包)
- shell的基本哲学
- fxc的使用及shader调试技巧
- 黑马程序员--正则表达式--基本示例
- 最短路路径还原
- 用fxc.exe编译shader文件(*.fx, *.hlsl)的设置
- IE9浏览器下margin:auto 不能居中问题
- 在VS中让Shader自动编译