2012-03-28 8 views
7

Cel:Jaki jest prawidłowy proces dla żądania echa ICMP w nieosiągalnych miejscach docelowych?

Potrzebuję móc pingować przełącznik sieciowy, aby określić, czy jest on dostępny. Ma to na celu poinformowanie użytkownika, że ​​okablowanie sieciowe jest odłączone, przełącznik sieciowy jest niedostępny lub jakiś inny problem leży w ścieżce komunikacji sieciowej. Rozumiem, że to nie jest kompleksowe narzędzie diagnostyczne, ale coś jest lepsze niż nic.

Projekt:

planowałem na temat korzystania z gniazd surowych ICMP do wysyłania wiadomości pięciu (5) ping do konkretnego adresu w IPv4 dot-notacji. Skonfiguruję filtr ICMP na gnieździe i nie utworzę własnego nagłówka IP. Transmisja ICMP odbywa się za pomocą metody sendto i odbioru za pomocą metody recvfrom. Będzie to miało miejsce w pojedynczym wątku (chociaż inny wątek może zostać użyty do rozbicia transmisji i odbioru). Odbieranie wiadomości będzie dalej filtrowane przez dopasowanie identyfikatora odebranej wiadomości do przesłanego identyfikatora. Zapisany identyfikator będzie bieżącym identyfikatorem procesu aplikacji. Jeśli zostanie odebrany komunikat ICMP_CUTOREPLY, a identyfikator wiadomości i zapisany identyfikator są zgodne, to licznik jest zwiększany aż do osiągnięcia pięciu (4) (licznik jest zerowy). Spróbuję wysłać ping, poczekać na odpowiedź i powtórzyć ten proces pięć (5) razy.

Problem:

Po wdrożeniu mój projekt, ilekroć ping szczególną poprawny adres sieciowy (słownie 192.168.11.15) z aktywnego uczestnika sieci, otrzymuję wiadomości ICMP_ECHOREPLY dla każdego z pięciu (5) pingi . Jednak zawsze, gdy pinguję poprawny adres sieciowy (powiedzmy 192.168.30.30) z nieaktywnymi uczestnikami sieci (co oznacza, że ​​żadne urządzenie nie jest podłączone do konkretnego adresu), otrzymuję jedną (1) komunikatów ICMP_DEST_UNREACH i cztery (4) komunikaty ICMP_ACTOREPLY. Identyfikator w odpowiedziach odpowiada identyfikatorowi zapisanemu w oprogramowaniu. Ilekroć wykonuję polecenie "ping 192.168.30.30" z wiersza poleceń, otrzymuję komunikat "From 192.168.40.50 icmp_seq = xx Destination Host Unreachable". Czy nie powinienem odbierać komunikatów ICMP_DEST_UNREACH zamiast komunikatów ICMP_ACTOREPLY?

Kod:

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); 
} 

W celu kompilacji, wystarczy wpisać 'g ++ -o main.cpp ping' w wierszu polecenia w Linuksie i powinna się skompilować (to znaczy, jeśli zainstalowany jest cały kod źródłowy).

Wniosek:

Czy ktoś może mi powiedzieć, dlaczego jestem otrzymaniu jednego (1) ICMP_DEST_UNREACH i cztery (4) komunikaty ICMP_ECHOREPLY z urządzenia, które nie znajduje się na danym adresie sieciowym?

UWAGA: Możesz zmienić adres IP sieci z pliku main.cpp. Po prostu zmień adres IP na urządzenie, które faktycznie istnieje w Twojej sieci lub urządzenie, które nie istnieje w Twojej sieci.

Nie interesują mnie również krytyka dotycząca stylu kodowania. Wiem, że to nie jest ładne, ma styl castowania w stylu C z obsadą C++, ma kiepskie zarządzanie pamięcią, itp., Ale to tylko prototypowy kod. Nie ma być ładna.

+0

Czy struktura ICMP_ECHO_REPLY ma ten sam adres co adres docelowy? A może ma router pośredni? – Beached

+0

Jeśli używasz '/ bin/ping -c5 192.168.30.30', co otrzymujesz? Drukowanie zawartości pakietu odpowiedzi może dostarczyć więcej informacji. Jeśli sam rozwiązałeś problem, opublikuj to, czego się nauczyłeś. –

+0

To brzmi bardziej jak problem z siecią niż pytanie programistyczne. – cxxl

Odpowiedz

4

Ok znalazłem błąd. Spójrz na te dwie linie.

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); 

obu funkcji korzysta pingaddr wskaźnik jako parametr, ale to powinno unikać, ponieważ w funkcji sendto() służy do punktu docelowego IP pakietu ICMP ale w recvfrom() służy odzyskać IP hosta, który jest odpowiadając.

Załóżmy, że pingaddr jest ustawiony z IP nieosiągalny. Po twojej pierwszej ICMP_REQUEST pierwsza brama odpowie Ci za pomocą ICMP_DEST_UNREACH i ... tutaj pojawia się błąd ... gdy wywoływana jest funkcja recvfrom, struktura pingaddr zostanie nadpisana przez IP bramy.

SO ... z drugiego pinga wskażesz adres IP bramy, który oczywiście istnieje i odpowie z numerem ICMP_ECHOREPLY.

ROZWIĄZANIE:

unikać przechodzą ten sam wskaźnik do struktury sockaddr_in zarówno sendto() i recvfrom().

Powiązane problemy