ping命令的设计与实现_第1页
ping命令的设计与实现_第2页
ping命令的设计与实现_第3页
ping命令的设计与实现_第4页
ping命令的设计与实现_第5页
已阅读5页,还剩17页未读 继续免费阅读

下载本文档

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

文档简介

1、设计报告课 程 计算机网络 设计名称 ping命令的设计与实现 专业班级 计科094 同组人姓名 同组人学号 实验日期 2013-04-10 指导教师 成 绩 2013 年 04 月 10 日设计目的和要求1、实验目的:Ping命令向目的主机发送ICMP ECHOREQUEST请求并接收目的主机返回的响应报文,用来检验本地主机和远程的主机是否连接。2.实验要求:利用ICMP数据包,测试主机的连通性,通过课程设计,使学生熟悉ICMP报文结构,使学生对ICMP有更深的理解。要求:输出参考系统自带ping程序,命令行运行:ping ip二、设计说明设计分析:使用原始套接字可以读写ICMP分组,利用原

2、始套接字发送ICMP回显请求,并接收ICMP回显应答,通过icmp_send()发送ICMP回显示请求包,icmp_recv() 接收ping目的主机的回复,并使用终端信号处理函数SIGINT处理信号,建立两个线程,一个用于发送数据,另一个用于接收响应数据,主程序等待两个线程运行完毕后再进行下一步动作。最后,主程序讲发送数据和接收的数据进行统计,并将结果打印出来。系统运行环境:虚拟机:Fedora14(linux操作系统) gcc设计中的重点和难点:ICMP数据包的打包和解包,以及从CRC16校验算法的分析实现输入和输出条件:在linux系统下运行ping 在出现4个响应包后按Ctrl+c键停

3、止发送。 三、系统详细设计 Ping命令的设计与实现 Ping命令向目的主机发送ICMP ECHOREQUEST请求并接收目的主机返回的响应报文,用来检验本地主机和远程的主机是否连接。n 协议格式图1.1中已经对协议的报文格式进行了说明。Ping 的客户端方式的类型为8,代码值为0,表示ICMP的回显请求。类型为0,代码为0是,是ICMP回显应答。检验和为16为的 crc16 的算法。0 7 8 15 16 31类型(8位)代码(8位)校验和(16位)此部分不同的类型和代码格式不同 图 1.1 ICMP报文的数据格式图1.2所示为 ping所使用的类型和代码格式。包含16位的标始符和16为的序

4、列号。序列号是用于标识发送或者响应的序号,而标示符通常用于表明发送和接收此报的用户,一眼用进程的PID来识别。0 7 8 15 16 31类型(8或0)代码(0)校验和标示符序列符占位字节图1.2 ping的数据格式例如一个用户的进程PID为1000,发送了一个序列号为1的回显请求报文,当此报文被目的主机正确处理并返回后,可以用PID来识别是否为当前的用户,并且用序列号来识别哪个报文被返回,通过发送报文到目的主机并接受响应,可以计算发送和接收二者之间的时间差,来判断网络的状况。如图1.3所示,ping程序一般按照图中的框架进行设计。主要分为发送数据和接收数据及计算时间差。发送数据对组织好的数据

5、进行发送,接收数据从网络上接收数据并判断其合法性,例如判断是否本进程发出的报文等。开始设置发送数据计算机发送数据校验和发送数据接收数据计算时速差解包判断正误结束图1.3 ping程序的基本框架由于ICMP必须使用原始套接字进行设计,要手动设置IP的头部和ICMP的头部并进行校验。n 校验和函数TCP/IP 协议栈使用的校验算法是比较经典的,对16为的数据进行累加计算,并返回计算结果。需要注意的是对奇数个字节数据的计算,是将最后的有效数据作为最高位的字节,低字节填充了0。/* CRC16校验和计算icmp_cksum参数:data:数据len:数据长度返回值:计算结果,short类型*/stat

6、ic unsigned short icmp_cksum(unsigned char *data, int len) int sum=0;/* 计算结果 */int odd = len & 0x01;/*是否为奇数*/unsigned short *value = (unsigned short*)data;/*将数据按照2字节为单位累加起来*/ while( len & 0xfffe) sum += *(unsigned short*)data;data += 2;len -=2; /*判断是否为奇数个数据,若ICMP报头为奇数个字节,会剩下最后一字节。*/ if( odd)

7、 unsigned short tmp = (*data)<<8)&0xff00; sum += tmp; sum = (sum >>16) + (sum & 0xffff); sum += (sum >>16) ; return sum;n 设置IP发送报文的头部ip头部格式:struct ip#if _BYTE_ORDER = _LITTLE_ENDIAN /* 如果为小端 */gned int ip_v:4;/* 版本 */#endif#if _BYTE_ORDER = _BIG_ENDIAN /*如果为大端*/unsigned int

8、 ip_v:4;/* 版本 */unsigned int ip_hl:4;/* 头部长度 */#endifu_int8_t ip_tos;/* TOS,服务类型 */u_short ip_len;/* 总长度 */u_short ip_id;/* 标识值 */u_short ip_off;* 段偏移值 */u_int8_t ip_ttl;/* TTL,生存时间 */u_int8_t ip_p;/* 协议类型 */u_short ip_sum;/* 校验和 */struct in_addr ip_src, ip_dst;/* 源地址和目的地址 */;n 设置ICMP发送报文的头部对于回显请求的I

9、CMP报文,下面是ICMP结构简化形式:struct icmpu_int8_t icmp_type;/* 消息类型 */u_int8_t icmp_code;/* 消息类型的子码 */u_int16_t icmp_cksum;/* 校验和 */ union u_char ih_pptr;/* ICMP_PARAMPROB */ struct in_addr ih_gwaddr;/* 网关地址 */ struct ih_idseq/* 显示数据报 */ u_int16_t icd_id;/* 数据报ID */ u_int16_t icd_seq;/* 数据报的序号 */ ih_idseq; ic

10、mp_hun; #define icmp_idicmp_hun.ih_idseq.icd_id#define icmp_seq icmp_hun.ih_idseq.icd_seq union struct u_int8_t id_data1;/* 数据 */ icmp_dun;#defineicmp_dataicmp_dun.id_data;即仅包含消息类型、消息代码、校验和、数据报的ID、数据报的序列号即ICMP数据段几个部分。校验和的值在计算之前其他的值应该先进行填充,而校验和也需要设置为0来占位,然后在计算真正的校验和值。ICMP回显得数据部分可以任意设置,但是以太网包的总长度不能小于以

11、太网的最小值,即总长度不能小于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报头*/static void icmp_pack(struct icmp *icmph, int seq, struct

12、timeval *tv, int length ) unsigned char i = 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< length; i+)icmp

13、h->icmp_datai = i;/* 计算校验和 */icmph->icmp_cksum = icmp_cksum(unsigned char*)icmph, length);n 剥离ICMP接受报文的头部函数icmp_unpack()用于剥离IP头部,分析ICMP头部的值。判断是否为正确的ICMP报文,并打印结果。参数buf为剥去了以太网部分数据的IP数据报文,len为数据长度。可以利用IP头部的参数快速地跳ICMP报文部分,IP结构的ip_hl标识IP头部的长度,由于ip_hl标识的是4字节单位,所以需要乘以4来获得ICMP段的地址。获得ICMP数据段后,判断其类型是否为I

14、CMP_ECHOREPLY,并核实其标识是否为本进程的PID。由于需要判断数据报文的往返时间,在本程序中需要先查找这个包发送时的时间,与当前时间进行计算后,可以得出本地主机与目标主机之间网络ICMP回显报文的差值。程序需要累加成功接收到的报文用于程序退出时的统计。/* 解压接收到的包,并打印信息 */static int icmp_unpack(char *buf,int len) int i,iphdrlen;struct ip *ip = NULL;struct icmp *icmp = NULL;int rtt;ip=(struct ip *)buf; /* IP头部 */iphdrle

15、n=ip->ip_hl*4;/* IP头部长度 */icmp=(struct icmp *)(buf+iphdrlen);/* ICMP段的地址 */len-=iphdrlen;if( len<8) /* 判断长度是否为ICMP包 */ printf("ICMP packets's length is less than 8n");return -1;/* ICMP类型为ICMP_ECHOREPLY并且为本进程的PID */if( (icmp->icmp_type=ICMP_ECHOREPLY) && (icmp->icmp_

16、id= pid) )struct timeval tv_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_inte

17、rnel = icmp_tvsub(tv_recv,tv_send); rtt = tv_internel.tv_sec*1000+tv_internel.tv_usec/1000; /* 打印结果,包含* ICMP段长度 源IP地址 包的序列号 TTL 时间差*/printf("%d byte from %s: icmp_seq=%u ttl=%d rtt=%d msn",len,inet_ntoa(ip->ip_src),icmp->icmp_seq,ip->ip_ttl,rtt );packet_recv +;/* 接收包数量加1 */elseret

