在linux中用C語言實現ping命令的部分功能

運用C語言編寫模擬常用網絡命令ping命令實現一個基於linux原始套接字和ICMP協議的ping程序。該程序能用於檢測主機或路由器工作是否正常。

程序中主要的函數

void alarm_handler(int); /*SIGALRM處理程序*/

void int_handler(int); /*SIGINT處理程序*/

void set_sighandler(); /*設置信號處理程序*/

void send_ping(); /*發送ping消息*/

void recv_reply(); /*接收ping應答*/

u16 checksum(u8 *buf, int len); /*計算校驗和*/

int handle_pkt(); /*ICMP應答消息處理*/

void get_statistics(int, int); /*統計ping命令的檢測結果*/

void bail(const char *); /*錯誤報告*/

頭文件:ping.h

  1. #define ICMP_ECHOREPLY 0 /* Echo應答*/  
  2. #define ICMP_ECHO   /*Echo請求*/  
  3.   
  4. #define BUFSIZE 1500    /*發送緩存最大值*/  
  5. #define DEFAULT_LEN 56  /**ping消息數據默認大小/  
  6.   
  7. /*數據類型別名*/  
  8. typedef unsigned char u8;  
  9. typedef unsigned short u16;  
  10. typedef unsigned int u32;  
  11.   
  12. /*ICMP消息頭部*/  
  13. struct icmphdr {  
  14.     u8 type;     /*定義消息類型*/  
  15.     u8 code;    /*定義消息代碼*/  
  16.     u16 checksum;   /*定義校驗*/  
  17.     union{  
  18.         struct{  
  19.         u16 id;  
  20.         u16 sequence;  
  21.     }echo;  
  22.     u32 gateway;  
  23.     struct{  
  24.         u16 unsed;  
  25.         u16 mtu;  
  26.     }frag; /*pmtu實現*/  
  27.     }un;  
  28.   /*ICMP數據佔位符*/  
  29.     u8 data[0];  
  30. #define icmp_id un.echo.id  
  31. #define icmp_seq un.echo.sequence  
  32. };  
  33. #define ICMP_HSIZE sizeof(struct icmphdr)  
  34. /*定義一個IP消息頭部結構體*/  
  35. struct iphdr {  
  36.     u8 hlen:4, ver:4;   /*定義4位首部長度,和IP版本號爲IPV4*/  
  37.     u8 tos;     /*8位服務類型TOS*/  
  38.     u16 tot_len;    /*16位總長度*/  
  39.     u16 id;         /*16位標誌位*/  
  40.     u16 frag_off;   /*3位標誌位*/  
  41.     u8 ttl;         /*8位生存週期*/  
  42.     u8 protocol;    /*8位協議*/  
  43.     u16 check;      /*16位IP首部校驗和*/  
  44.     u32 saddr;      /*32位源IP地址*/  
  45.     u32 daddr;      /*32位目的IP地址*/  
  46. };  
  47.   
  48. char *hostname;         /*被ping的主機名*/  
  49. int datalen = DEFAULT_LEN;  /*ICMP消息攜帶的數據長度*/  
  50. char sendbuf[BUFSIZE];      /*發送字符串數組*/   
  51. char recvbuf[BUFSIZE];      /*接收字符串數組*/  
  52. int nsent;          /*發送的ICMP消息序號*/  
  53. int nrecv;          /*接收的ICMP消息序號*/  
  54. pid_t pid;          /*ping程序的進程PID*/  
  55. struct timeval recvtime;    /*收到ICMP應答的時間戳*/  
  56. int sockfd;         /*發送和接收原始套接字*/  
  57. struct sockaddr_in dest;    /*被ping的主機IP*/  
  58. struct sockaddr_in from;    /*發送ping應答消息的主機IP*/  
  59. struct sigaction act_alarm;  
  60. struct sigaction act_int;  
  61.   
  62. /*函數原型*/  
  63. void alarm_handler(int);    /*SIGALRM處理程序*/  
  64. void int_handler(int);      /*SIGINT處理程序*/  
  65. void set_sighandler();      /*設置信號處理程序*/  
  66. void send_ping();       /*發送ping消息*/  
  67. void recv_reply();      /*接收ping應答*/  
  68. u16 checksum(u8 *buf, int len); /*計算校驗和*/  
  69. int handle_pkt();       /*ICMP應答消息處理*/  
  70. void get_statistics(intint);  /*統計ping命令的檢測結果*/  
  71. void bail(const char *);    /*錯誤報告*/  

