使用宏定义令表达更简洁,其中ICMP报头为8字节,数据报长度最大为64K字节。
校验和算法――这一算法称为网际校验和算法,把被校验的数据16位进行累加,然后取反码,若数据字节长度为奇数,则数据尾部补一个字节的0以凑成偶数。此算法适用于IPv4、ICMPv4、IGMPV4、ICMPv6、UDP和TCP校验和,更详细的信息请参考RFC1071,校验和字段为上述ICMP数据结构的icmp_cksum变量。
标识符――用于唯一标识ICMP报文, 为上述ICMP数据结构的icmp_id宏所指的变量。
顺序号――ping命令的icmp_seq便由这里读出,代表ICMP报文的发送顺序,为上述ICMP数据结构的icmp_seq宏所指的变量。
ICMP数据报
Ping命令中需要显示的信息,包括icmp_seq和ttl都已有实现的办法,但还缺rtt往返时间。为了实现这一功能,可利用ICMP数据报携带一个时间戳。使用以下函数生成时间戳:
#include
int gettimeofday(struct timeval *tp,void *tzp)
其中timeval结构如下:
struct timeval{
long tv_sec;
long tv_usec;
}
其中tv_sec为秒数,tv_usec微秒数。在发送和接收报文时由gettimeofday分别生成两个timeval结构,两者之差即为往返时间,即ICMP报文发送与接收的时间差,而timeval结构由ICMP数据报携带,tzp指针表示时区,一般都不使用,赋NULL值。
数据统计
系统自带的ping命令当它接送完所有ICMP报文后,会对所有发送和所有接收的ICMP报文进行统计,从而计算ICMP报文丢失的比率。为达此目的,定义两个全局变量:接收计数器和发送计数器,用于记录ICMP报文接受和发送数目。丢失数目=发送总数-接收总数,丢失比率=丢失数目/发送总数。
#include <stdio.h>
#include <stdlib.h>
#include <string.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 3
char 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);
void statistics(int signo)
{
printf("\n--------------------PINGstatistics-------------------\n");
printf("%d packetstransmitted, %d received , %%%dlost\n",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;
while(nleft>1)
{
sum+=*w++;
nleft-=2;
}
if( nleft==1)
{
*(unsigned char *)(&answer)=*(unsigned char*)w;
sum+=answer;
}
sum=(sum>>16)+(sum&0xffff);
sum+=(sum>>16);
answer=~sum;
return answer;
}
int pack(int pack_no)
{
inti,packsize;
struct icmp*icmp;
structtimeval *tval;
icmp=(structicmp*)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( (unsigned short*)icmp,packsize);
returnpacksize;
}
void send_packet()
{
intpacketsize;
while(nsend)
{
nsend++;
packetsize=pack(nsend);
if(sendto(sockfd,sendpacket,packetsize,0,(struct sockaddr*)&dest_addr,sizeof(dest_addr))<0)
{
perror("sendto error");
continue;
}
sleep(1);
}
}
void recv_packet()
{
int n,fromlen;
extern int errno;
signal(SIGALRM,statistics);
fromlen=sizeof(from);
while(nreceived)
{
alarm(MAX_WAIT_TIME);
if( (n=recvfrom(sockfd,recvpacket,sizeof(recvpacket),0,(structsockaddr *)&from,&fromlen))<0)
{
if(errno==EINTR) continue;
perror("recvfrom error");
continue;
}
gettimeofday(&tvrecv,NULL);
if(unpack(recvpacket,n)==-1) continue;
nreceived++;
}
}
int unpack(char *buf,int len)
{
inti,iphdrlen;
struct ip*ip;
struct icmp*icmp;
structtimeval *tvsend;
doublertt;
ip=(structip *)buf;
iphdrlen=ip->ip_hl<<2;
icmp=(structicmp *)(buf+iphdrlen);
len-=iphdrlen;
if(len<8)
{
printf("ICMP packets\'s length is less than 8\n");
return -1;
}
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;
printf("%d byte from %s: icmp_seq=%u ttl=%d rtt=%.3f ms\n",
len,inet_ntoa(from.sin_addr),icmp->icmp_seq,ip->ip_ttl,rtt);
}
else return-1;
} |