2012-03-28 71 views
7

目标:什么是无法访问目的地的ICMP回应请求/回复的正确过程?

我需要能够ping通网络交换机以确定它是否可用。这是为了告诉用户网络布线被拔掉,网络交换机不可用,或者其他问题存在于网络通信路径中。我意识到这不是一个全面的诊断工具,但有些事情总比没有好。

设计:

我计划使用ICMP与原始套接字送五(5)ping消息在IPv4点标记特定的地址。我将在套接字上设置一个ICMP过滤器,并且不会创建我自己的IP标头。 ICMP的传输将通过sendto方法并通过recvfrom方法进行接收。这将发生在单个线程上(尽管可以使用另一个线程来分开发送和接收)。通过将接收到的消息的ID与发送的ID进行匹配来进一步过滤消息的接收。存储的ID将是应用程序正在运行的进程ID。如果接收到ICMP_ECHOREPLY消息并且消息的ID和存储的ID匹配,则计数器递增,直到达到五(4)(该计数器从零开始)。我会尝试发送一个ping,等待它的回复,然后重复这个过程五(5)次。

问题:

已实施我的设计之后,每当我平活动网络参与者的特定有效的网络地址(比如说192.168.11.15),I接收ICMP_ECHOREPLY消息对每个五(5)的Ping 。但是,每当我ping一个有效的网络地址(比如说192.168.30.30)和非活动的网络参与者(意味着没有设备连接到特定地址),我就会得到一(1)个ICMP_DEST_UNREACH和四(4)个ICMP_ECHOREPLY消息。回复信息中的ID与存储在软件中的ID相匹配。每当我从命令行执行'ping 192.168.30.30'时,就会得到'来自192.168.40.50的icmp_seq = xx目标主机无法访问'。我不应该接收ICMP_DEST_UNREACH消息而不是ICMP_ECHOREPLY消息吗?

的代码:

Ping.h:

#include <netinet/in.h> 
#include <linux/ip.h> 
#include <linux/ipmc.h> 
#include <arpa/inet.h> 
#include <cstdio> 
#include <cstdlib> 
#include <stdint.h> 
#include <time.h> 
#include <errno.h> 
#include <string> 
#include <cstring> 
#include <netdb.h> 

class Ping 
{ 
    public: 
     Ping(std::string host) : _host(host) {} 
     ~Ping() {} 

     void start() 
     { 
      int sock = socket(AF_INET, SOCK_RAW, IPPROTO_ICMP); 
      if(sock < 0) 
      { 
       printf("Failed to create socket!\n"); 
       close(sock); 
       exit(1); 
      } 

      setuid(getuid()); 

      sockaddr_in pingaddr; 
      memset(&pingaddr, 0, sizeof(sockaddr_in)); 
      pingaddr.sin_family = AF_INET; 

      hostent *h = gethostbyname(_host.c_str()); 
      if(not h) 
      { 
       printf("Failed to get host by name!\n"); 
       close(sock); 
       exit(1); 
      } 

      memcpy(&pingaddr.sin_addr, h->h_addr, sizeof(pingaddr.sin_addr)); 

      // Set the ID of the sender (will go into the ID of the echo msg) 
      int pid = getpid(); 

      // Only want to receive the following messages 
      icmp_filter filter; 
      filter.data = ~((1<<ICMP_SOURCE_QUENCH) | 
          (1<<ICMP_DEST_UNREACH) | 
          (1<<ICMP_TIME_EXCEEDED) | 
          (1<<ICMP_REDIRECT) | 
          (1<<ICMP_ECHOREPLY)); 
      if(setsockopt(sock, SOL_RAW, ICMP_FILTER, (char *)&filter, sizeof(filter)) < 0) 
      { 
       perror("setsockopt(ICMP_FILTER)"); 
       exit(3); 
      } 

      // Number of valid echo receptions 
      int nrec = 0; 

      // Send the packet 
      for(int i = 0; i < 5; ++i) 
      { 
       char packet[sizeof(icmphdr)]; 
       memset(packet, 0, sizeof(packet)); 

       icmphdr *pkt = (icmphdr *)packet; 
       pkt->type = ICMP_ECHO; 
       pkt->code = 0; 
       pkt->checksum = 0; 
       pkt->un.echo.id = htons(pid & 0xFFFF); 
       pkt->un.echo.sequence = i; 
       pkt->checksum = checksum((uint16_t *)pkt, sizeof(packet)); 

       int bytes = sendto(sock, packet, sizeof(packet), 0, (sockaddr *)&pingaddr, sizeof(sockaddr_in)); 
       if(bytes < 0) 
       { 
        printf("Failed to send to receiver\n"); 
        close(sock); 
        exit(1); 
       } 
       else if(bytes != sizeof(packet)) 
       { 
        printf("Failed to write the whole packet --- bytes: %d, sizeof(packet): %d\n", bytes, sizeof(packet)); 
        close(sock); 
        exit(1); 
       } 

       while(1) 
       { 
        char inbuf[192]; 
        memset(inbuf, 0, sizeof(inbuf)); 

        int addrlen = sizeof(sockaddr_in); 
        bytes = recvfrom(sock, inbuf, sizeof(inbuf), 0, (sockaddr *)&pingaddr, (socklen_t *)&addrlen); 
        if(bytes < 0) 
        { 
         printf("Error on recvfrom\n"); 
         exit(1); 
        } 
        else 
        { 
         if(bytes < sizeof(iphdr) + sizeof(icmphdr)) 
         { 
          printf("Incorrect read bytes!\n"); 
          continue; 
         } 

         iphdr *iph = (iphdr *)inbuf; 
         int hlen = (iph->ihl << 2); 
         bytes -= hlen; 

         pkt = (icmphdr *)(inbuf + hlen); 
         int id = ntohs(pkt->un.echo.id); 
         if(pkt->type == ICMP_ECHOREPLY) 
         { 
          printf(" ICMP_ECHOREPLY\n"); 
          if(id == pid) 
          { 
           nrec++; 
           if(i < 5) break; 
          } 
         } 
         else if(pkt->type == ICMP_DEST_UNREACH) 
         { 
          printf(" ICMP_DEST_UNREACH\n"); 
          // Extract the original data out of the received message 
          int offset = sizeof(iphdr) + sizeof(icmphdr) + sizeof(iphdr); 
          if(((bytes + hlen) - offset) == sizeof(icmphdr)) 
          { 
           icmphdr *p = reinterpret_cast<icmphdr *>(inbuf + offset); 
           id = ntohs(p->un.echo.id); 
           if(origid == pid) 
           { 
            printf("  IDs match!\n"); 
            break; 
           } 
          } 
         } 
        } 
       } 
      } 

      printf("nrec: %d\n", nrec); 
     } 

