Internet Info, s.r.o. Lupa Root Měšec Podnikatel DigiZone Slunečnice Vitalianew Bomba Navrcholu Weblogy Jagg Woko Dobrý web Computer.cz SK: MojeLinky

Hlavní navigace

Sokety a C/C++: program ping

Dnes odešleme náš první ICMP paket. Bude se jednat o ECHO žádost. Odesíláním ECHO žádosti a přijetím ECHO odpovědi naprogramujeme jednoduchý program ping.

Dnes konečně odešleme první ICMP paket. Uděláme si velice jednoduchou implementaci programu ping. Program ping slouží k testování dostupnosti počítače v síti.

Princip programu ping

Program ping odešle na požadovaný počítač několikrát požadavek ECHO. Poté čeká na ECHO odpověď, kterou zmiňovaný počítač odpoví. Program ping si po odeslání ICMP paketu musí zapamatovat jeho identifikátor a pořadové číslo žádosti. Poté čeká a čte všechny příchozí ICMP pakety. Zajímá jej pouze ECHO odpověď se stejným identifikátorem a stejným pořadovým číslem. Jestliže přijde ECHO odpověď ve stanoveném čase, program vypíše, že počítač je dostupný. Také ping obvykle vypíše další informace včetně času, který uplynul od odeslání ICMP paketu – žádost o ECHO – k přijetí ICMP paketu – žádost o odpověď. Jestliže paket nepřijde v požadovaném čase, je daný počítač považován za nedostupný z našeho počítače.

Co naprogramujeme?

Napíšeme si velice jednoduchou (několikařádkovou) implementaci programu ping, který toho na rozdíl od jiných variant programu ping nebude mnoho umět. Nebude umět vlastně nic jiného než 5× odeslat ICMP paket a čekat na požadovaný ICMP paket. Podíváte-li se na program ping, který máte určitě nainstalován (ať už používáte Linux, MS Windows Ž, nebo cokoliv jiného), zjistíte, že je možné nastavovat mnoho užitečných věcí, které náš program umět nebude.

Příklad

Formality

Tak jako vždy, i nyní musím vložit hlavičkové soubory, začít funkci main a definovat proměnné. V příkladu musí být také funkce checksum, která je opsána z dokumentu RFC 1071. V MS WindowsŽ musíme také zavolat funkci WSAStartup.

#include <iostream>

#include <netinet/in.h>
#include <sys/socket.h>
#include <sys/types.h>
#include <netdb.h>
#include <arpa/inet.h>
#include <stdlib.h>

#include <netinet/ip.h>
#include <netinet/ip_icmp.h>
#include <unistd.h>
#include <string.h>

#define BUFSIZE 1024

using namespace std;

