在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(int, int); /*统计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

结果为:

猜你喜欢

转载自blog.csdn.net/qq_19004627/article/details/81396389