在网络上有不少ping命令的实现,但是要么连运行都不行,要么就是表层的侥幸的能成功一些例子。今我自己写了个ping命令程序,自习研究了一下,原来问题颇多。。。。。。 很多还得看底层协议代码才能搞清楚它的来龙去脉。。。 先将调试版ping命令程序代码贴上:
/***********************ping程序,completed by ben****************************/
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#define BUFSIZE 1024
#define MAX_PACK_NUM
char sendbuf[BUFSIZE];
char recvbuf[BUFSIZE];
//int buflen=128;
int datalen=56;
unsigned short pid;
struct sockaddr *sasend;
struct sockaddr *sarecv;
struct sockaddr_in *dst;
char abuf[INET_ADDRSTRLEN];
int sckadlen;
static unsigned short nsend=0;
int sockfd;
int recvnum=0;
int totaltime=0;
struct timeval sendtime,endtime;
//static int sendnum=0;
unsigned short cksum(unsigned short *addr ,int len)
{
int nleft=len;
unsigned 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;
}
void sendpkg()
{
//printf("icmp len is %d/n",sizeof(struct icmp));
int flag=0,n;
//memset(sendbuf+64,0,sizeof(sendbuf)-64);
memset(sendbuf,0,sizeof(sendbuf));//要初始化,原因是校验和那个域初始时要清零,就这个地方我调了很久,原来不知道为什么要
//初始化,因为后面都已经重新赋值了。
//memset(sendbuf,1,64);
//memset(sendbuf,0,64);
//memset(sendbuf,0xbb,sizeof(sendbuf));
//memset(sendbuf+2,0,2);
//for( n=0;n<64;n++)
// {
//a=(char)sendbuf[n];
// printf("sendbuf byte %d is %x/n",n,sendbuf[n]);
// }
struct icmp *icmp;
icmp=(struct icmp *)sendbuf;
icmp->icmp_type=ICMP_ECHO;
icmp->icmp_code=0;
icmp->icmp_seq=htons(nsend++);
//icmp->icmp_seq=htons(0);
//printf("seq is %d/n",icmp->icmp_seq);
//printf("icmp_type is %x/n",&icmp->icmp_type);
//printf("icmp_seq is %x/n",&icmp->icmp_seq);
//printf("icmp->icmp_id is %x/n",&icmp->icmp_id);
//printf("icmp len is %d/n",sizeof(struct icmp));
//printf("icmp_data is %x/n",&icmp->icmp_data);
//printf("pid is %d/n",pid);
//icmp->icmp_id=htons(pid);
icmp->icmp_id=htons(pid);
//printf("icmp->icmp_id is %x/n",icmp->icmp_id);
//printf("sendbuf byte 4 is %d/n",sendbuf[4]);
//int n;
memset(icmp->icmp_data,0xa5,datalen);
gettimeofday((struct timeval *)icmp->icmp_data,NULL);
struct timeval *temptival=(struct timeval *)icmp->icmp_data;
temptival->tv_sec=ntohl(temptival->tv_sec);
temptival->tv_usec=ntohl(temptival->tv_usec);
if(nsend==1) gettimeofday(&sendtime,NULL);
//if(flag==0) {flag++;gettimeofday(&sendtime,NULL);}
int len=8+datalen;
//printf("heheheheh/n");
icmp->icmp_cksum=htons(cksum((unsigned short *)icmp,len));
//icmp->icmp_cksum=cksum((unsigned short *)icmp,len);
//printf("sasend->sa_len is %d/n",sasend->sa_family);
//sleep(1);
unsigned char a=8;
//printf("a is %x/n",a);
//for(n=0;n<64;n++)
// {
// a=(char)sendbuf[n];
// printf("sendbuf byte %d is %x/n",n,(char)a);
//}
if((n=sendto(sockfd,sendbuf,len,0,sasend,sckadlen))<0)
{
perror("error is:");
//printf("error is %s/n", hstrerror(h_errno));
printf("send error:%d/n",errno);
exit(-1);
}
//printf("n is %d /n",n);
return;
}
void sig_alrm(int signo)
{
//if(sendnum>=3) return;
sendpkg();
alarm(1);
//sendnum++;
return;
}
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;
}
void catchint(int signo)
{
//echo();
//nocbreak();
//endwin();
gettimeofday(&endtime,NULL);
//printf("endtime.tv_sec is %u/n",endtime.tv_sec);
//printf("endtime.tv_usec is %u/n",endtime.tv_usec);
//printf("endtime.tv_sec is %u/n",sendtime.tv_sec);
//printf("endtime.tv_usec is %u/n",sendtime.tv_usec);
tv_sub(&endtime,&sendtime);
//printf("endtime.tv_sec is %u/n",endtime.tv_sec);
//printf("endtime.tv_usec is %u/n",endtime.tv_usec);
totaltime=endtime.tv_sec*1000+endtime.tv_usec/1000;
//printf("hssh is %d/n",totaltime);
int a=(nsend-recvnum)*100/((double)nsend);
printf("/n---www.a.shifen.com ping statistics ---/n");
printf("%d packets transmitted,%d recerved,%d%% packets loss,time %dms/n",
nsend,recvnum,a,totaltime);
signal(SIGINT,SIG_DFL);
exit(0);
}
void recvpkg()
{
int n,iph_len,icmplen;
struct timeval tval,*tvsend;
struct ip *ip;
struct icmp *icmp;
int len;
len=sizeof(struct sockaddr);
double rtt;
sarecv=calloc(1,sizeof(struct sockaddr));
//int num;
//for(num=0;num<3;num++)
for(;;)
{
//sleep(1);
//alarm(1);
//printf("in recvpkg/n");
memset(recvbuf,0,sizeof(recvbuf));
//sarecv=calloc(1,sizeof(struct sockaddr));//内存泄漏
n=recvfrom(sockfd,recvbuf,sizeof(recvbuf),0,sarecv,&len);
//printf("len is %d/n",len);
//printf("sarecv->sa_data is %s/n",sarecv->sa_data);
if(n<0)
{
if(errno==EINTR)
continue;
else
exit(-1);
}
//printf("n is %d/n",n);
//printf("error when recv/n");
//exit(-1);
//gettimeofday(tval,NULL);
ip=(struct ip*)recvbuf;
iph_len=ip->ip_hl<<2;
//printf("hehe/n");
if(ip->ip_p!=IPPROTO_ICMP)
return;
//printf("hododo/n");
icmp=(struct icmp*)(recvbuf+iph_len);
icmplen=n-iph_len;
if(icmplen<8) continue;
//printf("jkjk/n");
if(icmp->icmp_type==ICMP_ECHOREPLY&&ntohs(icmp->icmp_id)==pid)
{
if(icmplen<16)
continue;
recvnum++;
tvsend=(struct timeval*)icmp->icmp_data;
tvsend->tv_sec=ntohl(tvsend->tv_sec);
tvsend->tv_usec=ntohl(tvsend->tv_usec);
gettimeofday(&tval,NULL);//原来这里tval为空指针,造成非法访问内存
tv_sub(&tval,tvsend);
rtt=tval.tv_sec*1000.0+tval.tv_usec/1000.0;
printf("%d bytes from %s:seq=%u,ttl=%d,rtt=%3.f ms/n",
icmplen,abuf,ntohs(icmp->icmp_seq),ip->ip_ttl,rtt);
}
else
continue;
}
free(sarecv);
printf("out of circle /n");
return;
}
int main(int argc,char **argv)
{
//initscr();
//cbreak();
//noecho();
//struct hostent *host;
char *host;
char *h=NULL;
//char abuf[INET_ADDRSRTLEN];
if(argc==1)
{
printf("usage:./ping host_name/n");
exit(-1);
}
struct addrinfo hints,*res;
struct sockaddr_in *dst;
bzero(&hints,sizeof(struct addrinfo));
hints.ai_flags=AI_CANONNAME;
hints.ai_family=AF_UNSPEC;//0
hints.ai_socktype=0;
host=argv[1];
signal(SIGALRM,sig_alrm);
signal(SIGINT,catchint);
pid=getpid();
printf("hi,pid is %d/n",pid);
if(getaddrinfo(host,NULL,&hints,&res)!=0)
{
printf("please check the host name,getting the host failed");
exit(-1);
}
sasend=res->ai_addr;
sckadlen=res->ai_addrlen;
//printf("sckadlen is %d/n",sckadlen);
dst=(struct sockaddr_in *)res->ai_addr;//dst=(sockaddr_in *)&res->ai_addr;
//sasend=dst
h=inet_ntop(AF_INET,&dst->sin_addr,abuf,INET_ADDRSTRLEN);
if(h==NULL)
{
printf("error:/n");
exit(-1);
}
printf("h is %s/n",h);
printf("PING %s(%s):%d data bytes/n",res->ai_canonname?res->ai_canonname:h,h,datalen);
//int protocol;
struct protoent *protocol;
if( (protocol=getprotobyname("icmp") )==NULL)
{ perror("getprotobyname");
exit(1);
}
sockfd=socket(AF_INET,SOCK_RAW,protocol->p_proto); //IPPROTO_ICMP
int buflen=BUFSIZE;
//printf("come to here:/n");
setsockopt(AF_INET,SOL_SOCKET,SO_RCVBUF,&buflen,sizeof(buflen));
sig_alrm(SIGALRM);
//sendpkg();
recvpkg();
return 1;
}
对方接受到ICMP数据包在ip层还会做处理:
当一个I C M P报文到达时,I P层通过i n e t s w [ 4 ]的p r _ i n p u t
函数,间接调用i c m p _ i n p u t函数。
icmp_input->in_cksum():这个函数会重新计算校验和,如果校验和不对,接受到的包含ICMP报文IP包将就不会被发送。