int main(int argc, char *argv[])
{
  size_t size;
  hostent *host;
  icmphdr *icmp, *icmpRecv;
  iphdr *ip;
  int sock, total, lenght;
  unsigned int ttl;
  sockaddr_in sendSockAddr, receiveSockAddr;
  char buffer[BUFSIZE];
  fd_set mySet;
  timeval tv;
  char *addrString;
  in_addr addr;
  unsigned short int pid = getpid(), p;

  if (argc != 2)
  {
    cerr << "Syntaxe:\n\t" << argv[0]
     << " " << "adresa" << endl;
    return -1;
  }

Překlad doménového jména

Přeložíme doménové jméno, které je parametrem programu, na IP adresu.

  if ((host = gethostbyname(argv[1])) == NULL)
  {
    cerr << "Špatná adresa" << endl;
    return -1;
  }

Vytvoření soketu

Vytvoříme soket typu SOCK_RAW a použijeme protokol IPPROTO_ICMP.

  if ((sock =
     socket(PF_INET, SOCK_RAW, IPPROTO_ICMP)) == -1)
  {
    cerr << "Nelze vytvořit soket" << endl;
    return -1;
  }

Nastavení atributu TTL hlavičky IP

Nastavíme co největší položku TTL u odchozích paketů. Hodnotu nastavíme na 255. Použijeme nastavení voleb soketů. Volba se jmenuje IP_TTL, je v úrovni SOL_IP.

  ttl = 255;
  setsockopt(sock, IPPROTO_IP, IP_TTL,
       (const char *)&ttl, sizeof(ttl));

Vytvoříme ICMP paket – ECHO žádost

Postupně vyplníme všechny atributy ICMP paketu typu 8 kódu 0 (ECHO žádost). Nejprve alokujeme potřebnou paměť, potom vyplníme typ a kód ICMP paketu. Identifikátor ECHO žádosti by měl být vyplněn nějakým jednoznačným číslem. Stejný identifikátor bude mít ECHO odpověď, čímž zajistíme, že odpověď je určena pro nás. proměnná pid má v Linuxu hodnotu, kterou vrátila funkce getpid, v MS Windows Ž má hodnotu, kterou vrátila funkce GetCurrentPro­cessId. Zbývá ještě vyplnit kontrolní součet a pořadové číslo žádosti. Protože budeme posílat pět ECHO žádostí, které odešleme v cyklu, budeme kontrolní součet a pořadové číslo vyplňovat v cyklu před odesláním. Budou pro každý odeslaný ICMP paket ECHO žádost různé.

  icmp = (icmphdr *)malloc(sizeof(icmphdr));
  icmp->type = ICMP_ECHO;
  icmp->code = 0;
  icmp->un.echo.id = pid;

Příprava odeslání dat

Zaplníme strukturu sockaddr_in, kterou použijeme jako parametr funkce sendto. Číslo portu je nyní nedůležité. Zadáme 0. Adresáta ICMP paketu zaplníme tak, jak jsme zvyklí.

  sendSockAddr.sin_family = AF_INET;
  sendSockAddr.sin_port = 0;
  memcpy(&sendSockAddr.sin_addr,
         host->h_addr, host->h_length);

Doplnění dalších položek ICMP hlavičky

V cyklu budeme vyplňovat ty části ICMP hlavičky, které budou v každém ICMP paketu odlišné. Pořadové číslo ECHO žádosti společně s identifikátorem ECHO žádosti jednoznačně identifikuje ECHO žádost. ECHO odpověď na žádost má stejný identifikátor i stejné pořadové číslo. Zatímco identifikátor určuje de facto aplikaci, kterou byla ICMP žádost odeslána, pořadové číslo rozlišuje jednotlivé žádosti ECHO. Jestliže jedna aplikace odešle více žádostí o ECHO, měla by je rozlišit (číslovat) pomocí pořadových čísel. Vyplnění identifikátoru a pořadového čísla podle zmíněných pravidel není dáno žádnou normou nebo předpisem. Jedná se jen o doporučení.

Kontrolní součet nejprve nastavíme na 0, poté spočítáme z celého ICMP paketu pomocí již známé funkce. ICMP žádost ECHO může mít i tělo s libovolným obsahem. Stejná data, která odejdou v ECHO žádosti, se vrátí v ECHO odpovědi. Tělo by následovalo za hlavičkou. Kontrolní součet by se počítal z hlavičky i těla. My budeme posílat pouze hlavičku (takže tělo bude mít nulovou délku).

  for (p = 1; p <= 5; p++)
  {
    icmp->checksum = 0;
    icmp->un.echo.sequence = p;
    icmp->checksum =
         checksum((unsigned char *)icmp, sizeof(icmphdr));

Odešleme data

Pomocí sendto odešleme vyplněnou ICMP hlavičku ECHO žádosti.

    sendto(sock,
        (char *)icmp, sizeof(icmphdr), 0,
        (sockaddr *)&sendSockAddr, sizeof(sockaddr));

Počkáme maximálně 5 sekund

Pomocí funkce select počkáme maximálně 5 sekund, jestli nepřijde ECHO odpověď. Jestliže ne, považujeme ECHO žádost za ztracenou. Musíme počítat se situací, že přijde ICMP paket, který není určený pro nás (není ECHO odpověď nebo je ECHO odpověď s jiným identifikátorem nebo s jiným pořadovým číslem, než čekáme). Náš soket přijímá všechny ICMP pakety, které jsou dopraveny do počítače.

Takto napsaný program bude s jistotou fungovat jen v Linuxu. Je tady totiž menší problém, co udělat v případě, že přijde ICMP paket, který není pro nás. Funkce select se ukončí, my následně zjistíme, že není pro nás a zavoláme funkci select znova. Jaký ji ale dát poslední parametr? Podíváme-li se na manuálovou stránku funkce select, zjistíme, že v případě události na soketu bude ukončeno volání select a časový údaj (předávaný jako poslední parametr) bude změněn. Bude od původní hodnoty snížen o čas, který funkce select čekala. Toho já využívám. Problém je v tom, že se jedná o speciální vlastnost Linuxu. Rozhodně nefunguje v MS WindowsŽ. V jiných Unix-like systémech (kromě Linuxu) možná taky ne. V tom případě je nutné čas odečítat tak, jak to dělám v příkladu ke stažení pro MS WindowsŽ.

    tv.tv_sec = 5;
    tv.tv_usec = 0;
    do
    {
      FD_ZERO(&mySet);
      FD_SET(sock, &mySet);
      if (select(sock + 1, &mySet, NULL, NULL, &tv) < 0)
      {
            cerr << "Selhal select" << endl;
            break;
      }

Přijmeme data

Jestliže nastala událost na soketu, přijmeme data pomocí recvfrom.

      if (FD_ISSET(sock, &mySet))
      {
            size = sizeof(sockaddr_in);
            if ((lenght = recvfrom(sock,
               buffer, BUFSIZE, 0,
               (sockaddr *)&receiveSockAddr,
               &size)) == -1)
            {
              cerr << "Problém při přijímáni dat" << endl;
            }

Analýza dat

S přijímáním ICMP paketů jsme se setkali již v článku Sokety a C/C++ – přijímání ICMP paketů. V přijímaném bufferu je nejprve hlavička IP protokolu, až poté hlavička ICMP.

Přišel-li ICMP paket, musíme nejprve zjistit, jestli se jedná o ECHO odpověď. Jestliže ano, musíme zjistit, zda se jedná o ECHO odpověď se stejným identifikátorem, jaký měla naše žádost, a zda má stejné pořadové číslo jako naše žádost. Jestliže ano, vypíšeme informace o přijetí odpovědi. Jestliže ne, čekáme dál nebo vypíšeme informaci o nepřijetí. ICMP pakety přijímáme, dokud nepřijde požadovaná odpověď nebo dokud nevyprší čas 5 sekund.

      ip = (iphdr *) buffer;
      icmpRecv = (icmphdr *) (buffer + ip->ihl * 4);
      if ((icmpRecv->un.echo.id == pid)
            && (icmpRecv->type == ICMP_ECHOREPLY)
            && (icmpRecv->un.echo.sequence == p))
      {
          addrString =
              strdup(inet_ntoa(receiveSockAddr.sin_addr));
          host = gethostbyaddr(&receiveSockAddr.sin_addr,
                              4, AF_INET);
          cout << lenght << " bytů z "
               << (host == NULL? "?" : host->h_name)
               << " (" << addrString << "): icmp_seq="
               << icmpRecv->un.echo.sequence << " ttl="
               << (int)ip->ttl
               << " čas=nevím, neměřil jsem:-)" << endl;
          free(addrString);
      }
    }
    else
    {
      cout << "Čas vypršel" << endl;
      break;
    }
  } while (!((icmpRecv->un.echo.id == pid)
     && (icmpRecv->type == ICMP_ECHOREPLY)
     && (icmpRecv->un.echo.sequence == p)));

Konec

Ukončíme závorky, uvolníme paměť, uzavřeme soket, v MS Windows Ž zavoláme funkci WSACleanup a ukončíme funkci main.

  }
  close(sock);
  free(icmp);
  return 0;
}

