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 traceroute

Dnes napíšeme jednoduchou implementaci programu traceroute. Vysvětlíme si princip programu traceroute a seznámíme se blíže s ICMP paketem typu 11 kódu 0. V článku je podrobně okomentovaný zdrojový text.

Program traceroute slouží k zjištění počítačů, které se nacházejí mezi našim a zadaným počítačem. Data putující po síti (na úrovni internetové vrstvy protokolu TCP/IP – tedy vrstvy zajišťující komunikaci počítačů, které nemusejí být bezprostředně propojeny) procházejí různými počítači (směrovači), které se nalézají na cestě mezi dvěma komunikujícími počítači. Program traceroute slouží k zjištění IP adres počítačů (přesněji IP adres síťových karek, kterými data do počítače vstupují), které jsou mezi našim a zadaným vzdáleným počítačem.

Program traceroute je k dispozici snad v každém OS, který podporuje protokol TCP/IP. V MS WindowsŽ se program jmenuje tracert.

Princip traceroute

Princip programu je opět (tak jako u programu ping) založen na odesílání požadavku ECHO. Program traceroute je už ale trochu složitější než ping. Traceroute odesílá postupně ECHO žádosti na počítač. První ECHO žádost odešle s atributem TTL nastaveným na 1. Znamená to, že odeslaný IP paket může projít maximálně přes jeden počítač. Pak bude zahozen. V případě, že dojde k zahození IP paketu, kvůli snížení jeho TTL na 0, bude odesílatel IP paketu upozorněn na zahození odeslaného IP paketu ICMP paketem typu 11, kódu 0. Program přijme signalizační ICMP paket typu 11 kódu 0. Poznačí si (vypíše) jeho odesílatele. Zvýší TTL o jedna a opět odešle ECHO žádost. Takhle pokračuje dokud neobdrží ICMP paket ECHO odpověď.

Popis algoritmu

Počítač, ke kterému chceme zjistit cestu nazveme POČÍTAČ.

  1. Nastav hodnotu TTL na 1.
  2. Odešli ECHO žádost na POČÍTAČ.
  3. Přečti příchozí ICMP paket.
  4. Jestliže se jedná o ECHO odpověď k naší žádosti (shodný identifikátor i sekvenční číslo), potom konec. Nalezli jsme cestu.
  5. Jestliže se nejedná o ICMP paket typu 11, kódu 0, který oznamuje ztrátu námi odeslané žádosti, jdi na bod 3.
  6. Vypiš (poznamenej) adresu odesílatele ICMP paketu typu 11, kódu 0.
  7. Zvyš hodnotu TTL o 1.
  8. Jestliže hodnota TTL nepřekročila maximální možnou hodnotu TTL (256 a vyšší), pak jdi na bod 2.
  9. Konec – cestu jsme nenalezli

Popis ICMP paketu typu 11, kódu 0 (Čas vypršel – položka TTL klesla na 0)

ICMP paket signalizuje zahození IP paketu z důvodu snížení hodnoty TTL na 0. Je odeslán počítačem, který IP paket zahodil, počítači, který IP paket odeslal. Tím se odesílatel zahozeného IP paketu dozví, že došlo k zahození paketu, který odesílal. Také se dozví, kdo jej zahodil. Při popisu algoritmu 5 jsem napsal „který oznamuje ztrátu námi odeslané žádosti“. Měli bysme opravdu kontrolovat, jestli příchozí ICMP paket typu 11 a kódu 0 signalizuje zahození právě našeho paketu. Neměl by nám stačit fakt, že přišel ICMP paket typu 11 a kódu 0. Měli bysme si zjistit, který IP paket byl zahozen. Víme už totiž, že soket typu SOCK_RAW používající protokol IPPROTO_ICMP přijímá všechny pakety přicházející na počítač. Ne všechny ICMP pakety typu 11 a kódu 0, které soketem obdržíme, musejí informovat o zahození námi odeslaných IP paketů. Například program traceroute může v jeden okamžik běžet na počítači vícekrát.

ICMP paket typu 11 a kódu 0 má za svou hlavičkou tělo. Tělo ICMP paketu obsahuje IP hlavičku zahozeného IP paketu (typicky 20 bytů, ale nemusí tomu tak být vždy). Za IP hlavičkou následuje prvních 64 bytů těla zahozeného IP paketu. V našem případě byla zahozená ICMP žádost o ECHO. Proto za IP hlavičkou bude následovat ICMP hlavička ECHO žádosti. Právě z hlavičky ECHO žádosti, pomocí identifikátoru žádosti a sekvenčního čísla, zjistíme, zda se opravdu jedná o naši žádost. ICMP paket typu 11, kódu 3 je svou strukturou velmi podobný paketu „Nedosažitelný UDP port – typ 3, kód 3“. Přijatý buffer bude vypadat asi takto:

Tabulka č. 462
Typicky 20 bytů IP hlavička přijatého IP paketu. Paket v sobě nesl ICMP paket typu 11, kódu 0.
8 bytů ICMP hlavička typu 11 kódu 0.
Typicky 20 bytů IP hlavička zahozeného IP paketu. Paket v sobě nesl ICMP žádost o ECHO. Odesílatelem paketu jsme byli my.
Maximálně 64 bytů Tělo zahozeného IP paketu. V našem případě IP paket ve svém těle nesl ICMP paket ECHO žádost. ICMP paket měl pouze hlavičku (ICMP tělo v naších příkladech neposíláme). Proto místo 64 bytů zde bude pouze 8:
  • 8 bytů Hlavička ECHO žádosti, kterou jsme odeslali. (Obsahuje identifikátor žádosti a pořadové číslo žádosti.)
  • 0 bytů Tělo ECHO žádosti – my žádné tělo neposíláme.

Příklad

Nyní si jednoduchou implementaci programu traceroute naprogramujeme. Opět, tak jako u našeho pingu, bude náš traceroute za ostatními trochu zaostávat. Program traceroute má mnoho možností nastavení. Náš program nebude mít žádnou.

V programu postupně odesíláme ECHO žádosti a zvyšuje jim hodnotu TTL. Každá žádost má jiné sekvenční číslo. Je jistě pohodlné a přehledné mít hodnotu TTL a sekvenčního čísla stejnou. Já používám proměnnou ttl pro uchování hodnoty TTL poslední žádosti, ale také pro uchování hodnoty posledního sekvenčního čísla. Tím možná trochu matu pojmy a čtenáře, ale také ušetřím jednu proměnnou.

Formality

Vložíme potřebné hlavičkové soubory, deklarujeme funkce, definujeme makra, začneme funkci main a deklarujeme lokální proměnné ve funkcimain. Nakonec zkontrolujeme počet parametrů z příkazového řád­ku.

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

unsigned short checksum(unsigned char *addr, int count);
bool isLost(char *buffer, unsigned short int bufferLenght,
    unsigned short int id, unsigned short int sequence);

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

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

Překlad doménového jména

Z parametru zadaného z příkazové řádky získáme strukturu hostent.

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

Vytvoříme soket

Vytvářený soket bude typu SOCK_RAW s použitým protokolem IPPROTO_ICMP.

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

Částečně vyplníme hlavičku ICMP paketu

Vyplníme tu část ICMP hlavičky, kterou budou mít všechny žádosti stejné. Typ paketu bude žádost o ECHO. Podtyp 0 a identifikátor bude mít hodnotu identifikátoru procesu.

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

Zaplnění struktury sockaddr_in

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

Nastavení proměnné TTL na 1

Nastavíme proměnnou, která udává velikost TTL naposledy odeslané žádosti.

  ttl = 1;

Doplnění atributů ICMP hlavičky

