ping源码程序分析_第1页
ping源码程序分析_第2页
ping源码程序分析_第3页
ping源码程序分析_第4页
ping源码程序分析_第5页
已阅读5页,还剩12页未读 继续免费阅读

下载本文档

版权说明:本文档由用户提供并上传,收益归属内容提供方,若内容存在侵权,请进行举报或认领

文档简介

ping源码分析

10.4.1ping简介

Ping是网络中应用非常广泛的一个软件,它是基于TCMP协议的。下面首先对ICMP

协议做一简单介绍。

ICMP是IP层的一个协议,它是用来探测主机、路由维护、路由选择和流量控制的。

ICMP报文的最终报宿不是报宿计算机上的一个用户进程,而是那个计算机上的IP

层软件。也就是说,当一个带有错误信息的ICMP报文到达时,IP软件模块就处理

本身问题,而不把这个ICMP报文传送给应用程序。

ICMP报文类型有:回送(ECHO)回答(0);报宿不可到达(3);报源断开(4);

重定向(改变路由)(5);回送(ECHO)请求(8);数据报超时(11);数据

报参数问题(12);时间印迹请求(13);时间印迹回答(14);信息请求(15);

信息回答(16);地址掩码请求(17);地址掩码回答(18)。

虽然每种报文都有不同的格式,但它们开始都有下面三段:

一个8位整数报文?YPE(类型)段;

一个8位CODE(代码码)段,提供更多的报文类型信息;

一个16位CHECKSUM(校验和)段;

此外,报告差错的ICMP报文还包含产生问题数据报的网际报头及前64位数据。

一个ICMP回送请求与回送回答报文的格式如表10.17所示。

表10.17ICMP回送请求与回送回答报文格式类型CODE校验和[CHECKSUM]标识符

序列号《嵌入式Linux应用程序开发详解》一一第10章、嵌入式Linux网络编程

数据

10.4.2ping源码分析

下面的ping,c源码是在busybox里实现的源码。在这个完整的ping,c代码中有

较多选项的部分代码,因此,这里先分析除去选项部分代码的函数实现部分流程,

接下来再给出完整的p:ng代码分析。这样,读者就可以看到一个完整协议实现应

该考虑到的各个部分。

1.Ping代码主体流程

Ping,c主体流程图如下图10.8所示。另外,由于ping是IP层的协议,因此在建

立socket时需要使用S0CK_RAW选项。在循环等待回应信息处,用户可以指定“-f”

洪泛选项,这时就会使用select函数来指定在一定的时间内进行回应。

2.主要选项说明

Ping函数主要有以下几个选项:

d:调试选项(F_SODEBUG)

f:洪泛选项(F_FLOOD)

i:等待选项(F二NTERVAL)

r:路由选项(F_RROUTE)

1:广播选项(MULTICAST_NOLOOP)

对于这些选项,尤其是路由选项、广播选项和洪泛选项都会有不同的实现代码。

另外,ping函数可以接受用户使用的SIGINT和SIGALARM信号来结束程序,它

们分别指向了不同的结束代码,请读者阅读下面相关代码。

图10.8ping主体流程图

第1页共17页

3.源代码及注释

(1)主体代码

ping代码的主体部分可以四部分,首先是一些头函数及宏定义:

#include<sys/param.h>

#include<sys/socket.h>

#include<sys/file.h>

#include<sys/time.h>

itinclude<sys/signal.h>

#include<netinet/in.h>

#include<netinet/ip.h>

#include<netinet/ipicmp.h>

itinclude<arpa/inet.h>

itinclude<netdb.h>

itinclude<unistd.h>

itinclude<stdlib.h>

#include<string.h>

ttinclude<stdio.h>

#include<ctype.h>

ttinclude<errno.h>

#include<getopt.h>

itinclude<resolv.h>

#defineF.FLOOD0x001

#defineF.INTERVAL0x002

^defineF.NUMERIC0x004

^defineF_PINGFILLED0x008

