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

下载本文档

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

文档简介

ping源码分析

10.4.1ping简介

Ping是网络中应用非常广泛的一个软件,它是基于ICMP协议的。下面首先对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位整数报文TYPE(类型)段;

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

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

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

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

表10.17ICMP回送请求与回送回答报文格式

类型CODE校验和[CHECKSUM]

标识符序列号

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

数据

10.4.2ping源码分析

下面的ping.c源码是在busybox里实现的源码。在这个完整的ping.c代码中有较多选项

的部分代码,因此,这里先分析除去选项部分代码的函数实现部分流程,接下来再给出完

的ping代码分析。这样,读者就可以看到一个完整协议实现应该考虑到的各个部分。

1.Ping代码主体流程

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

时需要使用SOCK_RAW选项。在循环等待回应信息处,用户可以指定"・f”洪泛选项,

时就会使用select函数来指定在一定的时间内进行回应。

2.主要选项说明

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

•d:调试选项(F_SO_DEBUG)

•f:洪泛选项(F_FLOOD)

•i:等待选项(FJNTERVAL)

•r:路由选项(F_RROUTE)

•1:广播选项(MULTICAST_NOLOOP)

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

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

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

图10.8ping主体流程图

3.源代码及注释

(1)主体代码

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

#include<sys/param.h>

#include<sys/socket.h>

#include<sys/file.h>

#include<sys/time.h>

#include<sys/signal.h>

#include<netinet/in.h>

#include<netinet/ip.h>

#include<netinet/ip_icmp.h>

#include<arpa/inet.h>

#include<netdb.h>

#include<unistd.h>

#include<stdlib.h>

#include<string.h>

#include<stdio.h>

#include<ctype.h>

#include<errno.

#include<getopt.h>

#include<resolv.h>

#defineF_FLOOD0x001

#defineF_INTERVAL0x002

#defineF_NUMERIC0x004

#defineF_PINGFILLED0x008

#defineF_QUIET0x010

#defineF_RROUTE0x020

#defineF_SO_DEBUG0x040

#defineF_SO_DONTROUTE0x080

#defineF_VERBOSE0x100

/*多播选项*/

intmoptions;

#defineMULTICAST_NOLOOP0x001

#defineMULTICAST_TTL0x002

#defineMULTICAST_IF0x004

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

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

Intmain(intargczchar*argv[])

