ping的实现和代码分析

来源:互联网 发布:太原知达常青藤小升初 编辑:程序博客网 时间:2024/09/21 09:21

转自 http://blog.csdn.net/zzucsliang/article/details/41407387

介绍


ping命令是用来查看网络上另一个主机系统的网络连接是否正常的一个工具。ping命令的工作原理是:向网络上的另一个主机系统发送ICMP报文,如果指定系统得到了报文,它将把报文一模一样地传回给发送者,这有点象潜水艇声纳系统中使用的发声装置。

分析


由上面的执行结果可以看到,ping命令执行后显示出被测试系统主机名和相应IP地址、返回给当前主机的ICMP报文顺序号、ttl生存时间和往返时间rtt(单位是毫秒,即千分之一秒)。要写一个模拟ping命令,这些信息有启示作用。要真正了解ping命令实现原理,就要了解ping命令所使用到的TCP/IP协议。 ICMP(Internet Control Message,网际控制报文协议)是为网关和目标主机而提供的一种差错控制机制,使它们在遇到差错时能把错误报告给报文源发方。ICMP协议是IP层的一个协议,但是由于差错报告在发送给报文源发方时可能也要经过若干子网,因此牵涉到路由选择等问题,所以ICMP报文需通过IP协议来发送。ICMP数据报的数据发送前需要两级封装:首先添加ICMP报头形成ICMP报文,再添加IP报头形成IP数据报。由于IP层协议是一种点对点的协议,而非端对端的协议,它提供无连接的数据报服务,没有端口的概念,因此很少使用bind()和connect()函数,若有使用也只是用于设置IP地址。

明白了工作原理,我们就可以写我们自己的ping命令:myping

头文件和定义函数以及变量:


/** 作者: greenday* 名称: myping* 程序应用: ping命令是向目的主机发送ICMP报文,检验本地主机和远程的目的主机是否连接* 日期: 2014.11.22**//*ICMP必须使用原始套接字进行设计,要手动设置IP的头部和ICMP的头部并行校验*//***********主函数*********************************************myping.c*/#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/time.h>#include <stdio.h>#include <string.h> /* bzero */#include <netdb.h>#include <pthread.h>//保存发送包的状态值typedef struct pingm_pakcet{struct timeval tv_begin; //发送时间struct timeval tv_end; //接收到的时间short seq; //序列号int flag; //1,表示已经发送但是没有接收到回应,0,表示接收到回应}pingm_pakcet;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);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_sigint(int signo);static void icmp_usage();static pingm_pakcet pingpacket[128];#define K 1024#define BUFFERSIZE 72 //发送缓冲区的大小static unsigned char send_buff[BUFFERSIZE];static unsigned char recv_buff[2*K]; //防止接收溢出,设置大一些static struct sockaddr_in dest; //目的地址static int rawsock = 0; //发送和接收线程需要的socket描述符static pid_t pid; //进程PIDstatic int alive = 0; //是否接收到退出信号static short packet_send = 0; //已经发送的数据包数量static short packet_recv = 0; //已经接收的数据包数量static char dest_str[80]; //目的主机字符串static struct timeval tv_begin, tv_end, tv_interval;


计算发送和接收的时间


