Chinaunix首页 | 论坛 | 博客
  • 博客访问: 757864
  • 博文数量: 144
  • 博客积分: 0
  • 博客等级: 民兵
  • 技术积分: 1150
  • 用 户 组: 普通用户
  • 注册时间: 2014-03-17 14:32
个人简介

小公司研发总监,既当司令也当兵!

文章分类

全部博文(144)

分类: LINUX

2015-06-04 12:32:06

ICMP(Internet Control Message,网际控制报文协议)是为网关和目标主机而提供的一种差错控制机制,使它们在遇到差错时能把错误报告给报文源发方。ICMP协议是IP层的一个协议,但是由于差错报告在发送给报文源发方时可能也要经过若干子网,因此牵涉到路由选择等问题,所以ICMP报文需通过IP协议来发送。ICMP数据报的数据发送前需要两级封装:首先添加ICMP报头形成ICMP报文,再添加IP报头形成IP数据报。
IP报头格式

由于IP层协议是一种点对点的协议,而非端对端的协议,它提供无连接的数据报服务,没有端口的概念,因此很少使用bind()和connect() 函数,若有使用也只是用于设置IP地址。发送数据使用sendto()函数,接收数据使用recvfrom()函数。IP报头格式如下图:

 
其中ping程序只使用以下数据:

  • IP报头长度IHL(Internet Header Length)?D?D以4字节为一个单位来记录IP报头的长度,是上述IP数据结构的ip_hl变量。
  • 生存时间TTL(Time To Live)?D?D以秒为单位,指出IP数据报能在网络上停留的最长时间,其值由发送方设定,并在经过路由的每一个节点时减一,当该值为0时,数据报将被丢弃,是上述IP数据结构的ip_ttl变量。

ICMP报头格式

ICMP报文分为两种,一是错误报告报文,二是查询报文。每个ICMP报头均包含类型、编码和校验和这三项内容,长度为8位,8位和16位,其余选项则随ICMP的功能不同而不同。

Ping命令只使用众多ICMP报文中的两种:"请求回送'(ICMP_ECHO)和"请求回应'(ICMP_ECHOREPLY)。在Linux中定义如下:

 #define ICMP_ECHO   0
#define ICMP_ECHOREPLY  8


这两种ICMP类型报头格式如下:

 


下面是自己实现的一个Ping的演示程序,该程序需要root权限运行(raw socket, 系统的ping工具为何不需要root权限:因为ping工具的所有者是root,且其带有-s标记,所以使用时不需要加root权限):