V cyklu budeme zaplňovat ty atributy ICMP hlavičky, které se budou pro jednotlivé žádosti lišit. V každém průchodu cyklem odešleme jednu žádost.

  do
  {
    icmp->checksum = 0;
    icmp->un.echo.sequence = ttl;
    icmp->checksum =
        checksum((unsigned char *)icmp, sizeof(icmphdr));

Nastavení vlastnosti TTL u soketu

Nastavíme vlastnost IP_TTL úrovně SOL_IP.

    setsockopt(sock, SOL_IP, IP_TTL,
        (const char *)&ttl, sizeof(ttl));

Odešleme data

Odešleme ICMP žádost ECHO.

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

Přijímání ICMP paketů

Budeme přijímat všechny ICMP pakety, které přijdou. Zajímat nás budou ale jen některé.

    do
    {
      FD_ZERO(&mySet);
      FD_SET(sock, &mySet);
      if (select(sock + 1, &mySet, NULL, NULL, &tv) < 0)
      {
    cerr << "Selhal select" << endl;
    break;
      }
      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 příchozího ICMP paketu

Z IP hlavičky příchozího paketu zjistíme velikost hlavičky. Za IP hlavičkou se nachází ICMP hlavička (bude na ní ukazovat ukazatel icmpRecv). Zajímají nás pouze ECHO odpovědi nebo ICMP pakety typu 11, kódu 0. Funkce isLost je popsána níže. Vrací nám true v případě, že ICMP paket opravdu signalizuje ztrátu naší ECHO žádosti. Jedná-li se o ICMP pakety, které nás zajímají, vypíšeme o nich informace.

        ip = (iphdr *) buffer;
    icmpRecv = (icmphdr *) (buffer + ip->ihl * 4);
    if (isLost((char *)icmpRecv,
        lenght - ip->ihl * 4, pid, ttl))
    {
      addrString=strdup(inet_ntoa(receiveSockAddr.sin_addr));
      host = gethostbyaddr(&receiveSockAddr.sin_addr,
        4, AF_INET);
      cout << ttl << "\t" << lenght << " bytů z "
               << (host == NULL? "?" : host->h_name) << " ("
               << addrString << ") " << endl;
      free(addrString);
    }
        if ((icmpRecv->type == ICMP_ECHOREPLY)
            && (icmpRecv->code == 0)
            && (icmpRecv->un.echo.id == pid)
            && (icmpRecv->un.echo.sequence == ttl))
        {
          addrString =
              strdup(inet_ntoa(receiveSockAddr.sin_addr));
          host = gethostbyaddr(&receiveSockAddr.sin_addr,
                              4, AF_INET);
          cout << ttl << "\t" << lenght << " bytů z "
                   << (host == NULL? "?" : host->h_name) << " ("
                   << addrString << ") " << endl;
          free(addrString);
        }
      }

Čas vypršel

Jestliže nepřišel požadovaný ICMP paket za 5 sekund, vypíšeme hlášku.

      else
      {
    cout << "Čas vypršel" << endl;
    break;
      }

Podmínka pro přijímání ICMP paketu

Přijímáme ICMP pakety, dokud neobdržíme ECHO odpověď na naší otázku, nebo ICMP paket typu 11, kódu 3, který je určen pro nás.

    }while (!(isLost((char *)icmpRecv,lenght-ip->ihl*4, pid, ttl)
        || ((icmpRecv->type == ICMP_ECHOREPLY)
        && (icmpRecv->code == 0)
        && (icmpRecv->un.echo.id == pid)
        && (icmpRecv->un.echo.sequence == ttl))));

Zvýšíme TTL o 1

    ttl++;

Podmínka pro odesílání ICMP paketů

Odesílání ICMP paketů má smysl, dokud nepřijde ICMP odpověď na ECHO nebo dokud položka TTL nepřekročí maximální hodnotu.

  } while ((ttl != 256)
    && (!((icmpRecv->type == ICMP_ECHOREPLY)
    && (icmpRecv->code == 0)
    && (icmpRecv->un.echo.id == pid)
    && (icmpRecv->un.echo.sequence == ttl - 1))));