(

structtimevaltimeout;

structhostent*hp;

structsockaddr_in*to;

structprotoent*proto;

structin_addrifaddr;

inti;

intch,fdmask,hold,packlen,preload;

u_char*datap,*packet;

char*target,hnamebuf[MAXHOSTNAMELEN];

u_charttl,loop;

intam_i_root;

stahxcchar*null=NULL;

/*__environ=&null;*/

am_i_root=(getuid()==0);

/*

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

*/

if((s=socket(AF_INETZSOCK_RAWZIPPROTO_ICMP))<0){

if(errno==EPERM){

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

}

elseperror("ping:socket'*);

exit(2);

preload=0;

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

while((ch=getopt(argc,argv,*'I:LRc:dfh:i:1:np:qrs:t:v"))!=EOF)

switch(ch){

case'c1:

npackets=atoi(optarg);

if(npackets<=0){

(void)fprintf(stderr,

"ping:badntimberofpacketstotransmit.\n");

exit(2);

break;

/*调用选项*/

case*d':

options|=F_SO_DEBUG;

break;

//flood选项*/

case1f1:

if(•am_i_root){

(void)fprintf(stderr,

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

exit⑵;

}

options|=F_FLOOD;

setbuf(stdout,NULL);

break;

/*等待选项*/

case'i':/*waitbetweensendingpackets*/

interval=ahox(optarg);

if(interval<=0){

(void)fprintf(stderr,

"ping:badtiminginterval.\n");

exit(2);

}

options|=F_INTERVAL;

break;

case'11:

if(!am_i_root){

(void)fprintf(stderr,

"ping:%s\n"zstrerror(EPERM));

exit(2);

}

preload=atoi(optarg);

if(preload<0){

(void)fprintf(stderr,

',ping:badpreloadvalue.\nn);

exit⑵;

}

break;

•••

default:

usage();

argcoptind;

argv+=optind;

if(argc•=1)

usage();

target=*argv;

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

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

memset(&whereto,0,sizeof(structsockaddr));

to=(structsockaddr_in*)&whereto;

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",target);

exit(2);

)

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

hostname=target;

#else

/*调用gethostbyname识别主机名*/

hp=gethostbyname(target);

if(!hp){

(void)fprintf(stderr,

"ping:unknownhost%s\n",target);

exit(2);

}

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)printf("PING%s(%s):%ddatabytes\nH,hostname,

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

datalen);

else

(void)printf("PING%s:%ddatabytes\n",hostname,datalen);

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

(void)signal(SIGINT,finish);

(void)signal(SIGALRM,catcher);

♦♦♦

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

for(;;){

structsockaddr_infrom;

registerintcc;

intfromlen;

if(options&F_FLOOD){

/“形成1cMp回应数据包,在后面会有讲解*/

pinger();

/*设定等待实践*/

timeout.tv_sec=0;

timeout.tv_usec=10000;

fdinask=1«s;

/*调用select函数*/

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

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

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

continue;

}

fromlen=sizeof(from);

/*接收客户端信息*/

if((cc=recvfrom(s,(char*)packet,packlen,3,

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

if(errno==EINTR)

continue;

perror("ping:recvfrora");

continue;

}

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

if(npackets&&nreceived>=npackets)

break;

finish(0);

/*NOTREACHED*/

return0;

}

(2)其他函数

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

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

staticvoid

catcher(intignore)

(

intwaittime;

(void)ignore;

pinger();

/*调用catcher函数*/

(void)signal(SIGALRM,catcher);

if(!npackets||ntransmitted<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回应数据包,其中H)是该进程的

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

差,这里的数据校验用到了后面定义的in_cksum函数。其代码如下所示:

staticvoid

pinger(void)

(

registerstructicmphdr*icp;

registerintcc;

inti;

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

icp=(structicmphdr*)outpack;

icp->icmp_type=ICMP_ECHO;

icp->icmp_code=0;

icp->icmp_cksum=0;

icp->icmp_seq=ntransmitted++;

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

CLR(icp->icmp_seq%mx_dup_ck);

/*设定等待实践*/

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(sz(char*)outpack,ccz0,&whereto,

sizeof(structsockaddr));

if(i<0||i?=cc)(

if(i<0)

perror("ping:sendto");

(void)printf("ping:wrote%s%dchars,ret=%d\nH,

hostname,cczi);

)

if(»(options&F_QUIET)&&options&F_FLOOD)

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

}

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

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

void

pr_pack(char*bufzintcc,structsockaddr_in*from)

(

registerstructicmphdr*icp;

registerinti;

registeru_char*cpz*dp;

/*#if0*/

registeru_long1;

registerintj;

staticintold_rrlen;

staticcharold_rr[MAX_IPOPTLEN];

/*#endif*/

structiphdr*ip;

structtimevaltv,*tp;

longtriptime=0;

inthlen,dupflag;

(void)gettimeofday(&tvz(structtimezone*)NULL);

/*检查工P数据包头信息*/

ip=(structiphdr*)buf;

hlen=ip->ip_hl«2;

if(cc<datalen+ICMP_MINLEN)(

if(options&F_VERBOSE)

(void)fprintf(stderr,

"ping:packettooshort(%dbytes)from%s\n",ccz

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

return;

}

/*工CMP部分显示*/

cc-=hlen;

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

if(icp->icmp_type==ICMP_ECHOREPLY){

i£(icp->icmp_id•=idenh)

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/103);

tsum+=triptime;

if(triptime<tmin)

tmin=triptime;

if(triptime>tmax)

tmax=triptime;

}

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

++nrepeats;

--nreceived;

dupflag=1;

}else{

SET(icp->icmp_seq%mx_dup_ck);

dupflag=0;

}

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

if(options&F_QUIET)

return;

if(options&F_FLOOD)

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

else(

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

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

icp->icmp_seq);

(void)printf("ttl=%d'zip->ip_ttl);

if(timing)

(void)printf("time=%ld.%Idms",triptime/10,

triptime%10);

if(dupflag)

(void)printf("(DUP!),);

/*checkthedata*/

#ifndeficmp_data

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

#else

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

#endif

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

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

if(*cp!=*dp){

(void)printf("\nwrongdatabyte#%dshouldbeOx%xbutwasOx%x"z

i,*dp,*cp);

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

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

if((i%32)==8)

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

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

}

break;

}

}

)

}else{

/*We'vegotsomethingotherthananECHOREPLY*/

if(!(options&F_VERBOSE))

return;

(void)printf("%dbytesfrom%s:",ccz

pr_addr(from->sin_addr.s_addr));

pr_icmph(icp);

/*#if0*/

/*显示其他工P选项*/

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

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

switch(*cp){

caseIPOPT_EOL:

hlen=0;

break;

caseIPOPT_LSRR:

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

hlen-=2;

j=*++cp;

++cp;

if(j>IPOPT_MINOFF)

for(;;){

1=*++cp;

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

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

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

if(1==0)

(void)printf("\tO.0.0.0");

else

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

hlen-=4;

j-=4;

if(j<=IPOPT__MINOFF)

break;

(void)putchar('\n');

}

break;

caseIPOPT_RR:

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

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

hlen-=2;

if(i>j)

i=j;

i-=IPOPT_MINOFF;

if(i<=0)

continue;

if(i==old_rrlen

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

&&?memcmp((char*)cp,old_rrzi)

&&»(options&FFLOOD)){

(void)printf("\t(sameroute)");

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

hlen-=i;

cp+=i;

break;

}

old_rrlen=i;

memcpy(old_rr,cpzi);

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

for(;;){

1=*++cp;

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

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

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

if(1==0)

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

else

(void)printf,pr_addr(ntohl(1)));

hlen-=4;

i-=4;

if(i<=0)

break;

(void)putchar(*\n1);

}

break;

caseIPOPT_NOP:

(void)printf("\nNOP");

break;

default:

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

break;

}

/*#endif*/

if(*(options&F_FLOOD)){

(void)putchar(*\n');

(void)fflush(stdout);

}

)

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

staticint

incksxim(ushort*addr,intlen)

registerintnleft=len;

registeru_short*w=addr;

registerintsum=0;

u_shortanswer=0;

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

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=~sum;

return(answer);

)

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

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

staticvoid

finish(intignore)

(

(void)ignore;

(void)signal(SIGINT,SIG_IGN);

(void)putchar(*\n1);

(void)fflush(stdout);

(void)printf("--%spingstatistics--\n",hostname);

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

(void)printf("%ldpacketsreceived,",nreceived);

if(nrepeats)

(void)printf("+%ldduplicates,”,nrepeats);

if(ntransmitted)

if(nreceived>ntransmitted)

(void)printf("——somebody'sprintinguppackets!");

else

(void)printf("%d%%packetloss",

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

ntransmitted));

(void)putchar(1\n1);

if(nreceived&&timing)

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

ms\n",

tmin/10,tmin%10,

(tsum/(nreceived+nrepeats))/10z

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

tmax/10,tmax%10);

if(nreceived=O)exit(1);

exit(O);

}

#ifdefnotdef

staticchar*ttab[]={

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

"DestUnreachable"z/♦net,host,proto,port,frag,sr+IP*/

"SourceQuench",/*IP*/

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

"Echo",

"TimeExceeded"z/"传输超时*/

"ParameterProblem",/'*工P参数问题*/

"Timestamp'*,/*id+seq+threetimestamps*/

"TimestampReply",/*"*/

"InfoRequest",/*id+sq*/

"InfoReply"/*"*/

};

#endif

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

staticvoid

pr_icmph(structicmphdr*icp)

(

switch(icp->icmp_type){

/*1CMP回应*/

caseICMP_ECHOREPLY:

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

/*XXXID+Seq+Data*/

break;

/*1CMP终点不可达*/

caseICMP_DEST_UNREACH:

switch(icp->icmp_code){

caseICMP_NET_UNREACH:

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

break;

caseICMP_HOST_UNREACH:

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

break;

caseICMP_PROT_UNREACH:

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

break;

•••

default:

(void)printf("DestUnreachable,UnknownCode:%d\n",

icp->icmp_code);

break;

)

/*PrintreturnedIPheaderinformation*/

#ifndeficmp_data

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

#else

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

#endif

break;

default:

(void)printf("Redirect,BadCode:%d",icp->icnro_code);

break;

}

(void)printf("(Newaddr:%s)\n",

inet_ntoa(icp->icmp_gwaddr));

#ifndeficmp_data

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

#else

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

#endif

break;

caseICMP_ECHO:

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

/*XXXID+Seq+Data*/

break;

caseICMP_TIME_EXCEEDED:

switch(icp->icmp_code){

caseICMP_EXC_TTL:

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

break;

caseICMP_EXC_FRAGTIME:

(void)printf("Fragreassemblytime

温馨提示

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

最新文档

评论

0/150

提交评论