#include <stdio.h>#include <signal.h>#include <arpa/inet.h>#include <sys/types.h>#include <sys/socket.h>#include <unistd.h>#include <netinet/in.h>#include <netinet/ip.h>#include <netinet/ip_icmp.h>#include <netdb.h>#include <setjmp.h>#include <errno.h>#define PACKET_SIZE 4096#define MAX_WAIT_TIME 5#define MAX_NO_PACKETS 3char sendpacket[PACKET_SIZE];char recvpacket[PACKET_SIZE];int sockfd,datalen=56;int nsend=0,nreceived=0;struct sockaddr_in dest_addr;pid_t pid;struct sockaddr_in from;struct timeval tvrecv;void statistics(int signo);unsigned short cal_chksum(unsigned short *addr,int len);int pack(int pack_no);void send_packet(void);void recv_packet(void);int unpack(char *buf,int len);void tv_sub(struct timeval *out,struct timeval *in);/**//* *ping 是向目的地址发送一个icmp回显请求报文,然后等待回显应答报文 *并在超时打印超时信息,本代码没有实现超时检测 *icmp回显请求报文或回显应答报文格式 *| 类型(0/8) | 代码(0) | 校验和 | *| 标志符 | 序号 | *| 选项数据 | *其中标志符是本进程号,用于区别收到的其他报文,这样可以允许多个ping程序同时运行 *序号是从0开始,每次都加1 *有时第一个往返时间可能比后面的长,因为第一次可能要查找ARP */void statistics(int signo)...{ printf(" --------------------PING statistics------------------- "); printf("%d packets transmitted, %d received , %%%d lost ",nsend,nreceived, (nsend-nreceived)/nsend*100); close(sockfd); exit(1);}/**//*校验和算法*/unsigned short cal_chksum(unsigned short *addr,int len)...{ int nleft=len; int sum=0; unsigned short *w=addr; unsigned short answer=0; /**//*把ICMP报头二进制数据以2字节为单位累加起来*/ while(nleft>1) ...{ sum+=*w++; nleft-=2; } /**//*若ICMP报头为奇数个字节,会剩下最后一字节。 *把最后一个字节视为一个2字节数据的高字节,这个2字节数据的低字节为0,继续累加*/ if( nleft==1) ...{ *(unsigned char *)(&answer)=*(unsigned char *)w; sum+=answer; } sum=(sum>>16)+(sum&0xffff); sum+=(sum>>16); answer=~sum; return answer;}/**//*设置ICMP报头*/int pack(int pack_no)...{ int i,packsize; struct icmp *icmp; struct timeval *tval; icmp=(struct icmp*)sendpacket; icmp->icmp_type=ICMP_ECHO; icmp->icmp_code=0; icmp->icmp_cksum=0; icmp->icmp_seq=pack_no; icmp->icmp_id=pid; packsize=8+datalen; tval= (struct timeval *)icmp->icmp_data; gettimeofday(tval,NULL); /**//*记录发送时间*/ icmp->icmp_cksum=cal_chksum( cicmp,packsize); /**//*校验算法*/ return packsize;}/**//*发送三个ICMP报文*/void send_packet()...{ int packetsize; while( nsend<MAX_NO_PACKETS) ...{ nsend++; packetsize=pack(nsend); /**//*设置ICMP报头*/ if( sendto(sockfd,sendpacket,packetsize,0, (struct sockaddr *)&dest_addr,sizeof(dest_addr) )<0 ) ...{ perror("sendto error"); continue; } sleep(1); /**//*每隔一秒发送一个ICMP报文*/ }}/**//*接收所有ICMP报文*/void recv_packet()...{ int n,fromlen; extern int errno; signal(SIGALRM,statistics); fromlen=sizeof(from); while( nreceived<nsend) ...{ alarm(MAX_WAIT_TIME); if( (n=recvfrom(sockfd,recvpacket,sizeof(recvpacket),0, (struct sockaddr *)&from,&fromlen)) <0) ...{ if(errno==EINTR)continue; perror("recvfrom error"); continue; } gettimeofday(&tvrecv,NULL); /**//*记录接收时间*/ if(unpack(recvpacket,n)==-1)continue; nreceived++; }}//打印ip报文的信息void printIP(char *packet, int len)...{ struct sockaddr_in addr_src,addr_dst; struct ip *pip = (struct ip*)packet; printf("ip head len:%d ",pip->ip_hl<<2); printf("ip len:%d ",ntohs(pip->ip_len)); printf("ip pro id:%d ",pip->ip_p); printf("ip ttl:%d ",pip->ip_ttl); printf("ip offset:%d ",ntohs(pip->ip_off)&IP_OFFMASK); memset(&addr_src,0,sizeof(struct sockaddr_in)); memset(&addr_dst,0,sizeof(struct sockaddr_in)); memcpy(&addr_src,&pip->ip_src,sizeof(struct sockaddr_in)); memcpy(&addr_dst,&pip->ip_dst,sizeof(struct sockaddr_in)); printf("src ip:%x ",addr_src.sin_addr); printf("dst ip:%x ",addr_dst.sin_addr);}/**//*剥去ICMP报头*/int unpack(char *buf,int len)...{ int i,iphdrlen; struct ip *ip; struct icmp *icmp; struct timeval *tvsend; double rtt; ip=(struct ip *)buf; iphdrlen=ip->ip_hl<<2; /**//*求ip报头长度,即ip报头的长度标志乘4*/ icmp=(struct icmp *)(buf+iphdrlen); /**//*越过ip报头,指向ICMP报头*/ len-=iphdrlen; /**//*ICMP报头及ICMP数据报的总长度*/ if( len<8) /**//*小于ICMP报头长度则不合理*/ ...{ printf("ICMP packets's length is less than 8 "); return -1; } /**//*确保所接收的是我所发的的ICMP的回应*/ if( (icmp->icmp_type==ICMP_ECHOREPLY) && (icmp->icmp_id==pid) ) ...{ tvsend=(struct timeval *)icmp->icmp_data; tv_sub(&tvrecv,tvsend); /**//*接收和发送的时间差*/ rtt=tvrecv.tv_sec*1000+tvrecv.tv_usec/1000; /**//*以毫秒为单位计算rtt*/ /**//*显示相关信息*/ printf("%d byte from %s: icmp_seq=%u ttl=%d rtt=%.3f ms ", len, inet_ntoa(from.sin_addr), icmp->icmp_seq, ip->ip_ttl, rtt); } else return -1;}main(int argc,char *argv[])...{ struct hostent *host; struct protoent *protocol; unsigned long inaddr=0l; int waittime=MAX_WAIT_TIME; int size=50*1024; if(argc<2) ...{ printf("usage:%s hostname/IP address ",argv[0]); exit(1); } printf("step1 "); if( (protocol=getprotobyname("icmp") )==NULL) ...{ perror("getprotobyname"); exit(1); } printf("step2 "); /**//*生成使用ICMP的原始套接字,这种套接字只有root才能生成*/ if( (sockfd=socket(AF_INET,SOCK_RAW,protocol->p_proto) )<0) ...{ perror("socket error"); exit(1); } /**//* 回收root权限,设置当前用户权限*/ setuid(getuid()); /**//*扩大套接字接收缓冲区到50K这样做主要为了减小接收缓冲区溢出的 的可能性,若无意中ping一个广播地址或多播地址,将会引来大量应答*/ setsockopt(sockfd,SOL_SOCKET,SO_RCVBUF,&size,sizeof(size) ); bzero(&dest_addr,sizeof(dest_addr)); dest_addr.sin_family=AF_INET; /**//*判断是主机名还是ip地址*/ if( inaddr=inet_addr(argv[1])==INADDR_NONE) ...{ if((host=gethostbyname(argv[1]) )==NULL) /**//*是主机名*/ ...{ perror("gethostbyname error"); exit(1); } memcpy( (char *)&dest_addr.sin_addr,host->h_addr,host->h_length); } else /**//*是ip地址*/ ...{ dest_addr.sin_addr.s_addr = inaddr; //memcpy( (char *)&dest_addr,(char *)&inaddr,host->h_length); } /**//*获取main的进程id,用于设置ICMP的标志符*/ pid=getpid(); printf("PING %s(%s): %d bytes data in ICMP packets. ",argv[1], inet_ntoa(dest_addr.sin_addr),datalen); send_packet(); /**//*发送所有ICMP报文*/ recv_packet(); /**//*接收所有ICMP报文*/ statistics(SIGALRM); /**//*进行统计*/ return 0;}/**//*两个timeval结构相减*/void tv_sub(struct timeval *out,struct timeval *in)...{ if( (out->tv_usec-=in->tv_usec)<0) ...{ --out->tv_sec; out->tv_usec+=1000000; } out->tv_sec-=in->tv_sec;}/**//*------------- The End -----------*/