Sokety a C/C++: přijímání ICMP paketů

21. 7. 2003
Doba čtení: 4 minuty

Sdílet

V minulém dílu jsme se seznámili se strukturou a formátem ICMP paketu. Dnes si uděláme první jednoduchý příklad používající ICMP protokol. Bude se jednat o program monitorující všechny ICMP pakety přicházející na počítač.

Každý program, který na počítači běží, může přijímat všechny ICMP pakety. Programu stačí, aby vytvořil soket typu SOCK_RAW používající protokol IPPROTO_ICMP (samozřejmě musí mít k tomu přístupová – administrátorská – práva).

Použití protokolu ICMP v programu

Už jsem vše nastínil v minulém dílu. Chceme-li odeslat data (což v dnešním dílu ještě dělat nebudeme), musí odesílací buffer obsahovat řádně vyplněnou hlavičku ICMP protokolu. Za ní následují data, jestliže nějaká jsou. Při odesílání paketu nevkládáme hlavičku IP do odesílaného bufferu. Když ICMP paket přijímáme, v přijímajícím bufferu je IP hlavička i ICMP hlavička. Za ní teprve následují data, jsou-li nějaká.

Malá poznámka k WinSock

Celý seriál používám (píšu pro něj příklady) prostředí DEV-CPP založené na překladači MinGW. S překladačem MinGW jsou dodávány hlavičkové soubory, ve kterých bohužel není vše, co by v nich být mělo. Již v článku Sokety a C/C++ – Raw soket jsem narazil na problém, že nebyla k dispozici struktura reprezentující IP a UDP hlavičky. S protokolem ICMP tomu není jinak. Stejně jako tenkrát jsem i nyní pro WinSock vytvořil hlavičkový soubor, který je ke stažení jako součást příkladu pro MS WindowsŽ na konci článku. Pokud používáte jiný překladač, budete nejspíše mít potřebné struktury a makra definované v jeho hlavičkových souborech. Poté můžete použít buďto mé struktury, anebo raději „originální“ dodávané s překladačem. V takovém případě ale musíte počítat s tím, že se názvy struktur nebo atributů budou asi trochu lišit. Já pojmenoval struktury i atributy struktur stejně, jako jsou pojmenovány v Linuxu.

Příklad – přijímač ICMP paketů

Příklad je vlastně velice jednoduchý. Vytvoříme soket typu SOCK_RAW používající protokol IPPROTO_ICMP. Poté v cyklu voláme funkcirecvfrom, která čte příchozí ICMP pakety. Z každého ICMP paketu získám nějaké ty informace a vypíšu je. Přijmu tolik ICMP paketů, kolik si uživatel přál.

Formality

Nejprve provedeme náležité formality. Vložíme potřebné hlavičkové soubory, začneme funkci main, deklarujeme potřebné proměnné a ošetříme parametry příkazové řádky.

#include <stdio.h>
#include <stdlib.h>

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

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

#define BUFSIZE 1024

using namespace std;

int main(int argc, char *argv[])
{
  hostent *host;
  iphdr *ip;
  icmphdr *icmp;
  int sock;
  unsigned int count;
  sockaddr_in sender;
  char buffer[BUFSIZE];
  int bufferLenght;
  socklen_t lenght;
  char *stringIP, *unknown = "?";

  if (argc != 2)
  {
    printf
      ("Správná syntaxe:\n\t%s\tpočet sledovaných paketů\n",
      argv[0]);
    return -1;
  }
  count = atoi(argv[1]);

Vytvoření soketu

Vytvoříme soket typu raw (hodnota makra SOCK_RAW) s použitím protokolu ICMP (hodnota makra IPPROTO_ICMP).

  if ((sock = socket(AF_INET, SOCK_RAW, IPPROTO_ICMP)) == -1)
  {
    perror("Inicializace soketů");
    return -1;
  }

Příjem ICMP paketů

Postupně v cyklu budeme přijímat ICMP pakety. V bufferu, který přijmeme, je obsažena hlavička IP protokolu, hlavička ICMP protokolu a data, která ICMP paket přenáší.

for(unsigned int p = 0; p < count; p++)
{
 lenght = sizeof(sockaddr_in);
 if ((bufferLenght =
    recvfrom(sock, buffer, BUFSIZE, 0, (sockaddr *)&sender, &lenght))
    == -1)
 {
   printf("Příjem dat: %u\n", 0);
   close(sock);
   return -1;
 }

Získáme potřebné informace z jednotlivých hlaviček

První v bufferu je hlavička IP protokolu. Ukazatel na strukturu s atributy IP hlavičky bude ukazovat na začátek bufferu. Hned za IP hlavičkou je ICMP hlavička. Velikost IP hlavičky zjistíme pomocí atributu ihl. Tím zjistíme také polohu ICMP hlavičky v bufferu. Poté připravíme textovou reprezentaci adresy odesílatele a doménové jméno odesílatele. Všechny informace vypíšeme na standardní výstup.

ip = (iphdr*)buffer;
icmp = (icmphdr *) (buffer + ip->ihl * 4);
stringIP = strdup(inet_ntoa(sender.sin_addr));
host = gethostbyaddr((char *)&sender.sin_addr, 4, AF_INET);
printf(
  "\nPřišel ICMP paket velikosti %d bytů\nOdesílatel: %s (%s)\n"
  "TTL: %d\nTyp ICMP: %d\nKód ICMP: %d\n",
  bufferLenght, stringIP, (host == NULL? unknown : host->h_name),
  (int)ip->ttl, icmp->type, icmp->code);
free(stringIP);

Rozdělení podle typu paketu

Jestliže byl doručen nějaký typ paketu, kterého si chceme podrobněji všímat, vypíšeme o něm více informaci. Informace jsou obsaženy v proměnné části hlavičky ICMP paketu. Pomocí soketového API je celá věc implementována prostřednictvím unie jazyka C. Unie ve struktuře icmphdr se jmenuje un. Některé ICMP pakety obsahují za ICMP hlavičkou nějaká data. O těch si ale povíme až příště.

switch (icmp->type)
{
  /* Tady podle jednotlivých typů vypisuji
    podrobné informace. Viz příklad ke stažení.*/
}

Konec programu

Zavřeme soket a ukončíme program.

ict ve školství 24

  }
  close(sock);
  return 0;
}

Příklady ke stažení

Tabulka č. 457
Soubor OS
lin20.tgz Linux
win20.zip MS WindowsŽ

Příklady ke stažení lze podle potřeby samozřejmě upravit. Nerozeznají mnoho typů ICMP paketů a také by mohly vypisovat více informací.

Několikrát jsem se zmínil, že protokol ICMP může přenášet také nějaká data (nejen svou hlavičku s informacemi). Nejedná se ale o nějaká data protokolu vyšší vrstvy. Jedná se o data, která nějakým způsobem doplňují informace v hlavičce ICMP paketu. Na tuto situaci se podíváme příště.

Seriál: Sokety a C/C++