Ping程序

Posted by 大雁小鱼的博客 on November 4, 2018

Ping是什么

很多人都知道Ping这个程序,在你决定要看看某台机器是不是能连通的时候会使用Ping这个命令,比如像下面这样:

ping 192.168.1.1

Ping这个名字源于声纳定位,是由Mike Muuss编写的,其目的是为了测试另一台主机是否可达,它会发送ICMP回显请求报文给目的主机,并期望得到ICMP回显应答报文。

一般来说,如果我们不能PING通某台主机,就不能Telnet到那台主机,但随着防火墙等的出现,很多时候PING不通某台主机很有可能是被对方机拦截了PING,而Telnet依然可以到达那台主机,所以PING不PING得通已经不再是机器是否可达的唯一标准了。

熟悉ICMP的读者一定知道,ICMP回显请求报文和ICMP回显应答报文是所有ICMP报文中比较常见的2种而已,下图列出了所有的ICMP报文类型:

ICMP回显报文和ICMP应答报文的格式如下:

ICMP报文是在IP报文之下的,所以在ICMP报文之上还有一个IP报文头。

以下是我编写的PING程序,写得比较简陋:

#include <stdio.h>  
#include <netinet/in.h>  
#include <stdlib.h>  
#include <signal.h>  
#include <sys/time.h>  
#include <arpa/inet.h>  
#include <sys/types.h>  
#include <string.h>  
#include <sys/socket.h>  
#include <unistd.h>  
#include <netinet/in.h>  
#include <netinet/ip.h>    
#include <netinet/ip_icmp.h>    
#include <netdb.h>  
#include <setjmp.h>  
#include <errno.h>  

#define PACKET_SIZE   64   
#define MAX_WAIT_TIME  5    
#define MAX_NO_PACKETS 3    

unsigned char sendpacket[PACKET_SIZE];  
unsigned char recvpacket[PACKET_SIZE];  
int sockfd,datalen=56;  
int nsend=0,nreceived=0;  
struct sockaddr_in dest_addr;  
pid_t pid;  
struct sockaddr_in from;  
struct timeval tvrecv;  
void statistics();  
unsigned short cal_chksum(unsigned short *addr,int len);  
int pack(int pack_no);  
void send_packet(void);  
void recv_packet(void);  
int unpack(char *buf,int len);  
void tv_sub(struct timeval *out,struct timeval *in);  

void statistics()
{   
    printf("\n=============PING==========\n");
    printf("%d packages transmitted, %d received, %d lost\n",nsend,nreceived,nsend-nreceived);
    close(sockfd);
}

unsigned short cal_chksum(unsigned short *addr,int len)
{
	int nleft=len;
        int sum=0;
        unsigned short *w=addr;
        unsigned short answer=0;

/*把ICMP报头二进制数据以2字节为单位累加起来*/
        while(nleft>1)
        {       sum+=*w++;
                nleft-=2;
        }
		/*若ICMP报头为奇数个字节,会剩下最后一字节。把最后一个字节视为一个2字节数据的高字节,这个2字节数据的低字节为0,继续累加*/
        if( nleft==1)
        {       *(unsigned char *)(&answer)=*(unsigned char *)w;
                sum+=answer;
        }
        sum=(sum>>16)+(sum&0xffff);
        sum+=(sum>>16);
        answer=~sum;
	return answer;
}

int pack(int pack_no)
{     int i,packsize;
      struct icmp *icmp;
      struct timeval * tval;
      icmp = (struct icmp*)sendpacket;
      icmp->icmp_type=ICMP_ECHO;
      icmp->icmp_code=0;
      icmp->icmp_cksum=0;
      icmp->icmp_seq=htons(pack_no);
      icmp->icmp_id=htons(pid);//pid
      packsize=8+datalen;
      tval = (struct timeval *)icmp->icmp_data;
      gettimeofday(tval,NULL);
      icmp->icmp_cksum = cal_chksum((unsigned short*)icmp,packsize);
      return packsize;
}

