一.介绍
ping命令是用来查看网络上另一个主机系统的网络连接是否正常的一个工具。ping命令的工作原理是:向网络上的另一个主机系统发送ICMP报文,如果指定系统得到了报文,它将把报文一模一样地传回给发送者,这有点象潜水艇声纳系统中使用的发声装置。 例如,在Linux终端上执行ping
如下:
二.分析
由上面的执行结果可以看到,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
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/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;
- }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;
- static pid_t pid;
- static 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;
2.计算发送和接收的时间
- static void icmp_usage()
- {
-
- printf("ping aaa.bbb.ccc.ddd\n");
- }
-
- static void icmp_sigint(int signo)
- {
- alive = 0;
- gettimeofday(&tv_end,NULL);
- tv_interval = icmp_tvsub(tv_end, tv_begin);
-
- return;
- }
3.统计数据结果
-
-
- 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);
- }
-
-
-
-
- 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;
- }
4.校验和函数
-
-
-
-
-
-
-
-
-
-
- static unsigned short icmp_cksum(unsigned char *data, int len)
- {
- int sum = 0;
- int odd = len & 0x01;
-
- while(len & 0xfffe){
- sum += *(unsigned short*)data;
- data += 2;
- len -= 2;
- }
-
- if(odd){
- unsigned short tmp = ((*data)<<8)&0xff00;
- sum += tmp;
- }
- sum = (sum >> 16) + (sum & 0xffff);
- sum += (sum >> 16);
-
- return ~sum;
- }
5.ICMP头部校验打包和拆包
-
-
- static void icmp_pack(struct icmp *icmph, int seq, struct timeval *tv, int length)
- {
- unsigned char i = 0;
-
- icmph->icmp_type = ICMP_ECHO;
- icmph->icmp_code = 0;
- icmph->icmp_cksum = 0;
- icmph->icmp_seq = seq;
- icmph->icmp_id = pid & 0xffff;
- for(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;
- iphdrlen = ip->ip_hl * 4;
- icmp = (struct icmp *)(buf+iphdrlen);
- len -= iphdrlen;
-
- if(len < 8){
- printf("ICMP packets\'s length is less than 8\n");
- return -1;
- }
-
- if((icmp->icmp_type == ICMP_ECHOREPLY) && (icmp->icmp_id == pid)){
- struct timeval tv_interval,tv_recv,tv_send;
-
- 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_interval = icmp_tvsub(tv_recv,tv_send);
- rtt = tv_interval.tv_sec * 1000 + tv_interval.tv_usec/1000;
-
-
-
-
-
-
-
- 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 ++;
- }
- else {
- return -1;
- }
- }
6.计算时间差函数
-
-
-
-
-
-
-
- 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;
-
- if(tv.tv_usec < 0){
- tv.tv_sec --;
- tv.tv_usec += 1000000;
- }
-
- return tv;
- }
7.发送报文函数
-
- 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 ++;
-
- sleep(1);
- }
- }
8.接收目的主机的回复函数
-
- 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;
- }
- }
- }
9.设置ICMP头部(程序中不需要,这里只是作为了解)
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);
-
- rawsock = socket(AF_INET, SOCK_RAW, protocol->p_proto);
- if(rawsock < 0){
- perror("socket");
- return -1;
- }
-
- pid = getuid();
-
- setsockopt(rawsock, SOL_SOCKET, SO_RCVBUF, &size, sizeof(size));
- bzero(&dest, sizeof(dest));
-
- dest.sin_family = AF_INET;
-
- inaddr = inet_addr(argv[1]);
- if(inaddr == INADDR_NONE){
- host = gethostbyname(argv[1]);
- if(host == NULL){
- perror("gethostbyname");
- return -1;
- }
-
- memcpy((char *)&dest.sin_addr, host->h_addr, host->h_length);
- }
- 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);
-
- 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;
- }
三.程序运行方法
由于在程序中用到了<pthread.h>多线程,所以在编译时要加上-lpthread,并且想要运行的话要在root权限下,一般的用户是没有权限的
此程序编译方法为
- gcc -o myping myping.c -lpthread
运行:
结果为: