ping命令的设计与实现.docx
- 文档编号:18088705
- 上传时间:2023-08-13
- 格式:DOCX
- 页数:30
- 大小:62.71KB
ping命令的设计与实现.docx
《ping命令的设计与实现.docx》由会员分享,可在线阅读,更多相关《ping命令的设计与实现.docx(30页珍藏版)》请在冰点文库上搜索。
ping命令的设计与实现
设计报告
课程计算机网络
设计名称《ping命令的设计与实现》
专业班级计科094
同组人姓名
同组人学号
实验日期2013-04-10
指导教师
成绩
2013年04月10日
设计目的和要求
1、实验目的:
Ping命令向目的主机发送ICMPECHO—REQUEST请求并接收目的主机返回的响应报文,用来检验本地主机和远程的主机是否连接。
2.实验要求:
利用ICMP数据包,测试主机的连通性,通过课程设计,使学生熟悉ICMP报文结构,使学生对ICMP有更深的理解。
要求:
输出参考系统自带ping程序,命令行运行:
pingip
二、设计说明
设计分析:
使用原始套接字可以读写ICMP分组,利用原始套接字发送ICMP回显请求,并接收ICMP回显应答,通过icmp_send()发送ICMP回显示请求包,icmp_recv()接收ping目的主机的回复,并使用终端信号处理函数SIGINT处理信号,建立两个线程,一个用于发送数据,另一个用于接收响应数据,主程序等待两个线程运行完毕后再进行下一步动作。
最后,主程序讲发送数据和接收的数据进行统计,并将结果打印出来。
系统运行环境:
虚拟机:
Fedora14(linux操作系统)gcc
设计中的重点和难点:
ICMP数据包的打包和解包,以及从CRC16校验算法的分析实现
输入和输出条件:
在linux系统下运行ping在出现4个响应包后按Ctrl+c键停止发送。
三、系统详细设计
《Ping命令的设计与实现》
Ping命令向目的主机发送ICMPECHO—REQUEST请求并接收目的主机返回的响应报文,用来检验本地主机和远程的主机是否连接。
⏹协议格式
图1.1中已经对ICMP协议的报文格式进行了说明。
Ping的客户端方式的类型为8,代码值为0,表示ICMP的回显请求。
类型为0,代码为0是,是ICMP回显应答。
检验和为16为的crc16的算法。
078151631
类型(8位)
代码(8位)
校验和(16位)
此部分不同的类型和代码格式不同
图1.1ICMP报文的数据格式
图1.2所示为ping所使用的类型和代码格式。
包含16位的标始符和16为的序列号。
序列号是用于标识发送或者响应的序号,而标示符通常用于表明发送和接收此报的用户,一眼用进程的PID来识别。
078151631
类型
(8或0)
代码(0)
校验和
标示符
序列符
占位字节
图1.2ping的数据格式
例如一个用户的进程PID为1000,发送了一个序列号为1的回显请求报文,当此报文被目的主机正确处理并返回后,可以用PID来识别是否为当前的用户,并且用序列号来识别哪个报文被返回,通过发送报文到目的主机并接受响应,可以计算发送和接收二者之间的时间差,来判断网络的状况。
如图1.3所示,ping程序一般按照图中的框架进行设计。
主要分为发送数据和接收数据及计算时间差。
发送数据对组织好的数据进行发送,接收数据从网络上接收数据并判断其合法性,例如判断是否本进程发出的报文等。
图1.3ping程序的基本框架
由于ICMP必须使用原始套接字进行设计,要手动设置IP的头部和ICMP的头部并进行校验。
⏹校验和函数
TCP/IP协议栈使用的校验算法是比较经典的,对16为的数据进行累加计算,并返回计算结果。
需要注意的是对奇数个字节数据的计算,是将最后的有效数据作为最高位的字节,低字节填充了0。
/*CRC16校验和计算icmp_cksum
参数:
data:
数据
len:
数据长度
返回值:
计算结果,short类型
*/
staticunsignedshorticmp_cksum(unsignedchar*data,intlen)
{
intsum=0;/*计算结果*/
intodd=len&0x01;/*是否为奇数*/
unsignedshort*value=(unsignedshort*)data;
/*将数据按照2字节为单位累加起来*/
while(len&0xfffe){
sum+=*(unsignedshort*)data;
data+=2;
len-=2;
}
/*判断是否为奇数个数据,若ICMP报头为奇数个字节,会剩下最后一字节。
*/
if(odd){
unsignedshorttmp=((*data)<<8)&0xff00;
sum+=tmp;
}
sum=(sum>>16)+(sum&0xffff);
sum+=(sum>>16);
return~sum;
}
⏹设置IP发送报文的头部
ip头部格式:
structip
{
#if__BYTE_ORDER==__LITTLE_ENDIAN/*如果为小端*/
gnedintip_v:
4;/*版本*/
#endif
#if__BYTE_ORDER==__BIG_ENDIAN/*如果为大端*/
unsignedintip_v:
4;/*版本*/
unsignedintip_hl:
4;/*头部长度*/
#endif
u_int8_tip_tos;/*TOS,服务类型*/
u_shortip_len;/*总长度*/
u_shortip_id;/*标识值*/
u_shortip_off;*段偏移值*/
u_int8_tip_ttl;/*TTL,生存时间*/
u_int8_tip_p;/*协议类型*/
u_shortip_sum;/*校验和*/
structin_addrip_src,ip_dst;/*源地址和目的地址*/
};
⏹设置ICMP发送报文的头部
对于回显请求的ICMP报文,下面是ICMP结构简化形式:
structicmp
{
u_int8_ticmp_type;/*消息类型*/
u_int8_ticmp_code;/*消息类型的子码*/
u_int16_ticmp_cksum;/*校验和*/
union
{
u_charih_pptr;/*ICMP_PARAMPROB*/
structin_addrih_gwaddr;/*网关地址*/
structih_idseq/*显示数据报*/
{
u_int16_ticd_id;/*数据报ID*/
u_int16_ticd_seq;/*数据报的序号*/
}ih_idseq;
}icmp_hun;
#defineicmp_idicmp_hun.ih_idseq.icd_id
#defineicmp_seqicmp_hun.ih_idseq.icd_seq
union
{
struct
{
u_int8_tid_data[1];/*数据*/
}icmp_dun;
#defineicmp_dataicmp_dun.id_data
};
即仅包含消息类型、消息代码、校验和、数据报的ID、数据报的序列号即ICMP数据段几个部分。
校验和的值在计算之前其他的值应该先进行填充,而校验和也需要设置为0来占位,然后在计算真正的校验和值。
ICMP回显得数据部分可以任意设置,但是以太网包的总长度不能小于以太网的最小值,即总长度不能小于46,由于IP头部为20字节,ICMP头部为8个字节,以太网头部占用14个字节,因此ICMP回显包的最小值为46-20-8-14=4个字节。
ØICMP回显请求的类型为8,即ICMP-ECHO。
ØICMP回显请求的代码值为0.
ØICMP回显请求的序列号是一个16位的值,通常由一个递增的值生成。
ØICMP回显请求的ID用于区别,通常用进程的PID填充。
进行ICMP头部校验的代码如下:
/*设置ICMP报头*/
staticvoidicmp_pack(structicmp*icmph,intseq,structtimeval*tv,intlength)
{
unsignedchari=0;
/*设置报头*/
icmph->icmp_type=ICMP_ECHO;/*ICMP回显请求*/
icmph->icmp_code=0;/*code值为0*/
icmph->icmp_cksum=0;/*先将cksum值填写0,便于之后的cksum计算*/
icmph->icmp_seq=seq;/*本报的序列号*/
icmph->icmp_id=pid&0xffff;/*填写PID*/
for(i=0;i icmph->icmp_data[i]=i; /*计算校验和*/ icmph->icmp_cksum=icmp_cksum((unsignedchar*)icmph,length); } ⏹剥离ICMP接受报文的头部 函数icmp_unpack()用于剥离IP头部,分析ICMP头部的值。 判断是否为正确的ICMP报文,并打印结果。 参数buf为剥去了以太网部分数据的IP数据报文,len为数据长度。 可以利用IP头部的参数快速地跳ICMP报文部分,IP结构的ip_hl标识IP头部的长度,由于ip_hl标识的是4字节单位,所以需要乘以4来获得ICMP段的地址。 获得ICMP数据段后,判断其类型是否为ICMP_ECHOREPLY,并核实其标识是否为本进程的PID。 由于需要判断数据报文的往返时间,在本程序中需要先查找这个包发送时的时间,与当前时间进行计算后,可以得出本地主机与目标主机之间网络ICMP回显报文的差值。 程序需要累加成功接收到的报文用于程序退出时的统计。 /*解压接收到的包,并打印信息*/ staticinticmp_unpack(char*buf,intlen) { inti,iphdrlen; structip*ip=NULL; structicmp*icmp=NULL; intrtt; ip=(structip*)buf;/*IP头部*/ iphdrlen=ip->ip_hl*4;/*IP头部长度*/ icmp=(structicmp*)(buf+iphdrlen);/*ICMP段的地址*/ len-=iphdrlen; if(len<8)/*判断长度是否为ICMP包*/ { printf("ICMPpackets\'slengthislessthan8\n"); return-1; } /*ICMP类型为ICMP_ECHOREPLY并且为本进程的PID*/ if((icmp->icmp_type==ICMP_ECHOREPLY)&&(icmp->icmp_id==pid)) { structtimevaltv_internel,tv_recv,tv_send; /*在发送表格中查找已经发送的包,按照seq*/ pingm_pakcet*packet=icmp_findpacket(icmp->icmp_seq); if(packet==NULL) return-1; packet->flag=0;/*取消标志*/ tv_send=packet->tv_begin;/*获取本包的发送时间*/ gettimeofday(&tv_recv,NULL);/*读取此时时间,计算时间差*/ tv_internel=icmp_tvsub(tv_recv,tv_send); rtt=tv_internel.tv_sec*1000+tv_internel.tv_usec/1000; /*打印结果,包含 *ICMP段长度源IP地址包的序列号TTL时间差 */ printf("%dbytefrom%s: icmp_seq=%uttl=%drtt=%dms\n", len, inet_ntoa(ip->ip_src), icmp->icmp_seq, ip->ip_ttl, rtt); packet_recv++;/*接收包数量加1*/ } else return-1; } 函数的返回值为-1时表示褚翠,其他值则正常。 ⏹计算时间差 由于需要评估网络状况,在发送数据报文的时候保存发送时间,接收到报文后,计算两个时刻之间的差值,生成了ICMP源主机和目标主机之间的网络状况的时间评估。 /*计算时间差time_sub 参数: end,接收到的时间 begin,开始发送的时间 返回值: 使用的时间 */ staticstructtimevalicmp_tvsub(structtimevalend,structtimevalbegin) { structtimevaltv; /*计算差值*/ tv.tv_sec=end.tv_sec-begin.tv_sec; tv.tv_usec=end.tv_usec-begin.tv_usec; /*如果接收时间的usec值小于发送时的usec值,从usec域借位*/ if(tv.tv_usec<0) { tv.tv_sec--; tv.tv_usec+=1000000; } returntv; } ⏹发送报文 发送报文函数是一个线程,每隔1s向目的主机发送一个ICMP回显请求报文,它在整个程序处于激活状态(alive为1)是一直发送报文。 (1)获得当前的时间值,按照序列号packet_send将ICMP报文打包到缓冲区send_buff中后,发送到目的地址。 发送成功后,记录发送报文的状态: Ø序号seq为packet_send。 Ø标志flag为1,表示已经发送但是没有收到响应。 Ø发送时间为之前获得的时间。 (2)每次发送成功后序号值会增加1,即packet_send++. (3)在线程开始进入主循环while(alive)之前,将整个程序的开始发送时间记录下来,用于在程序退出的时候进行全局统计,即gettimeofday(&tv_begin,NULL),j将时间保存在变量tv_begin中。 /*发送ICMP回显请求包*/ staticvoid*icmp_send(void*argv) { structtimevaltv; tv.tv_usec=0; tv.tv_sec=1; gettimeofday(&tv_begin,NULL);/*保存程序开始发送数据的时间*/ while(alive) { intsize=0; structtimevaltv; gettimeofday(&tv,NULL);/*当前包的发送时间*/ icmp_pack((structicmp*)send_buff,packet_send,&tv,64);/*打包数据*/ size=sendto(rawsock,send_buff,64,0,(structsockaddr*)&dest,sizeof(dest)); /*将数据由指定的socket发送给目的地址*/ if(size<0) { perror("sendtoerror"); continue; } else { /*在发送包状态数组中找一个空闲位置*/ pingm_pakcet*packet=icmp_findpacket(-1); if(packet) { packet->seq=packet_send;/*设置seq*/ packet->flag=1;/*已经使用*/ gettimeofday(&packet->tv_begin,NULL);/*发送时间*/ packet_send++;/*计数增加*/ } } sleep (1);/*每隔一秒,发送一个ICMP回显请求包*/ } } ⏹接收报文 与发送函数一样,接收报文也用一个线程实现,使用select()轮询等待报文到来。 当接收到一个报文后使用函数icmp_unpack()来解包和查找报文之前发送时的记录,获取发送时间,计算收发差值并打印信息。 (1)接收成功后将合法的报文记录重置为没有使用,flag为0.。 (2)接收报文数量增加1. (3)为了防止丢包,select()轮询时间设置的比较短。 /*接收ping目的主机的回复*/ staticvoid*icmp_recv(void*argv) { /*轮训等待时间*/ structtimevaltv; tv.tv_usec=200; tv.tv_sec=0; fd_setreadfd; /*当没有信号发出一直接收数据*/ while(alive) { intret=0; FD_ZERO(&readfd); FD_SET(rawsock,&readfd); ret=select(rawsock+1,&readfd,NULL,NULL,&tv); switch(ret) { case-1: /*错误发生*/ break; case0: break;/*超时*/ default: /*收到一个包*/ { intfromlen=0; structsockaddrfrom; /*接收数据*/ intsize=recv(rawsock,recv_buff,sizeof(recv_buff),0); if(errno==EINTR) { perror("recvfromerror"); continue; } ret=icmp_unpack(recv_buff,size);/*解包并设置相关变量*/ if(ret==-1) continue; } break; } } } ⏹主函数过程 Ping程序的实现使用了两个线程,一个线程icmp_send()用于发送请求,一个线程icmp_recv()用于接收远程主机的响应。 当变量alive为0时,两个线程退出。 1.ping数据的数据结构 类型为结构structpingm_packet的变量pingpacket用于保存发送数据报文的状态: Øtv_begin用于保存发送的时间。 Øtv_end用于保存数据报文接收到的时间。 Øseq是序列号,用于标识报文,作为索引。 Øflag用于表示本单元的状态,1表示数据报文已经发送,但是没有收到回应包; 0表示已经接收到回应报文,这个单元可以再次用于标识发送的报文。 /*保存已经发送包的状态值*/ typedefstructpingm_pakcet{ structtimevaltv_begin;/*发送的时间*/ structtimevaltv_end;/*接收到的时间*/ shortseq;/*序列号*/ intflag;/*1,表示已经发送但没有接收到回应包 0,表示接收到回应包*/ }pingm_pakcet; staticpingm_pakcetpingpacket[128]; 2.SIGINT处理函数 本程序借去了信号SIGINT,用函数icmp_sigint()对SIGINT进行处理。 当用户按下Ctrl+C键时,将alive设置为0,使得接收和发送两个线程退出,并计算结束时的时间。 代码如下: /*终端信号处理函数SIGINT*/ staticvoidicmp_sigint(intsigno) { /*告诉接收和发送线程结束程序*/ alive=0; /*读取程序结束时间*/ gettimeofday(&tv_end,NULL); /*计算一下总共所用时间*/ tv_interval=icmp_tvsub(tv_end,tv_begin); return; } 3.查找数组中的标示函数icmp_findpacket() 函数icmp_findpacket()用于在数组pingpacket中查找一个报文的标识,参数为-1时表示查找一个空包,存放已经发送成功的数据报文;其他值表示查找seq匹配的标识。 /*查找一个合适的包位置 当seq为-1时,表示查找空包 其他值表示查找seq对应的包*/ staticpingm_pakcet*icmp_findpacket(intseq) { inti=0; pingm_pakcet*found=NULL; /*查找包的位置*/ if(seq==-1)/*查找空的位置*/ { for(i=0;i<128;i++) { if(pingpacket[i].flag==0) { found=&pingpacket[i]; break; } } } elseif(seq>=0)/*查找对应SEQ的包*/ { for(i=0;i<128;i++) { if(pingpacket[i].seq==seq) { found=&pingpacket[i]; break; } } } returnfound; } 4.统计数据结果函数icmp_statistics()
- 配套讲稿:
如PPT文件的首页显示word图标,表示该PPT已包含配套word讲稿。双击word图标可打开word文档。
- 特殊限制:
部分文档作品中含有的国旗、国徽等图片,仅作为作品整体效果示例展示,禁止商用。设计者仅对作品中独创性部分享有著作权。
- 关 键 词:
- ping 命令 设计 实现