void send_packet()
{   int packetsize;
    while(nsend<MAX_NO_PACKETS)
    {   nsend++;
        packetsize = pack(nsend);
         if(sendto(sockfd/**socket description*/,
		   sendpacket/**address*/,
		   packetsize/**length*/,0,
           (struct sockaddr*)&dest_addr,sizeof(dest_addr)) <0)
         {
             perror("sendto error");
            continue;
         }
        sleep(1);
    }
}


void recv_packet()
{       int n,fromlen;
        extern int errno;
        //signal(SIGALRM,statistics);
        fromlen = sizeof(from);
        while(nreceived<nsend)
        {    
             if((n=recvfrom(sockfd,recvpacket,sizeof(recvpacket),0,
                   (struct sockaddr *)&from,&fromlen))<0)
             {  if(errno == EINTR) continue;
                 perror("recvfrom error");
                 continue;
             }
             gettimeofday(&tvrecv,NULL);
             if(unpack(recvpacket,n) == -1) {
		     continue;
             }
             nreceived++;
        }
}


void recv_packet_origin()
{      int n,fromlen;
        extern int errno;
        signal(SIGALRM,statistics);
        fromlen = sizeof(from);
        while(nreceived<nsend)
        {    alarm(MAX_WAIT_TIME);
             if((n=recvfrom(sockfd,recvpacket,sizeof(recvpacket),0,
                   (struct sockaddr *)&from,&fromlen))<0)
             {  if(errno == EINTR) continue;
                 perror("recvfrom error");
                 continue;
             }
             gettimeofday(&tvrecv,NULL);
             if(unpack(recvpacket,n) == -1) continue;
             nreceived++;
        }
}


int unpack(char *buf,int len)
{
      int i,iphdrlen;
      struct ip *ip;
      struct icmp *icmp;
      struct timeval *tvsend;
      double rtt;
      ip = (struct ip*)buf;//header is ip
      iphdrlen = ip->ip_hl<<2;
      icmp = (struct icmp*)(buf + iphdrlen);
      len-= iphdrlen;
      if(len < 8){
            printf("ICMP packets\'s length is less then 8\n");
            return -1;
      }
      //if((icmp->icmp_type == ICMP_ECHOREPLY) && (icmp->icmp_id==pid))
      if(icmp->icmp_type == ICMP_ECHOREPLY)
      {    tvsend=(struct timeval*)icmp->icmp_data;
           tv_sub(&tvrecv,tvsend);
           rtt=tvrecv.tv_sec*1000 + tvrecv.tv_usec/1000;

	   struct ih_idseq *seq_part = (struct ih_idseq*) &(icmp->icmp_hun);

	   uint16_t seq = ntohs(seq_part->icd_seq);
           printf("%d byte from %s: icmp_seq=%u ttl=%d rtt=%.3f ms\n",
                len,
                inet_ntoa(from.sin_addr),
		seq,
                ip->ip_ttl,
                rtt);
         }
         else return -1;
}


 
void tv_sub(struct timeval *out,struct timeval *in){
     if((out->tv_usec-=in->tv_usec)<0)
{
      --out->tv_sec;
      out->tv_usec+=1000000;
 }
   out->tv_sec-=in->tv_sec;
}

int main(int argc,char *argv[]){
      struct hostent *host;
      struct protoent *protocol;
      unsigned long inaddr = 0l;
      int waittime = MAX_WAIT_TIME;
      int size = 50*1024;

      if(argc < 2){
        printf("usage: hostname/IP address \n");  exit(1);
       }

      if((protocol=getprotobyname("icmp")) == NULL){
             perror("getprotobyname error");  exit(1);
       }
        if((sockfd=socket(AF_INET,SOCK_RAW,protocol->p_proto))<0){
              perror("socket error"); exit(1);
         }
        setuid(getuid());
        setsockopt(sockfd,SOL_SOCKET,SO_RCVBUF,&size,sizeof(size));
        bzero(&dest_addr,sizeof(dest_addr));
        dest_addr.sin_family=AF_INET;
       host = gethostbyname(argv[1]);  
       memcpy((char*)&dest_addr.sin_addr,host->h_addr,host->h_length);

       pid=getpid();
       send_packet();
       recv_packet();
       statistics();
       return 0;
}

更多参考信息,请看地址:http://docs.52im.net/extend/docs/book/tcpip/vol1/7/