static void icmp_usage(){//ping加IP地址或者域名printf("ping aaa.bbb.ccc.ddd\n");}/*终端信号处理函数SIGINT*/static void icmp_sigint(int signo){alive = 0;gettimeofday(&tv_end,NULL);tv_interval = icmp_tvsub(tv_end, tv_begin);return;}


统计数据结果


/*统计数据结果函数******************************************打印全部ICMP发送的接收统计结果*/static void icmp_statistics(void){long time = (tv_interval.tv_sec * 1000) + (tv_interval.tv_usec/1000);printf("--- %s ping statistics ---\n", dest_str);printf("%d packets transmitted, %d received, %d%c packet loss, time %ld ms\n",packet_send,packet_recv,(packet_send-packet_recv)*100/packet_send,'%',time);}/*************查找数组中的标识函数***********************查找合适的包的位置当seq为1时,表示查找空包其他值表示查找seq对应的包*/static pingm_pakcet *icmp_findpacket(int seq){int i;pingm_pakcet *found = NULL;//查找包的位置if(seq == -1){for(i=0;i<128;i++){if(pingpacket[i].flag == 0){found = &pingpacket[i];break;}}}else if(seq >= 0){for(i =0 ;i< 128;i++){if(pingpacket[i].seq == seq){found = &pingpacket[i];break;}}}return found;}



校验和函数


/*************校验和函数*****************************TCP/IP协议栈使用的校验算法是比较经典的,对16位的数据进行累加计算,并返回计算结果,CRC16校验和计算icmp_cksum参数:data:数据len:数据长度返回值:计算结果,short类型*/static unsigned short icmp_cksum(unsigned char *data, int len){int sum = 0; //计算结果int odd = len & 0x01; //是否为奇数/*将数据按照2字节为单位累加起来*/while(len & 0xfffe){sum += *(unsigned short*)data;data += 2;len -= 2;}/*判断是否为奇数个数据,若ICMP报头为奇数个字节,会剩下最后一个字节*/if(odd){unsigned short tmp = ((*data)<<8)&0xff00;sum += tmp;}sum = (sum >> 16) + (sum & 0xffff); //高地位相加sum += (sum >> 16); //将溢出位加入return ~sum; //返回取反值}



ICMP头部校验打包和拆包


/**********进行ICMP头部校验********************///设置ICMP报头static void icmp_pack(struct icmp *icmph, int seq, struct timeval *tv, int length){unsigned char i = 0;//设置报头icmph->icmp_type = ICMP_ECHO; //ICMP回显请求icmph->icmp_code = 0; //code的值为0icmph->icmp_cksum = 0; //先将cksum的值填为0,便于以后的cksum计算icmph->icmp_seq = seq; //本报的序列号icmph->icmp_id = pid & 0xffff; //填写PIDfor(i=0; i< length; i++)icmph->icmp_data[i] = i; //计算校验和icmph->icmp_cksum = icmp_cksum((unsigned char*)icmph, length);}/*解压接收到的包,并打印信息*/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报头iphdrlen = ip->ip_hl * 4; //IP头部长度icmp = (struct icmp *)(buf+iphdrlen); //ICMP段的地址len -= iphdrlen;//判断长度是否为ICMP包if(len < 8){printf("ICMP packets\'s length is less than 8\n");return -1;}//ICMP类型为ICMP_ECHOREPLY并且为本进程的PIDif((icmp->icmp_type == ICMP_ECHOREPLY) && (icmp->icmp_id == pid)){struct timeval tv_interval,tv_recv,tv_send;//在发送表格中查找已经发送的包,按照seqpingm_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_interval = icmp_tvsub(tv_recv,tv_send);rtt = tv_interval.tv_sec * 1000 + tv_interval.tv_usec/1000;/*打印结果包含ICMP段的长度源IP地址包的序列号TTL时间差*/printf("%d byte from %s: icmp_seq=%u ttl=%d rtt=%d ms\n",len,inet_ntoa(ip->ip_src),icmp->icmp_seq,ip->ip_ttl,rtt);packet_recv ++; //接收包数量加1}else {return -1;}}



计算时间差函数


/************计算时间差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;tv.tv_usec = end.tv_usec - begin.tv_usec;//如果接收的时间的usec值小于发送时的usec,从uesc域借位if(tv.tv_usec < 0){tv.tv_sec --;tv.tv_usec += 1000000;}return tv;}


发送报文函数


//**********发送报文***************************static void *icmp_send(void *argv){//保存程序开始发送数据的时间gettimeofday(&tv_begin, NULL);while(alive){int size = 0;struct timeval tv;gettimeofday(&tv, NULL); //当前包的发送时间//在发送包状态数组中找到一个空闲位置pingm_pakcet *packet = icmp_findpacket(-1);if(packet){packet->seq = packet_send;packet->flag = 1;gettimeofday(&packet->tv_begin,NULL);}icmp_pack((struct icmp *)send_buff,packet_send,&tv, 64);//打包数据size = sendto(rawsock, send_buff,64,0,(struct sockaddr *)&dest, sizeof(dest));if(size < 0){perror("sendto error");continue;}packet_send ++;//每隔1s发送一个ICMP回显请求包sleep(1);}}



接收目的主机的回复函数


//***********接收ping目的主机的回复***********static void *icmp_recv(void *argv){//轮询等待时间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 :{//收到一个包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;}}}


设置ICMP头部(程序中不需要,这里只是作为了解)


/**********设置ICMP发送报文的头部*********************************回显请求的ICMP报文*//*struct icmp{u_int8_t icmp_type; //消息类型u_int8_t icmp_code; //消息类型的子码u_int16_t icmp_cksum; //校验和union{struct ih_idseq //显示数据报{u_int16_t icd_id; //数据报IDu_int16_t icd_seq; //数据报的序号}ih_idseq;}icmp_hun;#define icmp_id icmp_hun.ih_idseq.icd_id;#define icmp_seq icmp_hun.ih_idseq.icd_seq;union{u_int8_t id_data[1]; //数据}icmp_dun;#define icmp_data icmp_dun.id_data;}; */10.主函数//主程序int main(int argc, char const *argv[]){struct hostent *host = NULL;struct protoent *protocol = NULL;char protoname[] = "icmp";unsigned long inaddr = 1;int size = 128*K;if(argc < 2) //参数是否数量正确{icmp_usage();return -1;}//获取协议类型protocol = getprotobyname(protoname);if(protocol == NULL){perror("getprotobyname()");return -1;}//复制目的地址字符串memcpy(dest_str, argv[1],strlen(argv[1])+1);memset(pingpacket, 0, sizeof(pingm_pakcet) * 128);//socket初始化rawsock = socket(AF_INET, SOCK_RAW, protocol->p_proto);if(rawsock < 0){perror("socket");return -1;}pid = getuid(); //为与其他线程区别,加入pid//增大接收缓冲区,防止接收包被覆盖setsockopt(rawsock, SOL_SOCKET, SO_RCVBUF, &size, sizeof(size));bzero(&dest, sizeof(dest));//获取目的地址的IP地址dest.sin_family = AF_INET;//输入的目的地址为字符串IP地址inaddr = inet_addr(argv[1]);if(inaddr == INADDR_NONE){ //输入的是DNS地址host = gethostbyname(argv[1]);if(host == NULL){perror("gethostbyname");return -1;}//将地址复制到destmemcpy((char *)&dest.sin_addr, host->h_addr, host->h_length);} //IP地址字符串else {memcpy((char *)&dest.sin_addr, &inaddr,sizeof(inaddr));}//打印提示inaddr = dest.sin_addr.s_addr;printf("PING %s (%ld.%ld.%ld.%ld) 56(84) bytes of data.\n",dest_str,(inaddr&0x000000ff)>>0,(inaddr&0x0000ff00)>>8,(inaddr&0x00ff0000)>>16,(inaddr&0xff000000)>>24);//截取信号SIGINT,将icmp_sigint挂接上signal(SIGINT,icmp_sigint);/*发送数据并接收回应建立两个线程,一个用于发数据,另一个用于接收响应数据,主程序等待两个线程运行完毕后再进行下一步,最后对结果进行统计并打印*/alive = 1; //初始化可运行pthread_t send_id, recv_id; //建立两个线程,用于发送和接收int err = 0;err = pthread_create(&send_id, NULL, icmp_send, NULL); //发送if(err < 0){return -1;}err = pthread_create(&recv_id, NULL, icmp_recv, NULL); //接收if(err < 0){return -1;}//等待线程结束pthread_join(send_id, NULL);pthread_join(recv_id, NULL);//清理并打印统计结果close(rawsock);icmp_statistics();return 0;}


0 0
原创粉丝点击