点击(此处)折叠或打开

  1. #include <stdio.h>
  2. #include <signal.h>
  3. #include <arpa/inet.h>
  4. #include <sys/types.h>
  5. #include <sys/socket.h>
  6. #include <unistd.h>
  7. #include <netinet/in.h>
  8. #include <netinet/ip.h>
  9. #include <netinet/ip_icmp.h>
  10. #include <netdb.h>
  11. #include <setjmp.h>
  12. #include <errno.h>

  13. #define PACKET_SIZE 4096
  14. #define MAX_WAIT_TIME 5
  15. #define MAX_NO_PACKETS 4

  16. char g_sendPacketBuf[PACKET_SIZE];
  17. char g_recvPacket[PACKET_SIZE];
  18. int g_sockFd;
  19. int g_dataLen = 56;
  20. int g_nsend = 0;
  21. int g_nreceived = 0;
  22. struct sockaddr_in dest_addr;
  23. pid_t pid;
  24. struct sockaddr_in from;
  25. struct timeval tvrecv;

  26. void statistics(int signo);
  27. unsigned short cal_chksum(unsigned short *addr, int len);
  28. int pack(int pack_no);
  29. void send_packet(void);
  30. void recv_packet(void);
  31. int unpack(char *buf, int len);
  32. void tv_sub(struct timeval *out, struct timeval *in);

  33. void statistics(int signo)
  34. {
  35.     printf("/n--------------------PING statistics-------------------\n");
  36.     printf("%d packets transmitted, %d received , %%%d lost\n", g_nsend, g_nreceived,
  37.            (g_nsend - g_nreceived) / g_nsend * 100);
  38.     close(g_sockFd);
  39.     exit(1);
  40. }

  41. /*校验和算法*/
  42. unsigned short cal_chksum(unsigned short *addr, int len)
  43. {
  44.     int nleft = len;
  45.     int sum = 0;
  46.     unsigned short *w = addr;
  47.     unsigned short answer = 0;

  48.     /*把ICMP报头二进制数据以2字节为单位累加起来 */
  49.     while (nleft > 1)
  50.     {
  51.         sum += *w++;
  52.         nleft -= 2;
  53.     }
  54.     /*若ICMP报头为奇数个字节,会剩下最后一字节。把最后一个字节视为一个2字节数据的高字节,这个2字节数据的低字节为0,继续累加 */
  55.     if (nleft == 1)
  56.     {
  57.         *(unsigned char *)(&answer) = *(unsigned char *)w;
  58.         sum += answer;
  59.     }
  60.     sum = (sum >> 16) + (sum & 0xffff);
  61.     sum += (sum >> 16);
  62.     answer = ~sum;
  63.     return answer;
  64. }

  65. /*设置ICMP报头*/
  66. int pack(int pack_no)
  67. {
  68.     int i, packsize;
  69.     struct icmp *icmp;
  70.     struct timeval *tval;

  71.     icmp = (struct icmp *)g_sendPacketBuf;
  72.     icmp->icmp_type = ICMP_ECHO;
  73.     icmp->icmp_code = 0;
  74.     icmp->icmp_cksum = 0;
  75.     icmp->icmp_seq = pack_no;
  76.     icmp->icmp_id = pid;
  77.     packsize = 8 + g_dataLen;
  78.     tval = (struct timeval *)icmp->icmp_data;
  79.     gettimeofday(tval, NULL); /*记录发送时间 */
  80.     icmp->icmp_cksum = cal_chksum((unsigned short *)icmp, packsize); /*校验算法 */
  81.     return packsize;
  82. }

  83. /*发送三个ICMP报文*/
  84. void send_packet()
  85. {
  86.     int packetsize;

  87.     while (g_nsend < MAX_NO_PACKETS)
  88.     {
  89.         g_nsend++;
  90.         packetsize = pack(g_nsend); /*设置ICMP报头 */
  91.         if (sendto(g_sockFd, g_sendPacketBuf, packetsize, 0, (struct sockaddr *)&dest_addr, sizeof(dest_addr)) < 0)
  92.         {
  93.             perror("sendto error");
  94.             continue;
  95.         }
  96.         sleep(1); /*每隔一秒发送一个ICMP报文 */
  97.     }
  98. }

  99. /*接收所有ICMP报文*/
  100. void recv_packet()
  101. {
  102.     int n, fromlen;
  103.     extern int errno;

  104.     signal(SIGALRM, statistics);
  105.     fromlen = sizeof(from);
  106.     while (g_nreceived < g_nsend)
  107.     {
  108.         alarm(MAX_WAIT_TIME);
  109.         if ((n = recvfrom(g_sockFd, g_recvPacket, sizeof(g_recvPacket), 0, (struct sockaddr *)&from, &fromlen)) < 0)
  110.         {
  111.             if (errno == EINTR)
  112.                 continue;
  113.             perror("recvfrom error");
  114.             continue;
  115.         }
  116.         gettimeofday(&tvrecv, NULL); /*记录接收时间 */
  117.         if (unpack(g_recvPacket, n) == -1)
  118.             continue;
  119.         g_nreceived++;
  120.     }

  121. }

  122. /*剥去ICMP报头*/
  123. int unpack(char *buf, int len)
  124. {
  125.     int i, iphdrlen;
  126.     struct ip *ip;
  127.     struct icmp *icmp;
  128.     struct timeval *tvsend;
  129.     double rtt;

  130.     ip = (struct ip *)buf;
  131.     iphdrlen = ip->ip_hl << 2; /*求ip报头长度,即ip报头的长度标志乘4 */
  132.     icmp = (struct icmp *)(buf + iphdrlen); /*越过ip报头,指向ICMP报头 */
  133.     len -= iphdrlen; /*ICMP报头及ICMP数据报的总长度 */
  134.     if (len < 8) /*小于ICMP报头长度则不合理 */
  135.     {
  136.         printf("ICMP packets/'s length is less than 8\n");
  137.         return -1;
  138.     }
  139.     /*确保所接收的是我所发的的ICMP的回应 */
  140.     if ((icmp->icmp_type == ICMP_ECHOREPLY) && (icmp->icmp_id == pid))
  141.     {
  142.         tvsend = (struct timeval *)icmp->icmp_data;
  143.         tv_sub(&tvrecv, tvsend); /*接收和发送的时间差 */
  144.         rtt = tvrecv.tv_sec * 1000 + tvrecv.tv_usec / 1000; /*以毫秒为单位计算rtt */
  145.         /*显示相关信息 */
  146.         printf("%d byte from %s: icmp_seq=%u ttl=%d rtt=%.3f ms\n",
  147.                len, inet_ntoa(from.sin_addr), icmp->icmp_seq, ip->ip_ttl, rtt);
  148.     }
  149.     else
  150.         return -1;
  151. }

  152. int main(int argc, char *argv[])
  153. {
  154.     struct hostent *host;
  155.     struct protoent *protocol;
  156.     unsigned long inaddr = 0l;
  157.     int waittime = MAX_WAIT_TIME;
  158.     int size = 50 * 1024;

  159.     if (argc < 2)
  160.     {
  161.         printf("usage:%s hostname/IP address\n", argv[0]);
  162.         exit(1);
  163.     }

  164.     if ((protocol = getprotobyname("icmp")) == NULL)
  165.     {
  166.         perror("getprotobyname");
  167.         exit(1);
  168.     }
  169.     /*生成使用ICMP的原始套接字,这种套接字只有root才能生成 */
  170.     if ((g_sockFd = socket(AF_INET, SOCK_RAW, protocol->p_proto)) < 0)
  171.     {
  172.         perror("socket error");
  173.         exit(1);
  174.     }

  175.     printf("user id :%d\n", getuid());
  176.     /* 回收root权限,设置当前用户权限 */
  177.     setuid(getuid());

  178.     printf("userid:%d,effect user:%d\n", getuid(), geteuid());

  179.     /*
  180.      * 扩大套接字接收缓冲区到50K这样做主要为了减小接收缓冲区溢出的
  181.      * 的可能性,若无意中ping一个广播地址或多播地址,将会引来大量应答
  182.      */
  183.     setsockopt(g_sockFd, SOL_SOCKET, SO_RCVBUF, &size, sizeof(size));
  184.     bzero(&dest_addr, sizeof(dest_addr));
  185.     dest_addr.sin_family = AF_INET;

  186.     /*判断是主机名还是ip地址 */
  187.     if ((inaddr = inet_addr(argv[1])) == INADDR_NONE)
  188.     {
  189.         if ((host = gethostbyname(argv[1])) == NULL) /*是主机名 */
  190.         {
  191.             perror("gethostbyname error");
  192.             exit(1);
  193.         }
  194.         memcpy((char *)&dest_addr.sin_addr, host->h_addr, host->h_length);
  195.     }
  196.     else /*是ip地址 */
  197.         dest_addr.sin_addr.s_addr = inaddr;

  198.     /*获取main的进程id,用于设置ICMP的标志符 */
  199.     pid = getpid();
  200.     printf("PING %s(%s): %d bytes data in ICMP packets.\n", argv[1], inet_ntoa(dest_addr.sin_addr), g_dataLen);
  201.     send_packet(); /*发送所有ICMP报文 */
  202.     recv_packet(); /*接收所有ICMP报文 */
  203.     statistics(SIGALRM); /*进行统计 */

  204.     return 0;

  205. }

  206. /*两个timeval结构相减*/
  207. void tv_sub(struct timeval *out, struct timeval *in)
  208. {
  209.     if ((out->tv_usec -= in->tv_usec) < 0)
  210.     {
  211.         --out->tv_sec;
  212.         out->tv_usec += 1000000;
  213.     }
  214.     out->tv_sec -= in->tv_sec;
  215. }

  216. /*------------- The End -----------*/



阅读(2884) | 评论(0) | 转发(0) |
给主人留下些什么吧!~~