#defineF_QUTET0x010

#defineF.RROUTE0x020

^defineF_SO_DEBUG0x040

#defineFSO_DONTROUTE0x080

#defineF_VERBOSE0x100

/*多播选定*/

intinoptions;

^defineMULTTCAST_NOLOOP0x001

#defineMULTICAST_TTL0x002

ttdefineMULTICAST_IF0x004

《嵌入式Linux应用程序开发详解》一一第10章、嵌入式Linux网络编程

接下来的第2部分是建立socket并处理选项:

Intmain(intargc,char*argv[])

(

structtimevaltimeout;

structhostent*hp;

第2页共17页

structsockaddr_in*to;

structprotoent*proto;

structin_addrifaddr;

inti;

intch,fdmask,hold,packlen,preload;

u_char*datap,"packet;

char*targct,hnamcbuf[MAXHOSTNAMELEN];

u_charttl,loop;

intam_i_root;

•••

staticchar*null=NULL;

/*_environ=&null;*/

am_i_root=(getuid;)-0);

/*

*建立socket连接,并且测试是否是root用户

*/

if((s=socket(AFJNET,S0CK_RAW,1PPROTO_1CMP))<0){

if(errno==EPERM)7

fprintf(stderr,"ping:pingmustrunasroot\nn);

)

elseperror(r'ping:socket1');

exit(2);

)

preload=0;

datap=&outpack[8-sizeof(structtimeval)];

