版权说明:本文档由用户提供并上传,收益归属内容提供方,若内容存在侵权,请进行举报或认领
文档简介
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. 本站不保证下载资源的准确性、安全性和完整性, 同时也不承担用户因使用这些下载资源对自己和他人造成任何形式的伤害或损失。
最新文档
- 江苏省苏州市苏州地区学校2027届九上化学期中监测试题含解析
- 2026山东江海汇鑫期货有限公司招聘17人模拟试卷及参考答案详解(突破训练)
- 2026云南临沧市住房和城乡建设局招聘公益性岗位人员1人备考题库含答案详解(夺分金卷)
- 2026中国长城资产管理股份有限公司社会招聘3人备考题库含完整答案详解(名师系列)
- 2026中国智能城市建设行业市场供需分析与发展前景报告
- 2026燃料电池行业市场技术进展详细研究及氢能源应用与产业布局研究
- 2026日本机器人产业市场竞争格局发展现状投资规划分析研究
- 2026年四川中江县卫生健康系统事业单位 第二批医疗卫生辅助岗招募17人备考题库含答案详解【巩固】
- 2026贵州遵义市播州区退役军人事务局关于招聘城镇公益性岗位人员1人的考前冲刺试卷【重点】附答案详解
- 2026年固原市本级机关事业单位助理岗见习报名考前冲刺试卷及答案详解(有一套)
- 物业风控案例分析
- ECRS培训课件教学课件
- 临床放射生物学教案(2025-2026学年)
- 慢性肾脏病5期患者护理查房
- 静脉输液药物的配伍与护理观察
- 糖尿病患者白内障手术围术期管理
- 2025年银行担保品评估专项训练测试试卷(含答案)
- 2025年社会救助业务题库及答案
- 矿用电缆知识培训课件
- 玻璃模具设计方案
- 社群服务党建活动方案
评论
0/150
提交评论