    private: 
     int32_t checksum(uint16_t *buf, int32_t len) 
     { 
      int32_t nleft = len; 
      int32_t sum = 0; 
      uint16_t *w = buf; 
      uint16_t answer = 0; 

      while(nleft > 1) 
      { 
       sum += *w++; 
       nleft -= 2; 
      } 

      if(nleft == 1) 
      { 
       *(uint16_t *)(&answer) = *(uint8_t *)w; 
       sum += answer; 
      } 

      sum = (sum >> 16) + (sum & 0xFFFF); 
      sum += (sum >> 16); 
      answer = ~sum; 

      return answer; 
     } 

     std::string _host; 
}; 

main.cpp中:

#include "Ping.h" 

int main() 
{ 
//  Ping ping("192.168.11.15"); 
    Ping ping("192.168.30.30"); 
    ping.start(); 

    while(1) sleep(10); 
} 

为了编译,只需要输入 '克++的main.cpp -o平' 成一个Linux机器的命令行,它应该编译(即如果安装了所有的源代码)。

结论:

谁能告诉我为什么我从一个设备是不是在那个特定的网络地址接收一(1)ICMP_DEST_UNREACH和四(4)ICMP_ECHOREPLY消息?

注意:您可以从main.cpp文件更改网络IP地址。只需将IP更改为网络中实际存在的设备或网络上不存在的设备。

我也对编码风格的批评不感兴趣。我知道它并不漂亮,具有混合C++类型的'C'风格,缺乏内存管理等,但这只是原型代码。这并不意味着漂亮。

+0

ICMP_ECHO_REPLY结构是否具有与目标地址相同的地址?还是它有中间路由器? – Beached 2012-06-02 04:19:39

+0

如果你使用'/ bin/ping -c5 192.168.30.30',你会得到什么?打印回复数据包的内容可能会提供更多信息。如果你自己解决了这个问题,请发布你学到的东西。 – 2012-10-03 15:39:16

+0

这听起来更像是一个网络问题,而不是一个编程问题。 – cxxl 2012-12-11 11:28:47

回答

4

好吧,我发现了错误。看看这两行。

int bytes = sendto(sock, packet, sizeof(packet), 0, (sockaddr *)&pingaddr, sizeof(sockaddr_in)); 

bytes = recvfrom(sock, inbuf, sizeof(inbuf), 0, (sockaddr *)&pingaddr, (socklen_t *)&addrlen); 

两个功能使用pingaddr指针作为参数,但这应该在sendto()功能,因为可以避免用来指向的ICMP数据包的目的IP,但在recvfrom()用来找回IP这是主机回复。

比方说pingaddr设置IP不可达。在你的第一个网关ICMP_REQUEST后,第一个网关会回复给你一个ICMP_DEST_UNREACH,然后......错误...当recvfrom被调用时,pingadd结构将被网关的IP覆盖。

SO ...从第二次ping你将指向网关IP,显然,存在并将回复一个ICMP_ECHOREPLY

SOLUTION:

避免传递相同sockaddr_in结构指针既sendto()recvfrom()

相关问题