Další možnosti

ECHO žádost a ECHO odpověď je v podstatě přímo dělaná na testování dostupnosti počítače v síti. Existuje ale i jiná (horší) možnost, jak testovat dostupnost počítače. V minulém díle jsme dělali experimenty, ve kterých jsme odeslali UDP paket na nějaký počítač na nesmyslný port. Přišel nám zpátky ICMP paket oznamující nedostupnost daného portu. Co to ale znamená? Znamená to, že počítač existuje (musí existovat, když nám poslal ICMP paket), pouze na tom počítači nečeká žádný proces na námi zvoleném UDP portu. Tímto způsobem lze také testovat dostupnost počítače. Prostě odešleme UDP datagram na zvolený počítač a nesmyslný port. Poté čekáme na ICMP paket oznamující nedoručení. Viz příklad v minulém článku.

Tabulka č. 460
Soubor Operační systém
lin22.tgz Linux
win22.zip MS WindowsŽ

Příště se podíváme na program traceroute (tracert v MS WindowsŽ). Vysvětlíme si princip a pokusíme se o jednoduchou implementaci.

Školení unit testování v PHP

Akademie Root
  • Naučte se psát kód, kterým předejdete většině chyb už při vývoji.
  • Získejte spustitelnou dokumentaci kódu a usnadněte začleňování práce nových kolegů.
  • Postupy školené Jiřím Kneslem šetří čas při hledání chyb!
  • Psaní testů vás bude bavit!

Detailní informace o kurzu...

Ohodnoťte jako ve škole: 12345
Průměrná známka 3,21

Přehled názorů

slusny
optik 4. 8. 2003 17:15
Oprava
Jan 5. 8. 2003 18:14
└ 
Re: Oprava
Radim Dostál 8. 8. 2003 20:43
odoslanie ECHO ziadosti
Fedi 28. 8. 2003 10:39
└ 
Re: odoslanie ECHO ziadosti
Radim Dostál 22. 9. 2003 12:00
Ping pod linuxem
Swap 10. 11. 2003 17:31
└ 
Re: Ping pod linuxem
Swap 10. 11. 2003 17:33
Nefunguje
Blah 25. 10. 2007 19:04
└ 
Re: Nefunguje
Jirka 26. 5. 2008 21:23
checksum pro
vdecny ctenar 13. 4. 2009 14:05
       

Tento text je již více než dva měsíce starý. Chcete-li na něj reagovat v diskusi, pravděpodobně vám již nikdo neodpoví. Pro řešení aktuálních problémů doporučujeme využít naše diskusní fórum.

Zasílat nově přidané příspěvky e-mailem