Ukončení programu

Je-li hodnota TTL o 1 více než maximální možná, nebyl hledaný počítač nalezen. V takovém případě vypíšeme informaci o nedosažení počítače. Dále uvolníme paměť, uzavřeme soket a ukončíme main.

  if (ttl == 256)
  {
        cout << "Počítač nedosažen" << endl;
  }
  close(sock);
  free(icmp);
  return 0;
}

Funkce isLost

V programu používám funkci isLost, která vrací true v případě, že ICMP paket signalizuje zahození zadané ECHO žádosti. Prvním parametrem je ukazatel za IP hlavičku dat, která jsme obdrželi. Druhým parametrem je velikost dat, která jsou ještě za místem, na které se odkazuje první ukazatel, alokována. Další parametry jsou identifikátor a pořadové číslo naší žádosti.

bool isLost(char *buffer, unsigned short int bufferLenght,
    unsigned short int id, unsigned short int sequence)
{

Kontrola délky bufferu

Je-li buffer kratší než minimální možný, vrátíme false. Díky této podmínce se nemůže stát, že bysme četli data z nealokované paměťi. IP hlavička může být ale větší, proto zkontrolujeme velikost ještě jednou.

   if (bufferLenght < 2 * sizeof(icmphdr) + sizeof(iphdr))
   {
     return false;
   }

Nastavení ukazatelů

Nasměrujeme do bufferu ukazatele přesně tak, jak jdou data za sebou.

   icmphdr *icmpRecv = (icmphdr *)buffer;
   iphdr *ipSend = (iphdr *)(buffer + sizeof(icmphdr));
   icmphdr *icmpSend =
    (icmphdr*) ((char *)ipSend + ipSend->ihl * 4);

Kontrola velikosti bufferu

Nyní můžeme konečně s jistotou zkontrolovat velikost bufferu.

   if (bufferLenght <2 * sizeof(icmphdr) + ipSend->ihl * 4)
   {
     return false;
   }

Kontrola obsahu ICMP paketu

Vrátíme true v případě, že se jedná o ICMP paket typu 11 (makro ICMP_TIME_EXCE­EDED), kódu 0 (makro ICMP_EXC_TTL) a ve svém těle nese za IP hlavičkou námi odeslanou ECHO žádost. Jinak vrátíme false.

   if ((icmpRecv->type == ICMP_TIME_EXCEEDED)
    && (icmpRecv->code == ICMP_EXC_TTL)
    && (icmpSend->type == ICMP_ECHO)
    && (icmpSend->code == 0)
    && (icmpSend->un.echo.id == id)
    && (icmpSend->un.echo.sequence == sequence))
   {
        return true;
   }
   return false;
}

Příklady ke stažení

Tabulka č. 463
Soubor Operační systém
lin23.tgz Linux
win23.zip MS WindowsŽ

Příště se podíváme na hodnotu MTU. Vysvětlíme si pojem MTU a povíme si, jak změřit MTU mezi našim a vybraným počítačem.

Školení: XML – nejen nový formát pro výměnu dat

Akademie Root
  • Jiří Kosek vás seznámí s novým jazykem XML!
  • Revoluční změny do oblasti elektronického publikování, výměny a sdílení dat a elektronického obchodu.
  • Využití XML v nejrůznějších aplikacích a informačních systémech.
  • Získejte široký přehled o XLM na našem školení!

Detailní informace o kurzu...

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

Přehled názorů

mierne offtopic
Tomas 11. 8. 2003 00:10
└ 
Re: mierne offtopic
PaJaSoft 13. 8. 2003 15:01
 
└ 
Re: mierne offtopic
Tomas 13. 8. 2003 21:02
test velkosti bufferu
Andrej 12. 8. 2003 01:32
└ 
Re: test velkosti bufferu
Radim Dostál 12. 8. 2003 10:45
icmp.dll
optik 12. 8. 2003 20:33
       

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