Hlavní navigace

Sokety a C/C++: program ping

Radim Dostál

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.

Našli jste v článku chybu?

1. 12. 2012 0:20

Firzen (neregistrovaný)

Funkce pro výpočet kontrolního součtu je špatná, a výše zmíněná nelze nijak rozumně použít. Po relativně náročném hledání a úpravě jsem našel konečně funkční verzi:

uint16_t checksum(uint8_t *addr_b, unsigned len)
{
uint16_t* addr = (uint16_t*) addr_b;
uint16_t answer = 0;
/*
* Our algorithm is simple, using a 32 bit accumulator (sum), we add
* sequential 16 bit words to it, and at the end, fold back all the
* carry bits from the top 16 bits into the lower 16 bits.
*/
uint32_t sum = 0;






10. 11. 2003 17:33

Swap (neregistrovaný)

Jeste jsem zapomnel dodat, ze pod woknama to chodi!

Lupa.cz: Kdo pochopí vtip, může jít do ČT vyvíjet weby

Kdo pochopí vtip, může jít do ČT vyvíjet weby

DigiZone.cz: „Black Friday 2016“: závěrečné zhodnocení

„Black Friday 2016“: závěrečné zhodnocení

Podnikatel.cz: Přehledná titulka, průvodci, responzivita

Přehledná titulka, průvodci, responzivita

DigiZone.cz: Flix TV startuje i na Slovensku

Flix TV startuje i na Slovensku

Vitalia.cz: Jsou čajové sáčky toxické?

Jsou čajové sáčky toxické?

Podnikatel.cz: EET: Totálně nezvládli metodologii projektu

EET: Totálně nezvládli metodologii projektu

Vitalia.cz: Láska na vozíku: Přitažliví jsme pro tzv. pečovatelky

Láska na vozíku: Přitažliví jsme pro tzv. pečovatelky

Vitalia.cz: Znáte „černý detox“? Ani to nezkoušejte

Znáte „černý detox“? Ani to nezkoušejte

Podnikatel.cz: Zavře krám u #EET Malá pokladna a Teeta?

Zavře krám u #EET Malá pokladna a Teeta?

Podnikatel.cz: K EET. Štamgast už peníze na stole nenechá

K EET. Štamgast už peníze na stole nenechá

120na80.cz: Na ucho teplý, nebo studený obklad?

Na ucho teplý, nebo studený obklad?

Vitalia.cz: „Připluly“ z Německa a možná obsahují jed

„Připluly“ z Německa a možná obsahují jed

Měšec.cz: Zdravotní a sociální pojištění 2017: Připlatíte

Zdravotní a sociální pojištění 2017: Připlatíte

Vitalia.cz: Paštiky plné masa ho zatím neuživí

Paštiky plné masa ho zatím neuživí

Podnikatel.cz: Udávání kvůli EET začalo

Udávání kvůli EET začalo

Podnikatel.cz: Chaos u EET pokračuje. Jsou tu další návrhy

Chaos u EET pokračuje. Jsou tu další návrhy

Vitalia.cz: Spor o mortadelu: podle Lidlu falšovaná nebyla

Spor o mortadelu: podle Lidlu falšovaná nebyla

Podnikatel.cz: Snížení DPH na 15 % se netýká všech

Snížení DPH na 15 % se netýká všech

Vitalia.cz: I církev dnes vyrábí potraviny

I církev dnes vyrábí potraviny

Podnikatel.cz: Chtějte údaje k dani z nemovitostí do mailu

Chtějte údaje k dani z nemovitostí do mailu