源程序2:ping.c

  1. #include<stdio.h>  
  2. #include<stdlib.h>  
  3. #include<sys/time.h>  /*是Linux系統的日期時間頭文件*/  
  4. #include<unistd.h>    /* 是POSIX標準定義的unix類系統定義符號常量的頭文件,包含了許多UNIX系統服務的函數原型,例如read函數、write函數和getpid函數*/  
  5. #include<string.h>  
  6. #include<sys/socket.h>    /*對與引用socket函數必須*/  
  7. #include<sys/types.h>  
  8. #include<netdb.h> /*定義了與網絡有關的結構,變量類型,宏,函數。函數gethostbyname()用*/  
  9. #include<errno.h> /*sys/types.h中文名稱爲基本系統數據類型*/  
  10. #include<arpa/inet.h> /*inet_ntoa()和inet_addr()這兩個函數,包含在 arpa/inet.h*/  
  11. #include<signal.h>    /*進程對信號進行處理*/  
  12. #include<netinet/in.h>    /*互聯網地址族*/  
  13.   
  14. #include"ping.h"  
  15. #define IP_HSIZE sizeof(struct iphdr)   /*定義IP_HSIZE爲ip頭部長度*/  
  16. #define IPVERSION  4   /*定義IPVERSION爲4,指出用ipv4*/  
  17. /*設置的時間是一個結構體,倒計時設置,重複倒時,超時值設爲1秒*/  
  18. struct itimerval val_alarm={.it_interval.tv_sec = 1,      
  19.                                .it_interval.tv_usec=0,  
  20.                                .it_value.tv_sec=0,  
  21.                                .it_value.tv_usec=1  
  22.                                 };  
  23.   
  24. int main(int argc,char **argv)  /*argc表示隱形程序命令行中參數的數目,argv是一個指向字符串數組指針,其中每一個字符對應一個參數*/  
  25. {  
  26.     struct hostent *host; /*該結構體屬於include<netdb.h>*/   
  27.     int on =1;  
  28.   
  29.     if(argc<2){      /*判斷是否輸入了地址*/  
  30.     printf("Usage: %s hostname\n",argv[0]);  
  31.     exit(1);  
  32.     }  
  33.   
  34.     if((host=gethostbyname(argv[1]))==NULL){    /*gethostbyname()返回對應於給定主機名的包含主機名字和地址信息的結構指針,*/  
  35.       perror("can not understand the host name");   /*理解不了輸入的地址*/  
  36.       exit(1);  
  37.       }  
  38.   
  39.       hostname=argv[1];/*取出地址名*/  
  40.   
  41.       memset(&dest,0,sizeof dest);  /*將dest中前sizeof(dest)個字節替換爲0並返回s,此處爲初始化,給最大內存清零*/  
  42.       dest.sin_family=PF_INET;  /*PF_INET爲IPV4,internet協議,在<netinet/in.h>中,地址族*/   
  43.       dest.sin_port=ntohs(0);   /*端口號,ntohs()返回一個以主機字節順序表達的數。*/  
  44.       dest.sin_addr=*(struct in_addr *)host->h_addr_list[0];/*host->h_addr_list[0]是地址的指針.返回IP地址,初始化*/  
  45.   
  46.       if((sockfd = socket(PF_INET,SOCK_RAW,IPPROTO_ICMP))<0){ /*PF_INEI套接字協議族,SOCK_RAW套接字類型,IPPROTO_ICMP使用協議,調用socket函數來創建一個能夠進行網絡通信的套接字。這裏判斷是否創建成功*/  
  47.           perror("raw socket created error");  
  48.           exit(1);  
  49.        }  
  50.   
  51.        setsockopt(sockfd,IPPROTO_IP,IP_HDRINCL,&on,sizeof(on));  /*設置當前套接字選項特定屬性值,sockfd套接字,IPPROTO_IP協議層爲IP層,IP_HDRINCL套接字選項條目,套接字接收緩衝區指針,sizeof(on)緩衝區長度的長度*/  
  52.   
  53. setuid(getuid());/*getuid()函數返回一個調用程序的真實用戶ID,setuid()是讓普通用戶可以以root用戶的角色運行只有root帳號才能運行的程序或命令。*/  
  54. pid=getpid(); /*getpid函數用來取得目前進程的進程識別碼*/  
  55.   
  56. set_sighandler();/*對信號處理*/  
  57. printf("Ping %s(%s): %d bytes data in ICMP packets.\n",  
  58.        argv[1],inet_ntoa(dest.sin_addr),datalen);  
  59.   
  60.        if((setitimer(ITIMER_REAL,&val_alarm,NULL))==-1) /*定時函數*/  
  61.           bail("setitimer fails.");  
  62.   
  63.   
  64.           recv_reply();/*接收ping應答*/  
  65.   
  66.           return 0;  
  67. }  
  68. /*發送ping消息*/  
  69. void send_ping(void)  
  70. {  
  71.     struct iphdr *ip_hdr;   /*iphdr爲IP頭部結構體*/  
  72.     struct icmphdr *icmp_hdr;   /*icmphdr爲ICMP頭部結構體*/  
  73.     int len;  
  74.     int len1;  
  75.         /*ip頭部結構體變量初始化*/  
  76.     ip_hdr=(struct iphdr *)sendbuf; /*字符串指針*/     
  77.     ip_hdr->hlen=sizeof(struct iphdr)>>2;  /*頭部長度*/  
  78.     ip_hdr->ver=IPVERSION;   /*版本*/  
  79.     ip_hdr->tos=0;   /*服務類型*/  
  80.     ip_hdr->tot_len=IP_HSIZE+ICMP_HSIZE+datalen; /*報文頭部加數據的總長度*/  
  81.     ip_hdr->id=0;    /*初始化報文標識*/  
  82.     ip_hdr->frag_off=0;  /*設置flag標記爲0*/  
  83.     ip_hdr->protocol=IPPROTO_ICMP;/*運用的協議爲ICMP協議*/  
  84.     ip_hdr->ttl=255; /*一個封包在網絡上可以存活的時間*/  
  85.     ip_hdr->daddr=dest.sin_addr.s_addr;  /*目的地址*/  
  86.     len1=ip_hdr->hlen<<2;  /*ip數據長度*/  
  87.     /*ICMP頭部結構體變量初始化*/  
  88.     icmp_hdr=(struct icmphdr *)(sendbuf+len1);  /*字符串指針*/  
  89.     icmp_hdr->type=8;    /*初始化ICMP消息類型type*/  
  90.     icmp_hdr->code=0;    /*初始化消息代碼code*/  
  91.     icmp_hdr->icmp_id=pid;   /*把進程標識碼初始給icmp_id*/  
  92.     icmp_hdr->icmp_seq=nsent++;  /*發送的ICMP消息序號賦值給icmp序號*/      
  93.     memset(icmp_hdr->data,0xff,datalen);  /*將datalen中前datalen個字節替換爲0xff並返回icmp_hdr-dat*/    
  94.   
  95.     gettimeofday((struct timeval *)icmp_hdr->data,NULL); /* 獲取當前時間*/  
  96.   
  97.     len=ip_hdr->tot_len; /*報文總長度賦值給len變量*/  
  98.     icmp_hdr->checksum=0;    /*初始化*/  
  99.     icmp_hdr->checksum=checksum((u8 *)icmp_hdr,len);  /*計算校驗和*/  
  100.   
  101.     sendto(sockfd,sendbuf,len,0,(struct sockaddr *)&dest,sizeof (dest)); /*經socket傳送數據*/  
  102. }  
  103. /*接收程序發出的ping命令的應答*/  
  104. void recv_reply()  
  105. {  
  106.     int n,len;  
  107.     int errno;  
  108.   
  109.     n=nrecv=0;  
  110.     len=sizeof(from);   /*發送ping應答消息的主機IP*/  
  111.   
  112.     while(nrecv<4){  
  113.       if((n=recvfrom(sockfd,recvbuf,sizeof recvbuf,0,(struct sockaddr *)&from,&len))<0){ /*經socket接收數據,如果正確接收返回接收到的字節數,失敗返回0.*/  
  114.               if(errno==EINTR)  /*EINTR表示信號中斷*/  
  115.               continue;  
  116.               bail("recvfrom error");  
  117.             }  
  118.   
  119.             gettimeofday(&recvtime,NULL);   /*記錄收到應答的時間*/  
  120.   
  121.             if(handle_pkt())    /*接收到錯誤的ICMP應答信息*/  
  122.                   
  123.             continue;  
  124.             nrecv++;  
  125.     }  
  126.   
  127.     get_statistics(nsent,nrecv);     /*統計ping命令的檢測結果*/  
  128. }  
  129.  /*計算校驗和*/  
  130. u16 checksum(u8 *buf,int len)  
  131. {  
  132.     u32 sum=0;  
  133.     u16 *cbuf;  
  134.   
  135.     cbuf=(u16 *)buf;  
  136.   
  137.     while(len>1){  
  138.     sum+=*cbuf++;  
  139.     len-=2;  
  140.     }  
  141.   
  142.     if(len)  
  143.         sum+=*(u8 *)cbuf;  
  144.   
  145.         sum=(sum>>16)+(sum & 0xffff);  
  146.         sum+=(sum>>16);  
  147.   
  148.         return ~sum;  
  149. }  
  150.  /*ICMP應答消息處理*/  
  151. int handle_pkt()  
  152. {  
  153.     struct iphdr *ip;  
  154.     struct icmphdr *icmp;  
  155.   
  156.     int ip_hlen;  
  157.     u16 ip_datalen; /*ip數據長度*/  
  158.     double rtt; /* 往返時間*/  
  159.     struct timeval *sendtime;  
  160.   
  161.     ip=(struct iphdr *)recvbuf;  
  162.   
  163.     ip_hlen=ip->hlen << 2;  
  164.     ip_datalen=ntohs(ip->tot_len)-ip_hlen;  
  165.   
  166.     icmp=(struct icmphdr *)(recvbuf+ip_hlen);  
  167.   
  168.     if(checksum((u8 *)icmp,ip_datalen)) /*計算校驗和*/  
  169.        return -1;  
  170.   
  171.   
  172.        if(icmp->icmp_id!=pid)  
  173.         return -1;  
  174.   
  175.         sendtime=(struct timeval *)icmp->data; /*發送時間*/  
  176.         rtt=((&recvtime)->tv_sec-sendtime->tv_sec)*1000+((&recvtime)->tv_usec-sendtime->tv_usec)/1000.0;/* 往返時間*/  
  177.         /*打印結果*/  
  178.         printf("%d bytes from %s:icmp_seq=%u ttl=%d rtt=%.3f ms\n",  
  179.                ip_datalen, /*IP數據長度*/  
  180.                inet_ntoa(from.sin_addr),    /*目的ip地址*/  
  181.                icmp->icmp_seq, /*icmp報文序列號*/  
  182.                ip->ttl,  /*生存時間*/  
  183.                rtt);    /*往返時間*/  
  184.   
  185.           return 0;  
  186. }  
  187. /*設置信號處理程序*/  
  188. void set_sighandler()  
  189. {  
  190.     act_alarm.sa_handler=alarm_handler;  
  191.     if(sigaction(SIGALRM,&act_alarm,NULL)==-1)  /*sigaction()會依參數signum指定的信號編號來設置該信號的處理函數。參數signum指所要捕獲信號或忽略的信號,&act代表新設置的信號共用體,NULL代表之前設置的信號處理結構體。這裏判斷對信號的處理是否成功。*/  
  192.        bail("SIGALRM handler setting fails.");  
  193.   
  194.        act_int.sa_handler=int_handler;  
  195.        if(sigaction(SIGINT,&act_int,NULL)==-1)  
  196.          bail("SIGALRM handler setting fails.");  
  197. }  
  198.  /*統計ping命令的檢測結果*/  
  199. void get_statistics(int nsent,int nrecv)  
  200. {  
  201.     printf("--- %s ping statistics ---\n",inet_ntoa(dest.sin_addr)); /*將網絡地址轉換成“.”點隔的字符串格式。*/  
  202.     printf("%d packets transmitted, %d received, %0.0f%% ""packet loss\n",  
  203.            nsent,nrecv,1.0*(nsent-nrecv)/nsent*100);  
  204. }  
  205.  /*錯誤報告*/  
  206. void bail(const char * on_what)  
  207. {  
  208.     fputs(strerror(errno),stderr);  /*:向指定的文件寫入一個字符串(不寫入字符串結束標記符‘\0’)。成功寫入一個字符串後,文件的位置指針會自動後移,函數返回值爲0;否則返回EOR(符號常量,其值爲-1)。*/  
  209.     fputs(":",stderr);  
  210.     fputs(on_what,stderr);  
  211.     fputc('\n',stderr); /*送一個字符到一個流中*/  
  212.     exit(1);  
  213. }  
  214.   
  215.  /*SIGINT(中斷信號)處理程序*/  
  216. void int_handler(int sig)  
  217. {  
  218.     get_statistics(nsent,nrecv);    /*統計ping命令的檢測結果*/  
  219.     close(sockfd);  /*關閉網絡套接字*/  
  220.     exit(1);  
  221. }  
  222.  /*SIGALRM(終止進程)處理程序*/  
  223. void alarm_handler(int signo)  
  224. {  
  225.     send_ping();    /*發送ping消息*/  
  226.   
  227. }  

程序執行:

1、程序編譯。在linux終端下執行如下命令:

gcc ping.h -o myping -std=gnu99

因爲原始套接字的創建需要root用戶權限,所以爲了能讓所有的其他用戶也可以使用該程序,需要通過如下命令設置myping的set-user-id位:

$sudo chmod u+s myping

結果爲:

發佈了51 篇原創文章 · 獲贊 76 · 訪問量 82萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章