18、urn -1;函数的返回值为-1时表示褚翠,其他值则正常。n 计算时间差由于需要评估网络状况,在发送数据报文的时候保存发送时间,接收到报文后,计算两个时刻之间的差值,生成了ICMP源主机和目标主机之间的网络状况的时间评估。/*计算时间差time_sub参数:end,接收到的时间begin,开始发送的时间返回值:使用的时间*/static struct timeval icmp_tvsub(struct timeval end,struct timeval begin)struct timeval tv;/*计算差值*/tv.tv_sec = end.tv_sec - begin.tv_sec;

19、tv.tv_usec = end.tv_usec - begin.tv_usec;/* 如果接收时间的usec值小于发送时的usec值,从usec域借位 */if(tv.tv_usec < 0)tv.tv_sec -;tv.tv_usec += 1000000; return tv;n 发送报文发送报文函数是一个线程,每隔1s向目的主机发送一个ICMP回显请求报文,它在整个程序处于激活状态(alive为1)是一直发送报文。(1) 获得当前的时间值,按照序列号packet_send将ICMP报文打包到缓冲区send_buff中后,发送到目的地址。发送成功后,记录发送报文的状态:Ø

20、 序号seq 为packet_send。Ø 标志flag为1,表示已经发送但是没有收到响应。Ø 发送时间为之前获得的时间。(2) 每次发送成功后序号值会增加1,即packet_send+.(3) 在线程开始进入主循环while(alive)之前,将整个程序的开始发送时间记录下来,用于在程序退出的时候进行全局统计,即gettimeofday(&tv_begin, NULL),j将时间保存在变量tv_begin 中。/* 发送ICMP回显请求包 */static void * icmp_send(void *argv)struct timeval tv;tv.tv_us

21、ec = 0;tv.tv_sec = 1;gettimeofday(&tv_begin, NULL); /* 保存程序开始发送数据的时间 */while(alive)int size = 0;struct timeval tv;gettimeofday(&tv, NULL); /*当前包的发送时间*/icmp_pack(struct icmp *)send_buff, packet_send, &tv, 64 ); /*打包数据*/ size = sendto (rawsock, send_buff, 64, 0,(struct sockaddr *)&dest

22、, sizeof(dest) ); /*将数据由指定的socket发送给目的地址*/if(size <0)perror("sendto error");continue;else/* 在发送包状态数组中找一个空闲位置 */pingm_pakcet *packet = icmp_findpacket(-1);if(packet)packet->seq = packet_send;/* 设置seq */packet->flag = 1;/* 已经使用 */gettimeofday( &packet->tv_begin, NULL);/* 发送时间

23、 */packet_send +;/* 计数增加 */sleep(1);/* 每隔一秒,发送一个ICMP回显请求包 */n 接收报文与发送函数一样,接收报文也用一个线程实现,使用select()轮询等待报文到来。当接收到一个报文后使用函数icmp_unpack()来解包和查找报文之前发送时的记录,获取发送时间,计算收发差值并打印信息。(1) 接收成功后将合法的报文记录重置为没有使用,flag为0.。(2) 接收报文数量增加1.(3) 为了防止丢包,select()轮询时间设置的比较短。/* 接收ping目的主机的回复 */static void *icmp_recv(void *argv)/*

24、 轮训等待时间 */struct timeval tv;tv.tv_usec = 200;tv.tv_sec = 0;fd_set readfd;/* 当没有信号发出一直接收数据 */while(alive)int ret = 0;FD_ZERO(&readfd);FD_SET(rawsock, &readfd);ret = select(rawsock+1,&readfd, NULL, NULL, &tv);switch(ret)case -1: /* 错误发生 */break;case 0:break; /* 超时 */default: /* 收到一个包 *

25、/int fromlen = 0;struct sockaddr from;/* 接收数据 */int size = recv(rawsock, recv_buff,sizeof(recv_buff), 0) ;if(errno = EINTR)perror("recvfrom error");continue;ret = icmp_unpack(recv_buff, size); /* 解包并设置相关变量 */if(ret = -1)continue;break;n 主函数过程Ping程序的实现使用了两个线程,一个线程 icmp_send()用于发送请求,一个线程icmp

26、_recv()用于接收远程主机的响应。当变量alive为0时,两个线程退出。1. ping数据的数据结构 类型为结构struct pingm_packet的变量pingpacket用于保存发送数据报文的状态:Ø tv_begin 用于保存发送的时间。Ø tv_end 用于保存数据报文接收到的时间。Ø seq是序列号,用于标识报文,作为索引。Ø flag用于表示本单元的状态,1表示数据报文已经发送,但是没有收到回应包; 0表示已经接收到回应报文,这个单元可以再次用于标识发送的报文。/* 保存已经发送包的状态值 */typedef struct pingm_

