版权说明:本文档由用户提供并上传,收益归属内容提供方,若内容存在侵权,请进行举报或认领
文档简介
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. 本站不保证下载资源的准确性、安全性和完整性, 同时也不承担用户因使用这些下载资源对自己和他人造成任何形式的伤害或损失。
最新文档
- 2026对外经济贸易大学非事业编人员招聘1人备考题库(北京)含答案详解(考试直接用)
- 2026浙江杭州上城区交通室内辅助人员招聘15人备考题库及参考答案详解
- 2026上海药品审评核查中心招聘辅助人员17名备考题库及答案详解(名校卷)
- 2026广东肇庆市广宁县畜牧兽医局招聘兽医协管员备考题库附答案详解(培优a卷)
- 山西长治市市属学校2026届省属公费师范毕业生专项招聘11人备考题库含答案详解
- 2026年宁波市慈溪市公开招聘教师129人(第三批)备考题库及答案详解(各地真题)
- 大学毕业论文致谢词 范文6篇
- 2026华宇云商创新园区社会招聘工作人员考试备考试题及答案解析
- 2026浙江丽水市汽车运输集团股份有限公司招聘工作人员4人考试备考试题及答案解析
- 2026黑龙江佳木斯富锦市市政设施管护中心招聘一线工程技术人员3人考试备考试题及答案解析
- 天平使用步骤课件
- 高原铁路隧道供氧系统管道施工
- 2026年材料员之材料员基础知识考试题库300道附参考答案【考试直接用】
- 企业董事长助理岗位职责书
- 2025年宠物服务产业园区建设项目可行性研究报告及总结分析
- 校车驾驶员安全培训课件
- 民兵军事训练教案
- 2025年国家开放大学《人体解剖生理学》期末考试复习试题及答案解析
- 2026社区工作者考试必考题库及答案(考点梳理)
- 江苏钢结构厂房加高施工方案
- 新能源材料与器件制备技术 课件 第7章:锂离子电池电解质材料
评论
0/150
提交评论