while((ch=getoptlarge,argv,MT:LRc:dfh:i:1:np:qrs:t:v"))!=EOF)

switch(ch){

case'c':

npackets=atoi(optarg);

if(npackets<=0)[

(void)fprintf(stderr,

“ping:badnumberofpacketstotransmit.\n'r);

exit⑵;

)

break;

/*调用选项*/

case'd':

options|=F_SO_DEBUG;

break;

,"flood选项*/

caserf1:

第3页共17页

if(!am_i_root){

(void)fprintf(stderr,

"ping:%s\n'r,strerror(EPERM));

exit(2);

)

options|=F_FL00D;

sctbuf(stdout,NULL;;

break;

/'*等待选项*/

case'i':/*waitbetweensendingpackets*/

interval=atoi(optarg);

if(interval<=0){

(void)fprintf(stderr,

“ping:badtiminginterval.\n,f);

exit(2);

)

options|=F_INTERVAL;

break;

case111:

if(!am_i_root){

(void)fprintf(stderr,

"ping:%s\n",strerror(EPERM));

exit(2);

}

preload=atoi(optarg);

if(preload<0){

(void)fprintf(stderr,

“ping:badpreloadvalue.\n");

exit⑵;

}

break;

•••

default:

usage();

)

argc-=optind;

argv+=optind;

if(argc!=1)

usage();

target=*argv;

接下来的第3部分是用于获取地址,这里主要使用了inet_aton函数,将点分十

进制地址转化为二进制地址。当然,作为完整的ping程序有较完善的出错处理:

mcmset(&whereto,0,sizeof(structsockaddr>);

第4页共17页

to=(structsockaddr_in*)ftwhereto;

to->sin_family=AF_INET;

/*地址套换函数*/

if(inet_aton(target,&to->sin_addr)){

hostname=target;

}

else{

#if0

char*addr=resolve_name(target,0);

if(!addr){

(void)fprintf(stderr,

“ping:unknownhost%s\n'r,target);

exit(2);

}

to->sin_addr.s_addr=inet_addr(addr);

hostname=target;

#else

/*调用geIhosIbyname织别主机名*/

hp=gethostbyname(target);

if(!hp){

(void)fprintf(stderr,

“ping:unknownhost%s\n,r,target);

exit⑵;

)

to->sin_family=hp->h_addrtype;

if(hp->h_length>(int)sizeof(to->sin_addr)){

hp->h_length二sizeof(to->sin_addr);

}

memcpy(&to->sin_addr,hp->h_addr,hp->h_length);

(void)strncpy(hnamebuf,hp->h_name,sizeof(hnamebuf)-1);

hostname=hnamebuf;

#endif

)

接下来的一部分主要是对各个选项(如路由、多播)的处理,这里就不做介绍了。

再接下来是ping函数的最主要部分,就是接收无限循环回应信息,这里主要用到

了函数recvfrom。

另外,对用户中断信息也有相应的处理,如下所示:

if(to->sin_family==AF_INET)

(void)printfC'PING%s(%s):%ddatabytes\n",hostname,

inet_ntoa(*(structin_addr*)&to->sin_addr.s_addr),

datalen);

else

第5页共17页

(void)printf(MPING%s:%ddatabytes\n'f,hostname,datalen);

/*若程序接收到SIGINT或SIGALRM信号,调用相关的函数*/

(void)signal(SIGINT,finish);

(void)signal(STGALRM,catcher);

•••

/*循环等待客户端的回应信息*/

for(;;){

structsockaddr_infrom;

registerintcc;

intfromlen;

if(options&F_FL00D){

/*形成ICMP回应暖据包,在后面会有讲解*/

pingerO;

/*设定等待实践*/

timeout.tv_sec=0;

timeout.tv_usec=10000;

fdmask=1«s;

/*调用select函数*/

《嵌入式Linux应用程序开发详解》一一第10章、嵌入式Linux网络编程

if(select(s+1,(fd_set*)&fdmask,(fd_set*)NULL,

(fd_set*)NULL,&tineout)<1)

continue;

}

fromlen二sizeof(from);

/*接收客户端信息*/

if((cc二recvfrom(s,(char*)packet,packlen,0,

(structsockaddr*)&from,&fromlen))<0){

if(errno=EINTR)

continue;

perror(Hping:recvfromM);

continue;

)

pr_pack((char*)packet,cc,&from);

if(npackets&&nreceived>=npackets)

break;

)

finish(0);

/*NOTREACHED*/

return0;

)

(2)其他函数

下面的函数也是ping程序中用到的重要函数。首先catcher函数是用户在发送

SIGINT时调用的函数,在该函数中又调用了SIGALARM信号的处理来结束程序。

第6页共17页

staticvoid

catcher(intignore)

{

intwaittime;

(void)ignore;

pinger();

/*调用catcher函数*/

(void)signal(SIGALRM,catcher);

if(!npacketsIIntransmitted<npackets)

alarm((u_int)interval);

else{

if(nreceived){

waittime=2*tmax/1000;

if(!waittime)

waittime=1;

if(waittime>MAXWAIT)

waittime=MAXWAIT;

}else

waittime=MAXWAIT;

/*调用finish函数,并设定一定的等待实践*/

(void)signal(SIGALRM,finish);

(void)alarm((u_int)waittime);

)

)

Pinger函数也是一个非常重要的函数,用于形成ICMP回应数据包,其中TD是该

进程的ID,数据段中的前8字节用于存放时间间隔,从而可以计算ping程序从对

端返回的往返时延差,这里的数据校验用到了后面定义的in,ksum函数。其代码

如下所示:

staticvoid

pinger(void)

(

registerstructicmphdr*icp;

registerintcc;

inti;

/*形成icmp信息包,填写icmphdr结构体中的各项数据*/

icp=(structicmphdr*)outpack;

icp->icmp_type=ICMP_ECH0;

icp->icmp_code=0;

icp->icmp_cksum=0;

icp->icmp_seq=ntransmitted++;

icp->icmp_id=ident;/*TD*/

CLR(icp->icmpseq%mxdupck);

第7页共17页

/*设定等待实践*/

if(timing)

(void)gettimeofday(^structtimeval*)&outpack[8],

(structtimezone*)NULL);

cc=datalen+8;/*skipsICMPportion*/

《嵌入式Linux应用程序开发详解》一一第10章、嵌入式Linux网络编程

/*computeICMPchecksumhere*/

icp->icmp_cksum=in_cksum((u_short*)icp,cc);

i=sendto(s,(char*)outpack,cc,0,&whereto,

sizeof(structsockaddr));

if(i<0|i!=cc){

if(i<0)

perror(Hping:sendto'r);

(void)printf(Mping:wrote%s%dchars,ret=%d\n,f,

hostname,cc,i);

)

if(!(options&E_QUIET)&&options&F_rL00D)

(void)write(STD0UT_FILEN0,&DOT,1);

}

prepack是数据包显示函数,分别打印出IP数据包部分和ICMP回应信息。在规

范的程序中通常将数据的显示部分独立出来,这样就可以很好地加强程序的逻辑

性和结构性。

void

pr_pack(char*buf,inicc,structsockaddr_in*from)

(

registerstructicmphdr*icp;

registerinti;

registeru_char*cp,*dp;

/*#if0*/

registeru_long1;

registerintj;

staticintold_rrlen;

staticcharold_rr[MAX_IPOPTLEN];

/♦Sendif*/

structiphdr*ip;

structtimevaltv,*tp;

longtriptime=0;

inthlen,dupflag;

(void)gettimeofday(&tv,(structtimezoneNULL);

/*检查IP数据包头信息*/

ip=(structiphdr*)buf;

第8页共17页

hlen=ip->ip_hl<<2;

if(cc<datalen+ICMP_MINLEN){

if(options&F_VERBOSE)

(void)fprintf(stderr,

“ping:packettooshort(%dbytes)from%s\nH,cc,

inet_ntoa(*(structin_addr*)&from->sin_addr.s_addr));

return;

}

/*ICMP部分显示*/

cc-=hlen;

icp二(structicmphdr*)(buf+hlen);

if(icp->icmp_type==ICMP_ECHOREPLY){

if(icp->icmp_id1=ident)

return;/*'TwasnotourECHO*/

++nreceived;

if(timing){

#ifndeficmp_data

tp=(structtimeval*)(icp।1);

#else

tp=(structtimeval*)icp->icmp_data;

#endif

tvsub(&tv,tp);

triptime=tv.tv_sec*10000+(tv.tv_usec/100);

tsum+=triptime;

if(triptime<train)

tmin=triptime;

if(triptime>tmax)

tmax=triptime;

}

if(TST(icp->icmp_scq%mx_dup_ck)){

++nrepeats;

一一nreceived;

dupflag=1;

}else{

SET(icp->icmpseq%mxdupck);

dupflag=0;

)

《嵌入式Linux应用程序开发详解》一一第10章、嵌入式Linux网络编程

if(options&F_QUIET)

return;

if(options&F_FLOOD)

(void)write(STDOUT_FTLENO,&BSPACE,1);

else{

第9页共17页

(void)printf(M%dbytesfrom%s:icmp_seq=%u",cc,

inet_ntoa(*(structin_addr*)&from->sin_addr.s_addr),

icp->icmp_seq);

(void)printf(Mip->ip_ttl);

if(timing)

(void)printf(Mtime=%ld.%ldmsw,triptime/10,

triptime%10);

if(dupflag)

(void)printfC(DUP!)w);

/'*checkthedata*/

#ifndeficmp_data

cp=((u_char*)(icp+1)+8);

#else

cp=(u_char*)icp->icmp_data+8;

#endif

dp=&outpack[8+sizeof(structtimeval)];

for(i=8;i<datalen;++i,++cp,++dp)(

if(*cp!=*dp){

(void)printf("\nwrongdatabyte#%dshouldbeOx%xbutwasOx%xM,

i,*dp,*cp);

cp=(u_char*)(icp-1);

for(i=8;i<datalen;++i,++cp){

if((i%32)==8)

(void)printf(M\n\tM);

(void)printf(M%x",*cp);

)

break;

)

}

}

}else{

/*We'vegotsomethingotherthananECHOREPLY*/

if(1(options&F_VERBOSE))

return;

(void)printf(n%dbytesfrom%s:二cc,

pr_addr(from->sin_addr.s_addr));

pr_icmph(icp);

)

/*#if0*/

/*显示其他IP选项*/

cp=(u_char*)buf-sizeof(structiphdr);

for(;hlen>(int)sizeof(structiphdr);-hlen,++cp)

第io页共17页

switch(*cp){

caseIPOPT_EOL:

hlen=0;

break;

caseIP0PT_LSRR:

(void)printf("\nLSRR:");

hlen-=2;

j=*++cp;

++cp;

if(j>IP0PT_MIN0FF)

for(;;){

1=*++cp;

1=(1«8)+*++cp;

1=(1«8)+*++cp;

1=(1«8)+*++cp;

if(1=0)

(void)printf(M\t0.0.0.0");

else

(void)printf(,,\t%sn,pr_addr(ntohl(1)));

hlen-=4;

j-=4;

if(j<=IP0PT_MIN0FF)

break;

(void)putchar(r\n');

)

break;

caseTPOPT_RR:

j=*++cp;/*getlength*/

i=*++cp;/*andpointer*/

hlen-=2;

if(i>j)

i=j;

i-=IPOPT_M1NOFF;

if(i<=0)

continue;

if(i==old_rrlen

&&cp==(u_char*)buf+sizcof(structiphdr)+2

&&!inemcmp((char*)cp,old_rr,i)

&&!(options&F_FL00D)){

(void)printf("\t(saneroute)”);

i=((i+3)/4)*4;

hlen-=i;

cp+=i;

break;

笫u页共17页

}

old_rrlen=i;

memcpy(old_rr,cp,i);

(void)printf("\nRR:");

for(;;){

1=*++cp;

1=(1«8)+*++cp;

1=(1«8)+*++cp;

1=(1«8)+*++cp;

if(1==0)

(void)printf(M\t0.0.0.0M);

else

(void)printf("\t%s",pr_addr(ntohl(1)));

hlen-=4;

i-=4;

if(i<=0)

break;

(void)putchar('\n');

}

break;

caseIPOPT.NOP:

(void)printf("\nN0P*);

break;

default:

(void)printf("\nunknownoption%x0,*cp);

break;

}

/*#endif*/

if(!(options&FJ'LOOD)){

(void)putchar('\n');

(void)fflush(stdoutj;

)

)

in_cksum是数据校验程序,如下所示:

staticint

in_cksum(u_short*addr,intlen)

(

registerintnleft=len;

registeru_short*w=addr;

registerintsum=0;

u_shortanswer=0;

/*这里的算法很简单,就采用32bit的加法*/

第12页共17页

while(nleft>1)(

sum+=*w++;

nleft-=2;

)

if(nleft==1){

*(u_char*)(&answer)=*(u_char*)w;

sum+=answer;

}

/*把高16bit加到低16bit上去*/

sum=(sum»16)+(sum&Oxffff);

sum+=(sum»16);

answer=飞um;

return(answer);

}

《嵌入式Linux应用程序开发详解》一一第10章、嵌入式Linux网络编程

Finish程序是ping程序的结束程序,主要是打印出来一些统计信息,如下麻示:

staticvoid

finish(intignore)

(

(void)ignore;

(void)signal(SIGINT,SIG_IGN);

(void)putchar('\n');

(void)fflush(stdout);

(void)printf(M---%spingstatistics---\n\hostname);

(void)printf(M%ldpacketstransmitted,",ntransmitted);

(void)printf(M%ldpacketsreceived,”,nreceived);

if(nrepeats)

(void)printf(M+%ldduplicates,二nrepeats);

if(ntransmitted)

if(nreceived>ntransmitted)

(void)printf("--sonebody'sprintinguppackets!*');

else

(void)printfpacketloss",

(int)(((ntransmitted-nreceived)*100)/

ntransmitted));

(void)putchar('\n');

if(nreceived&&tining)

(void)printf("round-tripmin/avg/max=%ld.%ld/%lu.%ld/%ld.%ld

ms\n",

tmin/10,tmin%10,

(tsum/(nreceived-nrepeats))/10,

(tsum/(nreceived-nrepeats))%10,

tmax/10,tmax%10);

第13页共17页

if(nreceived==O)exit(1);

exit(0);

)

ttifdefnotdef

staticchar*ttab[]={

"EchoReply",/*ip+seq+udata*/

11DostUnreachablen,/*net,host,proto,port,frag,sr+IP*/

"SourceQuenchw,/*IP*/

"Redirect",/*redirect类型,gateway,+TP*/

"Echo",

"TimeExceeded'1,/*传输超时*/

"ParameterProblem'1,/*IP参数问题*/

“Timestamp",/*id-seq+threetimestamps*/

MTimestampReply",/*"*/

"InfoRequestw,/*id+sq*/

"InfoReply"/*"*/

};

ttendif

pjiemph函数是用于打印ICMP的回应信息,如下所示:

staticvoid

pr_icmph(structicmphdr*icp)

{

switch(icp->icmp_type){

/*ICMP回应*/

caseICMP_ECHOREPLY:

(void)printf("EchoReply\n");

/*XXXTD+Seq+Data*/

break;

/*ICMP终点不可达*/

caseICMP_DEST_UNREACH:

switch(icp->icmp_code){

caseTCMP_NET_UNREACH:

(void)printf("DestinationNetUnreachable\n");

break;

caseICMP_HOST_UNREACH:

(void)printf("DestinationHostUnreachable\n");

break;

case1CMP_PROT_UNREACH:

(void)printf("DestinationProtocolUnreachable\n");

break;

default:

第14页共17页

(void)printf(MDestUnreachable,UnknownCode:%d\n',,

icp->icmp_code);

break;

)

/*PrintreturnedIPheaderinformation*/

#ifndcficmpdata

pr_retip((structiphdr*)(icp+1));

ttelse

pr_retip((structiphdr*)icp->icmp_data);

ttendif

break;

•••

default:

(void)printf(Redirect,BadCode:%dM,icp->icmp_code);

break;

)

(void)printf("(Newaddr;%s)\nM,

inet_ntoa(icp->icmp_gwaddr));

#ifndeficmp_data

pr_retip((structiphdr*)(icp+1));

#else

pr_retip((structiphdr*)icp->icmp_data);

#endif

break;

caseICMPECHO:

(void)printf("EchoRequestXn'1);

/*XXXID+Seq+Data*/

break;

caseICMPTIME^EXCEEDED:

switch(icp->icmp_code){

caseICMP_EXC_TTL:

(void)printf("Timetoliveexceeded、/');

break;

caseICMP_EXC_FRAGTIME:

(void)printf("Fragr

温馨提示

  • 1. 本站所有资源如无特殊说明,都需要本地电脑安装OFFICE2007和PDF阅读器。图纸软件为CAD,CAXA,PROE,UG,SolidWorks等.压缩文件请下载最新的WinRAR软件解压。
  • 2. 本站的文档不包含任何第三方提供的附件图纸等,如果需要附件,请联系上传者。文件的所有权益归上传用户所有。
  • 3. 本站RAR压缩包中若带图纸,网页内容里面会有图纸预览,若没有图纸预览就没有图纸。
  • 4. 未经权益所有人同意不得将文件中的内容挪作商业或盈利用途。
  • 5. 人人文库网仅提供信息存储空间,仅对用户上传内容的表现方式做保护处理,对用户上传分享的文档内容本身不做任何修改或编辑,并不能对任何下载内容负责。
  • 6. 下载文件中如有侵权或不适当内容,请与我们联系,我们立即纠正。
  • 7. 本站不保证下载资源的准确性、安全性和完整性, 同时也不承担用户因使用这些下载资源对自己和他人造成任何形式的伤害或损失。

评论

0/150

提交评论