icmph->icmp_data[i]=htons(i);//注意主机字节序转换成网络字节序
}//发送的数据
//计算校验和
icmph->icmp_cksum=icmp_cksum((unsignedchar*)icmph,length+8);
}
//计算时间差函数
staticstructtimevalicmp_tvsub(structtimevalend,structtimevalbegin){
structtimevaltv;
tv.tv_sec=end.tv_sec-begin.tv_sec;
tv.tv_usec=end.tv_usec-begin.tv_usec;
if(tv.tv_usec<0){
tv.tv_sec--;
tv.tv_usec+=1000000;
}
returntv;
}
//发送报文
staticvoid*icmp_send(void*argv){
structtimevaltv;
tv.tv_usec=0;
tv.tv_sec=1;//每隔一秒发送报文
gettimeofday(&tv_begin,NULL);//保存程序开始发送数据的时间
while(alive){
memset(send_buff,0,sizeof(send_buff));
intsize=0;
structtimevaltv;
gettimeofday(&tv,NULL);//当前包发送的时间
icmp_pack((structicmp*)send_buff,packet_send,&tv,203);//packet_send为发送包的序号,发送的数据长度为64个字节,填充ICMP首部信息
size=sendto(rawsock,send_buff,203+8,0,(structsockaddr*)&dest,sizeof(dest));//dest为ICMP包发送的目的地址
if(size<0){
perror("sendtoerror");
continue;
}
else{
//在发送包的状态数组找一空闲位置记录发送状态信息
pingm_packet*packet=icmp_findpacket(-1);
if(packet){
packet->seq=packet_send;
packet->flag=1;
gettimeofday(&packet->tv_begin,NULL);
packet_send++;//发送序号+1
}
}
sleep
(1);
}
}
//寻找一个空闲位置,seq=-1表示空闲位置
staticpingm_packet*icmp_findpacket(intseq){
inti=0;
pingm_packet*found=NULL;
if(seq==-1){
for(i=0;i<128;i++){
if(pingpacket[i].flag==0){
found=&pingpacket[i];
break;
}
}
}elseif(seq>=0){//查找对应seq的数据包
for(i=0;i<128;i++){
if(pingpacket[i].seq==seq){
found=&pingpacket[i];
break;
}
}
}
returnfound;
}
//获得ICMP接收报文,buf存放的除去了以太网部分的IP数据报文,len为数据长度,ip_hl标识IP头部长度以4字节为单位,获得ICMP数据报后判断是否为ICMP_ECHOREPLY并检查是否为本进程的ID
staticinticmp_unpack(char*buf,intlen){
inti,iphdrlen;
structip*ip=NULL;
structicmp*icmp=NULL;
intrtt;//计算往返时延
ip=(structip*)buf;
iphdrlen=ip->ip_hl*4;//IP头部长度
icmp=(structicmp*)(buf+iphdrlen);//ICMP报文的地址
len-=iphdrlen;//ICMP报文的长度,ICMP报文至少8个字节
if(len<8){
printf("ICMPpackets\'slengthislessthan8\n");
return-1;
}
//判断ICMP报文的类型是否为ICMP_ECHOREPLY并且为本进程的PID
if((icmp->icmp_type==ICMP_ECHOREPLY)&&(icmp->icmp_id==pid)){
structtimevaltv_interval,tv_recv,tv_send;
//在发送数组中查找已经发送的包
pingm_packet*packet=icmp_findpacket(ntohs(icmp->icmp_seq));//网络字节序转换成主机字节序
if(packet==NULL){
return-1;
}
packet->flag=0;//表示已经响应了
//本包的发送时间
tv_send=packet->tv_begin;
//读取收到响应包的时间
gettimeofday(&tv_recv,NULL);
tv_interval=icmp_tvsub(tv_recv,tv_send);
//计算往返时延,即RTT
rtt=tv_interval.tv_sec*1000+tv_interval.tv_usec/1000;
//打印ICMP段长度,源IP,包的序列号,TTL,时间差
printf("%dbytefrom%s:
icmp_seq=%uttl=%drtt=%dms\n",len,inet_ntoa(ip->ip_src),icmp->icmp_seq,ip->ip_ttl,rtt);
packet_recv++;//接收包的数量加1
}
else{
return-1;
}
}
//接收报文
staticvoid*icmp_recv(void*argv){
structtimevaltv;
tv.tv_usec=200;//轮循时间
tv.tv_sec=0;
fd_setreadfd;
while(alive){
intret=0;
tv.tv_usec=200;//轮循时间
tv.tv_sec=0;
FD_ZERO(&readfd);
FD_SET(rawsock,&readfd);
ret=select(rawsock+1,&readfd,NULL,NULL,&tv);
intfromlen=0;
structsockaddrfrom;
switch(ret){
case-1:
//发生错误
break;
case0:
//超时
//printf("timeout\n");
break;
default:
//收到数据包
fromlen=sizeof(from);
intsize=recvfrom(rawsock,recv_buff,sizeof(recv_buff),0,(structsockaddr*)&from,&fromlen);//利用原始套接字,原始套接字与IP层网络协议栈核心打交道
if(errno==EINTR){
perror("recvfromerror");
}
//解包,得到RTT
ret=icmp_unpack(recv_buff,size);
if(ret==-1){
continue;
}
break;
}
}
}
//统计数据结果,成功发送的报文数量,成功接收的报文数量,丢失报文百分比和程序总共运行时间
staticvoidicmp_statistics(void){
longtime=(tv_interval.tv_sec*1000)+(tv_interval.tv_usec/1000);
printf("---%spingstatistics---\n",dest_str);//目的IP
printf("%dpacketstransmitted,%drecevied,%d%cpacketloss,time%dms\n",packet_send,packet_recv,(packet_send-packet_recv)*100/packet_send,'%',time);
}
//信号处理函数
staticvoidicmp_sigint(intsigno){
alive=0;//alive=0程序将会终止
gettimeofday(&tv_end,NULL);//程序结束时间
tv_interval=icmp_tvsub(tv_end,tv_begin);//计算程序一共运行了多长时间
return;
}
//主函数实现
intmain(intargc,char*argv[]){
structhostent*host=NULL;
structprotoent*protocol=NULL;
charprotoname[]="icmp";
unsignedlonginaddr=1;
intsize=128*K;
intret;
if(argc<2){
icmp_usage();
return-1;
}
//获取协议类型ICMP,协议类型的值作为设置原始套接字的第3个参数,type类型下的具体协议值不止一个,当type为SOCK_RAW
protocol=getprotobyname(protoname);
if(protocol==NULL){
perror("getprotobyname()");
return-1;
}
//复制目的地址
memcpy(dest_str,argv[1],strlen(argv[1])+1);
memset(pingpacket,0,sizeof(pingm_packet)*128);//pingpacket数组初始化
//建立原始套接字
rawsock=socket(AF_INET,SOCK_RAW,protocol->p_proto);
if(rawsock<0){
perror("rawsockerror");
return-1;
}
//得到程序的pid
pid=getuid();
//增大接收端缓冲区防止接收的包被覆盖
ret=setsockopt(rawsock,SOL_SOCKET,SO_RCVBUF,&size,sizeof(size));
if(ret==-1){
perror("SO_RCVBUFERROR");
return-1;
}
//输入的目的IP
inaddr=inet_addr(argv[1]);//转换成二进制IP
bzero(&dest,sizeof(dest));
dest.sin_family=AF_INET;//设置地址族
if(inaddr==INADDR_NONE){
//输入的是DNS
host=gethostbyname(argv[1]);
if(host==NULL){
perror("gethostbyname");
return-1;
}
memcpy((char*)&dest.sin_addr,host->h_addr,host->h_length);
}
else{
memcpy((char*)&dest.sin_addr,&inaddr,sizeof(inaddr));
}
inaddr=dest.sin_addr.s_addr;
//由于是ICMP不涉及到端口绑定
printf("PING%s(%d.%d.%d.%d)56(84)bytesofdata.\n",dest_str,(inaddr&0x000000FF)>>0,(inaddr&0x0000FF00)>>8,(inaddr&0x00FF0000)>>16,(inaddr&0xFF000000)>>24);
signal(SIGINT,icmp_sigint);
alive=1;
//定义两个线程,分别用于发送数据与接收数据
pthread_tsend_id,recv_id;
interr=0;
err=pthread_create(&send_id,NULL,icmp_send,NULL);
if(err<0){
return-1;
}
err=pthread_create(&recv_id,NULL,icmp_recv,NULL);
if(err<0){
return-1;
}
pthread_join(send_id,NULL);//等待子线程结束send
pthread_join(recv_id,NULL);//等待子线程的结束recv
close(rawsock);
icmp_statistics();
return0;
}
运行结果:
PING222.27.253.1(222.27.253.1)56(84)bytesofdata.
60bytefrom222.27.253.1:
icmp_seq=0ttl=255rtt=5ms
60bytefrom222.27.253.1:
icmp_seq=1ttl=255rtt=12ms
60bytefrom222.27.253.1:
icmp_seq=2ttl=255rtt=5ms
60bytefrom222.27.253.1:
icmp_seq=3ttl=255rtt=7ms
60bytefrom222.27.253.1:
icmp_seq=4ttl=255rtt=2ms
60bytefrom222.27.253.1:
icmp_seq=5ttl=255rtt=23ms
60bytefrom222.27.253.1:
icmp_seq=6ttl=255rtt=27ms
60bytefrom222.27.253.1:
icmp_seq=7ttl=255rtt=10ms
60bytefrom222.27.253.1:
icmp_seq=8ttl=255rtt=17ms
60bytefrom222.27.253.1:
icmp_seq