还剩7页未读,继续阅读
文本内容:
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;地址掩码回答18o虽然每种报文都有不同的格式,但它们开始都有下面三段一个8位整数报文TYPE类型段;一个8位CODE代码码段,提供更多的报文类型信息;一个16位CHECKSUM校验和段;此外,报告差错的ICMP报文还包含产生问题数据报的网际报头及前64位数据一个ICMP回送请求与回送回答报文的格式如表
10.17所示表
10.17ICMP回送请求与回送回答报文格式类型CODE校验和[CHECKSUM]标识符序列号《嵌入式Linux应用程序开发详解》一一第10章、嵌入式Linux网络编程数据
10.
4.2ping源码分析下面的ping,c源码是在busybox里实现的源码在这个完整的ping,c代码中有较多选项的部分代码,因此,这里先分析除去选项部分代码的函数实现部分流程,接下来再给出完整的ping代码分析这样,读者就可以看到一个完整协议实现应该考虑到的各个部分
1.Ping代码主体流程Ping,c主体流程图如下图
10.8所示另外,由于ping是IP层的协议,因此在建立socket时需要使用SOCK_RAW选项在循环等待回应信息处,用户可以指定“-记洪泛选项,这时就会使用select函数来指定在一定的时间内进行回应
2.主要选项说明Ping函数主要有以下几个选项:d调试选项F_SO_DEBUG f洪泛选项F_FLOODi等待选项F_INTERVALr路由选项F_RR0UTE1广播选项MULTICAST_NOLOOP对于这些选项,尤其是路由选项、广播选项和洪泛选项都会有不同的实现代码另外,ping函数可以接受用户使用的SIGINT和SIGALARM信号来结束程序,它们分别指向了不同的结束代码,请读者阅读下面相关代码图
10.8ping主体流程图voidprintf!,%d bytesfrom%sicmp_seq=%u,r,cc,inet_ntoa*struct inaddr*from-sin_addr.s addr,icp-icmp_seq;voidprintf f,ttl=%dH,ip-ip_ttl;if timingvoidprintf!,time=%ld.%ld ms,triptime/10,triptime%10;if dupflagvoidprintfDUP!;/*check thedata*/ttifndef icmp_datacp=u_char*icp+1+8;#elsecp=u_char*icp-icmp_data+8;ttendifdp=outpack[8+sizeofstruct timeval];for i=8;idatalen;++i,++cp,++dp{if*cp!=*dp{voidprintf!,\nwrong databyte#%d shouldbe Ox%x butwas Ox%x,r,i,*dp,*cp;cp=u_char*icp+1;for i=8;idatalen;++i,++cp{if i%32==8voidprintf\n\t;voidprintf!,%x二*cp;}break;}else{/*Weve gotsomething otherthan anECHOREPLY*/if!optionsF_VERB0SEreturn;voidprintf!,%d bytesfrom%s:cc,pr_addr from-sin_addr.s_addr;pr_icmphicp;/*#if0*//*显示其他IP选项*/cp=u_char*buf+sizeof struct iphdr;for;hlenintsizeof struct iphdr;一一hlen,++cp
3.源代码及注释1主体代码ping代码的主体部分可以四部分,首先是一些头函数及宏定义:^includesys/param.h ttinclude sys/socket.h ttinclude sys/file.h^includesys/time.h#includesys/signal.h^includenetinet/in.h ttincludenetinet/ip.h ttincludenetinet/ip_icmp.h#include arpa/inet.h ttincludenetdb.h^include unistd.httinclude stdlib.h ttincludestring.h#include stdio.httinclude ctype.h^include errno.h ttincludegetopt.h ttincluderesolv.h#define F_FL00D0x001#define F_INTERVAL0x002^defineF_NUMERIC0x004^define F_PINGFILLED0x008^define FQUIET0x010^defineF_RR0UTE0x020^define F_S0_DEBUG0x040#define F_S0_D0NTR0UTE0x080^define F_VERB0SE0x100/*多播选项*/int moptions;ttdefineMULTICAST_N0L00P0x001#defineMULTICAST_TTL0x002^defineMULTICAST_IF0x004《嵌入式Linux应用程序开发详解》一一第10章、嵌入式Linux网络编程接下来的第2部分是建立socket并处理选项Int mainintargc,char*argv[]{struct timevaltimeout;struct hostent*hp;struct sockaddr_in*to;struct protoent*proto;struct in_addr ifaddr;int i;int ch,fdmask,hold,packlen,preload;u_char*datap,packet;char target,hnamebuf[MAXHOSTNAMELEN];u_char ttl,loop;int am_i_root;•••static char*null=NULL;/*_environ=null;*/am_i_root=getuid==0;/**建立socket连接,并且测试是否是root用户*/if s=socket AF_INET,SOCK_RAW,IPPROTOJCMP0{if errno==EPERM{fprintf stderr,pingping mustrun asroot\nH;}else perrornpingsocketH;exit2;preload=0;datap=outpack[8+sizeofstruct timeval];while ch=getopt argc,argv,ILRcdfhi1npqrstv!!!=EOFswitchch{case cnpackets=atoioptarg;if npackets=0{voidfprintfstderr,pingbad numberof packetsto transmit.\n;exit2;break;/*调用选项*/case doptions|=F_S0_DEBUG;break;/*flood选项*/case f1if!am_i_root{voidfprintfstderr,ping%s\n,r,strerror EPERM;exit⑵;options|=F_FL00D;setbuf stdout,NULL;break;/*等待选项*/case i/*wait betweensending packets*/interval=atoioptarg;if interval=0{voidfprintfstderr,pingbad timinginterval.\nn;exit2;}options|=F_INTERVAL;break;case1if!am_i_root{voidfprintfstderr,ping%s\n,r,strerror EPERM;exit2;preload=atoioptarg;if preload0{voidfprintfstderr,“pingbad preloadvalue.\n!!;exit2;break;•••defaultusage;argc-=optind;argv+=optind;if argc!=1usage;target=*argv;接下来的第3部分是用于获取地址,这里主要使用了inet_aton函数,将点分十进制地址转化为二进制地址当然,作为完整的ping程序看较完善的出错处理:memsetwhereto,0,sizeof struct sockaddr;to=struct sockaddr_in*whereto;to-sin_family=AF_INET;/*地址转换函数*/if inet_atontarget,to-sin_addr{hostname二target;}else{#if0char*addr=resolve_nametarget,0;if!addr{voidfprintfstderr,pingunknown host%s\n”,target;exit⑵;}to-sin_addr.s_addr=inet_addraddr;hostname=target;#else/*调用gethostbyname识别主机名*/hp=gethostbynametarget;if!hp{voidfprintfstderr,pingunknown host%s\n,r,target;exit2;to-sin_family=hp-h_addrtype;if hp-h_lengthint sizeofto-sin_addr{hp-h_length=sizeof to-sin_addr;memcpyto-sin_addr,hp-h_addr,hp-h_length;voidstrncpyhnamebuf,hp-h_name,sizeofhnamebuf-1;hostname=hnamebuf;ttendif接下来的一部分主要是对各个选项如路由、多播的处理,这里就不做介绍了再接下来是ping函数的最主要部分,就是接收无限循环回应信息,这里主要用到了函数recvfrom另外,对用户中断信息也有相应的处理,如下所示if to-sin_family==AF_INETvoidprintf!,PING%s%s%d databytesn”,hostname,inet_ntoa*struct inaddr*to-sin addr.s_addr,datalen;elsevoidprintf!,PING%s%d databytesn”,hostname,datalen;/*若程序接收到SIGINT或SIGALRM信号,调用相关的函数*/voidsignalSIGINT,finish;voidsignal SIGALRM,catcher;/*循环等待客户端的回应信息*/for;;{struct sockaddr_in from;register int cc;int fromlen;if optionsF_FLOOD{/*形成ICMP回应薪据包,在后面会有讲解*/pinger;/*设定等待实践*/timeout.tv_sec=0;timeout.tv_usec=10000;fdmask=1s;/*调用select函数*/《嵌入式Linux应用程序开发详解》一一第10章、嵌入式Linux网络编程if selects+1,fd_set*fdmask,fd_set*NULL,fd_set*NULL,timeout1continue;fromlen=sizeof from;/*接收客户端信息*/if cc=recvfroms,char*packet,packlen,0,struct sockaddr*from,fromlen0{if errno二二EINTRcontinue;perrorHpingrecvfromn;continue;}pr_packchar*packet,cc,from;if npacketsnreceived=npacketsbreak;}finish0;/*NOTREACHED*/return0;2其他函数下面的函数也是ping程序中用到的重要函数首先catcher函数是用户在发送SIGINT时调用的函数,在该函数中又调用了SIGALARM信号的处理来结束程序static voidcatcherint ignoreint waittime;voidignore;pinger;/*调用catcher函数*/voidsignalSIGALRM,catcher;if!npackets||ntransmittednpackets alarmu_intinterval;else{if nreceived{waittime=2*tmax/1000;if Iwaittimewaittime=1;if waittimeMAXWAIT waittime=MAXWAIT;}elsewaittime=MAXWAIT;/*调用finish函数,并设定一定的等待实践*/voidsignalSIGALRM,finish;voidalarmu_intwaittime;Pinger函数也是一个非常重要的函数,用于形成ICMP回应数据包,其中ID是该进程的ID,数据段中的前8字节用于存放时间间隔,从而可以计算ping程序从对端返回的往返时延差,这里的数据校验用到了后面定义的in_cksum函数其代码如下所示static voidpingervoid{register struct icmphdr*icp;register intcc;int i;/*形成icmp信息包,填写icmphdr结构体中的各项数据*/icp=struct icmphdr*outpack;icp-icmp_type=ICMP_ECH0;icp-icmp_code=0;icp-icmp_cksum=0;icp-icmp_seq=ntransmitted++;icp-icmp_id=ident;/*ID*/CLRicp-icmp_seq%mx_dup_ck;/*设定等待实践*/if timingvoidgettimeofdaystruct timeval*outpack
[8],struct timezone*NULL;cc二datalen+8;/*skips ICMPportion*/《嵌入式Linux应用程序开发详解》一一第10章、嵌入式Linux网络编程/*compute ICMPchecksum here*/icp-icmp_cksum=in_cksumu_short*icp,cc;i=sendtos,char*outpack,cc,0,whereto,sizeof structsockaddr;if i0||i!=cc{if i0perrorpingsendto;voidprintf f,pingwrote%s%d chars,ret=%d\n,r,hostname,cc,i;if!optionsF_QUIEToptionsF_FL00D voidwriteSTD0UT_FILEN0,D0T,1;pr_pack是数据包显示函数,分别打印出IP数据包部分和ICMP回应信息在规范诂程序中通常将数据的显示部分独立出来,这样就可以很好地加强程序的逻辑性和结构性voidpr_packchar*buf,intcc,structsockaddr_in*from{register structicmphdr*icp;register inti;register u_char*cp,*dp;/*#if0*/register u_long1;register intj;static intold_rrlen;static charold_rr[MAX_IPOPTLEN];/*#endif*/structiphdr*ip;struct timevaltv,*tp;long triptime=0;int hlen,dupflag;voidgettimeofdaytv,struct timezone*NULL;/*检查IP数据包头信息*/ip=structiphdr*buf;hlen=ip-ip_hl2;if ccdatalen+ICMP_MINLEN{if optionsF_VERBOSEvoidfprintfstderr,pingpacket tooshort%d bytesfrom%s\nn,cc,inet_ntoa*struct in_addr*from-sin_addr.s_addr;return;/*ICMP部分显示*/cc-二hlen;icp=structicmphdr*buf+hlen;if icp-icmp_type==ICMP_ECHOREPLY{if icp-icmp_id!=ident ifreturn;/*1Twas notour ECHO*/++nreceived;if timing{ttifndef icmpdata tp=struct timeval#elsetp=structtimeval*icp+1;ttendiftvsubtv,tp;triptime*icp-icmp_data;=tv.tv_sec tsum+=triptime;if triptimetmin tmin=triptime;if*10000+tv.tv_usec/100;triptimetmax tmax=triptime;TSTicp-icmp_seq%mx_dup_ck{++nrepeats;一nreceived;dupflag=1;}else{SETicp-icmp_seq%mx_dup_ck;dupflag=0;《嵌入式Linux应用程序开发详解》一一第10章、嵌入式Linux网络编程if optionsF_QUIETreturn;if optionsFFLOODvoidwriteSTD0UT_FILEN0,BSPACE,1;else{。