27、pakcetstruct timeval tv_begin;/*发送的时间*/struct timeval tv_end;/*接收到的时间*/short seq;/*序列号*/int flag;/*1,表示已经发送但没有接收到回应包0,表示接收到回应包*/pingm_pakcet;static pingm_pakcet pingpacket128;2. SIGINT处理函数本程序借去了信号 SIGINT,用函数icmp_sigint() 对SIGINT进行处理。当用户按下Ctrl+C键时,将alive设置为0,使得接收和发送两个线程退出,并计算结束时的时间。代码如下:/* 终端信号处理函数SI

28、GINT */static void icmp_sigint(int signo)/* 告诉接收和发送线程结束程序 */alive = 0;/* 读取程序结束时间 */gettimeofday(&tv_end, NULL);/* 计算一下总共所用时间 */tv_interval = icmp_tvsub(tv_end, tv_begin);return;3 . 查找数组中的标示函数icmp_findpacket()函数icmp_findpacket()用于在数组pingpacket中查找一个报文的标识,参数为-1时表示查找一个空包,存放已经发送成功的数据报文;其他值表示查找seq匹配的

29、标识。/*查找一个合适的包位置当seq为-1时,表示查找空包其他值表示查找seq对应的包*/static pingm_pakcet *icmp_findpacket(int seq)int i=0;pingm_pakcet *found = NULL;/* 查找包的位置 */if(seq = -1)/* 查找空的位置 */for(i = 0;i<128;i+)if(pingpacketi.flag = 0)found = &pingpacketi;break;else if(seq >= 0)/*查找对应SEQ的包*/for(i = 0;i<128;i+)if(pin

30、gpacketi.seq = seq)found = &pingpacketi;break;return found;4. 统计数据结果函数 icmp_statistics() icmp_statistics()函数用于统计总体的结果,包含成功发送的报文数量、成功接收的报文数量、丢失报文的百分比和总共程序运行的时间。/* 打印全部ICMP发送接收统计结果 */static void icmp_statistics(void) long time = (tv_interval.tv_sec * 1000 )+ (tv_interval.tv_usec/1000);printf("

31、;- %s ping statistics -n",dest_str);/* 目的邋IP地址 */printf("%d packets transmitted, %d received, %d%c packet loss, time %d msn",packet_send,/* 发送 */packet_recv, /* 接收 */(packet_send-packet_recv)*100/packet_send, /* 丢失百分比 */'%',time); /* 时间 */主函数 main()在程序中需要注意如下几点:Ø 使用getpro

32、tobyname()函数获得icmp对应的ICMP协议值。Ø 对输入的目的主机地址兼容域名和IP地址,使用gethostbyname()函数获得DNS对应的IP地址,inet_addr()函数获得字符串类型的IP地址对应的整型值。Ø 为了防止远程主机发送过大的包或者本地来不及接收现象的发生,setsockopt(rawsock, 、SOL_SOCKET、SO_RCVBUF、&size、sizeof(size)函数将socket的接收缓冲区设置为128KB。Ø 对信号SIGINT进行了截取。Ø 建立两个线程icmp_send()和icmp_recv

33、()进行接收和发送,然后主程序等待两个线程结束。1主函数初始化一些必须的头文件、函数的声明及全局变量的声明放在这里。#include <sys/socket.h>#include <netinet/in.h>#include <netinet/ip.h>#include <netinet/ip_icmp.h>#include <unistd.h>#include <signal.h>#include <arpa/inet.h>#include <errno.h>#include <sys/ti

34、me.h>#include <stdio.h>#include <string.h> /* bzero */#include <netdb.h>#include <pthread.h>static pingm_pakcet *icmp_findpacket(int seq);static unsigned short icmp_cksum(unsigned char *data, int len);static struct timeval icmp_tvsub(struct timeval end,struct timeval begin

35、);static void icmp_statistics(void);static void icmp_pack(struct icmp *icmph, int seq, struct timeval *tv, int length );static int icmp_unpack(char *buf,int len);static void *icmp_recv(void *argv);static void * icmp_send(void *argv);static void icmp_sigint(int signo);static void icmp_usage();/* 保存已经

36、发送包的状态值 */typedef struct pingm_pakcetstruct timeval tv_begin;/*发送的时间*/struct timeval tv_end;/*接收到的时间*/short seq;/*序列号*/int flag;/*1,表示已经发送但没有接收到回应包0,表示接收到回应包*/pingm_pakcet;static pingm_pakcet pingpacket128;#define K 1024#define BUFFERSIZE 72/*发送缓冲区大小*/static unsigned char send_buffBUFFERSIZE;static

37、unsigned char recv_buff2*K;/*为防止接收溢出,接收缓冲区设置大一些*/static struct sockaddr_in dest;/*目的地址*/static int rawsock = 0;/*发送和接收线程需要的socket描述符*/static pid_t pid=0;/*进程PID*/static int alive = 0;/*是否接收到退出信号*/static short packet_send = 0;/*已经发送的数据包多少*/static short packet_recv = 0;/*已经接收的数据包多少*/static char dest_s

38、tr80;/*目的主机字符串*/static struct timeval tv_begin, tv_end,tv_interval; /*本程序开始发送、结束和时间间隔*/static void icmp_usage()/* ping加IP地址或者域名 */printf("ping aaa.bbb.ccc.dddn");2. 进行数据报文发送之前的准备工作主要包含获得目的主机的IP地址、构建原始套接字、初始化缓冲区和变量等工作、这些工作里面还包含接收缓冲区的修改,防止返回数据过大造成数据缓冲区溢出。在本段代码里使用了 getprotobyname()函数来获得ICMP协议

39、的类型,而不用用户记忆协议的具体类型,只要记住名称就可以了。/* 主程序 */int main(int argc, char *argv)struct hostent * host = NULL;/*hostent的定义如下:struct hostent char *h_name; 地址的正式名称。char *h_aliases; 空字节-地址的预备名称的指针。int h_addrtype; 地址类型; 通常是AF_INET。int h_length; 地址的比特长度。char *h_addr_list; 零字节-主机网络地址指针。网络字节顺序#define h_addr h_addr_lis

40、t0 h_addr_list中的第一地址。;*/struct protoent *protocol = NULL;/*取得协议编号0到4的协议数据struct protoent char *p_name; /名称char * p_aliases; /别名short * p_proto; /编号 */char protoname= "icmp"unsigned long inaddr = 1;int size = 128*K;/* 参数是否数量正确 */if(argc < 2)icmp_usage();return -1;/* 获取协议类型ICMP */protocol

41、 = getprotobyname(protoname);if (protocol = NULL)perror("getprotobyname()");return -1;/*getprotobyname():依照通讯协定 (protocol) 的名称来获取该通讯协定的其他资料。 格式: struct protoent * getprotobyname( const char *name ); 参数: name通讯协定名称 传回值: 成功 - 指向 struct protoent 的指针 */* 将目的地址字符串考出 */memcpy(dest_str, argv1, st

42、rlen(argv1)+1);memset(pingpacket, 0, sizeof(pingm_pakcet) * 128);/* socket初始化static int rawsock = 0;发送和接收线程需要的socket描述符*/ rawsock = socket(AF_INET, SOCK_RAW, protocol->p_proto);if(rawsock < 0)perror("socket");return -1;/* 为了与其他进程的ping程序区别,加入pid */pid = getuid();/* 增大接收缓冲区,防止接收的包被覆盖 *

43、/setsockopt(rawsock, SOL_SOCKET, SO_RCVBUF, &size, sizeof(size);/*原型:extern void bzero(void *s, int n); 用法:#include <string.h> 功能:置字节字符串s的前n个字节为零且包括0*/bzero(&dest, sizeof(dest);/* 获取目的地址的IP地址 */dest.sin_family = AF_INET;/* 获取协议类型ICMP */protocol = getprotobyname(protoname);if (protocol

44、= NULL)perror("getprotobyname()");return -1;/*getprotobyname():依照通讯协定 (protocol) 的名称来获取该通讯协定的其他资料。 格式: struct protoent * getprotobyname( const char *name ); 参数: name通讯协定名称 传回值: 成功 - 指向 struct protoent 的指针 */* 将目的地址字符串考出 */memcpy(dest_str, argv1, strlen(argv1)+1);memset(pingpacket, 0, sizeo

45、f(pingm_pakcet) * 128);/* socket初始化static int rawsock = 0;发送和接收线程需要的socket描述符*/ rawsock = socket(AF_INET, SOCK_RAW, protocol->p_proto);if(rawsock < 0)perror("socket");return -1;/* 为了与其他进程的ping程序区别,加入pid */pid = getuid();/* 增大接收缓冲区,防止接收的包被覆盖 */setsockopt(rawsock, SOL_SOCKET, SO_RCVBUF, &size, sizeof(size);/*原型:extern void bzero(void *s, int n); 用法:#include <string.h> 功能:置字节字符串s的前n个字节为零且包括0*/bzero(&dest, sizeo

温馨提示

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

评论

0/150

提交评论